にょきにょきシンセサイザー(Hex-cylindrical arpeggiator)

by keim_at_Si forked from Instrument (diff: 1034)
Dragging on Image to play arpeggio.
see also SSAO;http://wonderfl.net/c/uCLR
webpage; http://soundimpulse.sakura.ne.jp/hex-cylindrical-arpeggiator/
♥71 | Line 912 | Modified 2010-10-31 09:33:09 | MIT License
play

ActionScript3 source code

/**
 * Copyright keim_at_Si ( http://wonderfl.net/user/keim_at_Si )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/olZB
 */

package {
    import flash.display.*;
    import flash.events.*;
    import flash.geom.*;
    import flash.utils.*;
    import flash.filters.*;
    import org.si.sound.patterns.*;
    import org.si.utils.timer;
    import com.bit101.components.*;

    [SWF(width='465', height='465', backgroundColor='#000000', frameRate='30')]
    public class main extends Sprite {
        private var gl:Render3D, _ssao:BitmapData, _ssaoEnabled:Boolean = true;
        private var controler:Sprite;
        private var prevTime:uint;
        private var arX:Number, arY:Number, viewX:Number, viewY:Number, viewdX:Number, viewdY:Number;
        private var attrForce:Number = 20, dumpForce:Number = 0.125;
        
        // entry point
        function main() {
            var i:int, j:int;
            addChild(controler = new Sprite());
            
            HexagonalPillar.initialize(400, 260);
            addChild(gl = new Render3D(300)).visible = false;
            _ssao = new BitmapData(400, 260, false, 0);
            controler.addChild(new Bitmap(HexagonalPillar.screen));
            controler.x = 32;
            controler.y = 20;
            controler.addEventListener("enterFrame", _onEnterFrame);
            controler.addEventListener("mouseDown",  _onMouseDown);
            
            //timer.initialize(this, 10, "Total : ##ms","Depth : ##ms","Flat : ##ms","Proj : ##ms","SSAO : ##ms");
            //timer.title = "debug";
            
            arX = arY = viewX = viewY = 0.5;
            viewdX = viewdY = 0;
            
            soundManager = new SoundManager(_onArpeggiatorFrame);
            controlPanel = new ControlPanel(this, 32, 280, soundManager.synth);
            new CheckBox(controler, 4, 245, "Ambient Shadow", function(e:Event):void {_ssaoEnabled = e.target.selected;}).selected = true;
            
            soundManager.bpm = HexagonalPillar.bpm = 100;
            soundManager.play();

            prevTime = getTimer();
        }

        private function _onEnterFrame(e:Event) : void {
            var i:int, t:int = getTimer(), dt:Number = (t - prevTime) * 0.001, ax:Number , ay:Number, dp:Number;
            prevTime = t;

            ax = (arX - viewX) * attrForce * dt;
            ay = (arY - viewY) * attrForce * dt;
            viewdX += ax;
            viewdY += ay;
            viewX += (viewdX + ax * 0.5) * dt;
            viewY += (viewdY + ay * 0.5) * dt;
            dp = Math.pow(dumpForce, dt);
            viewdX *= dp;
            viewdY *= dp;
            
            //timer.start(0);
            stage.quality = "low";
            HexagonalPillar.depth.lock();
            HexagonalPillar.screen.lock();
            HexagonalPillar.update(gl, viewX, viewY, dt);
            //timer.start(4);
            if (_ssaoEnabled) {
                _ssao.lock();
                _ssao.applyFilter(HexagonalPillar.depth, _ssao.rect, _ssao.rect.topLeft, blur);
                _ssao.draw(HexagonalPillar.depth, null, null, "subtract");
                _ssao.threshold(HexagonalPillar.depth, _ssao.rect, _ssao.rect.topLeft, "==", 0, 0, 255);
                HexagonalPillar.screen.draw(_ssao, null, colt, "multiply");
                _ssao.unlock();
            }
            //timer.pause(4);
            HexagonalPillar.depth.unlock();
            HexagonalPillar.screen.unlock();
            stage.quality = "high";
            //timer.pause(0);
        }
        private var blur:BlurFilter = new BlurFilter(64, 64);
        private var colt:ColorTransform = new ColorTransform(-8, -8, -8, 1, 255, 255, 255, 0);
        
        private function _onMouseDown(e:Event) : void { 
            stage.addEventListener("mouseMove", _onMouseMove);
            stage.addEventListener("mouseUp", _onMouseUp);
            soundManager.arStart();
        }
        private function _onMouseMove(e:Event) : void { 
            arX = controler.mouseX*0.0025;
            arY = controler.mouseY*0.0038461538461538463;
            arX = (arX < 0) ? 0 : (arX > 1) ? 1 : arX;
            arY = (arY < 0) ? 0 : (arY > 1) ? 1 : arY;
            soundManager.arControl(arX, arY);
        }
        private function _onMouseUp(e:Event) : void { 
            soundManager.arStop();
            stage.removeEventListener("mouseMove", _onMouseMove);
            stage.removeEventListener("mouseUp", _onMouseUp);
        }
        
        private function _onArpeggiatorFrame(seq:Sequencer) : void { HexagonalPillar.find(int(arX*8+3.5), int(arY*2+0.5)).height = 40; }
    }
}


import flash.display.*;
import flash.geom.*;
import flash.events.*;
import org.si.sion.*;
import org.si.sion.events.*;
import org.si.sion.effector.*;
import org.si.sound.*;
import org.si.sound.synthesizers.*;
import com.bit101.components.*;
import org.si.utils.timer;

var soundManager:SoundManager;
var controlPanel:ControlPanel;

