流体シミュレーション/Particle Fluidを高速化&海っぽくしてみた。
SPH - Smoothed Particle Hydrodynamics
クリック:水を注ぐ
Click:Pouring
SPH(もどき)をASで実装してみました。
最適化していないので重いです。
タイムステップを考えていなかったりといろいろ適当な部分がありますので注意。
カーネルを除く基本的な計算方法は一緒です。
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
高速化 by shohei909
① 画面を縦横39分割して、隣り合う領域にある分子のみを判定する。
② stage.quality = "low";
③ int→uint
④ neighborの単方向化
などなどやってみました。
一番効果があったのは①でした。
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
/**
* Copyright shohei909 ( http://wonderfl.net/user/shohei909 )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/nDTf
*/
// forked from saharan's 流体シミュレーション/Particle Fluid
/*
* SPH - Smoothed Particle Hydrodynamics
*
* クリック:水を注ぐ
* Click:Pouring
*
* SPH(もどき)をASで実装してみました。
* 最適化していないので重いです。
* タイムステップを考えていなかったりといろいろ適当な部分がありますので注意。
* カーネルを除く基本的な計算方法は一緒です。
*
*/
/*
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
高速化 by shohei909
① 画面を縦横39分割して、隣り合う領域にある分子のみを判定する。
② stage.quality = "low";
③ int→uint
④ neighborの単方向化
などなどやってみました。
一番効果があったのは①でした。
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
*/
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.05;//重力
public static const RANGE:Number = 12;//影響半径
public static const RANGE2:Number = RANGE * RANGE;//影響半径の二乗
public static const DENSITY:Number = 1;//流体の密度
public static const PRESSURE:Number = 2;//圧力係数
public static const VISCOSITY:Number = 0.075;//粘性係数
public static const V_DIV:uint = Math.ceil(465 / RANGE); //横方向の分割数
public static const H_DIV:uint = Math.ceil(465 / RANGE); //縦方向の分割数
private var map:Vector.<Vector.<Vector.<Particle>>>;
private var img:BitmapData;
private var particles:Vector.<Particle>;
private var numParticles:uint;
private var color:ColorTransform;
private var filter:BlurFilter;
private var count:int;
private var press:Boolean;
private var text:Label;
public function SPH() {
addEventListener( Event.ADDED_TO_STAGE, initialize );
}
private function initialize( e:Event ):void {
color = new ColorTransform(0.95, 0.95, 0.95, 1,-10,-10,-10);
filter = new BlurFilter(8, 8, 2);
particles = new Vector.<Particle>();
numParticles = 0;
count = 0;
img = new BitmapData(465, 465, false, 0);
addEventListener(Event.EXIT_FRAME, frame);
stage.addChild( new Bitmap(img) );
stage.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):void {press = true;});
stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void {press = false;});
stage.tabChildren = false;
stage.mouseChildren = false;
stage.quality = "low";
stage.addChild( new Stats ).alpha = 0.8;
text = new Label( stage, 75, 0, "numParticles: " + numParticles );
stage.removeChildAt(0)
map = new Vector.< Vector.< Vector.<Particle> > >(H_DIV);
map.fixed = true;
for(var i:uint = 0; i<H_DIV; i++ ){
map[i] = new Vector.< Vector.<Particle> >(V_DIV);
map[i].fixed = true;
for(var j:uint = 0; j<H_DIV; j++ ){
map[i][j] = new Vector.< Particle >;
}
}
}
private function frame(e:Event):void {
if(press)
pour();
img.lock();
img.colorTransform(img.rect, color);
img.applyFilter(img, img.rect, new Point(), filter);
setNeighbors();
setDensity();
setPressure();
setForce();
move();
img.unlock();
}
private function pour():void {
for(var i:int = -3; i <= 3; i++) {
const p:Particle = particles[numParticles++] = new Particle(mouseX + i * 8, 5);
p.move();
}
text.text = "numParticles: " + numParticles;
}
private function setNeighbors():void {
var dx:Number, dy:Number;
const particles:Vector.<Particle> = this.particles;
//分割された各領域ごとで近くのパーティクルをさがす。
for(var i:uint= 0; i < H_DIV; i++) {
const mapi:Vector.<Vector.<Particle>> = map[i];
for(var j:uint = 0; j < V_DIV; j++){
var mapij:Vector.<Particle> = mapi[j];
var minN:int = i-1; if( minN < 0 ){ minN = 0 }
var minM:int = j-1; if( minM < 0 ){ minM = 0 }
var maxN:int = i+2; if( maxN > H_DIV ){ maxN = H_DIV }
var maxM:int = j+2; if( maxM > V_DIV ){ maxM = V_DIV }
for each( var pi:Particle in mapij ){
l:{
for(var n:uint = minN; n < maxN; n++ ){
const mapn:Vector.<Vector.<Particle>> = map[n];
for(var m:uint = minM; m < maxM; m++ ){
const mapnm:Vector.<Particle> = mapn[m];
for each(var pj:Particle in mapnm ){
//piと同じパーティクルが現れたら終了
if( ! pi.addNeighbor(pj) ){ break l; }
}
}
}
}
}
}
}
}
private function setDensity():void{
const particles:Vector.<Particle> = this.particles;
const numParticles:uint = this.numParticles;
for(var i:uint = 0; i < numParticles; i++) { particles[i].setDensity(); }
}
private function setPressure():void{
const particles:Vector.<Particle> = this.particles;
const numParticles:uint = this.numParticles;
for(var i:uint = 0; i < numParticles; i++) { particles[i].setPressure();}
}
private function setForce():void{
const particles:Vector.<Particle> = this.particles;
const numParticles:uint = this.numParticles;
for(var i:uint = 0; i < numParticles; i++) { particles[i].setForce(); }
}
private function move():void {
count++;
const img:BitmapData = this.img;
const particles:Vector.<Particle> = this.particles;
const numParticles:uint = this.numParticles;
for(var i:uint = 0; i < numParticles; i++) {
const pi:Particle = particles[i];
pi.move();
pi.moveMap( map );
pi.draw( img )
}
}
}
}
import flash.geom.Rectangle;
import flash.display.BitmapData;
class Particle {
private var x:Number; //x方向の位置
private var y:Number; //y方向の位置
private var mapX:uint; //x方向の位置
private var mapY:uint; //y方向の位置
private var vx:Number; //vx方向の位置
private var vy:Number; //vy方向の位置
private var fx:Number; //x方向の加速度
private var fy:Number; //y方向の加速
private var density:Number; //周辺のパーティクル密度
private var pressure:Number; //パーティクルにかかる圧力
private const C1:uint = 0xFFFFFF;
private const C2:uint = 0x0033DD;
private var color:uint;
private var neighbors:Vector.<Particle>;
public function Particle(x:Number, y:Number) {
this.x = x;
this.y = y;
vx = fx = fy = 0;
vy = 5;
neighbors = new Vector.<Particle>();
//color = 0xeeffff;
}
public function addNeighbor( p:Particle ):Boolean{
if( p === this ){ return false }
const dx:Number = x - p.x;
const dy:Number = y - p.y;
if( dx * dx + dy * dy < SPH.RANGE2) {
neighbors.push( p );
}
return true;
}
public function setDensity():void{
const px:Number = this.x;
const py:Number = this.y;
const neighbors:Vector.<Particle> = this.neighbors;
const l:uint = neighbors.length;
for(var j:uint = 0; j < l; j++) {
const pj:Particle = neighbors[j];
const dx:Number = px - pj.x;
const dy:Number = py - pj.y;
const weight:Number = 1 - Math.sqrt(dx * dx + dy * dy) / SPH.RANGE
const weight2:Number = weight * weight
density += weight2;
pj.density += weight2;
}
}
public function setPressure():void{
if( density < SPH.DENSITY){ density = SPH.DENSITY; }
pressure = density - SPH.DENSITY;
var c:Number = density / SPH.DENSITY;
c = c;
if(c <= 1){ color = C1 }
else { color = C2 }
}
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 neighbors:Vector.<Particle> = this.neighbors;
const pressure:Number = this.pressure;
const density:Number = this.density;
const l:uint = neighbors.length;
for(var j:uint = 0; j < l; j++) {
const pj:Particle = neighbors[j];
const dx:Number = px - pj.x;
const dy:Number = py - pj.y;
const dist:Number = Math.sqrt(dx * dx + dy * dy);
const weight:Number = 1 - dist / SPH.RANGE;
const pw:Number = weight * (pressure + pj.pressure) * (0.5 * SPH.PRESSURE)
const pressureWeight1:Number = pw / pj.density;
const pressureWeight2:Number = pw / density;
const vw:Number = weight * SPH.VISCOSITY;
const viscosityWeight1:Number = vw / pj.density;
const viscosityWeight2:Number = vw / density;
const viscosityX:Number = (pvx - pj.vx);
const viscosityY:Number = (pvy - pj.vy);
const dx2:Number = dx / dist;
const dy2:Number = dy / dist;
this.fx += dx2 * pressureWeight1 - viscosityX * viscosityWeight1;
this.fy += dy2 * pressureWeight1 - viscosityY * viscosityWeight1;
pj.fx -= dx2 * pressureWeight2 - viscosityX * viscosityWeight2;
pj.fy -= dy2 * pressureWeight2 - viscosityY * viscosityWeight2;
}
this.neighbors = new Vector.<Particle>();
}
public function move():void {
x += vx += fx;
y += vy += fy + SPH.GRAVITY;
fx = 0;
fy = 0;
density = 0;
if(x < 5){ vx -= vx; x = 10 - x; }
else if(x > 460){ vx = -vx; x = 920 - x }
if(y < 5){ vy = -vy; y = 10 - y; }
else if(y > 460){ vy = - vy; y = 920 - y; }
}
public function moveMap( map:Vector.<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 ){
const mp:Vector.<Particle> = map[ pmx ][ pmy ]
const index:int = mp.indexOf( this );
if( index != -1){
mp.splice( index, 1 );
}
map[ mx ][ my ].push( this );
this.mapY = my; this.mapX = mx;
}
}
public function draw( img:BitmapData ):void{
img.fillRect( new Rectangle( x - 1, y - 1, 3, 3), color );
}
}