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

Solar Nexus

initial attempt at Stage3D volumetrics
/**
 * Copyright yonatan ( http://wonderfl.net/user/yonatan )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/Kmaf
 */

// forked from yonatan's it's a knot
// forked from keim_at_Si's Metallic soft cube
package {
    import flash.display.*;
    import flash.events.*; 
    import flash.utils.*;
    import flash.geom.*;
    import flash.text.*;
    import flash.net.*;
    
    import com.adobe.utils.*;
    import com.bit101.components.*;
    import flash.display3D.*;
    import flash.display3D.textures.*;
    //import org.si.ptolemy.*;
    //import org.si.ptolemy.core.*;

    [SWF (width="465", height="465", backgroundColor="0x101010")]
    public class main extends Sprite {
/*      // for local
        private const cceURL:String = "cce.swf";
        private const envURL:String = "_env1.png";
/*/     // for wonderfl
        private const cceURL:String = "http://swf.wonderfl.net/swf/usercode/6/67/679c/679c3b410a599d83b3548ce73ce37b873ae4046b.swf?t=1319478894168";
        private const envURL:String = "http://assets.wonderfl.net/images/related_images/b/b2/b217/b2177f87d979a28b9bcbb6e0b89370e77ce22337";
//*/
        
        private var container:Sprite;
        private var ptolemy:Ptolemy;
        
        private var asm:AGALMiniAssembler = new AGALMiniAssembler();
        private var programs:Vector.<Program3D> = new Vector.<Program3D>();

        private var _mesh:Mesh = new Knot(2, 3, 72, 8, 3/5, 0);//i/60);
        
        private var _ifreq:Number;
        private var _phase:Number;
        private var _rotx:Number;

        private var _ambVector:Vector.<Number> = Vector.<Number>([0.2,0.2,0.3,1]);
        private var _difVectorDif:Vector.<Number> = Vector.<Number>([0.4,0.4,0.6,1]); 
        private var _col:uint = 0x142D2D;
        private var _amb:Number = 0.4;
        private var _dif:Number = 0.9;
        private var _ref:Number = 0;
        private var _pow:Number = 64;
        private var _spc:Number = 100;
        private var _shp:Number = 0.75;
        private var _specMap:BitmapData;
        private var _specTex:Texture;
        private var _shpereMap:BitmapData;
        private var _shpereTex:Texture;

        private var _occlusionTex:Texture;
        private var _emissionTex:Texture;
        private var _emissionBmd:BitmapData;
        private var _fxSrcTex:Texture;
        private var _fxDstTex:Texture;
        private var _fxIdVertices:Vector.<Number> = new <Number> [
            -1,  1,  0,   0, 0,
            -1, -3,  0,   0, 2,
            3,   1,  0,   2, 0
        ];
        private var _fxIndices:Vector.<uint> = new <uint> [0, 1, 2];
        private var _fxVerticesBuffer:VertexBuffer3D;
        private var _fxIndicesBuffer:IndexBuffer3D;
        private var _fxPasses:int = 7;
        private var _fxScale:Number = 3;

        private var _startTime:Number;
        private var _lightPosition:Vector3D = new Vector3D();
        private var _halfVector:Vector3D = new Vector3D();
        
        function main() {
            Wonderfl.disable_capture();
            ptolemy = new Ptolemy(this, 8, 8, 450, 450);
            ptolemy.addEventListener(Event.COMPLETE, setup);
            ptolemy.load(new URLRequest(cceURL), "cce", "img", true);
            ptolemy.load(new URLRequest(envURL), "env", "img", true);
        }
        
        
        private function setup(e:Event) : void {
            var px:Number, py:Number, i:int, j:int, k:int, i0:int;
            var context3D:Context3D = ptolemy.context3D, prog:Program3D;
            removeEventListener(Event.COMPLETE, setup);
            
            context3D.enableErrorChecking = true;
            
            // create shape 
            _mesh.allocateBuffer(context3D);
            _mesh.upload(true, true);

            // init lighting stuff
            _occlusionTex = context3D.createTexture(0x200, 0x200, "bgra", true);
            _emissionTex = context3D.createTexture(0x80, 0x80, "bgra", false);
            _fxSrcTex = context3D.createTexture(0x80, 0x80, "bgra", true);
            _fxDstTex = context3D.createTexture(0x80, 0x80, "bgra", true);
            _emissionBmd = new BitmapData(0x80, 0x80, false, 0);
            var s:Shape = new Shape();
            var m:Matrix = new Matrix();
            m.createGradientBox(0x80, 0x80);
            s.graphics.beginGradientFill("radial", [0x100804, 0], [1, 1], [0x20, 0xff], m);
            s.graphics.drawRect(0, 0, 0x80, 0x80);
            _emissionBmd.draw(s);
            _emissionTex.uploadFromBitmapData(_emissionBmd);
            _fxVerticesBuffer = context3D.createVertexBuffer(3, 5);
            _fxIndicesBuffer  = context3D.createIndexBuffer(3);
            _fxVerticesBuffer.uploadFromVector(_fxIdVertices, 0, 3);
            _fxIndicesBuffer.uploadFromVector(_fxIndices, 0, 3);

            _shpereMap = ptolemy.resources["env"].bitmapData;
            _shpereTex = context3D.createTexture(512, 512, "bgra", false);
            _shpereTex.uploadFromBitmapData(_shpereMap);
            _specMap = new BitmapData(256, 1, false, 0);
            _specTex = context3D.createTexture(256, 1, "bgra", false);
            for (i=0; i<shaders.length; i++) {
                prog = context3D.createProgram();
                prog.upload(asm.assemble("vertex", shaders[i].vs), asm.assemble("fragment", shaders[i].fs));
                programs.push(prog);
            }
            context3D.setProgramConstantsFromVector("vertex",   9, Vector.<Number>([0, 0.5, 1, 2]));
            context3D.setProgramConstantsFromVector("fragment", 9, Vector.<Number>([0, 0.5, 1, 2]));
            
            addChild(container = new Sprite());
            container.x = container.y = 8;
            var ColorChooserEx:Class = ptolemy.resources["cce"].getClass();
            new ColorChooserEx(container, 0, 0, _col,  function(e:Event):void { _col = e.target.value; updateColor();});
            new HUISlider(container, 0, 20, "ambient", function(e:Event):void { _amb = e.target.value; updateColor();}).setSliderParams(0,1,_amb);
            new HUISlider(container, 0, 40, "diffuse", function(e:Event):void { _dif = e.target.value; updateColor();}).setSliderParams(0,1,_dif);
            new HUISlider(container, 0, 60, "power",   function(e:Event):void { _pow = e.target.value; updateSpecMap();}).setSliderParams(0,64,_pow);
            new HUISlider(container, 0, 80, "specular",function(e:Event):void { _spc = e.target.value; updateSpecMap();}).setSliderParams(0,255,_spc);
            new HUISlider(container, 0, 100, "refrection",function(e:Event):void { _ref = e.target.value; }).setSliderParams(0,1,_ref);
            updateSpecMap();
            updateColor();

            _rotx = 0;
            _startTime = getTimer();
            addEventListener(Event.ENTER_FRAME, draw);
            

            _ifreq = 3.141592653589793/(60000/(80));
        }

        private function updateSpecMap() : void {
            for (var i:int=0; i<256; i++) {
                var c:int = int(Math.pow(i*0.0039215686, _pow)*_spc);
                _specMap.setPixel32(i, 0, ((c<255)?c:255)*0x10101);
            }
            _specTex.uploadFromBitmapData(_specMap);

        }
        
        private function updateColor() : void {
            var r:Number = ((_col>>16)&255)*0.00392156862745098,
                g:Number = ((_col>>8)&255)*0.00392156862745098,
                b:Number = (_col&255)*0.00392156862745098, difamb:Number = _dif - _amb;
            _difVectorDif[0] = r * difamb;
            _difVectorDif[1] = g * difamb;
            _difVectorDif[2] = b * difamb;
            _ambVector[0] = r * _amb;
            _ambVector[1] = g * _amb;
            _ambVector[2] = b * _amb;
        }
        
        private function draw(e:Event) : void {
            var context3D:Context3D = ptolemy.context3D,
                sigl:SiGLCore = ptolemy.sigl, 
                time:Number = getTimer() - _startTime;
            
            _rotx += Math.sin(time*0.0002) * 3;
            sigl.id().re(_rotx, time*0.002, time*0.005);
            sigl.magnification = 70;
            
            // lighting vector
            _lightPosition.x = 232-mouseX;
            _lightPosition.y = mouseY-232;
            _lightPosition.z = 50;
            _lightPosition.normalize();
            _halfVector.copyFrom(_lightPosition);
            _halfVector.z += 1;
            _halfVector.normalize();
            _inv.copyFrom(sigl.modelViewMatrix);
            _inv.invert();
            var l:Vector3D = _inv.deltaTransformVector(_lightPosition);
            var h:Vector3D = _inv.deltaTransformVector(_halfVector);
            
            // draw knot on _occlusionTex
            context3D.setRenderToTexture(_occlusionTex, true);
            context3D.clear(0, 0, 0, 0);
            context3D.setBlendFactors(Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);
            context3D.setProgram(programs[0]);
            context3D.setTextureAt(0, _specTex);
            context3D.setTextureAt(1, _shpereTex);
            context3D.setVertexBufferAt(0, _mesh.vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
            context3D.setVertexBufferAt(1, _mesh.vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_3);
            context3D.setProgramConstantsFromMatrix("vertex",   0, sigl.modelViewProjectionMatrix, true);
            context3D.setProgramConstantsFromMatrix("vertex",   4, sigl.modelViewMatrix, true);
            context3D.setProgramConstantsFromVector("fragment", 0, Vector.<Number>([l.x, l.y, l.z, 0]));
            context3D.setProgramConstantsFromVector("fragment", 1, Vector.<Number>([h.x, h.y, h.z, 0]));
            context3D.setProgramConstantsFromVector("fragment", 2, _ambVector);
            context3D.setProgramConstantsFromVector("fragment", 3, _difVectorDif);
            context3D.setProgramConstantsFromVector("fragment", 4, Vector.<Number>([_ref, 0, 0, 0]));
            context3D.drawTriangles(_mesh.indexBuffer, 0, _mesh.indices.length/3);

            // draw emission on _fxSrcTex
            context3D.setRenderToTexture(_fxSrcTex, false);
            context3D.clear();
            context3D.setBlendFactors(Context3DBlendFactor.ONE, Context3DBlendFactor.ZERO);
            context3D.setProgram(programs[1]);
            context3D.setTextureAt(0, _emissionTex);
            context3D.setTextureAt(1, null);
            context3D.setVertexBufferAt(0, _fxVerticesBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
            context3D.setVertexBufferAt(1, _fxVerticesBuffer, 3, Context3DVertexBufferFormat.FLOAT_2);
            context3D.drawTriangles(_fxIndicesBuffer, 0, 1);
            // erase occlusion from _fxSrcTex
            context3D.setBlendFactors(Context3DBlendFactor.ZERO, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);
            context3D.setTextureAt(0, _occlusionTex);
            context3D.drawTriangles(_fxIndicesBuffer, 0, 1);
            // draw effect layers
            var s:Number = 1 + (_fxScale-1) / (1 << _fxPasses); // scale multiplier
            var v:Vector.<Number> = new <Number> [
                -1,  1,  0,   0, 0,
                -1, -3,  0,   0, 2,
                3,   1,  0,   2, 0
            ];
            context3D.setBlendFactors(Context3DBlendFactor.ONE, Context3DBlendFactor.ONE);
            for(var i:int=0; i<_fxPasses; i++) {
                v[0]  = s*-1; v[1]  = s*1;
                v[5]  = s*-1; v[6]  = s*-3;
                v[10] = s*3;  v[11] = s*1;
                _fxVerticesBuffer.uploadFromVector(_fxIdVertices, 0, 3);
                context3D.setRenderToTexture(_fxDstTex, false);
                context3D.setTextureAt(0, _fxSrcTex);
                context3D.clear();
                context3D.drawTriangles(_fxIndicesBuffer, 0, 1);
                _fxVerticesBuffer.uploadFromVector(v, 0, 3);
                context3D.drawTriangles(_fxIndicesBuffer, 0, 1);
                s *= s;
                var tmp:Texture = _fxSrcTex;
                _fxSrcTex = _fxDstTex;
                _fxDstTex = tmp;
            }
            // assemble everything
            _fxVerticesBuffer.uploadFromVector(_fxIdVertices, 0, 3);
            context3D.setRenderToBackBuffer();
            context3D.clear(0.5,0.25,0.125,1);
            context3D.setBlendFactors(Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);
            context3D.setTextureAt(0, _occlusionTex);
            context3D.drawTriangles(_fxIndicesBuffer, 0, 1);
            context3D.setBlendFactors(Context3DBlendFactor.ONE, Context3DBlendFactor.ONE);
            context3D.setTextureAt(0, _fxSrcTex);
            context3D.drawTriangles(_fxIndicesBuffer, 0, 1);

            // if(!_sc) { 
            //     _sc = new BitmapData(450, 450, false);
            //     with(addChildAt(new Bitmap(_sc), 0)){x=y=8};
            //     context3D.drawToBitmapData(_sc);
            // }

            context3D.present();
        }
        private var _sc:BitmapData;
        private var _inv:Matrix3D = new Matrix3D();
    }
}

var vs0:String = <agal><![CDATA[
m44 op, va0, vc0
nrm vt0.xyz, va1.xyz
mov vt0.w, vc9.x
mov v0, vt0
m44 vt0, vt0, vc4
mul vt0, vt0, vc9.yyy
add v1,  vt0, vc9.yyy
]]></agal>;
var fs0:String = <agal><![CDATA[
dp3 ft0, v0, fc0
sat ft0, ft0
mul ft0, fc3, ft0
add ft0, ft0, fc2
dp3 ft1, v0, fc1
tex ft3, ft1.xy, fs0 <2d,clamp,nearest>
tex ft4, v1.xy, fs1 <2d,repeat,nearest>
mul ft4, ft4, fc9.w
sub ft4, ft4, fc9.z
mul ft4, ft4, fc4.x
sat ft2, ft4
add ft4, ft4, fc9.z
sat ft1, ft4
add ft0, ft0, ft2
mul ft0, ft0, ft1
add oc, ft0, ft3
]]></agal>;

var vs1:String = <><![CDATA[
mov op, va0 // return xyz coords
mov v0, va1 // store uv in v0
]]></>
var fs1:String = <><![CDATA[
tex oc, v0, fs0 <linear> // sample input at uv coords
]]></>

var shaders:Array = [
{"vs":vs0,"fs":fs0},
{"vs":vs1,"fs":fs1}
];





/* Tiny Ptolemy */ {
    import flash.net.*;
    import flash.geom.*;
    import flash.events.*;
    import flash.system.*;
    import flash.display.*;
    import flash.display3D.*;
    import com.adobe.utils.*;

    /** Operation Center */
    class Ptolemy extends EventDispatcher {
    // variables ----------------------------------------
        public var context3D:Context3D;
        public var sigl:SiGLCore;
        public var resources:* = {};

        private var _loadedResourceCount:int;
    // constructor ----------------------------------------
        function Ptolemy(parent:DisplayObjectContainer, xpos:Number, ypos:Number ,width:int, height:int) : void {
            var stage:Stage = parent.stage, stage3D:Stage3D = stage.stage3Ds[0];
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;
            stage.quality = StageQuality.LOW;
            stage3D.x = xpos; stage3D.y = ypos;
            stage3D.addEventListener(Event.CONTEXT3D_CREATE, function(e:Event):void{
                context3D = e.target.context3D;
                if (context3D) {
                    context3D.enableErrorChecking = true;                   // check internal error
                    context3D.configureBackBuffer(width, height, 0, false);  // disable AA/ enable depth/stencil
                    context3D.setBlendFactors(Context3DBlendFactor.SOURCE_ALPHA, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);
                    context3D.setCulling(Context3DTriangleFace.NONE);       // culling back face
                    context3D.setRenderToBackBuffer();
                    sigl = new SiGLCore(width, height);
                    dispatchEvent(e.clone());
                    if (--_loadedResourceCount == 0) dispatchEvent(new Event(Event.COMPLETE));
                } else dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, "Context3D not found"));
            });
            stage3D.requestContext3D();
            _loadedResourceCount = 1;
        }
    // load resource ----------------------------------------
        public function load(urlRequest:URLRequest, id:String=null, type:String=null, checkPolicyFile:Boolean=false) : EventDispatcher {
            var loader:Loader, urlLoader:URLLoader;
            _loadedResourceCount++;
            if (type == "img") {
                loader = new Loader();
                loader.load(urlRequest, new LoaderContext(checkPolicyFile));
                loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e:Event) : void {
                    resources[id] = e.target.content;
                    if (--_loadedResourceCount == 0) dispatchEvent(new Event(Event.COMPLETE));
                });
                return loader;
            }
            urlLoader = new URLLoader(urlRequest);
            urlLoader.dataFormat = (type == "txt") ? URLLoaderDataFormat.TEXT : URLLoaderDataFormat.BINARY;
            urlLoader.addEventListener(Event.COMPLETE, function(e:Event) : void {
                resources[id] = e.target.data;
                if (--_loadedResourceCount == 0) dispatchEvent(new Event(Event.COMPLETE));
            });
            return urlLoader;
        }
    }
    
    /** SiGLCore provides basic matrix operations. */
    class SiGLCore {
    // variables ----------------------------------------
        public var modelViewMatrix:Matrix3D = new Matrix3D();
        public var projectionMatrix:PerspectiveMatrix3D = new PerspectiveMatrix3D();
        public var viewWidth:Number, viewHeight:Number;
        private var _mvMatrixStac:Vector.<Matrix3D> = new Vector.<Matrix3D>();
        private var _mvpMatrix:Matrix3D = new Matrix3D(), _mvpMatrixDirty:Boolean, _toDegree:Number, _toRadian:Number;
        private var _cameraPosition:Vector3D = new Vector3D(0,0,0), _magnification:Number, _zNear:Number, _zFar:Number, _fieldOfView:Number;
        static private var _tv:Vector.<Number> = new Vector.<Number>(16, true), _tm:Matrix3D = new Matrix3D();
    // properties ----------------------------------------
        public function get modelViewProjectionMatrix() : Matrix3D {
            if (_mvpMatrixDirty) {
                _mvpMatrix.copyFrom(projectionMatrix);
                _mvpMatrix.prepend(modelViewMatrix);
                _mvpMatrixDirty = false;
            }
            return _mvpMatrix;
        }
        public function get angleMode() : String { return (_toRadian == 1) ? "radian" : "degree"; }
        public function set angleMode(mode:String) : void {
            _toDegree = (mode == "radian") ? 57.29577951308232 : 1;
            _toRadian = (mode == "radian") ? 1 : 0.017453292519943295;
        }
        public function get fieldOfView() : Number { return _fieldOfView / _toRadian; }
        public function set fieldOfView(fov:Number) : void { _fieldOfView = fov * _toRadian; _updateProjectionMatrix(); }
        public function get magnification() : Number { return _magnification; }
        public function set magnification(mag:Number) : void { _magnification = mag; _updateProjectionMatrix(); }
        public function get cameraPosition() : Vector3D { return _cameraPosition; }
    // constructor ----------------------------------------
        function SiGLCore(width:Number=1, height:Number=1) {
            viewWidth = width;
            viewHeight = height;
            angleMode = "degree";
            _zNear = -100;
            _zFar = 100;
            _magnification = 1;
            modelViewMatrix.identity();
            this.fieldOfView = 60;
            _mvpMatrixDirty = true;
        }
    // matrix operations ----------------------------------------
        public function forceUpdateMatrix() : SiGLCore { _mvpMatrixDirty = true; return this; }
        public function setZRange(zNear:Number=-100, zFar:Number=100) : SiGLCore { _zNear = zNear; _zFar = zFar; _updateProjectionMatrix(); return this; }
        public function clear() : SiGLCore { _mvMatrixStac.length = 0; return id(); }
        public function id() : SiGLCore { modelViewMatrix.identity(); _mvpMatrixDirty = true; return this; }
        public function push() : SiGLCore { _mvMatrixStac.push(modelViewMatrix.clone()); return this; }
        public function pop() : SiGLCore { modelViewMatrix = _mvMatrixStac.pop(); return this; }
        public function r(angle:Number, axis:Vector3D, pivotPoint:Vector3D = null) : SiGLCore { modelViewMatrix.prependRotation(angle*_toDegree, axis, pivotPoint); _mvpMatrixDirty = true; return this; }
        public function s(scaleX:Number, scaleY:Number, scaleZ:Number=1) : SiGLCore { modelViewMatrix.prependScale(scaleX, scaleY, scaleZ); _mvpMatrixDirty = true; return this; }
        public function t(x:Number, y:Number, z:Number=0) : SiGLCore { modelViewMatrix.prependTranslation(x, y, z); _mvpMatrixDirty = true; return this; }
        public function re(angleX:Number, angleY:Number, angleZ:Number) : SiGLCore {
            var rx:Number = angleX*_toRadian, ry:Number=angleY*_toRadian, rz:Number=angleZ*_toRadian,
                sx:Number = Math.sin(rx), sy:Number = Math.sin(ry), sz:Number = Math.sin(rz), 
                cx:Number = Math.cos(rx), cy:Number = Math.cos(ry), cz:Number = Math.cos(rz);
            _tv[0] = cz*cy; _tv[1] = sz*cy; _tv[2] = -sy; _tv[4] = -sz*cx+cz*sy*sx; _tv[5] = cz*cx+sz*sy*sx;
            _tv[6] = cy*sx; _tv[8] = sz*sx+cz*sy*cx; _tv[9] = -cz*sx+sz*sy*cx;
            _tv[10] = cy*cx; _tv[14] = _tv[13] = _tv[12] = _tv[11] = _tv[7] = _tv[3] = 0; _tv[15] = 1;
            _tm.copyRawDataFrom(_tv); modelViewMatrix.prepend(_tm); _mvpMatrixDirty = true;
            return this;
        }
        private function _updateProjectionMatrix() : void {
            var aspect:Number = viewWidth / viewHeight;
            _cameraPosition.z = (viewHeight * 0.5) / Math.tan(_fieldOfView * 0.5);
            if (_zNear <= _cameraPosition.z) _zNear = _cameraPosition.z + 0.001;
            projectionMatrix.perspectiveFieldOfViewRH(_fieldOfView, aspect, _zNear - _cameraPosition.z, _zFar - _cameraPosition.z);
            projectionMatrix.prependTranslation(-_cameraPosition.x, -_cameraPosition.y, -_cameraPosition.z);
            projectionMatrix.prependScale(_magnification, _magnification, _magnification);
        }
    }


    /** Mesh */
    class Mesh {
    // variables ----------------------------------------
        public var vertices:Vector.<Number> = new Vector.<Number>();
        public var faces:Vector.<Face> = new Vector.<Face>();
        public var vertexBuffer:VertexBuffer3D;
        public var indexBuffer:IndexBuffer3D;
        public var data32PerVertex:int;
        private var _indices:Vector.<uint> = new Vector.<uint>(), _indexDirty:Boolean=true;
        private var _normalOffset:int;
    // properties ----------------------------------------
        public function get vertexCount() : int { return vertices.length / data32PerVertex; }
        public function set vertexCount(count:int) : void { vertices.length = count * data32PerVertex; }
        public function get indices() : Vector.<uint> {
            var idx:Vector.<uint> = _indices, f:Face, i:int, imax:int, j:int;
            if (_indexDirty) {
                idx.length = imax = faces.length * 3;
                for (i=0,j=0; i<imax; j++) {
                    f = faces[j];
                    idx[i] = f.i0; i++;
                    idx[i] = f.i1; i++;
                    idx[i] = f.i2; i++;
                }
                _indexDirty = false;
            }
            return idx;
        }
    // contructor ----------------------------------------
        function Mesh(bufferFormat:String="V3") {
            var res:* = /(V(\d))(N(\d))?(T(\d))?(C(\d))?/.exec(bufferFormat);
            data32PerVertex = int(res[2]) + int(res[4]) + int(res[6]) + int(res[8]);
            _normalOffset = (int(res[4]) > 0) ? int(res[2]) : -1;
            this.data32PerVertex = data32PerVertex;
        }
    // oprations ----------------------------------------
        public function allocateBuffer(context3D:Context3D) : Mesh {
            vertexBuffer = context3D.createVertexBuffer(vertexCount, data32PerVertex);
            indexBuffer  = context3D.createIndexBuffer(indices.length);
            return this;
        }
        public function upload(vertex:Boolean=true, index:Boolean=true) : Mesh {
            if (vertex) vertexBuffer.uploadFromVector(vertices, 0, vertexCount);
            if (index) indexBuffer.uploadFromVector(indices, 0, indices.length);
            return this;
        }
        /** register face */
        public function face(i0:int, i1:int, i2:int, mat:int=0) : Mesh {
            faces.push(Face.alloc(i0, i1, i2));
            _indexDirty = true;
            return this;
        }
        public function qface(i0:int, i1:int, i2:int, i3:int) : Mesh {
            faces.push(Face.alloc(i0, i1, i2), Face.alloc(i3, i2, i1));
            _indexDirty = true;
            return this;
        }
        public function updateFaceNormal(updateVertexNormal:Boolean=true) : Mesh {
            var vtx:Vector.<Number> = vertices, vcount:int = vertexCount, fcount:int = faces.length, 
                i:int, istep:int, f:Face, iw:Number, fidx:int, 
                i0:int, i1:int, i2:int, n0:Vector3D, n1:Vector3D, n2:Vector3D, 
                x01:Number, x02:Number, y01:Number, y02:Number, z01:Number, z02:Number;
            // calculate face normals
            for (i=0; i<fcount; i++) {
                f=faces[i];
                i0=f.i0*data32PerVertex; i1=f.i1*data32PerVertex; i2=f.i2 * data32PerVertex;
                x01 = vtx[i1]-vtx[i0]; x02 = vtx[i2]-vtx[i0]; i0++; i1++; i2++;
                y01 = vtx[i1]-vtx[i0]; y02 = vtx[i2]-vtx[i0]; i0++; i1++; i2++;
                z01 = vtx[i1]-vtx[i0]; z02 = vtx[i2]-vtx[i0];
                f.normal.setTo(y02*z01-y01*z02, z02*x01-z01*x02, x02*y01-x01*y02);
                f.normal.normalize();
            }
            // calculate vertex normals
            if (updateVertexNormal && (_normalOffset != -1)) {
                istep = data32PerVertex - 2;
                // initialize
                for (i=0, i0=_normalOffset; i<vcount; i++, i0+=istep) { vtx[i0]=0; i0++; vtx[i0]=0; i0++; vtx[i0]=0; }
                // sum up
                for (i=0; i<fcount; i++) {
                    f = faces[i];
                    i0 = f.i0 * data32PerVertex + _normalOffset;
                    vtx[i0]+=f.normal.x; i0++; vtx[i0]+=f.normal.y; i0++; vtx[i0]+=f.normal.z;
                    i0 = f.i1 * data32PerVertex + _normalOffset;
                    vtx[i0]+=f.normal.x; i0++; vtx[i0]+=f.normal.y; i0++; vtx[i0]+=f.normal.z;
                    i0 = f.i2 * data32PerVertex + _normalOffset;
                    vtx[i0]+=f.normal.x; i0++; vtx[i0]+=f.normal.y; i0++; vtx[i0]+=f.normal.z;
                }
                /*  normalize vertex vector (if need)
                for (i=0, i0=_normalOffset; i<vcount; i++, i0+=istep) {
                    x01 = vtx[i0]; i0++; y01 = vtx[i0]; i0++; z01 = vtx[i0]; i0-=2;
                    iw = 1 / Math.sqrt(x01*x01 + y01*y01 + z01*z01);
                    vtx[i0] = x01 * iw; i0++; vtx[i0] = y01 * iw; i0++; vtx[i0] = z01 * iw;
                }//*/
            }
            return this;
        }
    }


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

