Particle Fluid + WaterFilter
流体シミュレーション+水フィルタ (Particle Fluid + WaterFilter)
/**
* Copyright shohei909 ( http://wonderfl.net/user/shohei909 )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/7i7A
*/
// forked from shohei909's 流体シミュ+水フィルタ
// forked from saharan's 流体シミュ最適化/Particle Fluid Optimization
/*
* 流体シミュ最適化/Particle Fluid Optimization
*
* クリック:水を注ぐ
* Click:Pouring
*
* Order:O(n)
* ・空間分割により高速化。
* ・近接粒子の保存方法を変更し最適化。
* ・その他パラメーター調整。より水っぽく!
*/
//image: http://www.flickr.com/photos/ttdesign/2558614528/
package {
import flash.system.LoaderContext;
import flash.net.URLRequest;
import flash.utils.*;
import flash.text.*;
import flash.filters.*;
import flash.geom.*;
import flash.events.*;
import flash.display.*;
import net.hires.debug.Stats;
import mx.utils.Base64Decoder;
[SWF(frameRate = 60)]
public class SPH extends Sprite {
public static const GRAVITY:Number = 0.05;//重力
public static const RANGE:Number = 7;//影響半径
public static const RANGE2:Number = RANGE * RANGE;//影響半径の二乗
public static const DENSITY:Number = 0.2;//流体の密度
public static const PRESSURE:Number = 2;//圧力係数
public static const VISCOSITY:Number = 0.09;//粘性係数
public static const NUM_GRIDS:int = 66;//グリッド数(≒ 465 / RANGE)
public static const INV_GRID_SIZE:Number = 1/ (465 / NUM_GRIDS);//グリッドサイズの逆数(≒ 1 / RANGE)
public static const IMG_URL:String = "http://assets.wonderfl.net/images/related_images/a/a2/a227/a227dbacb189f4866a9f2d80acce25498fdb2de3"
public static const SHADER_DATA:String = "pQEAAACkCwB3YXRlckZpbHRlcqAMbmFtZXNwYWNlAEFJRgCgDHZlbmRvcgBBZG9iZVN5c3RlbXMAoAh2ZXJzaW9uAAIAoAxkZXNjcmlwdGlvbgB3YXRlcgChAQIAAAxfT3V0Q29vcmQAoQEBAAACZGlzdGFuY2UAogFtaW5WYWx1ZQAAAAAAogFkZWZhdWx0VmFsdWUAQsgAAKEBAQAAAWhlaWdodACiAWRlZmF1bHRWYWx1ZQBAoAAAowAEYmFja0ltYWdlAKMBBHdhdGVySW1hZ2UAoQIEAQAPZHN0AB0CAMEAABAAMgIAIAAAAAAyAgAQv4AAAB0DAMECABAAAQMAwQIAsAAwBADxAwAQAR0DAPMEABsAMgIAIAAAAAAyAgAQP4AAAB0EAMECABAAAQQAwQIAsAAwBQDxBAAQAR0EAPMFABsAMgIAIL+AAAAyAgAQAAAAAB0FAMECABAAAQUAwQIAsAAwBgDxBQAQAR0FAPMGABsAMgIAID+AAAAyAgAQAAAAAB0GAMECABAAAQYAwQIAsAAwBwDxBgAQAR0GAPMHABsAHQIAIAMAAAABAgAgAwBAAB0CABACAIAAAQIAEAMAgAAdAgAgAgDAAAMCACADAMAAHQIAEAIAgAAdAgAgBAAAAAECACAEAEAAHQcAgAIAgAABBwCABACAAB0CACAHAAAAAwIAIAQAwAAdBwCAAgCAAB0CACAFAAAAAQIAIAUAQAAdBwBAAgCAAAEHAEAFAIAAHQIAIAcAQAADAgAgBQDAAB0HAEACAIAAHQIAIAYAAAABAgAgBgBAAB0HACACAIAAAQcAIAYAgAAdAgAgBwCAAAMCACAGAMAAHQcAIAIAgAAdAgAgBwCAAAICACAHAEAAHQgAgAIAgAAdAgAgBwAAAAICACACAMAAHQgAQAIAgAAdAgAgAgDAAAECACAHAAAAHQcAEAIAgAABBwAQBwBAAB0CACAHAMAAAQIAIAcAgAAdBwAQAADAAAMHABACAIAAMgIAIECAAAAECAAgAgCAAAMIACAHAMAAHQIAIAgAgAABAgAgAACAAB0HABAAAMAAAwcAEAIAgAAdCAAxCAAQAAMIADEHAPAAHQgAwQIAEAABCADBCACwADAJAPEIABAAHQEA8wkAGwA="
/*
<languageVersion:1.0;>
kernel waterFilter<namespace:"AIF";vendor:"AdobeSystems";version:2;description:"water";>{
parameter float distance<minValue:0.0;defaultValue:100.0;>;
parameter float height<defaultValue:5.0;>;
input image4 backImage;
input image4 waterImage;
output pixel4 dst;
void evaluatePixel(){
float2 o = outCoord();
float4 a = sampleNearest(waterImage,o+float2(0.0,-1.0));
float4 b = sampleNearest(waterImage,o+float2(0.0,1.0));
float4 l = sampleNearest(waterImage,o+float2(-1.0,0.0));
float4 r = sampleNearest(waterImage,o+float2(1.0,0.0));
float a2 = (a.r+a.g+a.b)*a.a;
float b2 = (b.r+b.g+b.b)*b.a;
float l2 = (l.r+l.g+l.b)*l.a;
float r2 = (r.r+r.g+r.b)*r.a;
dst = sampleNearest( backImage, o+float2(r2-l2,b2-a2)*(height*(height*(a2+b2+l2+r2)/4.0+distance)) );
}
}
*/
private var shader:Shader;
private var shaderFilter:ShaderFilter;
private var img:BitmapData;
private var bitmap:Bitmap;
private var background:Loader;
private var particles:Vector.<Particle>;
private var numParticles:uint;
private var neighbors:Vector.<Neighbor>;
private var numNeighbors:uint;
private var color:ColorTransform;
private var filter:BlurFilter;
private var count:int;
private var press:Boolean;
private var labP:TextField;
private var grids:Vector.<Vector.<Grid>>;
public function SPH() {
initialize();
}
private function initialize():void {
color = new ColorTransform(1,1,1,0)
filter = new BlurFilter(4,4)
particles = new Vector.<Particle>();
numParticles = 0;
neighbors = new Vector.<Neighbor>();
numNeighbors = 0;
grids = new Vector.<Vector.<Grid>>(NUM_GRIDS, true);
for(var i:int = 0; i < NUM_GRIDS; i++) {
grids[i] = new Vector.<Grid>(NUM_GRIDS, true);
for(var j:int = 0; j < NUM_GRIDS; j++)
grids[i][j] = new Grid();
}
count = 0;
var decoder:Base64Decoder = new Base64Decoder ;
decoder.decode( SHADER_DATA );
var ba:ByteArray = decoder.toByteArray();
shader = new Shader( ba );
background = new Loader();
background.load( new URLRequest(IMG_URL), new LoaderContext(true) );
addChild(background);
img = new BitmapData(465, 465, true, 0);
addChild( new Bitmap(img) ).alpha = 0.5;
shader.data.height.value = [3];
shader.data.distance.value = [5];
shader.data.waterImage.input = img;
shaderFilter = new ShaderFilter( shader );
background.filters = [ shaderFilter ];
addEventListener(Event.ENTER_FRAME, frame);
labP = new TextField();
labP.selectable = false;
var tf:TextFormat = new TextFormat();
tf.color = 0xffffff;
tf.size = 12;
tf.font = "MS UI Gothic";
labP.defaultTextFormat = tf;
labP.width = 256;
labP.x = 70;
var st:Stats = new Stats();
st.alpha = 0.75;
addChild(st);
addChild(labP);
stage.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):void {press = true;});
stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void {press = false;});
}
private function frame(e:Event):void {
if(press)
pour();
move();
img.lock();
img.colorTransform(img.rect, color);
draw();
img.applyFilter( img, img.rect, new Point, filter );
img.unlock();
shader.data.waterImage.input = img;
background.filters = [ shaderFilter ];
labP.text = "Particle : " + numParticles;
}
private function draw():void {
var w:Number = Sphere.W/2;
var h:Number = Sphere.H/2;
for(var i:int = 0; i < numParticles; i++) {
var p:Particle = particles[i];
img.draw( sphere, new Matrix(1,0,0,1,p.x-w,p.y-h) );
}
}
private function pour():void {
if(count % 3 == 0)
for(var i:int = -4; i <= 4; i++) {
particles[numParticles++] = new Particle(mouseX + i * RANGE * 0.8, 5);
particles[numParticles - 1].vy = 3;
}
}
private function move():void {
count++;
var i:int;
var j:int;
var dist:Number;
var p:Particle;
updateGrids();
findNeighbors();
calcPressure();
calcForce();
for(i = 0; i < numParticles; i++) {
p = particles[i];
p.move();
}
}
private function updateGrids():void {
var i:int;
var j:int;
for(i = 0; i < NUM_GRIDS; i++)
for(j = 0; j < NUM_GRIDS; j++)
grids[i][j].clear();
for(i = 0; i < numParticles; i++) {
var p:Particle = particles[i];
p.fx = p.fy = p.density = 0;
p.gx = p.x * INV_GRID_SIZE;
p.gy = p.y * INV_GRID_SIZE;
if(p.gx < 0)
p.gx = 0;
if(p.gy < 0)
p.gy = 0;
if(p.gx > NUM_GRIDS - 1)
p.gx = NUM_GRIDS - 1;
if(p.gy > NUM_GRIDS - 1)
p.gy = NUM_GRIDS - 1;
grids[p.gx][p.gy].add(p);
}
}
private function findNeighbors():void {
numNeighbors = 0;
for(var i:int = 0; i < numParticles; i++) {
var p:Particle = particles[i];
var xMin:Boolean = p.gx != 0;
var xMax:Boolean = p.gx != NUM_GRIDS - 1;
var yMin:Boolean = p.gy != 0;
var yMax:Boolean = p.gy != NUM_GRIDS - 1;
findNeighborsInGrid(p, grids[p.gx][p.gy]);
if(xMin) findNeighborsInGrid(p, grids[p.gx - 1][p.gy]);
if(xMax) findNeighborsInGrid(p, grids[p.gx + 1][p.gy]);
if(yMin) findNeighborsInGrid(p, grids[p.gx][p.gy - 1]);
if(yMax) findNeighborsInGrid(p, grids[p.gx][p.gy + 1]);
if(xMin && yMin) findNeighborsInGrid(p, grids[p.gx - 1][p.gy - 1]);
if(xMin && yMax) findNeighborsInGrid(p, grids[p.gx - 1][p.gy + 1]);
if(xMax && yMin) findNeighborsInGrid(p, grids[p.gx + 1][p.gy - 1]);
if(xMax && yMax) findNeighborsInGrid(p, grids[p.gx + 1][p.gy + 1]);
}
}
private function findNeighborsInGrid(pi:Particle, g:Grid):void {
for(var j:int = 0; j < g.numParticles; j++) {
var pj:Particle = g.particles[j];
if(pi == pj)
continue;
var distance:Number = (pi.x - pj.x) * (pi.x - pj.x) + (pi.y - pj.y) * (pi.y - pj.y);
if(distance < RANGE2) {
if(neighbors.length == numNeighbors)
neighbors[numNeighbors] = new Neighbor();
neighbors[numNeighbors++].setParticle(pi, pj);
}
}
}
private function calcPressure():void {
for(var i:int = 0; i < numParticles; i++) {
var p:Particle = particles[i];
if(p.density < DENSITY)
p.density = DENSITY;
p.pressure = p.density - DENSITY;
}
}
private function calcForce():void {
var i:int;
for(i = 0; i < numNeighbors; i++) {
var n:Neighbor = neighbors[i];
n.calcForce();
}
}
}
}
import flash.geom.Point;
import flash.display.BitmapData;
var sphere:BitmapData = new Sphere;
class Sphere extends BitmapData{
static public const W:int = 15;
static public const H:int = 15;
function Sphere(){
var w:int = W, h:int = H;
super( w, h, true, 0 );
for( var i:int = 0; i < w; i++ ){
for( var j:int = 0; j < h; j++ ){
var r:Number = 1-(Math.sqrt((w/2-i)*(w/2-i)+(h/2-j)*(h/2-j)))*2/w
if(r<0){r=0}
setPixel32( i, j, ((r*0xFF) << 24) + 0xFFFFFF )
}
}
}
}
class Particle {
public var x:Number;
public var y:Number;
public var gx:int;
public var gy:int;
public var vx:Number;
public var vy:Number;
public var fx:Number;
public var fy:Number;
public var density:Number;
public var pressure:Number;
public function Particle(x:Number, y:Number) {
this.x = x;
this.y = y;
vx = vy = fx = fy = 0;
}
public function move():void {
vy += SPH.GRAVITY;
vx += fx;
vy += fy;
x += vx;
y += vy;
if(x < 5)
vx += (5 - x) * 0.5 - vx * 0.5;
if(y < 5)
vy += (5 - y) * 0.5 - vy * 0.5;
if(x > 460)
vx += (460 - x) * 0.5 - vx * 0.5;
if(y > 460)
vy += (460 - y) * 0.5 - vy * 0.5;
}
}
class Neighbor {
public var p1:Particle;
public var p2:Particle;
public var distance:Number;
public var nx:Number;
public var ny:Number;
public var weight:Number;
public function Neighbor() {
}
public function setParticle(p1:Particle, p2:Particle):void {
this.distance = distance;
this.p1 = p1;
this.p2 = p2;
nx = p1.x - p2.x;
ny = p1.y - p2.y;
distance = Math.sqrt(nx * nx + ny * ny);
weight = 1 - distance / SPH.RANGE;
var temp:Number = weight * weight * weight;
p1.density += temp;
p2.density += temp;
temp = 1 / distance;
nx *= temp;
ny *= temp;
}
public function calcForce():void {
var pressureWeight:Number = weight * (p1.pressure + p2.pressure) / (p1.density + p2.density) * SPH.PRESSURE;
var viscosityWeight:Number = weight / (p1.density + p2.density) * SPH.VISCOSITY;
p1.fx += nx * pressureWeight;
p1.fy += ny * pressureWeight;
p2.fx -= nx * pressureWeight;
p2.fy -= ny * pressureWeight;
var rvx:Number = p2.vx - p1.vx;
var rvy:Number = p2.vy - p1.vy;
p1.fx += rvx * viscosityWeight;
p1.fy += rvy * viscosityWeight;
p2.fx -= rvx * viscosityWeight;
p2.fy -= rvy * viscosityWeight;
}
}
class Grid {
public var particles:Vector.<Particle>;
public var numParticles:uint;
public function Grid() {
particles = new Vector.<Particle>;
numParticles = 0;
}
public function clear():void {
numParticles = 0;
}
public function add(p:Particle):void {
particles[numParticles++] = p;
}
}