class SoundManager {
    public var synth:AnalogSynth;
    private var sion:SiONDriver, ar:Arpeggiator, dm:DrumMachine, _autoPlay:Boolean;
    function SoundManager(onenterframe:Function) {
        sion = new SiONDriver();
        ar = new Arpeggiator("Fp", 1, [0,3,2,5,6,4,1]);
        dm = new DrumMachine(0,2,1,1,2,0);
        ar.synthesizer = synth = new AnalogSynth(0,1,5,0.5,0.1);
        ar.onEnterFrame = onenterframe;
        synth.setVCAEnvelop(0.2, 0.5, 0.75, 0.2);
        synth.setVCFEnvelop(0.5, 0.2, 0.1, 0.6, 0.6);
        _autoPlay = false;
    }
    public function set bpm(n:Number) : void { sion.bpm = n; }
    public function set autoPlay(b:Boolean) : void { 
        _autoPlay = b;
        ar.play();
    }
    public function setChorus(n:Number) : void { ar.effectSend1 = n; }
    public function setDelay(n:Number) : void { ar.effectSend2 = n; }
    public function play() : void {
        ar.volume = 0.3;
        ar.effectSend1 = 0.4;
        ar.effectSend2 = 0.2;
        dm.volume = 0.3;
        sion.play("#EFFECT1{chorus};#EFFECT2{delay};#A=[f8.f8.fffrfrr4];%5@2@v28@f40,2,60,32,,,72,60q5s40l16o3$AA(-3)A(-7)A(-5)");
        dm.play();
    }
    public function arStart() : void { ar.play(); }
    public function arStop() : void { if (!_autoPlay) ar.stop(); }
    public function arControl(nx:Number, ny:Number) : void {
        ar.scaleIndex = nx * 20 - 10;
        ar.scaleIndex = nx * 20 - 10;
        ar.gateTime = ny;
        ar.noteLength = [2,1,2,1][int(ny * 3.9)];
        ar.portament = (ny == 0 || ny == 1) ? 5 : 0;
    }
}

class ControlPanel extends Sprite {
    private var _wsList:Array = [0,4,5,1,6];
    private var _pitches:Array = [-1536,-768,-704,-640,-576,-512,-448,-384,-320,-256,-192,-128,-64,-48,-32,-24,-16,-12,-8,-6,-4,-3,-2,-1,0,1,2,3,4,6,8,12,16,24,32,48,64,128,192,256,320,384,448,512,576,640,704,768,1536];
    private var _bal:Knob, _det:Knob, _bpm:Knob, _dly:Knob, _cho:Knob, _ws1:Array = [], _ws2:Array = [], _con:Array = [], _detL:Label;
    private var vca:EnvelopControler, vcf:EnvelopControler, res:VSlider, auto:CheckBox;
    private var aat:Label, adt:Label, asl:Label, art:Label, fco:Label, fre:Label, fat:Label, fdt:Label, fpk:Label;
    private var _synth:AnalogSynth;
    function ControlPanel(parent:DisplayObjectContainer, xpos:Number, ypos:Number, synth:AnalogSynth) {
        var i:int, g:Graphics = graphics, vtx:Vector.<Number> = new Vector.<Number>();
        var cmd:Vector.<int> = Vector.<int>([1,2,2,2,2,2,2,2,2,1,2,2,2,1,2,2,2,2,2,1,2,2,2,1,2,2,2,2,2,2,2,2]);
        for (i=0; i<9; i++) vtx.push(20+i*2, 8-Math.sin(i*0.7853981633974483)*4);
        vtx.push(40+0,  8, 40+4,  4, 40+12, 12, 40+16,  8);
        vtx.push(60+0,  8, 60+0,  4, 60+8,   4, 60+8,  12, 60+16, 12, 60+16, 8);
        vtx.push(80+0,  8, 80+8,  4, 80+8,  12, 80+16,  8);
        for (i=0; i<9; i++) vtx.push(100+i*2, 8+(Math.random()*3+1)*(1-((i&1)<<1)));
        super();
        parent.addChild(this);
        x = xpos;
        y = ypos;
        _synth = synth;
        
        Style.LABEL_TEXT = 0xa0a0a0;
        Style.BACKGROUND = 0x606060;
        for (i=0; i<5; i++) {
            _ws1.push(new RadioButton(this, i*20+22, 18, "", false, _funcWSSelected(0, i)));
            _ws2.push(new RadioButton(this, i*20+22, 34, "", false, _funcWSSelected(1, i)));
            _ws1[i].groupName = "ws1";
            _ws2[i].groupName = "ws2";
        }
        new Label(this, 0, 14, "ws1");
        new Label(this, 0, 30, "ws2");
        g.lineStyle(1, Style.LABEL_TEXT);
        g.drawPath(cmd, vtx);
        _con.push(new RadioButton(this, 144, 18, "", false, function(e:Event):void{_synth.con = 0;}));
        _con.push(new RadioButton(this, 164, 18, "", false, function(e:Event):void{_synth.con = 1;}));
        _con.push(new RadioButton(this, 184, 18, "", false, function(e:Event):void{_synth.con = 2;}));
        _con[0].selected = _ws1[3].selected = _ws2[2].selected = true;
        new Label(this, 120, 14, "con");
        new Label(this, 140,  0, "Non");
        new Label(this, 160,  0, "Rng");
        new Label(this, 180,  0, "Syn");
        for (i=0; i<3; i++) _con[i].groupName = "con";
        _bal = _newKnob(205, 0, "bal", -100, 100, 0, function(e:Event):void{ _synth.balance = (_bal.value+100)* 0.005; });
        _det = _newKnob(230, 0, "det", 0, _pitches.length-1, _pitches.length>>1, _onChangeVCO2Pitch);
        _dly = _newKnob(255, 0, "dly", 0, 100, 20, function(e:Event):void{soundManager.setDelay(_dly.value*0.01);});
        _cho = _newKnob(280, 0, "cho", 0, 100, 40, function(e:Event):void{soundManager.setChorus(_cho.value*0.01);});
        _bpm = _newKnob(305, 0, "bpm", 80, 160, 100, function(e:Event):void{soundManager.bpm = HexagonalPillar.bpm = _bpm.value;});
        _det.showValue = false;
        _det.value = (_pitches.length>>1)+4;
        _detL = new Label(_det, 0, 36, "");
        _onChangeVCO2Pitch(null);
        
        vca = new EnvelopControler(this, 5, 56, 140, 80, {
            "at":_synth.attackTime  * 1.4285714285714286,
            "dt":_synth.decayTime   * 1.4285714285714286, 
            "sl":_synth.sustainLevel,
            "rt":_synth.releaseTime * 1.4285714285714286
        });
        vca.onUpdate = _onVCAUpdate;
        new Label(vca, 110, 0, "VCA");
        
        vcf = new EnvelopControler(this, 155, 56, 140, 80, {
            "at":_synth.vcfAttackTime  * 1.4285714285714286,
            "tl":_synth.vcfPeakCutoff, 
            "dt":_synth.vcfDecayTime   * 1.4285714285714286, 
            "sl":_synth.cutoff
        });
        vcf.onUpdate = _onVCFUpdate;
        new Label(vcf, 110, 0, "VCF");
        res = new VSlider(this, 305, 56, _onVCFUpdate);
        res.setSliderParams(0, 1, _synth.resonance);
        res.setSize(10, 80);
        res.tick = 0.0078125;
        
        auto = new CheckBox(this, 335, 10, "autoplay", function(e:Event) : void{ soundManager.autoPlay = auto.selected; });
        auto.selected = false;
        
        var ly:Number = 14;
        aat = envparam(this, "attackT.", _synth.attackTime);
        adt = envparam(this, "decayT.",  _synth.decayTime);
        asl = envparam(this, "sustainL.",_synth.sustainLevel);
        art = envparam(this, "releaseT.",_synth.releaseTime);
        fco = envparam(this, "cutoff",   _synth.cutoff);
        fre = envparam(this, "resonan.", _synth.resonance);
        fat = envparam(this, "vcfAtt.",  _synth.vcfAttackTime);
        fdt = envparam(this, "vcfDec.",  _synth.vcfDecayTime);
        fpk = envparam(this, "vcfPeak",  _synth.vcfPeakCutoff);
        
        function envparam(parent:DisplayObjectContainer, label:String, value:Number) : Label {
            ly +=12;
            new Label(parent, 335, ly, label);
            return new Label(parent, 375, ly, value.toFixed(2)); 
        }
    }
    
