Cloth Simulation 3D

by saharan
3Dの布シミュレーション

クリックでボール発射、ドラッグで回転

Click : Launch ball!
Drag : Rotate

今回はばねの計算を後退オイラー法で解いているので動きが安定しています。
ボール同士の衝突も一応あります。
(時々布がくるっとなるのは自己衝突を考えていないためです)

所々コメント書いてるので興味のある方は参考にどうぞ。
♥109 | Line 768 | Modified 2012-02-08 20:35:06 | MIT License
play

ActionScript3 source code

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

package {
    import com.adobe.utils.*;
    import flash.display.*;
    import flash.display3D.*;
    import flash.display3D.textures.*;
    import flash.events.*;
    import flash.net.*;
    import flash.geom.*;
    import net.hires.debug.*;
    
    /**
     * Cloth Simulation 3D
     * @author saharan
     */
    [SWF(width = "465", height = "465", frameRate = "60")]
    public class ClothSim3D extends Sprite {
        private var gl:EGraphics;
        private var cloth:EMesh;
        private var table:EMesh;
        private var ball:EMesh;
        private var res:EResourceLoader;
        private var s1:EShader;
        private var s2:EShader;
        private var rx:Number;
        private var ry:Number;
        private var rvx:Number;
        private var rvy:Number;
        private var press:Boolean;
        private var pmouseX:Number;
        private var pmouseY:Number;
        private var vs:Vector.<Vtx>;
        private var ss:Vector.<Spg>;
        private var bs:Vector.<Ball>;
        private var cs:Vector.<Cst>;
        private var numv:uint;
        private var nums:uint;
        private var numc:uint;
        
        public function ClothSim3D() {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null): void {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.addEventListener(MouseEvent.MOUSE_DOWN, function(e:Event):void { press = true; });
            stage.addEventListener(MouseEvent.MOUSE_UP, function(e:Event):void { press = false; });
            rx = 0;
            ry = 0;
            rvx = 0;
            rvy = 0;
            pmouseX = 0;
            pmouseY = 0;
            vs = new Vector.<Vtx>();
            ss = new Vector.<Spg>();
            bs = new Vector.<Ball>();
            cs = new Vector.<Cst>();
            //
            gl = new EGraphics(stage.stage3Ds[0], 465, 465, start);
        }
        
        public function createBall(divV:uint, divH:uint, radius:Number):EMesh {
            var mesh:EMesh = new EMesh();
            var theta:Number;
            var phi:Number;
            var dTheta:Number = Math.PI * 2 / divV;
            var dPhi:Number = Math.PI / divH;
            var numVertices:uint = (divH + 1) * divV - (divV - 1) * 2;
            mesh.addVertex(0, radius, 0);
            phi = dPhi;
            for (var i:int = 1; i < divH; i++) {
                theta = Math.PI * 2;
                for (var j:int = 0; j < divV; j++) {
                    var index:int = (i - 1) * divV + j + 1;
                    mesh.addVertex(radius * Math.sin(phi) * Math.cos(theta), radius * Math.cos(phi), radius * Math.sin(phi) * Math.sin(theta), j / divV, i / divH);
                    theta -= dTheta;
                }
                phi += dPhi;
            }
            mesh.addVertex(0, -radius, 0);
            for (i = 0; i < divH; i++) {
                for (j = 0; j < divV; j++) {
                    if (i == 0) {
                        mesh.addFace(0, (j + 1) % divV + 1, j + 1);
                    } else if (i == divH - 1) {
                        mesh.addFace(numVertices - 1, (i - 1) * divV + j + 1, (i - 1) * divV + (j + 1) % divV + 1);
                    } else {
                        mesh.addFace((i - 1) * divV + j + 1, (i - 1) * divV + (j + 1) % divV + 1, i * divV + (j + 1) % divV + 1);
                        mesh.addFace((i - 1) * divV + j + 1, i * divV + (j + 1) % divV + 1, i * divV + j + 1);
                    }
                }
            }
            return mesh;
        }
        
        public function createClothMesh(divV:uint, divH:uint, size:Number):EMesh {
            var mesh:EMesh = new EMesh();
            var x:Number = -size * 0.5;
            var y:Number = size * 0.5;
            var dx:Number = size / divV;
            var dy:Number = -size / divH;
            for (var j:int = 0; j <= divH; j++) {
                x = -size * 0.5;
                for (var i:int = 0; i <= divV; i++) {
                    mesh.addVertex(x, y, 0, i / divV, j / divH);
                    x += dx;
                }
                y += dy;
            }
            for (i = 0; i < divH; i++) {
                for (j = 0; j < divV; j++) {
                    mesh.addFace(i * (divV + 1) + j, i * (divV + 1) + j + 1, (i + 1) * (divV + 1) + j);
                    mesh.addFace(i * (divV + 1) + j + 1, (i + 1) * (divV + 1) + j + 1, (i + 1) * (divV + 1) + j);
                }
            }
            return mesh;
        }
        
        private function start():void {
            s1 = new EShader(vertexShaderCode, fragmentShaderCode, gl.context3D);
            s2 = new EShader(vertexShaderCode, fragmentShaderCodeBall, gl.context3D);
            var dx:uint = 24; // この辺は適当計算なのでいじると変になる
            var dy:uint = 24;
            var size:uint = 250;
            var x:Number;
            var y:Number = 0;
            var d:Number = size / dx;
            numv = 0;
            numc = 0;
            for (var j:int = 0; j <= dy; j++) {
                x = -size * 0.5;
                for (var i:int = 0; i <= dx; i++) {
                    var tv:Vector3D = new Vector3D(x, size * 0.7, y);
                    vs[numv++] = new Vtx(tv.x, tv.y, tv.z, j == 0 && (i % 12 == 0));
                    x += d;
                }
                y -= d;
            }
            nums = 0;
            for (j = 0; j <= dy; j++) {
                for (i = 0; i <= dx; i++) {
                    var idx:int = j * (dx + 1) + i;
                    if (i < dx) ss[nums++] = new Spg(vs[idx], vs[idx + 1], 0.6);
                    if (j < dy) ss[nums++] = new Spg(vs[idx], vs[idx + dx + 1], 0.6);
                    if (i < dx - 1) ss[nums++] = new Spg(vs[idx], vs[idx + 2], 0.3);
                    if (j < dy - 1) ss[nums++] = new Spg(vs[idx], vs[idx + (dx + 1) * 2], 0.3);
                    if (i < dx && j < dy) ss[nums++] = new Spg(vs[idx], vs[idx + dx + 2], 0.5);
                }
            }
            ball = createBall(16, 8, 1);
            ball.initVertexBuffer(gl);
            ball.initIndexBuffer(gl);
            ball.calcNormals();
            cloth = createClothMesh(dx, dy, size);
            cloth.initVertexBuffer(gl);
            cloth.initIndexBuffer(gl);
            table = new EMesh();
            table.addVertex(-200, -102, -200, 0, 0, 0.8, 0.8, 0.8);
            table.addVertex(200, -102, -200, 0, 0, 0.8, 0.8, 0.8);
            table.addVertex(200, -102, 200, 0, 0, 0.8, 0.8, 0.8);
            table.addVertex(-200, -102, 200, 0, 0, 0.8, 0.8, 0.8);
            table.addFace(0, 1, 2);
            table.addFace(0, 2, 3);
            table.initVertexBuffer(gl);
            table.initIndexBuffer(gl);
            table.calcNormals();
            var prj:PerspectiveMatrix3D = new PerspectiveMatrix3D();
            prj.perspectiveFieldOfViewRH(60 * Math.PI / 180, 1, 0.01, 2000);
            gl.context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 4, prj, true);
            stage.addEventListener(MouseEvent.MOUSE_DOWN, function(e:Event = null):void {
                var ang:Number = (ry - 90) * Math.PI / 180 + Math.random() * 0.5 - 0.25;
                var sin:Number = Math.sin(ang);
                var cos:Number = Math.cos(ang);
                var ang2:Number = ang + Math.random() * 0.25 - 0.125;
                var sin2:Number = Math.sin(ang2);
                var cos2:Number = Math.cos(ang2);
                bs.push(new Ball(-cos * 200, 50, -sin * 200, cos2 * 12, Math.random() * 3, sin2 * 12));
            } );
            addEventListener(Event.ENTER_FRAME, frame);
        }
        
        private function frame(e:Event = null): void {
            if (press) {
                rvx += (mouseY - pmouseY) * 0.0625;
                rvy += (mouseX - pmouseX) * 0.0625;
            }
            pmouseX = mouseX;
            pmouseY = mouseY;
            rvx *= 0.9;
            rvy *= 0.9;
            rx += rvx;
            ry += rvy;
            if (rx > 90) rx += (90 - rx) * 0.5;
            if (rx < -90) rx += (-90 - rx) * 0.5;
            //
            simulate();
            var vts:Vector.<Number> = cloth.vertices;
            for (var i:int = EGraphics.OFFSET_VERTEX, j:int = 0; i < vts.length; i += EGraphics.NUM_DATAS_IN_VERTEX, j++) {
                vts[i] = vs[j].x;
                vts[i + 1] = vs[j].y;
                vts[i + 2] = vs[j].z;
            }
            cloth.calcNormals();
            //
            var mdl:Matrix3D = new Matrix3D();
            mdl.prependTranslation(0, 0, -400);
            mdl.prependRotation(rx, Vector3D.X_AXIS);
            mdl.prependRotation(ry, Vector3D.Y_AXIS);
            var lightVec:Vector3D = new Vector3D(0, -1, -2);
            lightVec.normalize();
            gl.context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([lightVec.x, lightVec.y, lightVec.z, 1]));
            //
            gl.beginScene(0, 0, 0);
            gl.setShader(s1);
            gl.context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mdl, true);
            gl.context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 8, Vector.<Number>([0.8, 0.2, 0.2, 1]));
            gl.context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 11, Vector.<Number>([-1, 0, 0, 1]));
            gl.context3D.setCulling(Context3DTriangleFace.FRONT);
            gl.renderMesh(cloth); // render back
            gl.context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 11, Vector.<Number>([1, 0, 0, 1]));
            gl.context3D.setCulling(Context3DTriangleFace.BACK);
            gl.renderMesh(cloth); // render front
            //
            gl.setShader(s2);
            gl.context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 8, Vector.<Number>([0.8, 0.8, 0.8, 1]));
            gl.renderMesh(table);
            var numb:uint = bs.length;
            for (i = 0; i < numb; i++) {
                var mtx:Matrix3D = mdl.clone();
                mtx.prependTranslation(bs[i].x, bs[i].y, bs[i].z);
                mtx.prependScale(bs[i].r - 3, bs[i].r - 3, bs[i].r - 3);
                gl.context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mtx, true);
                gl.context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 8, bs[i].c);
                gl.renderMesh(ball);
            }
            //
            gl.endScene();
        }
        
        private function simulate():void {
            var i:int;
            var j:int;
            while (numc > 0) cs[--numc] = null; // for GC
            var numb:uint = bs.length;
            for (j = 0; j < numb; j++) { // 簡易衝突判定 (n^2)
                var c:Cst;
                for (i = 0; i < numv; i++) {
                    c = vs[i].collide(bs[j]); // 球と布の接触
                    if (c) cs[numc++] = c;
                }
                for (i = 0; i < j; i++) {
                    c = bs[i].collide(bs[j]); // 球同士の接触
                    if (c) cs[numc++] = c;
                }
            }
            for (j = 0; j < 4; j++) { // 連立方程式を解く (反復回数で精度が上下)
                for (i = 0; i < nums; i++) ss[i].solve();
                for (i = 0; i < numc; i++) cs[i].solve();
                for (i = 0; i < numv; i++) { // 床との接触
                    var v:Vtx = vs[i];
                    if (v.y < -100) if (v.vy < 0) v.vy = 0;
                }
                for (i = 0; i < numb; i++) { // 床との接触
                    var b:Ball = bs[i];
                    if (b.x > -200 && b.x < 200 && b.z > -200 && b.z < 200 && b.y <= -100 + b.r && b.vy < 0) b.vy *= -0.4;
                }
            }
            for (i = 0; i < numb; i++) {
                bs[i].move();
                if (bs[i].y < -300) bs.splice(i--, 1), numb--; // 落下判定
            }
            for (i = 0; i < numv; i++) vs[i].move();
        }
    }
}