class Knot extends Mesh {
    private var _p:int, _q:int, _sections:int, _slices:int, _sides:int, _thickness:Number, _offset:Number;
    public var sideNormals:Vector.<Number>;

    public function Knot(p:int = 2, q:int = 3, sections:int = 72, sides:int = 4, thickness:Number = 2/3, offset:Number = 0) {
        var numVerts:int = sections*sides*8;
        _p = p;
        _q = q;
        _sections = sections;
        _slices = sections * 2;
        _sides = sides;
        _thickness = thickness;
        _offset = offset;
        super("V3N3");
        sideNormals = new Vector.<Number>(_slices*_sides*3, true)
        _createVertices();
        _createFaces();
        updateFaceNormal(true);
    }

    protected function _createFaces():void {
        for(var section:int=0; section<_sections; section++) {
            for(var side:int=0; side<_sides; side++) {
                var i:int = section*_sides*8 + side*8;
                // section
                face(i+1, i+0, i+4).face(i+4, i+5, i+1)
                i++;
                face(i+1, i+0, i+4).face(i+4, i+5, i+1)
                i++;
                face(i+1, i+0, i+4).face(i+4, i+5, i+1)
                i--; i--;
                // connector
                var i0:int, i1:int, i2:int, i3:int;
                i0=i+5; i1=i+6;
                i += _sides*8;
                if(section+1 == _sections) {
                    i+=8; // compensate for twist
                    if(side+1 == _sides) i = 0;
                }
                i %= vertexCount;
                i2=i+1; i3=i+2;
                face(i0, i2, i1); face(i2, i3, i1);
            }
        }
    }

