Stirrable Particle Fluid
/**
* Copyright Quasimondo ( http://wonderfl.net/user/Quasimondo )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/AfAs
*/
// 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: I replaced the vectors that store the particles and neighbors with linked lists
also changed the drawing routine from bitmap access to vector access.
*/
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.0;//重力
public static const RANGE:Number = 24;//影響半径
public static const RANGE2:Number = RANGE * RANGE;//影響半径の二乗
public static const DENSITY:Number = 1;//流体の密度
public static const PRESSURE:Number = 1;//圧力係数
public static const VISCOSITY:Number = 0.095;//粘性係数
public static const DIV:uint = Math.ceil(465 / RANGE ); //横方向の分割数
public static const DIV2:uint = DIV * DIV;
private var map:Vector.<Vector.<Particle>>;
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 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)
map = new Vector.<Vector.<Particle>>(DIV2,true);
for(var i:int = DIV2; --i>-1; ) map[i] = new Vector.<Particle>();
mouseParticle = new Particle( mouseX, mouseY);
mouseParticle.density = SPH.DENSITY * 0.2;
mouseParticle.pressure = 1;
addParticles(2000);
}
private function frame(e:Event):void {
imgVec.length = 0;
imgVec.length = vecSize;
// if(press)
// pour();
setNeighbors();
setDensity();
setForce();
move();
//img.lock();
img.setVector( rect, imgVec );
/*
img.applyFilter( img, rect, origin, blur1);
img.colorTransform( rect, ct );
img.applyFilter( img, rect, origin, blur2);
//img.draw(img,null,null,"add");
*/
//img.unlock();
}
private function onMouseMove(event:MouseEvent):void
{
mouseParticle.x = mouseX;
mouseParticle.y = mouseY;
mouseParticle.vx = mouseX - lastX;
mouseParticle.vy = mouseY - lastY;
lastX = mouseX;
lastY = mouseY;
}
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.<Vector.<Particle>> = map;
var a:int, b:int, i:int, j:int;
var m1:Vector.<Particle>, m2:Vector.<Particle>;
for( i = DIV2; --i >-1; )
{
m1 = mp[i];
for ( a = m1.length; --a > 0; )
{
if ( press ) m1[a].addNeighbor(mouseParticle);
for ( b = a; --b > -1; )
{
m1[a].addNeighbor(m1[b]);
}
}
}
for( i = 0; i < DIV - 1; i++)
{
for( j = 0; j < DIV; j++)
{
m1 = mp[int(j*DIV+i)];
m2 = mp[int(j*DIV+i+1)];
for ( a = m1.length; --a > -1; )
{
for ( b = m2.length; --b > -1; )
{
m1[a].addNeighbor(m2[b]);
}
}
}
}
for( i= 0; i < DIV; i++)
{
for( j = 0; j < DIV-1; j++)
{
m1 = mp[int(j*DIV+i)];
m2 = mp[int((j+1)*DIV+i)];
for ( a = m1.length; --a > -1; )
{
for ( b = m2.length; --b > -1; )
{
m1[a].addNeighbor(m2[b]);
}
}
}
}
for( i = 0; i < DIV - 1; i++)
{
for( j = 0; j < DIV -1; j++)
{
m1 = mp[int(j*DIV+i)];
m2 = mp[int((j+1)*DIV+i+1)];
for ( a = m1.length; --a > -1; )
{
for ( b = m2.length; --b > -1; )
{
m1[a].addNeighbor(m2[b]);
}
}
m1 = mp[int(j*DIV+i+1)];
m2 = mp[int((j+1)*DIV+i)];
for ( a = m1.length; --a > -1; )
{
for ( b = m2.length; --b > -1; )
{
m1[a].addNeighbor(m2[b]);
}
}
}
}
}
private function setDensity():void{
var p:Particle = first;
while ( p != null )
{
p.setDensity();
p = p.next;
}
}
private function setForce():void{
var p:Particle = first;
while ( p != null )
{
p.setForce();
p = p.next;
}
}
private function move():void
{
//count++;
const img:BitmapData = this.img;
var p:Particle = first;
while ( p != null )
{
p.move();
p.moveMap( map );
p.draw( imgVec, img.width )
p = p.next;
}
}
}
}
import flash.geom.Rectangle;
import flash.display.BitmapData;
final class Neighbor {
public var next:Neighbor;
public var particle:Particle;
private static var reuse:Vector.<Neighbor> = new Vector.<Neighbor>();
private static var count:int = 0;
public static function getNeighbor( p:Particle ):Neighbor
{
var n:Neighbor;
if ( count > 0 )
n = reuse[int(--count)];
else
n = new Neighbor();
n.particle = p;
n.next = null;
return n;
}
public static function recycleNeighbors( list:Neighbor ):void
{
while ( list != null )
{
reuse[int(count++)] = list;
list = list.next;
}
}
public function Neighbor()
{}
}
final class Particle {
public var next:Particle;
public var nextInZone:Particle;
public var x:Number; //x方向の位置
public var y:Number; //y方向の位置
private var mapX:uint; //x方向の位置
private var mapY:uint; //y方向の位置
public var vx:Number; //vx方向の位置
public var vy:Number; //vy方向の位置
private var fx:Number; //x方向の加速度
private var fy:Number; //y方向の加速
public var density:Number; //周辺のパーティクル密度
public var pressure:Number; //パーティクルにかかる圧力
private var color:uint;
public var firstNeighbor:Neighbor;
public var lastNeighbor:Neighbor;
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;
mapX = uint(-1);
//color = 0xeeffff;
}
public function addNeighbor( p:Particle ):void
{
const dx:Number = x - p.x;
const dy:Number = y - p.y;
if( dx * dx + dy * dy < SPH.RANGE2) {
if ( lastNeighbor == null )
{
lastNeighbor = firstNeighbor = Neighbor.getNeighbor(p);
} else {
lastNeighbor = lastNeighbor.next = Neighbor.getNeighbor(p);
}
if ( p.lastNeighbor == null )
{
p.lastNeighbor = p.firstNeighbor = Neighbor.getNeighbor(this);
} else {
p.lastNeighbor = p.lastNeighbor.next = Neighbor.getNeighbor(this);
}
}
}
public function setDensity():void{
const px:Number = x;
const py:Number = y;
var rg:Number = SPH.RANGE;
var d:Number = 0;
var n:Neighbor = firstNeighbor;
while ( n != null )
{
const pj:Particle = n.particle;
const dx:Number = px - pj.x;
const dy:Number = py - pj.y;
const weight:Number = 1 - Math.sqrt(dx * dx + dy * dy) / rg;
d += weight * weight;
// pj.density += weight2;
n = n.next;
}
if( d < SPH.DENSITY) d = SPH.DENSITY;
density = d;
pressure = density - SPH.DENSITY;
}
public function setForce():void{
const px:Number = this.x;
const py:Number = this.y;
const pvx:Number = this.vx;
const pvy:Number = this.vy;
const pressure:Number = this.pressure;
const density:Number = this.density;
var weight:Number, pw:Number, vw:Number, pj:Particle, dx:Number, dy:Number, dist:Number;
var pr:Number = SPH.PRESSURE;
var vs:Number = SPH.VISCOSITY;
var rg:Number = SPH.RANGE;
var ffx:Number = 0;
var ffy:Number = 0;
var n:Neighbor = firstNeighbor;
while ( n != null )
{
pj = n.particle;
dx = px - pj.x;
dy = py - pj.y;
dist = Math.sqrt(dx * dx + dy * dy);
if ( dist == 0 )
{
dx = 0.001 * (Math.random() - 0.5);
dy = 0.001 * (Math.random() - 0.5);
dist = Math.sqrt(dx * dx + dy * dy);
}
weight = 1 - dist / rg;
pw = weight * (pressure + pj.pressure) / (2 * pr * pj.density);
vw = weight / pj.density * vs;
dist = 1 / dist;
ffx += dx * dist * pw - (pvx - pj.vx) * vw;
ffy += dy * dist * pw - (pvy - pj.vy) * vw;
n = n.next;
}
fx = ffx;
fy = ffy;
Neighbor.recycleNeighbors( firstNeighbor );
lastNeighbor = firstNeighbor = null;
}
public function move():void {
x += vx += fx;
y += vy += fy + SPH.GRAVITY;
// vx *= 0.99;
// vy *= 0.99;
if(x < 0){ vx = -vx; x = -x }
else if(x > 464){ vx = -vx; x -= x -464 }
//while(x < 0){ x += 464 }
// while(x > 464){ x -= 464 }
if(y < 0){ vy = -vy; y = -y }
else if(y > 464){ vy = - vy; y -= y - 464 }
// mapX = x / SPH.RANGE;
// mapY = y / SPH.RANGE;
}
public function moveMap( map:Vector.<Vector.<Particle>> ):void {
const mx:int = this.x / SPH.RANGE;
const my:int = this.y / SPH.RANGE;
const pmx:int = this.mapX;
const pmy:int = this.mapY;
if( mx != pmx || my != pmy )
{
if ( pmx != -1 )
{
var m:Vector.<Particle> = map[ int(pmx * SPH.DIV + pmy) ];
m.splice( m.indexOf(this),1);
}
map[ int(mx * SPH.DIV + my) ].push( this );
this.mapY = my; this.mapX = mx;
}
}
public function draw( imgVec:Vector.<uint>, w:int ):void{
var idx:int = int(x)+ int(y)* w;
// imgVec[idx] = imgVec[int(idx+1)] = imgVec[int(idx+w)] = imgVec[int(idx+w+1)] = color;
imgVec[idx] = color;
}
}