class Vtx { // Vertex : 質点
    public var x:Number;
    public var y:Number;
    public var z:Number;
    public var vx:Number;
    public var vy:Number;
    public var vz:Number;
    public var fix:Boolean;
    public var m:Number; // 質量の逆数
    
    public function Vtx(x:Number, y:Number, z:Number, fix:Boolean) {
        this.x = x;
        this.y = y;
        this.z = z;
        vx = Math.random() * 0.01 - 0.005;
        vy = Math.random() * 0.01 - 0.005;
        vz = Math.random() * 0.01 - 0.005;
        this.fix = fix;
        m = fix ? 0 : 1; // 固定なら質量無限 (逆数0)
    }
    
    public function move():void {
        if (fix) vx = vy = vz = 0;
        else {
            x += vx;
            y += vy;
            if (y < -100) y = -100;
            z += vz;
            vy -= 0.15;
        }
    }
    
    public function collide(b:Ball):Cst {
        var dx:Number = x - b.x;
        if (dx < -b.r || dx > b.r) return null;
        var dy:Number = y - b.y;
        if (dy < -b.r || dy > b.r) return null;
        var dz:Number = z - b.z;
        if (dz < -b.r || dz > b.r) return null;
        var d2:Number = dx * dx + dy * dy + dz * dz;
        if (d2 < b.r * b.r) {
            var d:Number = 1 / Math.sqrt(d2);
            dx *= d;
            dy *= d;
            dz *= d;
            return new Cst(this, b, null, dx, dy, dz, 0);
        }
        return null;
    }
}

