3D水面 / Water 3D

by saharan
実装の仕方のせいか重くなってしまいました。
ちょっと解像度低めです。

・解説コメントをちょっと入れました
・粘性係数調整

Click&Drag:Make wave
♥131 | Line 184 | Modified 2011-04-10 19:29:58 | MIT License | (replaced)
play

Related images

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/pTKv
 */

package {
    import flash.system.LoaderContext;
    import flash.display.*;
    import flash.events.*;
    import flash.net.*;
    import net.hires.debug.*;

    [SWF(frameRate = "30", width="465", height="465")]

    /**
     * Water 3D
     *
     * ・解説コメントをちょっと入れました
     * 
     * Click&Drag:Make wave
     * 
     * @author saharan
     */
    public class Water3D extends Sprite {
        private const NUM_DETAILS:int = 48;
        private const INV_NUM_DETAILS:Number = 1 / NUM_DETAILS;
        private const MESH_SIZE:Number = 100;
        private var count:uint;
        private var bmd:BitmapData;
        private var loader:Loader;
        private var vertices:Vector.<Vertex>;
        private var transformedVertices:Vector.<Number>;
        private var indices:Vector.<int>;
        private var uvt:Vector.<Number>;
        private var width2:Number;
        private var height2:Number;
        private var heights:Vector.<Vector.<Number>>;
        private var velocity:Vector.<Vector.<Number>>;
        private var press:Boolean;

        public function Water3D():void {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }

        private function init(e:Event = null):void {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            stage.quality = StageQuality.LOW;
            width2 = 465 / 2;
            height2 = 465 / 2;
            // var s:Stats = new Stats();
            // s.alpha = 0.8;
            // addChild(s);
            count = 0;
            loader = new Loader();
            loader.load(new URLRequest("http://assets.wonderfl.net/images/related_images/b/b2/b217/b2177f87d979a28b9bcbb6e0b89370e77ce22337"), new LoaderContext(true));
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
                function(e:Event = null):void {
                    bmd = Bitmap(loader.content).bitmapData;
                    addEventListener(Event.ENTER_FRAME, frame);
                });
            stage.addEventListener(MouseEvent.MOUSE_DOWN,
                function(e:Event = null):void {
                    drag();
                    press = true;
                });
            stage.addEventListener(MouseEvent.MOUSE_UP,
                function(e:Event = null):void {
                    press = false;
                });
            stage.addEventListener(MouseEvent.MOUSE_MOVE,
                function(e:Event = null):void {
                    if (press) drag();
                });
            vertices = new Vector.<Vertex>(NUM_DETAILS * NUM_DETAILS, true);
            transformedVertices = new Vector.<Number>(NUM_DETAILS * NUM_DETAILS * 2, true);
            indices = new Vector.<int>();
            uvt = new Vector.<Number>(NUM_DETAILS * NUM_DETAILS * 2, true);
            var i:int;
            var j:int;
            // 頂点初期化。外側2つ分は表示しないので無駄な処理&メモリに・・・
            for (i = 2; i < NUM_DETAILS - 2; i++) {
                for (j = 2; j < NUM_DETAILS - 2; j++) {
                    vertices[getIndex(j, i)] = new Vertex(
                        (j - (NUM_DETAILS - 1) * 0.5) / NUM_DETAILS * MESH_SIZE, 0,
                        (i - (NUM_DETAILS - 1) * 0.5) / NUM_DETAILS * MESH_SIZE);
                    if (i != 2 && j != 2) {
                        indices.push(getIndex(i - 1, j - 1), getIndex(i, j - 1), getIndex(i, j));
                        indices.push(getIndex(i - 1, j - 1), getIndex(i, j), getIndex(i - 1, j));
                    }
                }
            }
            // 水面関係初期化
            heights = new Vector.<Vector.<Number>>(NUM_DETAILS, true);
            velocity = new Vector.<Vector.<Number>>(NUM_DETAILS, true);
            for (i = 0; i < NUM_DETAILS; i++) {
                heights[i] = new Vector.<Number>(NUM_DETAILS, true);
                velocity[i] = new Vector.<Number>(NUM_DETAILS, true);
                for (j = 0; j < NUM_DETAILS; j++) {
                    heights[i][j] = 0;
                    velocity[i][j] = 0;
                }
            }
        }

        private function frame(e:Event = null):void {
            count++;
            move();
            setMesh();
            transformVertices();
            draw();
        }

        private function setMesh():void {
            for (var i:int = 2; i < NUM_DETAILS - 2; i++) {
                for (var j:int = 2; j < NUM_DETAILS - 2; j++) {
                    const index:int = getIndex(i, j);
                    vertices[index].y = heights[i][j] * 0.15;
                    
                    // ---Sphere map---
                    
                    var nx:Number;
                    var ny:Number;
                    // nz is 1
                    nx = (heights[i][j] - heights[i - 1][j]) * 0.15;
                    ny = (heights[i][j] - heights[i][j - 1]) * 0.15;
                    var len:Number = 1 / Math.sqrt(nx * nx + ny * ny + 1);
                    nx *= len;
                    ny *= len;
                    // ちょっと式を変更して平面でもテクスチャが見えるように
                    uvt[index * 2] = nx * 0.5 + 0.5 + ((i - NUM_DETAILS * 0.5) * INV_NUM_DETAILS * 0.25);
                    uvt[index * 2 + 1] = ny * 0.5 + 0.5 + ((NUM_DETAILS * 0.5 - j) * INV_NUM_DETAILS * 0.25);
                }
            }
        }

        public function move():void {
            
            // ---Water simulation---
            
            var i:int;
            var j:int;
            var mx:Number = mouseX / 465 * NUM_DETAILS;
            var my:Number = (1 - mouseY / 465) * NUM_DETAILS;
            for (i = 1; i < NUM_DETAILS - 1; i++) {
                for (j = 1; j < NUM_DETAILS - 1; j++) {
                    heights[i][j] += velocity[i][j];
                    if (heights[i][j] > 100) heights[i][j] = 100;
                    else if (heights[i][j] < -100) heights[i][j] = -100;
                }
            }
            for (i = 1; i < NUM_DETAILS - 1; i++) {
                for (j = 1; j < NUM_DETAILS - 1; j++) {
                    velocity[i][j] = (velocity[i][j] +
                        (heights[i - 1][j] + heights[i][j - 1] + heights[i + 1][j] +
                        heights[i][j + 1] - heights[i][j] * 4) * 0.5) * 0.95;
                }
            }
        }

        public function drag():void {
            var i:int;
            var j:int;
            var mx:Number = mouseX / 465 * NUM_DETAILS;
            var my:Number = (1 - mouseY / 465) * NUM_DETAILS;
            for (i = mx - 3; i < NUM_DETAILS - 1 && mx + 3; i++) {
                for (j = my - 3; j < NUM_DETAILS - 1 && my + 3; j++) {
                    if (i > 1 && j > 1 && i < NUM_DETAILS - 1 && j < NUM_DETAILS - 1) {
                        var len:Number = 3 - Math.sqrt((mx - i) * (mx - i) + (my - j) * (my - j));
                        if (len < 0) len = 0;
                        velocity[i][j] -= len * (press ? 1 : 5);
                    }
                }
            }
        }

        private function draw():void {
            graphics.clear();
            graphics.beginFill(0x202020);
            graphics.drawRect(0, 0, 465, 465);
            graphics.endFill();
            graphics.beginBitmapFill(bmd);
            graphics.drawTriangles(transformedVertices, indices, uvt, TriangleCulling.POSITIVE);
            graphics.endFill();
        }

        private function getIndex(x:int, y:int):int {
            return y * NUM_DETAILS + x;
        }

        private function transformVertices():void {
            
            // x軸回転とビュー変換・プロジェクション変換を実行
            
            var angle:Number = 70 * Math.PI / 180;
            var sin:Number = Math.sin(angle);
            var cos:Number = Math.cos(angle);
            for (var i:int = 0; i < vertices.length; i++) {
                var v:Vertex = vertices[i];
                if(v != null) {
                    var x:Number = v.x;
                    // x軸回転行列もどき
                    var y:Number = cos * v.y - sin * v.z;
                    var z:Number = sin * v.y + cos * v.z;
                    // ちょこっとビュー変換っぽいことをする(カメラから離す)
                    z = 1 / (z + 60);
                    // 簡易プロジェクション変換
                    x *= z;
                    y *= z;
                    // スクリーン座標を求める
                    x = x * 232.5 + 232.5;
                    y = y * 232.5 + 182.5;
                    transformedVertices[i * 2] = x;
                    transformedVertices[i * 2 + 1] = y;
                }
            }
        }
    }
}

class Vertex {
    public var x:Number;
    public var y:Number;
    public var z:Number;

    public function Vertex(x:Number, y:Number,z:Number) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

Forked