    private function _newKnob(xpos:Number, ypos:Number, label:String, min:Number, max:Number, val:Number, onChange:Function) : Knob {
        var knob:Knob = new Knob(this, xpos, ypos, label, onChange);
        knob.radius = 8;
        knob.labelPrecision = 0;
        knob.minimum = min;
        knob.maximum = max;
        knob.value = val;
        return knob;
    }
    
    private function _onChangeVCO2Pitch(e:Event) : void {
        var pitch:Number = _pitches[int(_det.value)]*0.015625;
        _synth.vco2pitch = pitch;
        _detL.text = pitch.toFixed(2);
        _detL.draw();
        _detL.x = 8 - _detL.width * 0.5;
    }
    private function _onVCAUpdate(e:Event=null) : void {
        _synth.setVCAEnvelop(vca.at*0.7, vca.dt*0.7, vca.sl, vca.rt*0.7);
        aat.text = _synth.attackTime.toFixed(2);
        adt.text = _synth.decayTime.toFixed(2);
        asl.text = _synth.sustainLevel.toFixed(2);
        art.text = _synth.releaseTime.toFixed(2);
    }
    private function _onVCFUpdate(e:Event=null) : void {
        _synth.setVCFEnvelop(vcf.sl*vcf.tl, res.value, vcf.at*0.7, vcf.dt*0.7, vcf.tl);
        fco.text = _synth.cutoff.toFixed(2);
        fre.text = _synth.resonance.toFixed(2);
        fat.text = _synth.vcfAttackTime.toFixed(2);
        fdt.text = _synth.vcfDecayTime.toFixed(2);
        fpk.text = _synth.vcfPeakCutoff.toFixed(2);
    }
    private function _funcWSSelected(opNum:int, index:int) : Function {
        return function(e:Event) : void { _wsSelected(opNum, index); };
    }
    private function _wsSelected(opNum:int, index:int) : void {
        if (opNum == 0) _synth.ws1 = _wsList[index];
        else _synth.ws2 = _wsList[index];
    }
}


// custom component "ControlPad" shown in Arpeggiator panel
class ControlPad extends Component {
    public var back:Sprite, pointer:Sprite, rx:Number=0.5, ry:Number=0.5, w:Number, h:Number;
    public var onStart:Function=null, onChange:Function, onStop:Function=null;
    function ControlPad(parent:DisplayObjectContainer, x:Number, y:Number, width:Number, height:Number) {
        super(parent, x, y);
        addChild(back = new Sprite());
        back.filters = [getShadow(2, true)];
        back.addEventListener(MouseEvent.MOUSE_DOWN, onBackClick);
        addChild(pointer = new Sprite());
        pointer.filters = [getShadow(1)];
        pointer.buttonMode = true;
        pointer.useHandCursor = true;
        pointer.addEventListener(MouseEvent.MOUSE_DOWN, onDrag);
        setSize(width, height);
        w = width - 12;
        h = height - 12;
    }
    
    override public function draw() : void {
        super.draw();
        back.graphics.clear();
        back.graphics.beginFill(Style.BACKGROUND);
        back.graphics.drawRect(0, 0, width, height);
        back.graphics.endFill();
        pointer.graphics.beginFill(0x8080ff, 0.5);
        pointer.graphics.lineStyle(2,Style.BUTTON_FACE);
        pointer.graphics.drawCircle(5, 5, 5);
        pointer.graphics.endFill();
        updatePointerPosition();
    }
    
    protected function onDrag(e:Event) : void {
        stage.addEventListener(MouseEvent.MOUSE_UP, onDrop);
        stage.addEventListener(MouseEvent.MOUSE_MOVE, onSlide);
        pointer.startDrag(false, new Rectangle(0, 0, w, h));
        if (onStart != null) onStart();
    }
    
    protected function onDrop(e:MouseEvent) : void {
        stage.removeEventListener(MouseEvent.MOUSE_UP, onDrop);
        stage.removeEventListener(MouseEvent.MOUSE_MOVE, onSlide);
        stopDrag();
        if (onStop != null) onStop();
    }

    protected function onSlide(e:MouseEvent) : void {
        var _rx:Number = rx, _ry:Number = ry;
        rx = pointer.x / w;
        ry = pointer.y / h;
        rx = (rx<0) ? 0 : (rx>1) ? 1 : rx;
        ry = (ry<0) ? 0 : (ry>1) ? 1 : ry;
        if (_rx != rx || _ry != ry) onChange();
    }
    
    protected function onBackClick(e:MouseEvent) : void {
        pointer.x = mouseX - 6;
        pointer.y = mouseY - 6;
        onSlide(e);
        onDrag(null);
    }
    
    public function setPointer(x:Number, y:Number) : void {
        rx = x;
        ry = y;
        updatePointerPosition();
    }
    
    public function updatePointerPosition() : void {
        pointer.x = rx * w;
        pointer.y = ry * h;
    }
}