    protected function _createVertices():void {
        var i:int = 0;
        function _vertex(v:Vector3D, texv:Number):void {
            vertices[i++] = v.x;
            vertices[i++] = v.y;
            vertices[i++] = v.z;
            vertices[i++] = 0; // space for normals
            vertices[i++] = 0;
            vertices[i++] = 0;
        }
        for(var section:int=0; section<_sections; section++) {
            for(var side:int=0; side<_sides; side++) {
                var p:Number;
                for each(p in [0, 0.4, 0.6, 1]) _vertex(_edgePoint(section*2, side, p), p);
                for each(p in [0, 0.4, 0.6, 1]) _vertex(_edgePoint(section*2+1, side, p), p);
            }
        }
    }

    // 0 <= p <= 1 is the offset along the edge
    protected function _edgePoint(slice:int, side:int, p:Number):Vector3D {
        var u:Number = Math.PI*2 * slice/_slices;
        var v:Number = Math.PI*2 * side/_sides - u*(_sides-1)/_sides;
        v -= Math.PI / _sides; // make side 0 parallel to Z axis
        var v0:Vector3D = _extrudedPoint(u, v, _thickness);
        var v1:Vector3D = _extrudedPoint(u, v + Math.PI*2/_sides, _thickness);
        return _vlerp(v0, v1, p);
    }

