Multiphase flow
forked from 混相流 / Multiphase flow (diff: 435)
Water like simulation fork. Click: pour water Double click: clean Planing to play with this more and start from exploring if its possible to optimize it. Changes: - Its now resizable and can be of unequal box sizes. Switch to fullscreen and resize your browser :) - Changed stats for my own class that allows to track how much time separate code parts take and allow to explore optimizations better
ActionScript3 source code
/**
* Copyright wonderwhyer ( http://wonderfl.net/user/wonderwhyer )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/bzPB
*/
package {
import flash.utils.*;
import flash.text.*;
import flash.filters.*;
import flash.geom.*;
import flash.events.*;
import flash.display.*;
[SWF(frameRate = "60")]
public class Fluid extends Sprite {
public static const GRAVITY:Number = 0.05;
public static const RANGE:Number = 10;//Radius of influence
public static const RANGE2:Number = RANGE * RANGE;
public static const DENSITY:Number = 2;//fluid baeline density
public static const PRESSURE:Number = 1;//Pressure coefficient
public static const PRESSURE_NEAR:Number = 1;//Close-pressure coefficient
public static const VISCOSITY:Number = 0.1;//Viscosity
public static var NUM_GRIDS_X:int;//number of grid cells
public static var NUM_GRIDS_Y:int;
public static var GRID_CELL_SIZE:Number = 15;//aproximate grid cell size, will be rounded to match size of the box
public static var INV_GRID_SIZE:Number;
public static var BOX_SIZE_X:Number;
public static var BOX_SIZE_Y:Number;
private var particles:Vector.<Particle>;
private var numParticles:uint;
private var neighbors:Vector.<Neighbor>;
private var numNeighbors:uint;
private var count:int;
private var press:Boolean;
private var bitmap:BitmapData;
private var view:Bitmap;
private var grids:Vector.<Vector.<Grid>>;
private const COLOR_TRANSFORM:ColorTransform = new ColorTransform(0.5, 0.5, 0.5);
public var particleCount:TextField;
public var lastMouseX:Number = stage.mouseX;
public var lastMouseY:Number = stage.mouseY;
public var fps:FPS;
public function Fluid() {
initialize();
}
private function initialize():void {
stage.doubleClickEnabled = true;
particles = new Vector.<Particle>();
numParticles = 0;
neighbors = new Vector.<Neighbor>();
numNeighbors = 0;
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
view = addChild(new Bitmap()) as Bitmap;
initGrid();
count = 0;
addEventListener(Event.ENTER_FRAME, frame);
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.DOUBLE_CLICK,function(e:MouseEvent):void {
particles.length=0;
numParticles=0;
});
stage.addEventListener(Event.RESIZE,function(e:Event):void{
initGrid();
});
particleCount = new TextField();
particleCount.textColor=0xffffff;
particleCount.x = 100;
particleCount.y = 50;
addChild(particleCount);
fps = new FPS();
addChild(fps);
fps.textColor="0xFF";
fps.fpsColor="0xFF0000";
fps.graphWidth=100;
fps.graphHeight=50;
fps.init();
fps.graphics.beginFill(0x0,0.5);
fps.graphics.drawRect(0,0,200,110);
}
private function initGrid():void
{
var min:Number = Math.min(stage.stageWidth,stage.stageHeight)
BOX_SIZE_X = stage.stageWidth;
BOX_SIZE_Y = stage.stageHeight;
var NUM_GRIDS:int = Math.round(min/GRID_CELL_SIZE);
GRID_CELL_SIZE = min / NUM_GRIDS;
INV_GRID_SIZE = 1/GRID_CELL_SIZE;
NUM_GRIDS_X = Math.floor(stage.stageWidth/GRID_CELL_SIZE);
NUM_GRIDS_Y = Math.floor(stage.stageHeight/GRID_CELL_SIZE);
BOX_SIZE_X = NUM_GRIDS_X*GRID_CELL_SIZE;
BOX_SIZE_Y = NUM_GRIDS_Y*GRID_CELL_SIZE;
grids = new Vector.<Vector.<Grid>>(NUM_GRIDS_X, true);
for(var i:int = 0; i < NUM_GRIDS_X; i++) {
grids[i] = new Vector.<Grid>(NUM_GRIDS_Y, true);
for(var j:int = 0; j < NUM_GRIDS_Y; j++)
grids[i][j] = new Grid();
}
view.bitmapData = bitmap = new BitmapData(BOX_SIZE_X, BOX_SIZE_Y, false, 0);
view.x = (stage.stageWidth-view.width)*0.5;
view.y = (stage.stageHeight-view.height)*0.5;
}
private function frame(e:Event):void {
fps.clear();
fps.startCounting("frame");
fps.startCounting("pour");
if(press)
pour();
fps.stopCounting("pour");
lastMouseX = view.mouseX;
lastMouseY = view.mouseY;
move();
particleCount.text = particles.length.toString();
fps.stopCounting("frame");
}
private function pour():void {
var dx:Number = view.mouseX-lastMouseX;
var dy:Number = view.mouseY-lastMouseY;
var l:Number = Math.sqrt(dx*dx+dy*dy);
if(dx==0 && dy==0) l=1;
var ang:Number = Math.atan2(dx,dy);
for(var i:int = -4; i <= 4; i++) {
particles[numParticles++] = new Particle(view.mouseX+Math.random()*50-25, view.mouseY+Math.random()*50-25,
count / 10 % 5);
const p:Particle = particles[numParticles - 1];
p.vy = 5*dy/l;
p.vx = 5*dx/l;
}
}
private function move():void {
count++;
bitmap.lock();
fps.startCounting("transform");
//bitmap.colorTransform(bitmap.rect, COLOR_TRANSFORM);
bitmap.fillRect(bitmap.rect,0);
fps.stopCounting("transform");
fps.startCounting("updateGrid");
updateGrids();
fps.stopCounting("updateGrid");
bitmap.unlock();
fps.startCounting("findNeighbours");
findNeighbors();
fps.stopCounting("findNeighbours");
fps.startCounting("calcForce");
calcForce();
fps.stopCounting("calcForce");
}
private function updateGrids():void {
var i:uint;
var j:uint;
for(i = 0; i < NUM_GRIDS_X; i++)
for(j = 0; j < NUM_GRIDS_Y; j++)
grids[i][j].numParticles = 0;
for(i = 0; i < numParticles; i++) {
const p:Particle = particles[i];
p.move();
//bitmap.fillRect(new Rectangle(p.x - 1, p.y - 1, 3, 3), p.color);
bitmap.setPixel(p.x,p.y,p.color);
p.fx = p.fy = p.density = p.densityNear = 0;
p.gx = p.x * INV_GRID_SIZE; // which grid cell this particle fits in
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_X - 1)
p.gx = NUM_GRIDS_X - 1;
if(p.gy > NUM_GRIDS_Y - 1)
p.gy = NUM_GRIDS_Y - 1;
}
}
private function findNeighbors():void { // searching neighbours in current and sometimes neighbour grid cells
numNeighbors = 0;
for(var i:uint = 0; i < numParticles; i++) {
const p:Particle = particles[i];
const xMin:Boolean = p.gx != 0;
const xMax:Boolean = p.gx != NUM_GRIDS_X - 1;
const yMin:Boolean = p.gy != 0;
const yMax:Boolean = p.gy != NUM_GRIDS_Y - 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]);
grids[p.gx][p.gy].add(p);
}
}
private function findNeighborsInGrid(pi:Particle, g:Grid):void {
for(var j:uint = 0; j < g.numParticles; j++) {
var pj:Particle = g.particles[j];
var nx:Number = pi.x-pj.x;
var ny:Number = pi.y-pj.y;
var distance:Number = nx*nx+ny*ny;
if(distance < RANGE2) {
if(neighbors.length == numNeighbors)
neighbors[numNeighbors] = new Neighbor();
var neighbour:Neighbor = neighbors[numNeighbors++];
neighbour.setParticle(pi, pj,nx,ny,distance);
}
}
}
private function calcForce():void {
for(var i:uint = 0; i < numNeighbors; i++)
neighbors[i].calcForce();
}
}
}
import flash.display.Sprite;
import flash.events.Event;
import flash.text.TextField;
import flash.display.BitmapData;
import flash.geom.Matrix;
import flash.display.Graphics;
import flash.display.Bitmap;
import flash.text.TextFormat;
import flash.utils.getTimer;
import flash.system.System;
import flash.geom.Point;
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 const RANGE:Number = Fluid.RANGE;
public const PRESSURE:Number = Fluid.PRESSURE;
public const PRESSURE_NEAR:Number = Fluid.PRESSURE_NEAR;
public const DENSITY:Number = Fluid.DENSITY;
public const VISCOSITY:Number = Fluid.VISCOSITY;
public function Neighbor() {
}
public function setParticle(p1:Particle, p2:Particle,nx:Number,ny:Number,distance2:Number):void {
this.distance = distance;
this.p1 = p1;
this.p2 = p2;
distance = Math.sqrt(nx * nx + ny * ny);
this.nx = nx;
this.ny = ny;
distance = Math.sqrt(distance2);
weight = 1 - distance / RANGE;
var density:Number = weight * weight; // pressure force
p1.density += density;
p2.density += density;
density *= weight * PRESSURE_NEAR;
p1.densityNear += density;
p2.densityNear += density;
const invDistance:Number = 1 / distance;
this.nx *= invDistance;
this.ny *= invDistance;
}
public function calcForce():void {
var p:Number;
if(p1.type != p2.type)
p = (p1.density + p2.density - DENSITY * 1.2) * PRESSURE;
else
p = (p1.density + p2.density - DENSITY * 2) * PRESSURE;
const pn:Number = (p1.densityNear + p2.densityNear) * PRESSURE_NEAR;
var pressureWeight:Number = weight * (p + weight * pn);
var viscosityWeight:Number = weight * VISCOSITY;
var fx:Number = nx * pressureWeight;
var fy:Number = ny * pressureWeight;
fx += (p2.vx - p1.vx) * viscosityWeight;
fy += (p2.vy - p1.vy) * viscosityWeight;
p1.fx += fx;
p1.fy += fy;
p2.fx -= fx;
p2.fy -= fy;
}
}
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 densityNear:Number;
public var color:int;
public var type:int;
public const GRAVITY:Number = Fluid.GRAVITY;
public function Particle(x:Number, y:Number, type:int) {
this.x = x;
this.y = y
this.type = type;
vx = vy = fx = fy = 0;
switch(type) {
case 0:
color = 0x6060ff;
break;
case 1:
color = 0xff6000;
break;
case 2:
color = 0xff0060;
break;
case 3:
color = 0x00d060;
break;
case 4:
color = 0xd0d000;
break;
}
}
public function move():void {
vy += GRAVITY;
if(density > 0) {
vx += fx / (density * 0.9 + 0.1); //
vy += fy / (density * 0.9 + 0.1); //
}
x += vx;
y += vy;
if(x < 5)
vx += (5 - x) * 0.5 - vx * 0.5;
if(x > Fluid.BOX_SIZE_X-5)
vx += (Fluid.BOX_SIZE_X-5 - x) * 0.5 - vx * 0.5;
if(y < 5)
vy += (5 - y) * 0.5 - vy * 0.5;
if(y > Fluid.BOX_SIZE_Y-5)
vy += (Fluid.BOX_SIZE_Y-5 - y) * 0.5 - vy * 0.5;
}
}
class Grid {
public var particles:Vector.<Particle>;
public var numParticles:uint;
public function Grid() {
particles = new Vector.<Particle>;
}
public function add(p:Particle):void {
particles[numParticles++] = p;
}
}
class FPS extends Sprite {
public var fps:TextField;
public var ms:TextField;
public var mem:TextField;
public var output:TextField;
public var startPoints:Object;
public var collectedTime:Object;
public var lastTime:Object;
public var graphColors:Object;
public var textColor:String="0xFF";
public var fpsColor:String="0xFF0000";
public var graphWidth:uint=200;
public var graphHeight:uint=50;
public var maxFPS:uint;
public var col:int;
public var iFPScolor:uint;
public var fpsMultiply:Number;
public var countersMultiply:Number;
public var tt:int;
public var pt:int;
public var bmd:BitmapData;
public var bmp:Bitmap;
public var m:Matrix;
public var tmp:BitmapData;
public var sp:Sprite;
public var g:Graphics;
public var lfp:Number;
public function FPS() {}
public function init():void {
mouseChildren=false;
fps = new TextField();
fps.width=55;
ms = new TextField();
ms.width=35;
ms.x=55;
mem = new TextField();
mem.width=80;
mem.x=90;
output = new TextField();
output.width=170;
maxFPS=stage.frameRate;
col=int(textColor);
iFPScolor=uint(fpsColor);
fpsMultiply = graphHeight/(maxFPS*1.2);
countersMultiply=maxFPS*graphHeight/1050;
startPoints={};
collectedTime={};
graphColors={};
lastTime={};
tt=pt=getTimer();
var tf:TextFormat = new TextFormat(null,12,0xFF);
fps.defaultTextFormat = tf;
ms.defaultTextFormat = tf;
mem.defaultTextFormat = tf;
output.defaultTextFormat = tf;
output.x=0;
output.autoSize=flash.text.TextFieldAutoSize.LEFT;
output.y=20+graphHeight+10;
output.selectable=true;
ms.selectable=true;
mem.selectable=true;
pt=tt;
bmd=new BitmapData(graphWidth,graphHeight,true,0);
bmp=new Bitmap(bmd);
addChild(bmp);
bmp.y=20;
m = new Matrix();
m.tx=-1;
tmp=new BitmapData(bmd.width,bmd.height,true,0);
sp = new Sprite();
g=sp.graphics;
lfp=0;
addEventListener(Event.ENTER_FRAME,frame);
addChild(fps);
addChild(ms);
addChild(mem);
addChild(output);
}
public function setValue(name:String,val:Number):void {
if (! startPoints.hasOwnProperty(name)) {
startPoints[name]=0;
collectedTime[name]=0;
lastTime[name]=0;
}
startPoints[name] = val;
collectedTime[name] = val;
}
public function startCounting(name:String):void {
if (! startPoints.hasOwnProperty(name)) {
startPoints[name]=0;
collectedTime[name]=0;
lastTime[name]=0;
}
startPoints[name]=getTimer();
}
public function stopCounting(name:String):void {
var t:int=getTimer();
collectedTime[name]+=getTimer()-startPoints[name];
startPoints[name]=getTimer();
//trace(collectedTime[name]);
}
public function getData(name:String):Number {
return collectedTime[name];
}
public function clear():void {
for (var name:String in collectedTime) {
lastTime[name]=collectedTime[name];
collectedTime[name]=0;
}
}
public function addToGraph(name:String,color:uint):void {
if (graphColors==null) {
init();
}
graphColors[name]=color;
}
public function removeFromGraph(name:String,color:uint):void {
if (graphColors.hasOwnProperty(name)) {
graphColors[name]=null;
}
}
public function frame(evt:Event):void {
tt=getTimer();
var dt:int=tt-pt;
var fp:Number=Math.round((10000/dt))/10;
fps.text="FPS: "+fp.toString()+" /";
ms.text=(dt).toString()+" ms";
mem.text="mem: "+Math.round(100*System.totalMemory/1048576)/100+" Mb";
output.text="";
for (var name:String in lastTime) {
output.appendText(name+" : "+lastTime[name]+"\n");
}
tmp.fillRect(tmp.rect,0);
tmp.draw(bmd,m);
bmd.copyPixels(tmp,tmp.rect,new Point());
g.clear();
g.lineStyle(0,iFPScolor);
g.moveTo(bmd.width-2,bmd.height-lfp*fpsMultiply);
g.lineTo(bmd.width-1,bmd.height-fp*fpsMultiply);
pt=tt;
lfp=fp;
for (name in graphColors) {
if (graphColors[name]!=null) {
g.lineStyle(0,graphColors[name]);
g.moveTo(bmd.width-2,bmd.height-lastTime[name]*countersMultiply);
g.lineTo(bmd.width-1,bmd.height-collectedTime[name]*countersMultiply);
}
}
bmd.draw(sp);
}
}