// custom component "EnvelopControler" shown in BassSequencer panel
class EnvelopControler extends ControlPad {
    public var env:Sprite, at:Number, tl:Number, dt:Number, sl:Number, sr:Number, rt:Number;
    public var srFixed:Boolean, tlFixed:Boolean, rlFixed:Boolean, dragIndex:int, pt:Array, onUpdate:Function;
    function EnvelopControler(parent:DisplayObjectContainer, x:Number, y:Number, width:Number, height:Number, p:*) {
        at=p["at"]; dt=p["dt"]; sl=p["sl"];
        srFixed=!("sr" in p); tlFixed=!("tl" in p); rlFixed=!("rt" in p);
        tl=(tlFixed)?1:p["tl"]; sr=(srFixed)?1:p["sr"]; rt=(rlFixed)?1:p["rt"];
        dragIndex = 0;
        super(parent, x, y, width, height);
        back.addChild(env = new Sprite());
        env.filters = [getShadow(1)];
        addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
        onChange = _onChange;
        onStart = _onStart;
        onStop = _onStop;
        rx = at * 0.3;
        ry = 1 - tl;
    }
    
    override public function draw() : void {
        super.draw();
        updateEnvelop();
    }
    
    protected function updateEnvelop() : void {
        pt = [[at*w*0.3,(1-tl)*h], [(at+dt)*w*0.3,(1-sl*tl)*h], [w*0.7,(1-sl*tl)*sr*h], [(rlFixed)?w:(0.7+rt*0.3)*w,(rlFixed)?((1-sl*tl)*sr*h):h]];
        env.graphics.clear();
        env.graphics.lineStyle(2,Style.BUTTON_FACE);
        env.graphics.moveTo(5, h+5);
        env.graphics.lineTo(pt[0][0]+5, pt[0][1]+5);
        env.graphics.lineTo(pt[1][0]+5, pt[1][1]+5);
        env.graphics.lineTo(pt[2][0]+5, pt[2][1]+5);
        env.graphics.lineTo(pt[3][0]+5, pt[3][1]+5);
    }
    
    protected function _onStart() : void { removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); }
    protected function _onStop()  : void { addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); }
    
    protected function onMouseMove(e:MouseEvent) : void {
        var i:int, dx:Number=pt[0][0]-mouseX, dy:Number=pt[0][1]-mouseY, d2:Number=dx*dx+dy*dy;
        for (dragIndex=0, i=1; i<4; i++) {
            dx = pt[i][0] - mouseX;
            dy = pt[i][1] - mouseY;
            if (dx*dx+dy*dy < d2) {
                d2 = dx*dx+dy*dy;
                dragIndex = i;
            }
        }
        pointer.x = pt[dragIndex][0];
        pointer.y = pt[dragIndex][1];
    }
    
    protected function _onChange() : void {
        var n:Number;
        switch (dragIndex) {
        case 0:
            at = (rx>=0.3) ? 1 : (rx * 3.3333333333333333);
            tl = (tlFixed) ? 1 : (1 - ry);
            rx = at * 0.3;
            ry = 1 - tl;
            break;
        case 1:
            n = rx - at * 0.3;
            dt = (n<0) ? 0 : (n>=0.3) ? 1 : (n * 3.3333333333333333);
            sl = (tl==0) ? 0 : ((1 - ry)/tl);
            if (sl > 1) sl = 1;
            rx = (at + dt)*0.3;
            ry = 1 - sl * tl;
            break;
        case 2:
            rx = 0.7;
            sl = (tl==0) ? 0 : ((1 - ry)/tl);
            if (sl > 1) sl = 1;
            ry = 1 - sl * tl;
            break;
        case 3:
            rt = (rlFixed) ? 1 : (rx<0.7) ? 0 : ((rx-0.7) * 3.3333333333333333);
            rx = rt * 0.3 + 0.7;
            ry = (rlFixed) ? (1 - sl * tl) : 1;
            break;
        default:
            break;
        }
        updatePointerPosition();
        updateEnvelop();
        onUpdate();
    }
}