class Spg { // Spring : ばねダンパモデル
    public var v1:Vtx;
    public var v2:Vtx;
    private var rest:Number; // 休息距離
    private var str:Number;
    private var m:Number;
    
    public function Spg(v1:Vtx, v2:Vtx, str:Number) {
        this.v1 = v1;
        this.v2 = v2;
        this.str = str;
        m = 1 / (v1.m + v2.m); // 適正質量
        rest = Math.sqrt((v2.x - v1.x) * (v2.x - v1.x) + (v2.y - v1.y) * (v2.y - v1.y) + (v2.z - v1.z) * (v2.z - v1.z));
    }
    
    public function solve():void { // 後退オイラー法
        if (v1.m + v2.m == 0) return;
        var dx:Number = v2.x - v1.x + v2.vx - v1.vx; // t+1 時でのばね係数を計算
        var dy:Number = v2.y - v1.y + v2.vy - v1.vy;
        var dz:Number = v2.z - v1.z + v2.vz - v1.vz;
        var dist:Number = Math.sqrt(dx * dx + dy * dy + dz * dz);
        var fs:Number = (dist - rest) * str;
        dist = 1 / dist;
        var nx:Number = dx * dist;
        var ny:Number = dy * dist;
        var nz:Number = dz * dist;
        var fd:Number = ((v2.vx - v1.vx) * nx + (v2.vy - v1.vy) * ny + (v2.vz - v1.vz) * nz) * 0.4 * str;
        var f:Number = (fs + fd) * m;
        var fx:Number = nx * f;
        var fy:Number = ny * f;
        var fz:Number = nz * f;
        v1.vx += fx * v1.m;
        v1.vy += fy * v1.m;
        v1.vz += fz * v1.m;
        v2.vx -= fx * v2.m;
        v2.vy -= fy * v2.m;
        v2.vz -= fz * v2.m;
    }
}

