7000 Particle Stirrable Fluid
/**
* Copyright Quasimondo ( http://wonderfl.net/user/Quasimondo )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/kk2R
*/
// forked from Quasimondo's Stirrable Particle Fluid
// forked from Quasimondo's Particle Fluid (Linked List Optimization)
// forked from shohei909's 流体シミュレーション/Particle Fluidを高速化してみた。
// forked from saharan's 流体シミュレーション/Particle Fluid
/*
* SPH - Smoothed Particle Hydrodynamics
*
* クリック:水を注ぐ
* Click:Pouring
*
* SPH(もどき)をASで実装してみました。
* 最適化していないので重いです。
* タイムステップを考えていなかったりといろいろ適当な部分がありますので注意。
* カーネルを除く基本的な計算方法は一緒です。
*
*/
/*
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
高速化 by shohei909
① 画面を縦横39分割して、隣り合う領域にある分子のみを判定する。
② stage.quality = "low";
③ int→uint
④ neighborの単方向化
などなどやってみました。
一番効果があったのは①でした。
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
Quasimondo: optimized my linked list method with the neighbor technique used by
Saharan in this sketch: http://wonderfl.net/c/2pg0
In the latest version I optimized the grid with some linked list structure, too
*/
package {
import flash.text.*;
import flash.filters.*;
import flash.geom.*;
import flash.events.*;
import flash.display.*;
import net.hires.debug.Stats;
import com.bit101.components.Label;
[SWF(frameRate = 60)]
final public class SPH extends Sprite {
public static const GRAVITY:Number = 0;//重力
public static const RANGE:Number = 10;//影響半径
public static const RANGE2:Number = RANGE * RANGE;//影響半径の二乗
public static const DENSITY:Number = 0.25;//流体の密度
public static const PRESSURE:Number = 2;//圧力係数
public static const VISCOSITY:Number = 0.04;//粘性係数
public static const DIV:uint = Math.ceil(465 / RANGE ); //横方向の分割数
public static const DIV2:uint = DIV * DIV;
public static const INV_DIV:Number = 1 / RANGE; //横方向の分割数
private var map:Vector.<GridCell>;
private var img:BitmapData;
private var imgVec:Vector.<uint>;
private var first:Particle;
private var last:Particle;
private var mouseParticle:Particle;
private var lastX:int;
private var lastY:int;
private var mouse_vx:Number;
private var mouse_vy:Number;
private var firstNeighbor:Neighbor;
private var grid:GridCell;
//private var numParticles:uint;
// private const blur1:BlurFilter = new BlurFilter(8, 8, 3);
//private const ct:ColorTransform = new ColorTransform(7,7,16);
//private const blur2:BlurFilter = new BlurFilter(4, 4, 2);
//private var count:int;
private var press:Boolean;
//private var text:Label;
private const origin:Point = new Point();
private var rect:Rectangle;
private var vecSize:int;
public function SPH() {
addEventListener( Event.ADDED_TO_STAGE, initialize );
}
private function initialize( e:Event ):void {
opaqueBackground = 0;
scrollRect = new Rectangle(0,0,465,465);
img = new BitmapData(465, 465, false, 0);
vecSize = img.width * img.height ;
imgVec = new Vector.<uint>();
rect = img.rect;
addEventListener(Event.ENTER_FRAME, frame);
addChild( new Bitmap(img) ).opaqueBackground = 0;
stage.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):void {press = true;});
stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void {press = false;});
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
stage.tabChildren = false;
stage.mouseChildren = false;
stage.quality = "low";
stage.addChild( new Stats ).opaqueBackground = 0;
//text = new Label( stage, 75, 0, "numParticles: " + numParticles );
//stage.removeChildAt(0)
var g:GridCell;
map = new Vector.<GridCell>(DIV2,true);
for( var i:int = 0; i < DIV2; i++ )
{
if ( i == 0 ) g = grid = map[i] = new GridCell();
else g = g.next = map[i] = new GridCell();
}
i = 0;
for ( var y:int = 0; y < DIV; y++)
{
for ( var x:int = 0; x < DIV; x++)
{
if ( x < DIV -1 ) {
map[i].right = map[i+1];
if ( y < DIV - 1 )
{
map[i].downright = map[i+1+DIV];
}
}
if ( y < DIV -1 )
{
map[i].down = map[i+DIV];
if ( x > 0 )
{
map[i].downleft = map[i-1+DIV];
}
}
i++;
}
}
mouseParticle = new Particle( mouseX, mouseY);
mouseParticle.density = SPH.DENSITY * 0.2;
mouseParticle.pressure = 1;
addParticles(7000);
}
private function frame(e:Event):void
{
updateGrid();
setNeighbors();
mouseParticle.vx = mouse_vx * 3;
mouseParticle.vy = mouse_vy * 3;
mouseParticle.density = SPH.DENSITY * 0.02;
setPressure();
mouseParticle.pressure = 0.4;
setForce();
move();
imgVec.length = 0;
imgVec.length = vecSize;
draw();
img.setVector( rect, imgVec );
}
private function onMouseMove(event:MouseEvent):void
{
mouseParticle.x = mouseX;
mouseParticle.y = mouseY;
mouse_vx = mouseX - lastX;
mouse_vy = mouseY - lastY;
lastX = mouseX;
lastY = mouseY;
}
private function updateGrid():void
{
var mapX:int, mapY:int;
var mp:Vector.<GridCell> = map;
var g:GridCell = grid;
while ( g != null )
{
g.firstParticle.nextInGrid = null;
g.lastParticle = g.firstParticle;
g = g.next;
}
var p:Particle = first;
while ( p != null )
{
p.fx = p.fy = p.density = 0;
mapX = p.x * INV_DIV;
mapY = p.y * INV_DIV;
if(mapX < 0) mapX = 0;
else if(mapX > DIV - 1) mapX = DIV - 1;
if(mapY < 0) mapY = 0;
else if(mapY > DIV - 1) mapY = DIV - 1;
g = mp[int( mapX + DIV * mapY)];
p.nextInGrid = null;
g.lastParticle = g.lastParticle.nextInGrid = p;
p = p.next;
}
}
private function addParticles( count:int ):void
{
if ( last == null )
{
first = new Particle(465 * Math.random(), 465 * Math.random());
last = first;
} else {
last = last.next = new Particle(465 * Math.random(), 465 * Math.random());
}
//last.move();
while ( count-- > 0 )
{
last = last.next = new Particle(465 * Math.random(), 465 * Math.random());
//last.move();
}
// text.text = "numParticles: " + numParticles;
}
private function setNeighbors():void
{
var mp:Vector.<GridCell> = map;
var a:int, b:int, i:int, j:int, d:Number;
var m1:Vector.<Particle>, m2:Vector.<Particle>;
var p1:Particle, p2:Particle;
if ( firstNeighbor != null ) Neighbor.recycleNeighbors( firstNeighbor );
var n:Neighbor = firstNeighbor = Neighbor.getDummy();
var g:GridCell;
if ( press )
{
g = grid;
while ( g != null )
{
p1 = g.firstParticle.nextInGrid;
while ( p1 != null )
{
if ( ( d = p1.getSquaredDistance( mouseParticle )) < RANGE2 * 2) n = n.next = Neighbor.getNeighbor( p1, mouseParticle, d * 0.5 );
p1 = p1.nextInGrid;
}
g = g.next;
}
}
g = grid;
while ( g != null )
{
p1 = g.firstParticle.nextInGrid;
while ( p1 != null )
{
p2 = p1.nextInGrid;
while ( p2 != null )
{
if ( ( d = p1.getSquaredDistance( p2 )) < RANGE2 ) n = n.next = Neighbor.getNeighbor( p1, p2, d );
p2 = p2.nextInGrid;
}
p1 = p1.nextInGrid;
}
g = g.next;
}
g = grid;
while ( g != null )
{
if ( g.right != null )
{
p1 = g.firstParticle.nextInGrid;
while ( p1 != null )
{
p2 = g.right.firstParticle.nextInGrid;
while ( p2 != null )
{
if ( ( d = p1.getSquaredDistance( p2 )) < RANGE2 ) n = n.next = Neighbor.getNeighbor( p1, p2, d );
p2 = p2.nextInGrid;
}
p1 = p1.nextInGrid;
}
}
if ( g.down != null )
{
p1 = g.firstParticle.nextInGrid;
while ( p1 != null )
{
p2 = g.down.firstParticle.nextInGrid;
while ( p2 != null )
{
if ( ( d = p1.getSquaredDistance( p2 )) < RANGE2 ) n = n.next = Neighbor.getNeighbor( p1, p2, d );
p2 = p2.nextInGrid;
}
p1 = p1.nextInGrid;
}
if ( g.downright != null )
{
p1 = g.firstParticle.nextInGrid;
while ( p1 != null )
{
p2 = g.downright.firstParticle.nextInGrid;
while ( p2 != null )
{
if ( ( d = p1.getSquaredDistance( p2 )) < RANGE2 ) n = n.next = Neighbor.getNeighbor( p1, p2, d );
p2 = p2.nextInGrid;
}
p1 = p1.nextInGrid;
}
}
if ( g.downleft != null )
{
p1 = g.firstParticle.nextInGrid;
while ( p1 != null )
{
p2 = g.downleft.firstParticle.nextInGrid;
while ( p2 != null )
{
if ( ( d = p1.getSquaredDistance( p2 )) < RANGE2 ) n = n.next = Neighbor.getNeighbor( p1, p2, d );
p2 = p2.nextInGrid;
}
p1 = p1.nextInGrid;
}
}
}
g = g.next;
}
}
private function setPressure():void{
var p:Particle = first;
while ( p != null )
{
if(p.density < DENSITY) p.density = DENSITY;
p.pressure = p.density - DENSITY;
p = p.next;
}
}
private function setForce():void{
var p:Neighbor = firstNeighbor.next;
while ( p != null )
{
p = p.calcForce();
}
}
private function move():void
{
var p:Particle = first;
while ( p != null )
{
p = p.move();
}
}
private function draw():void
{
var p:Particle = first;
while ( p != null )
{
if ( p.x >= 0 && p.y >= 0 && p.x < 465 && p.y < 465 )
imgVec[int(int(p.x) + 465 * int(p.y))] = 0xffffffff;
p = p.next;
}
}
}
}
final class GridCell
{
public var right:GridCell;
public var down:GridCell;
public var downright:GridCell;
public var downleft:GridCell;
public var next:GridCell;
public var firstParticle:Particle;
public var lastParticle:Particle;
public function GridCell()
{
firstParticle = new Particle(0,0);
lastParticle = firstParticle;
}
}
import flash.geom.Rectangle;
import flash.display.BitmapData;
final 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 var next:Neighbor;
private static var reuse:Vector.<Neighbor> = new Vector.<Neighbor>();
private static var count:int = 0;
public static function getDummy( ):Neighbor
{
var n:Neighbor;
if ( count > 0 )
n = reuse[int(--count)];
else
n = new Neighbor();
n.next = null;
return n;
}
public static function getNeighbor( p1:Particle, p2:Particle, squaredDistance:Number ):Neighbor
{
var n:Neighbor;
if ( count > 0 )
n = reuse[int(--count)];
else
n = new Neighbor();
n.setParticle( p1, p2, squaredDistance );
n.next = null;
return n;
}
public static function recycleNeighbors( list:Neighbor ):void
{
while ( list != null )
{
reuse[int(count++)] = list;
list = list.next;
}
}
public function setParticle(p1:Particle, p2:Particle, squaredDistance:Number):void {
this.p1 = p1;
this.p2 = p2;
distance = Math.sqrt(squaredDistance);
weight = 1 - distance / SPH.RANGE;
var temp:Number = weight * weight * weight;
p1.density += temp;
p2.density += temp;
nx = p1.x - p2.x;
ny = p1.y - p2.y;
temp = 1 / distance;
nx *= temp;
ny *= temp;
}
public function calcForce():Neighbor {
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;
return next;
}
}
final class Particle {
public var next:Particle;
public var nextInGrid:Particle;
public var x:Number; //x方向の位置
public var y:Number; //y方向の位置
public var vx:Number; //vx方向の位置
public var vy:Number; //vy方向の位置
public var fx:Number; //x方向の加速度
public var fy:Number; //y方向の加速
public var density:Number; //周辺のパーティクル密度
public var pressure:Number; //パーティクルにかかる圧力
public var color:uint;
public function Particle(x:Number, y:Number) {
this.x = x;
this.y = y;
vx = fx = fy = 0;
vy = 0;//5 * Math.random();
var clr:int = 0xa0 + Math.random() * 0x40;
color = 0xff000000 | (clr<<16) | (clr << 8) | clr;
//color = 0xeeffff;
}
public function getSquaredDistance( p:Particle ):Number
{
var dx:Number = x - p.x;
var dy:Number = y - p.y;
return ( dx*dx+dy*dy );
}
public function move():Particle {
x += vx += fx;
y += vy += fy + SPH.GRAVITY;
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;
return next;
}
}