/** 3D engine class */
class Render3D extends Shape {
//--------------------------------------------------variables
    public var matrix:Matrix3D;
    private var _projectionMatrix:Matrix3D;                              // projection matrix
    private var _matrixStac:Vector.<Matrix3D> = new Vector.<Matrix3D>(); // matrix stac
    private var _cmdWire:Vector.<int> = Vector.<int>([1,2]);             // commands to draw wire
    private var _cmdTriangle:Vector.<int> = Vector.<int>([1,2,2]);       // commands to draw triangle
    private var _cmdQuadrangle:Vector.<int> = Vector.<int>([1,2,2,2]);   // commands to draw quadrangle
    private var _data:Vector.<Number> = new Vector.<Number>(8, true);    // data to draw shape
    private var _clippingZ:Number;                                       // clipping z value
    private var _depthMap:BitmapData = new BitmapData(256, 256, false);  // texture for depth buffer rendering
    private var _zeroVector:Vector3D = new Vector3D(0,0,0,1);            // zero vector
    
//--------------------------------------------------constructor
    function Render3D(focus:Number=300, clippingZ:Number=-0.1) {
        var projector:PerspectiveProjection = new PerspectiveProjection()
        projector.focalLength = focus;
        _projectionMatrix = projector.toMatrix3D();
        _clippingZ = -clippingZ;
        matrix = new Matrix3D();
        _matrixStac.length = 1;
        _matrixStac[0] = matrix;
        var u:int, v:int;
        for (v=0; v<256; v++) 
            for (u=0; u<256; u++) 
                //_depthMap.setPixel(255-u, 255-v, (v<<8)|u);
                _depthMap.setPixel(255-u, 255-v, (u<<16)|(u<<8)|u);
    }
    
//--------------------------------------------------control matrix
    public function clear() : Render3D { matrix = _matrixStac[0]; _matrixStac.length = 1; return this; }
    public function push() : Render3D { _matrixStac.push(matrix.clone()); return this; }
    public function pop() : Render3D { matrix = (_matrixStac.length == 1) ? matrix : _matrixStac.pop(); return this; }
    public function id() : Render3D { matrix.identity(); return this; }
    public function t(x:Number, y:Number, z:Number) : Render3D { matrix.prependTranslation(x, y, z); return this; }
    public function tv(v:Vector3D) : Render3D { matrix.prependTranslation(v.x, v.y, v.z); return this; }
    public function s(x:Number, y:Number, z:Number) : Render3D { matrix.prependScale(x, y, z); return this; }
    public function sv(v:Vector3D) : Render3D { matrix.prependScale(v.x, v.y, v.z); return this; }
    public function r(angle:Number, axis:Vector3D) : Render3D { matrix.prependRotation(angle, axis); return this; }
    public function rv(v:Vector3D) : Render3D { matrix.prependRotation(v.w, v); return this; }
    public function rx(angle:Number) : Render3D { matrix.prependRotation(angle, Vector3D.X_AXIS); return this; }
    public function ry(angle:Number) : Render3D { matrix.prependRotation(angle, Vector3D.Y_AXIS); return this; }
    public function rz(angle:Number) : Render3D { matrix.prependRotation(angle, Vector3D.Z_AXIS); return this; }
    public function mult(mat:Matrix3D) : Render3D { matrix.prepend(mat); return this; }
    
//--------------------------------------------------projections
    public function project(pmesh:ProjectionMesh) : Render3D {
        matrix.transformVectors(pmesh.base.vertices, pmesh.verticesOnWorld);
        matrix.transformVectors(pmesh.base.gravPoints, pmesh.gravPointsOnWorld);
        pmesh.position = matrix.position;
        pmesh.sortZ = pmesh.position.lengthSquared;
        matrix.position = _zeroVector;
        matrix.transformVectors(pmesh.base.faceNormals,  pmesh.faceNormalsOnWorld);
        matrix.position = pmesh.position;
        var nearZ:Number = -Number.MAX_VALUE, farZ:Number = _clippingZ,
            vtx:Vector.<Number> = pmesh.verticesOnWorld,
            gp:Vector.<Number> = pmesh.gravPointsOnWorld, 
            fn:Vector.<Number> = pmesh.faceNormalsOnWorld,
            flist:Vector.<Face> = pmesh.base.faces,
            f:Face, f0:Face, i:int, imax:int, i0:int, i1:int, i2:int, 
            z0:Number, z1:Number, z2:Number, gpi:int, dot:Number;
        imax = flist.length;
        pmesh.faceProjected.length = 0;
        for (i=0; i<imax; i++) {
            f = flist[i];
            z0 = vtx[(f.i0<<1) + f.i0 + 2];
            z1 = vtx[(f.i1<<1) + f.i1 + 2];
            z2 = vtx[(f.i2<<1) + f.i2 + 2];
            if (z0<_clippingZ && z1<_clippingZ && z2<_clippingZ) {
                gpi = f.gpi - 2;
                dot  = gp[gpi] * fn[gpi]; gpi++;
                dot += gp[gpi] * fn[gpi]; gpi++;
                dot += gp[gpi] * fn[gpi];
                if (dot <= 0) {
                    if (nearZ < z0) nearZ = z0;
                    if (nearZ < z1) nearZ = z1;
                    if (nearZ < z2) nearZ = z2;
                    if (farZ  > z0) farZ  = z0;
                    if (farZ  > z1) farZ  = z1;
                    if (farZ  > z2) farZ  = z2;
                    pmesh.faceProjected.push(f);
                }
            }
        }
        pmesh.nearZ = nearZ;
        pmesh.farZ  = farZ;
        pmesh.indexDirty = true;
        pmesh.screenProjected = false;
        pmesh.faceProjected.sort(function(f1:Face, f2:Face):Number{ return gp[f1.gpi] - gp[f2.gpi]; });
        
        return this;
    }
    
//--------------------------------------------------rendering
    public function renderSolid(pmesh:ProjectionMesh, materialList:Array, light:Light) : Render3D {
        var idx:int, mat:Material, f:Face, vout:Vector.<Number> = pmesh.verticesOnScreen, 
            fn:Vector.<Number> = pmesh.faceNormalsOnWorld, g:Graphics = graphics;
        if (!pmesh.screenProjected) {
            Utils3D.projectVectors(_projectionMatrix, pmesh.verticesOnWorld, vout, pmesh.base.texCoords);
            pmesh.screenProjected = true;
        }
        g.clear();
        var i:int, imax:int = pmesh.faceProjected.length;
        for (i=0; i<imax; i++) {
            f = pmesh.faceProjected[i];
            mat = materialList[f.mat];
            idx = f.i0<<1;
            _data[0] = vout[idx]; idx++;
            _data[1] = vout[idx];
            idx = f.i1<<1;
            _data[2] = vout[idx]; idx++;
            _data[3] = vout[idx];
            idx = f.i2<<1;
            _data[4] = vout[idx]; idx++;
            _data[5] = vout[idx];
            g.beginFill(mat.getColor(light, fn[f.gpi-2], fn[f.gpi-1], fn[f.gpi]), mat.alpha);
            if (f.i3 == -1) {
                g.drawPath(_cmdTriangle, _data);
            } else {
                idx = f.i3<<1;
                _data[6] = vout[idx]; idx++;
                _data[7] = vout[idx];
                g.drawPath(_cmdQuadrangle, _data);
            }
            g.endFill();
        }
        return this;
    }
    
    public function renderWire(pmesh:ProjectionMesh, color:uint, alpha:Number=1, width:Number=1) : Render3D {
        var idx:int, vout:Vector.<Number> = pmesh.verticesOnScreen, g:Graphics = graphics;
        if (!pmesh.screenProjected) {
            Utils3D.projectVectors(_projectionMatrix, pmesh.verticesOnWorld, vout, pmesh.base.texCoords);
            pmesh.screenProjected = true;
        }
        g.clear();
        g.lineStyle(width, color, alpha);
        for each (var wire:Wire in pmesh.base.wires) {
            idx = wire.i0<<1;
            _data[0] = vout[idx]; idx++;
            _data[1] = vout[idx];
            idx = wire.i1<<1;
            _data[2] = vout[idx]; idx++;
            _data[3] = vout[idx];
            g.drawPath(_cmdWire, _data);
        }
        return this;
    }
    