class Cst { // Constrant : 拘束 (接触点)
    public var v:Vtx;
    public var b1:Ball;
    public var b2:Ball;
    private var btb:Boolean;
    private var nx:Number;
    private var ny:Number;
    private var nz:Number;
    private var tv:Number;
    private var f:Number;
    private var m:Number;
    
    public function Cst(v:Vtx, b1:Ball, b2:Ball, nx:Number, ny:Number, nz:Number, tv:Number) {
        this.v = v;
        btb = !v;
        this.b1 = b1;
        this.b2 = b2;
        this.nx = nx;
        this.ny = ny;
        this.nz = nz;
        this.tv = tv; // 目標とする相対速度 (eを反発係数として -e * 法線方向の相対速度)
        if (btb) m = 1 / (b1.m + b2.m); // 適正質量
        else m = 1 / (v.m + b1.m);
        f = 0; // Warm Startを使う場合はこの値を前回のループの値に設定する (安定性が格段に上昇)
    }
    
    public function solve():void { // めり込まない拘束条件 : 法線方向の相対速度 >= 0
        var r:Number;
        var fce:Number;
        var nf:Number;
        var sub:Number;
        var fx:Number;
        var fy:Number;
        var fz:Number;
        if (btb) { // ball vs ball
            r = (b2.vx - b1.vx) * nx + (b2.vy - b1.vy) * ny + (b2.vz - b1.vz) * nz; // 相対速度
            fce = m * (r - tv); // 適正質量を掛ける
            nf = fce + f;
            if (nf < 0) nf = 0; // 離れる方向へは力を適用しない
            sub = nf - f; // 前回の解との差分を適用
            fx = nx * sub;
            fy = ny * sub;
            fz = nz * sub;
            b1.vx += fx * b1.m;
            b1.vy += fy * b1.m;
            b1.vz += fz * b1.m;
            b2.vx -= fx * b2.m;
            b2.vy -= fy * b2.m;
            b2.vz -= fz * b2.m;
            f = nf; // 力を蓄積 (この方法は垂直効力や摩擦力など解が非線形である場合のみ有効)
        } else { // vertex vs ball
            r = (b1.vx - v.vx) * nx + (b1.vy - v.vy) * ny + (b1.vz - v.vz) * nz; // 相対速度
            fce = m * (r - tv); // 適正質量を掛ける
            nf = fce + f;
            if (nf < 0) nf = 0; // 離れる方向へは力を適用しない
            sub = nf - f; // 前回の解との差分を適用
            fx = nx * sub;
            fy = ny * sub;
            fz = nz * sub;
            v.vx += fx * v.m;
            v.vy += fy * v.m;
            v.vz += fz * v.m;
            b1.vx -= fx * b1.m;
            b1.vy -= fy * b1.m;
            b1.vz -= fz * b1.m;
            f = nf; // 力を蓄積 (この方法は垂直効力や摩擦力など解が非線形である場合のみ有効)
        }
    }
}

