top of page
Search
cedarcantab

Hash based grid


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>

4 views0 comments

Recent Posts

See All

p2 naive broadphase

var Broadphase = require('../collision/Broadphase'); module.exports = NaiveBroadphase; /** * Naive broadphase implementation. Does N^2...

sopiro motor constranit

import { Matrix2, Vector2 } from "./math.js"; import { RigidBody } from "./rigidbody.js"; import { Settings } from "./settings.js";...

Comments


記事: Blog2_Post
bottom of page