    public function renderDepth(pmesh:ProjectionMesh) : Render3D {
        var i:int, imax:int = pmesh.vertexImax, g:Graphics = graphics, depth:Number,
            nearZ:Number = (_clippingZ < pmesh.nearZ) ? _clippingZ : pmesh.nearZ,
            r:Number = 1/(pmesh.farZ - nearZ), duvt:Vector.<Number> = _depthUVT;
        duvt.length = imax;
        for (i=2; i<imax; i+=5) {
            depth = (pmesh.verticesOnWorld[i] - nearZ) * r;
            duvt[i] = 0; i--;   // t
            duvt[i] = 0; i--;   // v
            duvt[i] = depth;    // u
        }
        Utils3D.projectVectors(_projectionMatrix, pmesh.verticesOnWorld, pmesh.verticesOnScreen, duvt);
        g.clear();
        g.beginBitmapFill(_depthMap, null, false, true);
        g.drawTriangles(pmesh.verticesOnScreen, pmesh.indicesProjected, duvt);
        g.endFill();
        return this;
    }
    private var _depthUVT:Vector.<Number> = new Vector.<Number>();
}

/** Point class that has both source and projected points. */
class Point3D extends Vector3D {
    public var world:Vector3D;
    function Point3D(x:Number=0, y:Number=0, z:Number=0, w:Number=1) { super(x,y,z,w); world=clone(); }
}

/** Face class */
class Face {
    public var index:int, i0:int, i1:int, i2:int, i3:int, gpi:int, mat:int;
    public var normal:Vector3D = new Vector3D();
    static private var _freeList:Vector.<Face> = new Vector.<Face>();
    static public function free(face:Face) : void { _freeList.push(face); }
    static public function alloc(index:int, i0:int, i1:int, i2:int, i3:int, mat:int) : Face { 
        var f:Face = _freeList.pop() || new Face();
        f.index=index; f.i0=i0; f.i1=i1; f.i2=i2; f.i3=i3; f.gpi=0; f.mat=mat;
        return f;
    }
}

/** Wire class */
class Wire {
    public var index:int, i0:int, i1:int;
    static private var _freeList:Vector.<Wire> = new Vector.<Wire>();
    static public function free(wire:Wire) : void { _freeList.push(wire); }
    static public function alloc(index:int, i0:int, i1:int) : Wire { 
        var w:Wire = _freeList.pop() || new Wire();
        w.index=index; w.i0=i0; w.i1=i1;
        return w;
    }
}

/** Mesh class. */
class Mesh {
    public var vertices:Vector.<Number>;                    // vertex
    public var verticesCount:int;                           // vertex count
    public var texCoords:Vector.<Number>;                   // texture coordinate
    public var gravPoints:Vector.<Number>;                  // gravity point
    public var faceNormals:Vector.<Number>;                 // face normal
    public var faces:Vector.<Face> = new Vector.<Face>();   // face list
    public var wires:Vector.<Wire> = new Vector.<Wire>();   // wireframe list
    
    /** constructor */
    function Mesh(vertexList:Array=null, faceList:Array=null) {
        if (vertexList) this.vertices = Vector.<Number>(vertexList);
        else            this.vertices = new Vector.<Number>();
        this.verticesCount = vertices.length / 3;
        this.texCoords = new Vector.<Number>();
        this.gravPoints = new Vector.<Number>();
        this.faceNormals = new Vector.<Number>();
        if (faceList) {
            for (var i:int=0; i<faceList.length; i+=3) face(faceList[i], faceList[i+1], faceList[i+2]);
            updateFaces(true);
        }
    }
    
    /** clear all faces */
    public function clear() : Mesh {
        var i:int, imax:int = faces.length;
        for (i=0; i<imax; i++) Face.free(faces[i]);
        faces.length = 0;
        gravPoints.length = 0;
        faceNormals.length = 0;
        return this;
    }
    
    /** register face */
    public function face(i0:int, i1:int, i2:int, mat:int=0) : Mesh {
        faces.push(Face.alloc(faces.length, i0, i1, i2, -1, mat));
        return this;
    }
    
    /** register quadrangle face. set div=true to divide into 2 triangles. */
    public function qface(i0:int, i1:int, i2:int, i3:int, mat:int=0, div:Boolean=true) : Mesh {
        if (div) {
            faces.push(Face.alloc(faces.length,   i0, i1, i2, -1, mat), 
                       Face.alloc(faces.length+1, i3, i2, i1, -1, mat));
        }
        else faces.push(Face.alloc(faces.length, i0, i1, i3, i2, mat));
        return this;
    }
    
    /** register wire */
    public function wire(i0:int, i1:int) : Mesh {
        wires.push(Wire.alloc(wires.length, i0, i1));
        return this;
    }
    
    /** update face gravity points and normal vectors. create wireframes when updateWire==true */
    public function updateFaces(updateWire:Boolean = false, facetAngle:Number = 180) : Mesh {
        var vtx:Vector.<Number> = vertices, gp:Vector.<Number> = gravPoints, fn:Vector.<Number> = faceNormals, 
            i:int, imax:int = faces.length, f:Face, vidx:int, i0:int, i1:int, i2:int, i3:int, 
            x01:Number, x02:Number, y01:Number, y02:Number, z01:Number, z02:Number;
        verticesCount = vertices.length/3;
        for (i=0; i<imax; i++) {
            f = faces[i];
            if (!f.gpi) {
                gp.length += 3;
                fn.length += 3;
                f.gpi = gp.length - 1;
            }
            vidx = f.gpi - 2;
            i0 = (f.i0<<1) + f.i0;
            i1 = (f.i1<<1) + f.i1;
            i2 = (f.i2<<1) + f.i2;
            x01 = vtx[i1] - vtx[i0];
            x02 = vtx[i2] - vtx[i0];
            gp[vidx] = (vtx[i0] + vtx[i1] + vtx[i2]) * 0.333333333333;
            vidx++; i0++; i1++; i2++;
            y01 = vtx[i1] - vtx[i0];
            y02 = vtx[i2] - vtx[i0];
            gp[vidx] = (vtx[i0] + vtx[i1] + vtx[i2]) * 0.333333333333;
            vidx++; i0++; i1++; i2++;
            z01 = vtx[i1] - vtx[i0];
            z02 = vtx[i2] - vtx[i0];
            gp[vidx] = (vtx[i0] + vtx[i1] + vtx[i2]) * 0.333333333333;
            f.normal.x = y02 * z01 - y01 * z02;
            f.normal.y = z02 * x01 - z01 * x02;
            f.normal.z = x02 * y01 - x01 * y02;
            f.normal.w = 0;
            f.normal.normalize();
            vidx = f.gpi - 2;
            fn[vidx] = f.normal.x; vidx++;
            fn[vidx] = f.normal.y; vidx++;
            fn[vidx] = f.normal.z;
            if (f.i3 != -1) {
                i3 = (f.i3<<1) + f.i3;
                vidx = f.gpi - 2;
                gp[vidx] = gp[vidx] * 0.75 + gp[i3] * 0.25; vidx++; i3++;
                gp[vidx] = gp[vidx] * 0.75 + gp[i3] * 0.25; vidx++; i3++;
                gp[vidx] = gp[vidx] * 0.75 + gp[i3] * 0.25;
            }
        }
        if (updateWire) {
            var facetCos:Number = Math.cos((180-facetAngle)*57.29577951308232);
            for (i=0; i<imax; i++) {
                f = faces[i];
                _wire(f, f.i0, f.i1);
                _wire(f, f.i1, f.i2);
                if (f.i3==-1) _wire(f, f.i2, f.i0);
                else { _wire(f, f.i2, f.i3); _wire(f, f.i3, f.i0); }
            }
        }
        return this;
        
        function _wire(f0:Face, i0:int, i1:int) : void {
            var fidx:int = findFaceByVertexIndex(i0, i1);
            if (fidx == -1 || facetCos >= f0.normal.dotProduct(faces[fidx].normal)) {
                var i:int, imax:int = wires.length, w:Wire;
                for (i=0; i<imax; i++) {
                    w = wires[i];
                    if ((w.i0==i0 && w.i1==i1) || (w.i0==i1 && w.i1==i0)) return;
                }
                wire(i0, i1);
            }
        }
    }
    