class Ball { // Ball : 球
    public var x:Number;
    public var y:Number;
    public var z:Number;
    public var c:Vector.<Number>;
    public var vx:Number;
    public var vy:Number;
    public var vz:Number;
    public var r:Number;
    public var m:Number;
    
    public function Ball(x:Number, y:Number, z:Number, vx:Number = 0, vy:Number = 0, vz:Number = 0) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.vx = vx;
        this.vy = vy;
        this.vz = vz;
        r = 25 + Math.random() * 25;
        m = 1 / (r * r * r * 4 / 3 * Math.PI * 0.00025); // 質量の逆数
        c = new Vector.<Number>(4, true);
        c[0] = 0.3 + Math.random() * 0.7;
        c[1] = 0.3 + Math.random() * 0.7;
        c[2] = 0.3 + Math.random() * 0.7;
        c[3] = 1;
    }
    
    public function move():void {
        x += vx;
        y += vy;
        if (x > -200 && x < 200 && z > -200 && z < 200 && y < -100 + r) y = -100 + r;
        z += vz;
        vy -= 0.15;
    }
    
    public function collide(b:Ball):Cst {
        var dx:Number = x - b.x;
        var dy:Number = y - b.y;
        var dz:Number = z - b.z;
        var d2:Number = dx * dx + dy * dy + dz * dz;
        if (d2 < (r + b.r) * (r + b.r)) {
            var d:Number = 1 / Math.sqrt(d2);
            dx *= d;
            dy *= d;
            dz *= d;
            return new Cst(null, this, b, dx, dy, dz, 0);
        }
        return null;
    }
}

var vertexShaderCode:String = <![CDATA[
m44 vt0, va0, vc0;
mov v5, vt0.xyz; // set vertex position
m44 vt0, vt0, vc4;
mov op, vt0;
mov vt0, va1;
mul vt0.xyz, vt0.xyz, vc8.xyz;
mov v0, vt0; // vertex color
m33 vt0.xyz, va2.xyz, vc0; // transform vertex normal
nrm vt0.xyz, vt0.xyz; // normalize vertex normal
mov v1, vt0.xyz; // set vertex normal
mov v2, va3.xy; // set vertex uv
]]>;

