class Particle{
constructor(position){
this.position = position;
this.prevPosition = position;
this.velocity = new Vector2(0,0);
this.color = "#28b0ff";
}
}
class FluidHashGrid{
constructor(cellSize){
this.hashMap = new Map();
this.particles = [];
this.hashMapSize = 1000000;
this.p1Prime = 6614058611;
this.p2Prime = 7528850467;
this.cellSize = cellSize;
}
initialize(particles){
this.particles = particles;
}
clearGrid(){
this.hashMap.clear();
}
cellIndexToHash(x, y){
let hash = (x this.p1Prime ^ y this.p2Prime) % this.hashMapSize
return hash;
}
getGridIdFromPos(pos){
let x = parseInt(pos.x / this.cellSize);
let y = parseInt(pos.y / this.cellSize);
return this.cellIndexToHash(x,y);
}
getContentOfCell(id){
let content = this.hashMap.get(id);
if(content == null) return [];
else return content
}
mapParticlesToCell(){
for(let i=0; i<this.particles.length; i++){
let pos = this.particles[i].position;
let x = parseInt(pos.x / this.cellSize);
let y = parseInt(pos.y / this.cellSize);
let hash = this.cellIndexToHash(x,y);
let entries = this.hashMap.get(hash);
if(entries == null){
let newArray = [this.particles[i]];
this.hashMap.set(hash, newArray);
}else{
entries.push(this.particles[i]);
}
}
}
}
class Playground{
constructor(){
this.simulation = new Simulation();
this.mousePos = new Vector2(0,0);
}
update(dt){
this.simulation.update(dt, this.mousePos);
}
draw(){
this.simulation.draw();
}
onMouseMove(position){
this.mousePos = position;
}
onMouseDown(button){
console.log("Mouse button pressed: " + button);
}
onMouseUp(button){
console.log("Mouse button released: " + button);
}
}
class Simulation{
constructor(){
this.particles = [];
this.fluidHashGrid = new FluidHashGrid(25);
this.AMOUNT_PARTICLES = 1000;
this.VELOCITY_DAMPING = 1;
this.instantiateParticles();
this.fluidHashGrid.initialize(this.particles);
}
// creating a rectangular grid of particles
instantiateParticles(){
let offsetBetweenParticles = 10;
let offsetAllParticles = new Vector2(750,100);
let xparticles = Math.sqrt(this.AMOUNT_PARTICLES);
let yparticles = xparticles;
for(let x=0; x<xparticles; x++){
for(let y=0; y<yparticles; y++){
let position = new Vector2(x*offsetBetweenParticles + offsetAllParticles.x,
y*offsetBetweenParticles + offsetAllParticles.y);
this.particles.push(new Particle(position));
//this.particles[this.particles.length-1].velocity = Scale(new Vector2(-0.5 + Math.random(),-0.5 + Math.random()),200);
}
}
}
// Algorithm 1
update(dt, mousePos){
// line 1 - 3
this.applyGravity();
// line 6 - 10
this.predictPositions(dt);
// do neighbour search and reseting
this.neighbourSearch(mousePos);
// line 16
this.doubleDensityRelaxation();
// line 18 - 20
this.computeNextVelocity(dt);
// make sure particles stay in the world
this.worldBoundary();
}
neighbourSearch(mousePos){
this.fluidHashGrid.clearGrid();
this.fluidHashGrid.mapParticlesToCell();
let gridHashId = this.fluidHashGrid.getGridIdFromPos(mousePos);
let contentOfCell = this.fluidHashGrid.getContentOfCell(gridHashId);
for(let i = 0; i < this.particles.length;i++){
this.particles[i].color = "#28b0ff";
}
for(let i=0;i<contentOfCell.length;i++){
let particle = contentOfCell[i];
particle.color = "red";
}
}
worldBoundary(){
for(let i = 0; i < this.particles.length;i++){
let pos = this.particles[i].position;
let prevPos = this.particles[i].prevPosition;
if(pos.x < 0){
this.particles[i].velocity.x =-1;
}
if(pos.y < 0){
this.particles[i].velocity.y =-1;
}
if(pos.x > canvas.width-1){
this.particles[i].velocity.x =-1;
}
if(pos.y > canvas.height-1){
this.particles[i].velocity.y =-1;
}
}
}
predictPositions(dt){
for(let i = 0; i < this.particles.length;i++){
this.particles[i].prevPosition = this.particles[i].position.Cpy();
this.particles[i].position = Add(this.particles[i].position, Scale(this.particles[i].velocity,dt * this.VELOCITY_DAMPING));
}
}
computeNextVelocity(dt){
for(let i = 0; i < this.particles.length;i++){
this.particles[i].velocity = Scale(Sub(this.particles[i].position,this.particles[i].prevPosition),1/dt);
}
}
applyGravity(){
}
doubleDensityRelaxation(){
}
draw(){
for(let i = 0; i < this.particles.length;i++){
let pos = this.particles[i].position;
DrawUtils.drawPoint(pos,5,this.particles[i].color);
}
}
}
<html>
<head>
<title>2D Fluid Simulation for beginners</title>
<style>
* { padding: 0; margin: 0; }
canvas { background: #eee; display: block; margin: 20px auto; }
</style>
</head>
<body>
<canvas id="canvas" width="1280px" height="720px" style="border: solid 1px black"></canvas>
<script src="Playground.js"></script>
<script src="Simulation.js"></script>
<script src="Vector2.js"></script>
<script src="DrawUtils.js"></script>
<script src="Particle.js"></script>
<script src="FluidHashGrid.js"></script>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
let lastTime = performance.now();
let currentTime = 0;
let deltaTime = 0;
let playground = new Playground();
mainLoop();
function updatePlayground(dt){
Clear();
playground.update(deltaTime);
playground.draw();
}
function mainLoop(){
window.requestAnimationFrame(mainLoop);
// Measuring the time it took to perform one update iteration
currentTime = performance.now()
deltaTime = (currentTime - lastTime) / 1000;
//Update Simulation
updatePlayground(deltaTime);
lastTime = currentTime;
}
function Clear(){
ctx.fillStyle = "white";
ctx.fillRect(0,0,canvas.width,canvas.height);
}
// Getting the mousepos relative to the canvas
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas.addEventListener('mousemove', function(evt) {
let mouse = getMousePos(canvas, evt);
playground.onMouseMove(new Vector2(mouse.x, mouse.y));
}, false);
canvas.addEventListener('mousedown', function(evt) {
let mouse = getMousePos(canvas, evt);
playground.onMouseDown(evt.button);
}, false);
canvas.addEventListener('mouseup', function(evt) {
let mouse = getMousePos(canvas, evt);
playground.onMouseUp(evt.button);
}, false);
</script>
</body>
</html>
Comments