    /** find face by 2 vertex indexes. */
    public function findFaceByVertexIndex(i0:int, i1:int, prevIndex:int=-1) : int {
        var i:int, imax:int = faces.length, f:Face;
        for (i=prevIndex+1; i<imax; i++) {
            f = faces[i];
            if ((f.i0==i0 && f.i1==i1) || (f.i0==i1 && f.i1==i0) || (f.i1==i0 && f.i2==i1) || (f.i1==i1 && f.i2==i0)) return i;
            if (f.i3==-1) {
                if ((f.i2==i0 && f.i0==i1) || (f.i2==i1 && f.i0==i0)) return i;
            } else {
                if ((f.i2==i0 && f.i3==i1) || (f.i2==i1 && f.i3==i0) || (f.i3==i0 && f.i0==i1) || (f.i3==i1 && f.i0==i0)) return i;
            }
        }
        return -1;
    }
}

/** mesh for projection. */
class ProjectionMesh {
    /** ProjectionMesh sorter */
    static public function sorter(p0:ProjectionMesh, p1:ProjectionMesh) : Number { return p1.sortZ - p0.sortZ; }
    
    public var verticesOnWorld:Vector.<Number>;     // vertex on camera coordinate
    public var verticesOnScreen:Vector.<Number>;    // vertex on screen
    public var gravPointsOnWorld:Vector.<Number>;   // gravity points on camera coordinate
    public var faceNormalsOnWorld:Vector.<Number>;  // face normals on camera coordinate
    public var faceProjected:Vector.<Face>;         // projected faces
    public var vnormals:Vector.<Vector3D>;          // vertex normal
    public var nearZ:Number, farZ:Number;           // z buffer range
    public var position:Vector3D, sortZ:Number;     // object position and sorting Number
    public var screenProjected:Boolean = false;     // flag to projection on screen
    public var indexDirty:Boolean = false;          // flag to recalculate indexes
    public var base:Mesh = null;
    private var _projectedFaceIndices:Vector.<int> = new Vector.<int>();
    
    /** indices of projected faces */
    public function get indicesProjected() : Vector.<int> {
        var idx:Vector.<int> = _projectedFaceIndices, f:Face, i:int, imax:int, j:int;
        if (indexDirty) {
            idx.length = imax = faceProjected.length * 3;
            for (i=0,j=0; i<imax; j++) {
                f = faceProjected[j];
                idx[i] = f.i0; i++;
                idx[i] = f.i1; i++;
                idx[i] = f.i2; i++;
            }
            indexDirty = true;
        }
        return idx;
    }

    public function get vertexImax() : int { return (base.verticesCount<<1) + base.verticesCount; }
    public function get projected() : Boolean { return (position != null); }
    
    /** constructor */
    function ProjectionMesh(m:Mesh=null) {
        this.verticesOnWorld = new Vector.<Number>();
        this.verticesOnScreen = new Vector.<Number>();
        this.gravPointsOnWorld = new Vector.<Number>();
        this.faceNormalsOnWorld = new Vector.<Number>();
        this.faceProjected = new Vector.<Face>();
        this.vnormals = null;
        this.position = null;
        this.base = m || new Mesh();
    }
}

/** Light class */
class Light extends Point3D {
    public var halfVector:Vector3D = new Vector3D();
    
    /** constructor (set position) */
    function Light(x:Number=1, y:Number=1, z:Number=1) {
        super(x, y, z, 0);
        normalize();
    }

    /** projection */
    public function transformBy(viewMatrix:Matrix3D) : void {
        world = viewMatrix.deltaTransformVector(this);
        halfVector.x = world.x;
        halfVector.y = world.y;
        halfVector.z = world.z + 1; 
        halfVector.normalize();
    }
}

/** Material class */
class Material extends BitmapData {
    public var alpha:Number = 1;    // The alpha value is available for renderSolid()
    public var doubleSided:int = 0; // set doubleSided=-1 if double sided material
    
    /** constructor */
    function Material(dif:int=128, spc:int=128) { super(dif, spc, false); }
    
    /** set color. */
    public function setColor(col:uint, amb:int=64, dif:int=192, spc:int=0,  pow:Number=8) : Material {
        fillRect(rect, col);
        var lmap:LightMap = new LightMap(width, height);
        draw(lmap.diffusion(amb, dif), null, null, "hardlight");
        draw(lmap.specular (spc, pow), null, null, "add");
        lmap.dispose();
        return this;
    }
    
    /** calculate color by light and normal vector. */
    public function getColor(l:Light, nx:Number, ny:Number, nz:Number) : uint {
        var dir:Vector3D = l.world, hv:Vector3D = l.halfVector;
        var ln:int = int((dir.x * nx + dir.y * ny + dir.z * nz) * (width-1)),
            hn:int = int((hv.x  * nx + hv.y  * ny + hv.z  * nz) * (height-1));
        if (ln<0) ln = (-ln) & doubleSided;
        if (hn<0) hn = (-hn) & doubleSided;
        return getPixel(ln, hn);
    }
}