var fragmentShaderCode:String = <![CDATA[
mov ft0, v1; // normal
mov ft1.xyz, fc0.xyz; // light vector
mul ft1.xyz, ft1.xyz, fc9.xxx;
nrm ft1.xyz, ft1.xyz;
dp3 ft7.x, ft0.xyz, ft1.xyz; // calc brightness
mul ft7.x, ft7.x, fc11.x; // back or front
sat ft7.x, ft7.x;
add ft7.x, ft7.x, fc10.w;
sat ft7.x, ft7.x;

mov ft2.xyz, v5.xyz; // calc view vector
mul ft2.xyz, ft2.xyz, fc9.xxx;
nrm ft2.xyz, ft2.xyz;

mul ft4.xyz, v0.xyz, ft7.xxx;

mul ft1.xyz, ft1.xyz, fc9.xxx; // calc reflection vector
dp3 ft3.x, ft1.xyz, ft0.xyz;
mul ft3.x, ft3.x, fc8.w;
mul ft3.xyz, ft0.xyz, ft3.xxx;
sub ft3.xyz, ft1.xyz, ft3.xyz;
nrm ft3.xyz, ft3.xyz;

dp3 ft1.x, ft2.xyz, ft3.xyz; // calc specular
sat ft1.x, ft1.x;
pow ft7.y, ft1.x, fc9.y;
mul ft7.y, ft7.y, fc10.w;
add ft4.xyz, ft4.xyz, ft7.yyy; // add specular
sat ft4.xyz, ft4.xyz; // clamp
mov ft4.w, fc8.z;

mov oc, ft4;
]]>;

var fragmentShaderCodeBall:String = <![CDATA[
mov ft0, v1; // normal
mov ft1.xyz, fc0.xyz; // light vector
mul ft1.xyz, ft1.xyz, fc9.xxx;
nrm ft1.xyz, ft1.xyz;
dp3 ft7.x, ft0.xyz, ft1.xyz; // calc brightness
sat ft7.x, ft7.x;
add ft7.x, ft7.x, fc10.w;
sat ft7.x, ft7.x;

mov ft2.xyz, v5.xyz; // calc view vector
mul ft2.xyz, ft2.xyz, fc9.xxx;
nrm ft2.xyz, ft2.xyz;

mul ft4.xyz, v0.xyz, ft7.xxx;

mul ft1.xyz, ft1.xyz, fc9.xxx; // calc reflection vector
dp3 ft3.x, ft1.xyz, ft0.xyz;
mul ft3.x, ft3.x, fc8.w;
mul ft3.xyz, ft0.xyz, ft3.xxx;
sub ft3.xyz, ft1.xyz, ft3.xyz;
nrm ft3.xyz, ft3.xyz;

dp3 ft1.x, ft2.xyz, ft3.xyz; // calc specular
sat ft1.x, ft1.x;
pow ft7.y, ft1.x, fc9.w;
mul ft7.y, ft7.y, fc10.w;
add ft4.xyz, ft4.xyz, ft7.yyy; // add specular
sat ft4.xyz, ft4.xyz; // clamp
mov ft4.w, fc8.z;

mov oc, ft4;
]]>;

// ika shrGL

import flash.display.*;
import flash.events.*;
import flash.display3D.*;
import flash.display3D.textures.*;
import flash.geom.*;
import flash.net.*;
import flash.system.*;
import com.adobe.utils.*;
import flash.utils.*;

class EGraphics {
    public static const OFFSET_VERTEX:uint = 0;
    public static const OFFSET_NORMAL:uint = 3;
    public static const OFFSET_COLOR:uint = 6;
    public static const OFFSET_UV:uint = 10;
    public static const NUM_DATAS_IN_VERTEX:uint = 13;
    //
    private var s3d:Stage3D;
    private var c3d:Context3D;
    private var p3d:Program3D;
    private var callback:Function;
    private var width:int;
    private var height:int;
    //
    private var modelview:Matrix3D;
    private var projection:Matrix3D;
    
    public function EGraphics(s3d:Stage3D, width:int, height:int, callback:Function) {
        this.s3d = s3d;
        this.width = width;
        this.height = height;
        this.callback = callback;
        s3d.addEventListener(Event.CONTEXT3D_CREATE, init);
        s3d.requestContext3D(Context3DRenderMode.AUTO);
    }
    
