Water Flow
Water Flow
ドラッグで流す
スペースキーでリセット
Water Flow in PORTAL2
http://www.valvesoftware.com/publications/2010/siggraph2010_vlachos_waterflow.pdf
を参考に実装
流れのベクトルに沿って移流した2枚のテクスチャを交互に補間しながら切り替えて、流れているように見せる
/**
* Copyright Nao_u ( http://wonderfl.net/user/Nao_u )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/t3uv
*/
// forked from Nao_u's 2D流体
// forked from Nao_u's 波紋+環境マップ
//
// Water Flow
//
// ドラッグで流す
// スペースキーでリセット
//
//
// Water Flow in PORTAL2
// http://www.valvesoftware.com/publications/2010/siggraph2010_vlachos_waterflow.pdf
// を参考に実装
//
// 流れのベクトルに沿って移流した2枚のテクスチャを交互に補間しながら切り替えて、流れているように見せる
//
//
package {
import flash.display.Sprite;
import flash.events.*;
[SWF(width="465", height="465", backgroundColor="0x000000", frameRate="30")]
public class FlashTest extends Sprite {
public function FlashTest() {
Main = this;
startLoad();
//initialize();
stage.addEventListener(Event.ENTER_FRAME,update);
stage.addEventListener(KeyboardEvent.KEY_UP, keyCheckUp);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyCheckDown);
stage.addEventListener(MouseEvent.MOUSE_UP, MouseCheckUp);
stage.addEventListener(MouseEvent.MOUSE_DOWN, MouseCheckDown);
}
}
}
import flash.display.*;
import flash.events.*
import flash.text.TextField;
import flash.geom.*;
import flash.utils.getTimer;
import flash.ui.Keyboard;
import flash.net.*;
var Main:Sprite;
var SCREEN_W:Number = 465;
var SCREEN_H:Number = 465;
var Text:TextField
var Tex:ProcTex;
var BITMAP_W:int = 465/2;
var BITMAP_H:int = 465/2;
var TexData: BitmapData;
var loaderA:Loader;
var loaderB:Loader;
var bLoad:Boolean = false;
function startLoad():void{
loaderA = new Loader();
loaderA.load( new URLRequest("http://img.f.hatena.ne.jp/images/fotolife/N/Nao_u/20090913/20091231171735.jpg") );
loaderA.contentLoaderInfo.addEventListener( Event.COMPLETE, loadComplete );
}
function loadComplete(e:Event):void {
loaderB = new Loader();
loaderB.contentLoaderInfo.addEventListener(Event.INIT, initialize);
loaderB.loadBytes(loaderA.contentLoaderInfo.bytes);
}
function initialize(e:Event):void {
var loader:Loader = loaderB;
TexData = new BitmapData(loader.width, loader.height, false);
TexData.draw(loader);
Tex = new ProcTex( BITMAP_W, BITMAP_H );
Tex.Bmp.x = 0;
Tex.Bmp.y = 0;
Text = new TextField();
Text.text = "----";
Text.autoSize = "left";
Main.addChild(Text);
bLoad = true;
var num:int = 1;
for( var i:int=0; i<num; i++ ){
AgentAry.push( new Agent( new Point( Rand(30,435), Rand(30,435) ) ) );
AgentAry[i].update();
}
}
var g_ColAnm:Number = 0.0;
var g_Scale0:Number = 0.0;
var g_Scale1:Number = 0.0;
var g_Ratio0:Number = 0.0;
var g_Ratio1:Number = 0.0;
// 更新
var mxPrev:int;
var myPrev:int;
var Pow:Number = 1.0;
function update(e :Event):void{
var time:int = getTimer();
graphicClear();
Tex.draw();
for( var i:int=0; i<AgentAry.length; i++ ){
AgentAry[i].update();
}
var mx:int = Main.mouseX * BITMAP_W / SCREEN_W;
var my:int = Main.mouseY * BITMAP_H / SCREEN_H;
Pow += Math.abs(mx - mxPrev) + Math.abs(my - myPrev);
if( Pow > 1.0 ) Pow = 1.0;
var vx:Number = (mx - mxPrev)*0.01;
var vy:Number = (my - myPrev)*0.01;
if( MouseData & MOUSE_LEFT ) Tex.addPower( mx, my, vx, vy, Pow );
Pow *= 0.5;
mxPrev = mx;
myPrev = my;
g_ColAnm += 1.0/12000.0;
if( g_ColAnm > 1.0 ) g_ColAnm -= 1.0;
g_Scale0 += 1.0/30.0;
if( g_Scale0 > 1.0 ) g_Scale0 -= 1.0;
g_Scale1 = g_Scale0 + 0.5;
if( g_Scale1 > 1.0 ) g_Scale1 -= 1.0;
g_Ratio0 = g_Scale0;
if( g_Ratio0 > 0.5 ) g_Ratio0 = 1.0-g_Ratio0;
g_Ratio0 *= 2;
g_Ratio1 = 1.0 - g_Ratio0;
//g_Ratio0 = Math.pow( g_Ratio0, 1.0/2.2);
//g_Ratio1 = Math.pow( g_Ratio1, 1.0/2.2);
if( KeyData & KEY_SPACE && !(KeyPrev & KEY_SPACE) ){
for( i=0; i<AgentAry.length; i++ ){
AgentAry[i].Pos.x = Rand(30,435);
AgentAry[i].Pos.y = Rand(30,435);
AgentAry[i].setColor();
}
Tex.resetFlow();
}
var endTime:int = getTimer() - time;
Text.text = " 生成時間:" + endTime + "[ms]";
KeyPrev = KeyData;
}
// テクスチャ生成クラス
class ProcTex{
public var BmpData:BitmapData;
public var TmpBmpData:BitmapData;
public var Bmp:Bitmap;
public var Width:int;
public var Height:int;
public var HeightBuf:Array;
public var VelXBuf:Vector.<Number>;
public var VelYBuf:Vector.<Number>;
public var PressBuf:Vector.<Number>;
public var NoiseBuf:Vector.<int>;
public var HeightIdxDst:int;
public var HeightIdxSrc0:int;
public var HeightIdxSrc1:int;
public var BufWidth:int;
public var BufHeight:int;
public var PixelBufSize:int = 256;
public var PixelBuf:Vector.<int>;
public function ProcTex( w:int, h:int ){
Width = w;
Height = h;
BmpData = new BitmapData(Width, Height, false, 0xffffff);
Bmp = new Bitmap(BmpData);
Bmp.scaleX = 2.0;
Bmp.scaleY = 2.0;
Main.addChild(Bmp);
// 波高バッファの生成
VelXBuf = new Vector.<Number>;
VelYBuf = new Vector.<Number>;
PressBuf = new Vector.<Number>;
NoiseBuf = new Vector.<int>;
BufWidth = Width+2;
BufHeight = Height+2;
for( var x:int=-1; x<Width+1; x++ ){
for( var y:int=-1; y<Height+1; y++ ){
var idx:int = (x+1) + (y+1)*BufWidth;
VelXBuf.push( 0.0 );
VelYBuf.push( 0.0 );
PressBuf.push( 0.0 );
}
}
for( x=0; x<Width*2; x++ ){
for( y=0; y<Height*2; y++ ){
var c:int = Rand(0,100);
NoiseBuf.push( c );//+c*256+c*65536 );
//NoiseBuf.push( x*20 + y*10 );
}
}
// 環境マップテクスチャバッファの生成
PixelBuf = new Vector.<int>;
for( x=0; x<PixelBufSize; x++ ){
for( y=0; y<PixelBufSize; y++ ){
var col:int = TexData.getPixel(x, y);
PixelBuf.push(col);
}
}
HeightIdxSrc0=0;
HeightIdxSrc1=1;
HeightIdxDst=2;
}
public function draw():void{
HeightIdxSrc0++;
HeightIdxSrc1++;
HeightIdxDst++;
if( HeightIdxSrc0 > 2 ) HeightIdxSrc0 = 0;
if( HeightIdxSrc1 > 2 ) HeightIdxSrc1 = 0;
if( HeightIdxDst > 2 ) HeightIdxDst = 0;
calcWave();
createBmp( HeightIdxDst );
}
// 水面に力を加える
public function addPower( px:int, py:int, vx:Number, vy:Number, pow:Number ):void{
var r:int = 6;
//var buf:Vector.<Number> = HeightBuf[HeightIdxDst];
for( var lx:int=-r; lx<=r; lx++ ){
for( var ly:int=-r; ly<=r; ly++ ){
const d:Number = Math.sqrt(lx*lx+ly*ly);
if (d < r) {
var p:Number = Math.cos(Math.PI/2 * d/r);
var x:int = px + lx;
var y:int = py + ly;
if( x < 0 || x >= Width || y < 0 || y >= Height ) break;
var idx:int = (x+1) + (y+1)*BufWidth;
// buf[idx] += pow * p;
// 上下左右の圧力差から、速度を更新
VelXBuf[idx] += vx;
VelYBuf[idx] += vy;
}
}
}
}
public function getNoiseTex( x:Number, y:Number ):int{
var idx:int = int(x) + int(y)*BufWidth;
while( idx >= BufWidth*BufWidth ) idx -= BufWidth*BufWidth;
while( idx < 0 ) idx += BufWidth*BufWidth;
var c0:Number = NoiseBuf[idx];
var c1:Number = NoiseBuf[idx+1];
var c2:Number = NoiseBuf[idx+BufWidth];
var c3:Number = NoiseBuf[idx+BufWidth+1];
//x -= 0.5;
//y -= 0.5;
var dx:Number = x - int(x);
var dy:Number = y - int(y);
var iidx:Number = 1.0-dx;
var iidy:Number = 1.0-dy;
return (c0*iidx + c1*dx) * iidy + (c2*iidx + c3*dx) * dy;
}
// 水面画像の生成
public function createBmp( idx:int ):void{
var col:Number, colU:Number, colL:Number;
var vecX0:Number, vecY0:Number, vecX1:Number, vecY1:Number, vx:Number, vy:Number;
var ix:int, iy:int, idx0:int, idx1:int;
BmpData.lock();
for( var x:int=0; x<Width-10; x++ ){
for( var y:int=0; y<Height-10; y++ ){
idx = (x+1) + (y+1)*BufWidth;
vx = VelXBuf[idx];
vy = VelYBuf[idx];
vecX0 = vx * 800.0 * g_Scale0;
vecY0 = vy * 800.0 * g_Scale0;
vecX1 = vx * 800.0 * g_Scale1;
vecY1 = vy * 800.0 * g_Scale1;
// idx0 = (x/2-vecX0) + int((y/2-vecY0))*BufWidth;
// idx1 = (y/2-vecY1) + int((x/2-vecX1))*BufWidth;
// while( idx0 >= BufWidth*BufWidth-100 ) idx0 -= BufWidth*BufWidth-100;
// while( idx1 >= BufWidth*BufWidth-100 ) idx1 -= BufWidth*BufWidth-100;
// while( idx0 < 0 ) idx0 += BufWidth*BufWidth;
// while( idx1 < 0 ) idx1 += BufWidth*BufWidth;
// var c0:Number = NoiseBuf[idx0];
// var c1:Number = NoiseBuf[idx1];
var c0:Number = getNoiseTex(x/1.0-vecX0, y/1.00-vecY0);
var c1:Number = getNoiseTex((x+10)/1.0-vecX1, (y+10)/1.0-vecY1);
//if( x>200 && y>200 ) c1 = 128;
var c:Number = c1 * g_Ratio1 + c0 * g_Ratio0;
//c = Math.pow( c/256, 1.0/2.2) * 256;
//c += g_ColAnm*255;
if( c >= 255 ) c = 255;
if( c > 128 ) c = 255-c;
//if( c < 16 ) c += 16;
c *= 2;
var cc:int = c;
var cr:int = c;
//cc *= g_Ratio1;
BmpData.setPixel(x, y, cc+cc*256+cr*65536);
// BmpData.setPixel(x, y, int(VelXBuf[idx]*399+128)+int(VelYBuf[idx]*399+128)*256);
}
}
BmpData.unlock();
}
// 流れを計算
public function calcWave():void{
var idx:int, idxU:int, idxD:int, idxL:int, idxR:int;
var x:int, y:int;
// 圧力を計算
var px:Number, py:Number;
for( x=0; x<Width-2; x++ ){
for( y=0; y<Height-2; y++ ){
idx = (x+1) + (y+1)*BufWidth;
idxU = idx-BufWidth;
idxD = idx+BufWidth;
idxL = idx-1;
idxR = idx+1;
px = (VelXBuf[idxL] - VelXBuf[idxR]);
py = (VelYBuf[idxU] - VelYBuf[idxD]);
PressBuf[idx] = (px + py) * 0.99;
}
}
// 速度を更新
for( x=0; x<Width-2; x++ ){
for( y=0; y<Height-2; y++ ){
idx = (x+1) + (y+1)*BufWidth;
idxU = idx-BufWidth;
idxD = idx+BufWidth;
idxL = idx-1;
idxR = idx+1;
// 上下左右の圧力差から、速度を更新
VelXBuf[idx] += (PressBuf[idxL] - PressBuf[idxR]) * 0.25;
VelYBuf[idx] += (PressBuf[idxU] - PressBuf[idxD]) * 0.25;
}
}
// 粘性を計算
var tmp:Number = 0;
var mul:Number = 0.2;
for( x=0; x<Width-2; x++ ){
for( y=0; y<Height-2; y++ ){
idx = (x+1) + (y+1)*BufWidth;
idxU = idx-BufWidth;
idxD = idx+BufWidth;
idxL = idx-1;
idxR = idx+1;
tmp = VelXBuf[idx];
VelXBuf[idx] +=
(
(VelXBuf[idxL] - tmp) +
(VelXBuf[idxR] - tmp) +
(VelXBuf[idxU] - tmp) +
(VelXBuf[idxD] - tmp)
) * mul;
tmp = VelYBuf[idx];
VelYBuf[idx] +=
(
(VelYBuf[idxL] - tmp) +
(VelYBuf[idxR] - tmp) +
(VelYBuf[idxU] - tmp) +
(VelYBuf[idxD] - tmp)
) * mul;
}
}
}
public function resetFlow():void{
for( var x:int=0; x<Width-2; x++ ){
for( var y:int=0; y<Height-2; y++ ){
var idx:int = (x+1) + (y+1)*BufWidth;
PressBuf[idx] = 0;
VelXBuf[idx] = 0;
VelYBuf[idx] = 0;
}
}
}
}
var AgentAry:Vector.<Agent> = new Vector.<Agent>;
class Agent{
public var Sp:Sprite;
public var Pos:Point = new Point;
public var Color:int;
public function Agent( p:Point ){
Sp=new Sprite();
Pos = p;
Sp.x = Pos.x;
Sp.y = Pos.y;
setColor( );
Main.stage.addChild(Sp);
}
public function setColor( ):void{
var ix:int = Pos.x;
var iy:int = Pos.y;
Color = (ix*256 / 456)*256 +
((iy*256 / 456)<<16)+ 128;
}
public function update( ):void{
var x:int = (Pos.x+1)/2;
var y:int = (Pos.y+1)/2;
if( x<0 ) x=0;
if( y<0 ) y=0;
if( x>BITMAP_W ) x=BITMAP_W-1;
if( y>BITMAP_H ) y=BITMAP_H-1;
var idx:int = (x) + (y)*Tex.BufWidth;
Pos.x += Tex.VelXBuf[idx]*50;
Pos.y += Tex.VelYBuf[idx]*50;
Sp.x = Pos.x;
Sp.y = Pos.y;
if( Pos.x < 9 ) { Pos.x = 8; }
if( Pos.y < 9 ) { Pos.y = 8; }
if( Pos.x > SCREEN_W-9 ) { Pos.x = SCREEN_W-10; }
if( Pos.y > SCREEN_H-9 ) { Pos.y = SCREEN_H-10; }
// drawCircle( Pos.x, Pos.y, 5, Color );
// drawCircle( Pos.x, Pos.y, 2, Color );
drawRect( Pos.x, Pos.y, 3, 3, Color );
}
}
function graphicClear():void{
Main.graphics.clear();
}
function drawCircle( x:Number, y:Number, size:Number, col:int ):void{
//Main.graphics.lineStyle(0,0x000000);
Main.graphics.beginFill( col, 1 );
Main.graphics.drawCircle( x, y, size );
Main.graphics.endFill();
}
function drawRect(sx:Number, sy:Number, ex:Number, ey:Number, col:int ):void{
// Main.graphics.lineStyle( 0, col );
Main.graphics.beginFill( col );
Main.graphics.drawRect( sx, sy, ex, ey );
Main.graphics.endFill();
}
function Rand( min:Number, max:Number ):Number{
return Math.random() * (max-min) + min;
}
var MOUSE_LEFT:int = 0x01;
var MOUSE_LEFT_TRG:int = 0x02;
var MouseData:int;
function MouseCheckDown(event:MouseEvent):void{
MouseData |= MOUSE_LEFT;
MouseData |= MOUSE_LEFT_TRG;
}
function MouseCheckUp(event:MouseEvent):void{
MouseData &= ~MOUSE_LEFT;
}
function MouseUpdate():void{
MouseData &= ~MOUSE_LEFT_TRG;
}
var KEY_UP:int = 0x01;
var KEY_DOWN:int = 0x02;
var KEY_LEFT:int = 0x04;
var KEY_RIGHT:int = 0x08;
var KEY_SPACE:int = 0x10;
var KeyData:int;
var KeyPrev:int;
function keyCheckDown(event:KeyboardEvent):void {
switch (event.keyCode){
case Keyboard.UP: KeyData |= KEY_UP; break;
case Keyboard.DOWN: KeyData |= KEY_DOWN; break;
case Keyboard.LEFT: KeyData |= KEY_LEFT; break;
case Keyboard.RIGHT: KeyData |= KEY_RIGHT; break;
case Keyboard.SPACE: KeyData |= KEY_SPACE; break;
}
}
function keyCheckUp(event:KeyboardEvent):void {
switch (event.keyCode){
case Keyboard.UP: KeyData &= ~KEY_UP; break;
case Keyboard.DOWN: KeyData &= ~KEY_DOWN; break;
case Keyboard.LEFT: KeyData &= ~KEY_LEFT; break;
case Keyboard.RIGHT: KeyData &= ~KEY_RIGHT; break;
case Keyboard.SPACE: KeyData &= ~KEY_SPACE; break;
}
}