/** Light map */
class LightMap extends BitmapData {
    function LightMap(dif:int, spc:int) { super(dif, spc, false); }
    
    public function diffusion(amb:int, dif:int) : BitmapData {
        var col:int, rc:Rectangle = new Rectangle(0, 0, 1, height), ipk:Number = 1 / width;
        for (rc.x=0; rc.x<width; rc.x+=1) {
            col = ((rc.x * (dif - amb)) * ipk) + amb;
            fillRect(rc, (col<<16)|(col<<8)|col);
        }
        return this;
    }
    
    public function specular(spc:int, pow:Number) : BitmapData {
        var col:int, rc:Rectangle = new Rectangle(0, 0, width, 1),
            mpk:Number = (pow + 2) * 0.15915494309189534, ipk:Number = 1 / height;
        for (rc.y=0; rc.y<height; rc.y+=1) {
            col = Math.pow(rc.y * ipk, pow) * spc * mpk;
            if (col > 255) col = 255;
            fillRect(rc, (col<<16)|(col<<8)|col);
        }
        return this;
    }
}

/** Hexagonal Pillars */
class HexagonalPillar extends ProjectionMesh {
    public var ratio:Number, height:Number = 10, wave:Number = 0, row:int, col:int;
    function HexagonalPillar(row:int, col:int) {
        super();
        base.vertices.length = 36;
        for (i=1; i<6; i++) base.qface(i-1,i,i+5,i+6,0,true);
        base.qface(5,0,11,6,0,true);
        for (var i:int=1; i<5; i++) base.face(0,i+1,i);
        this.row = row; this.col = col;
        height = pillarHeight;
        ratio = 0.75;
        _updateVertex();
    }
    private function _updateVertex() : void {
        var i:int, j:int;
        for (i=0, j=18; i<18;) {
            base.vertices[j] = hexVertex[i]; j++;
            base.vertices[i] = hexVertex[i] * ratio; i++;
            base.vertices[j] = hexVertex[i]; j++;
            base.vertices[i] = hexVertex[i] * ratio; i++;
            base.vertices[j] = -20; j++;
            base.vertices[i] = height+wave; i++;
        }
        base.updateFaces();
    }
    public function project(gl:Render3D, hd:Number) : void {
        var x:Number = (row-8+scroll) * radius * 1.5, 
            y:Number = ((col*2+(row&1))-2) * radius * 0.8660254037844385;
        wave = Math.sin(x*0.02702445293410575 + wavePhase + col * 0.39269908169872414 * Math.sin(wavePhase*0.0625)) * waveHeight;
        height = (height-pillarHeight)*hd + pillarHeight;
        _updateVertex();
        gl.push().t(x, y, 0).project(this).pop();
        nearZ = -30;
        farZ = -150;
    }
    public function render(gl:Render3D) : void {
        //timer.start(1);
        depth.draw(gl.renderDepth(this), centering);
        //timer.pause(1);
        //timer.start(2);
        screen.draw(gl.renderSolid(this, materials, light), centering);
        //timer.pause(2);
    }
    static public var pillarHeight:Number = 0, heightDump:Number = 0.03125;
    static public var wavePhaseDelta:Number, waveHeight:Number = 10;
    static public var radius:Number = 10, scroll:Number=0, wavePhase:Number=0, speed:Number=1;
    static public var hexVertex:Vector.<Number> = new Vector.<Number>(18, true);
    static public var pillars:Array = new Array(45);//45
    static public var centering:Matrix, screen:BitmapData, depth:BitmapData;
    static public var materials:Array = [(new Material()).setColor(0xc0e0f0, 64, 192, 8, 40)];
    static public var light:Light = new Light(1,0.5,0.25);
    static public var camera:Vector3D = new Vector3D(0, 0, -80);
    static public function set bpm(n:Number) : void { wavePhaseDelta = n*0.02617993877991494; }
    static public function initialize(w:Number, h:Number) : void {
        var i:int, imax:int = pillars.length;
        for (i=0; i<18; i+=3) {
            hexVertex[i]   = Math.cos(i*0.3490658503988659) * radius;
            hexVertex[i+1] = Math.sin(i*0.3490658503988659) * radius;
            hexVertex[i+2] = 0;
        }
        for (i=0; i<imax; i++) pillars[i] = new HexagonalPillar(i%15, i/15);
        screen = new BitmapData(w, h, false, 0);
        depth  = new BitmapData(w, h, false, 0);
        centering = new Matrix(1, 0, 0, 1, w*0.5, h*0.5);
    }
    static public function update(gl:Render3D, nx:Number, ny:Number, dt:Number) : void {
        var i:int, imax:int = pillars.length, hd:Number = Math.pow(heightDump, dt);
        var head:Number = (0.5-nx)*70;
        var pitch:Number = (0.5-ny)*40;
        if (head < -30) head = -30;
        else if (head > 30) head = 30;
        if (pitch < -15) pitch = -15;
        else if (pitch > 15) pitch = 15;
        _scroll(dt);
        wavePhase += wavePhaseDelta * dt;
        light.transformBy(gl.id().tv(camera).rx(pitch-40).ry(-head).matrix);
        depth.fillRect(depth.rect, 0);
        screen.fillRect(screen.rect, 0xffffff);
        //timer.start(3);
        for (i=0; i<imax; i++) pillars[i].project(gl, hd);
        pillars.sort(ProjectionMesh.sorter);
        //timer.pause(3);
        for (i=0; i<imax; i++) pillars[i].render(gl);
    }
    static private function _scroll(dt:Number) : void {
        scroll += speed * dt;
        if (scroll > 2) {
            scroll -= 2;
            var i:int, imax:int = pillars.length;
            for (i=0; i<imax; i++) { 
                pillars[i].row += 2;
                if (pillars[i].row > 14) pillars[i].row-=15;
            }
        }
    }
    static public function find(row:int, col:int) : HexagonalPillar {
        var i:int, imax:int = pillars.length;
        row -= (scroll>=1) ? 1 : 0;
        for (i=0; i<imax; i++) { 
            if (pillars[i].row == row && pillars[i].col == col) return pillars[i];
        }
        return null;
    }
}

Forked