    private function init(e:Event):void {
        c3d = s3d.context3D;
        c3d.enableErrorChecking = true;
        c3d.configureBackBuffer(width, height, 0, true);
        c3d.setBlendFactors(Context3DBlendFactor.SOURCE_ALPHA,
            Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA);
        p3d = c3d.createProgram();
        c3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 8, Vector.<Number>([0, 0.5, 1, 2]));
        c3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 9, Vector.<Number>([-1, 8, 16, 32]));
        c3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 10, Vector.<Number>([0.03125, 0.0625, 0.125, 0.25]));
        c3d.setProgram(p3d);
        callback();
    }
    
    public function setShader(shader:EShader):void {
        c3d.setProgram(shader.program3D);
    }
    
    public function renderMesh(mesh:EMesh):void {
        var vtxbuf:VertexBuffer3D = mesh.vertexBuffer;
        c3d.setVertexBufferAt(0, vtxbuf, OFFSET_VERTEX, Context3DVertexBufferFormat.FLOAT_3);
        c3d.setVertexBufferAt(1, vtxbuf, OFFSET_COLOR, Context3DVertexBufferFormat.FLOAT_4);
        c3d.setVertexBufferAt(2, vtxbuf, OFFSET_NORMAL, Context3DVertexBufferFormat.FLOAT_3);
        c3d.setVertexBufferAt(3, vtxbuf, OFFSET_UV, Context3DVertexBufferFormat.FLOAT_2);
        vtxbuf.uploadFromVector(mesh.vertices, 0, mesh.numVertices);
        var idxbuf:IndexBuffer3D = mesh.indexBuffer;
        idxbuf.uploadFromVector(mesh.indices, 0, mesh.numIndices);
        c3d.drawTriangles(idxbuf);
    }
    
    public function createVertexBuffer(numVertices:uint):VertexBuffer3D {
        return c3d.createVertexBuffer(numVertices, NUM_DATAS_IN_VERTEX);
    }
    
    public function createIndexBuffer(numIndices:uint):IndexBuffer3D {
        return c3d.createIndexBuffer(numIndices);
    }
    
    public function beginScene(r:Number = 0, g:Number = 0, b:Number = 0):void {
        c3d.clear(r, g, b, 1);
    }
    
    public function endScene():void {
        c3d.present();
    }
    
    public function get stage3D():Stage3D {
        return s3d;
    }
    
    public function get context3D():Context3D {
        return c3d;
    }
}

class EMesh {
    private var vts:Vector.<Number>;
    private var ids:Vector.<uint>;
    //
    private var vtxbuf:VertexBuffer3D;
    private var idxbuf:IndexBuffer3D;
    //
    private var numvtx:uint;
    private var numfce:uint;
    
    public function EMesh() {
        vts = new Vector.<Number>();
        ids = new Vector.<uint>();
        //
        vtxbuf = null;
        idxbuf = null;
        //
        numvtx = 0;
        numfce = 0;
    }
    
    public function addVertex(x:Number, y:Number, z:Number, u:Number = 0, v:Number = 0, r:Number = 1, g:Number = 1, b:Number = 1):void {
        var idx:uint = numvtx * EGraphics.NUM_DATAS_IN_VERTEX;
        vts.length += EGraphics.NUM_DATAS_IN_VERTEX;
        var i:uint = idx + EGraphics.OFFSET_VERTEX;
        vts[i] = x;
        vts[i + 1] = y;
        vts[i + 2] = z;
        i = idx + EGraphics.OFFSET_COLOR;
        vts[i] = r;
        vts[i + 1] = g;
        vts[i + 2] = b;
        i = idx + EGraphics.OFFSET_UV;
        vts[i] = u;
        vts[i + 1] = v;
        numvtx++;
    }
    
    public function addFace(i1:uint, i2:uint, i3:uint):void {
        var idx:uint = numfce * 3;
        ids[idx++] = i1;
        ids[idx++] = i2;
        ids[idx++] = i3;
        numfce++;
    }
    