    protected function _extrudedPoint(u:Number, v:Number, length:Number):Vector3D {
        v -= Math.PI*2*_offset/_sections*(_sides-1)/_sides;
        // calculate curve point and normal vectors from u
        var cp:Vector3D = _curvePoint(u);
        var ncp:Vector3D = _curvePoint(u+0.0001);
        var t:Vector3D = ncp.subtract(cp);
        var n:Vector3D = ncp.add(cp);
        var b:Vector3D = t.crossProduct(n);
        n = b.crossProduct(t);
        n.normalize();
        b.normalize();
        // extrude in direction v
        var cx:Number = length * Math.sin(v);
        var cy:Number = length * -Math.cos(v);
        return new Vector3D(
            cp.x + cx*n.x + cy*b.x, 
            cp.y + cx*n.y + cy*b.y, 
            cp.z + cx*n.z + cy*b.z
        );
    }

    protected function _curvePoint(t:Number):Vector3D {
        t += Math.PI*2*_offset/_sections;
        return new Vector3D(
            2 * Math.sin(t*_p) - Math.sin(t),
            -(2 * Math.cos(t*_p) + Math.cos(t)),
            Math.sin(t*_q)
        );
    }

    protected function _vlerp(v0:Vector3D, v1:Vector3D, p:Number):Vector3D {
        return new Vector3D(
            v0.x*(1-p) + v1.x*p,
            v0.y*(1-p) + v1.y*p,
            v0.z*(1-p) + v1.z*p,
            v0.w*(1-p) + v1.w*p
        );
    }
}