    public function calcNormals():void {
        const numDatasInVertex:uint = EGraphics.NUM_DATAS_IN_VERTEX;
        const offsetVertex:uint = EGraphics.OFFSET_VERTEX;
        const offsetNormal:uint = EGraphics.OFFSET_NORMAL;
        var idx:uint = 0;
        var i:uint;
        var num:uint = numvtx * numDatasInVertex;
        for (i = offsetNormal; i < num; i += numDatasInVertex) {
            vts[i] = 0;
            vts[i + 1] = 0;
            vts[i + 2] = 0;
        }
        for (i = 0; i < numfce; i++) {
            var i1:uint = ids[idx] * numDatasInVertex;
            var i2:uint = ids[idx + 1] * numDatasInVertex;
            var i3:uint = ids[idx + 2] * numDatasInVertex;
            var v1:uint = i1 + offsetVertex;
            var v2:uint = i2 + offsetVertex;
            var v3:uint = i3 + offsetVertex;
            //
            var x1:Number = vts[v2] - vts[v1];
            var y1:Number = vts[v2 + 1] - vts[v1 + 1];
            var z1:Number = vts[v2 + 2] - vts[v1 + 2];
            var x2:Number = vts[v3] - vts[v1];
            var y2:Number = vts[v3 + 1] - vts[v1 + 1];
            var z2:Number = vts[v3 + 2] - vts[v1 + 2];
            //
            var nx:Number = y2 * z1 - z2 * y1;
            var ny:Number = z2 * x1 - x2 * z1;
            var nz:Number = x2 * y1 - y2 * x1;
            var nl:Number = 1 / Math.sqrt(nx * nx + ny * ny + nz * nz);
            nx *= nl;
            ny *= nl;
            nz *= nl;
            //
            v1 = i1 + offsetNormal;
            v2 = i2 + offsetNormal;
            v3 = i3 + offsetNormal;
            vts[v1] += nx;
            vts[v1 + 1] += ny;
            vts[v1 + 2] += nz;
            vts[v2] += nx;
            vts[v2 + 1] += ny;
            vts[v2 + 2] += nz;
            vts[v3] += nx;
            vts[v3 + 1] += ny;
            vts[v3 + 2] += nz;
            idx += 3;
        }
    }
    
    public function initVertexBuffer(g:EGraphics):void {
        vtxbuf = g.createVertexBuffer(numvtx);
    }
    
    public function initIndexBuffer(g:EGraphics):void {
        idxbuf = g.createIndexBuffer(numfce * 3);
    }
    
    public function get numVertices():uint {
        return numvtx;
    }
    
    public function get numIndices():uint {
        return numfce * 3;
    }
    
    public function get vertices():Vector.<Number> {
        return vts;
    }
    
    public function get indices():Vector.<uint> {
        return ids;
    }
    
    public function get vertexBuffer():VertexBuffer3D {
        return vtxbuf;
    }
    
    public function get indexBuffer():IndexBuffer3D {
        return idxbuf;
    }
}

class EShader {
    private var vtxcode:ByteArray;
    private var frgcode:ByteArray;
    private var p3d:Program3D;
    
    public function EShader(vertex:String, fragment:String, c3d:Context3D) {
        var agal:AGALMiniAssembler = new AGALMiniAssembler();
        agal.assemble(Context3DProgramType.VERTEX, vertex);
        vtxcode = agal.agalcode;
        agal.assemble(Context3DProgramType.FRAGMENT, fragment);
        frgcode = agal.agalcode;
        p3d = c3d.createProgram();
        p3d.upload(vtxcode, frgcode);
    }
    
    public function get vertexShader():ByteArray {
        return vtxcode;
    }
    
    public function get fragmentShader():ByteArray {
        return frgcode;
    }
    
    public function get program3D():Program3D {
        return p3d;
    }
}

class EResourceLoader {
    private var result:Array;
    private var loadings:Vector.<Function>;
    private var numLoadings:uint;
    private var callback:Function;
    
    public function EResourceLoader(callback:Function) {
        this.callback = callback;
        loadings = new Vector.<Function>();
        numLoadings = 0;
        result = new Array();
    }
    
    public function addImage(url:String, name:String):void {
        var loader:Loader = new Loader();
        loadings[numLoadings++] = function():void {
            loader.load(new URLRequest(url), new LoaderContext(true));
        };
        loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e:Event):void {
            result[name] = loader.content;
            if (--numLoadings == 0) callback();
        });
    }
    
    public function getImage(name:String):Bitmap {
        return Bitmap(result[name]);
    }
    
    public function loadAll():void {
        for (var i:uint = 0; i < numLoadings; i++) {
            loadings[i]();
            loadings[i] = null;
        }
    }
}

Forked