blaze-2d
Version:
A fast and simple WebGL 2 2D game engine written in TypeScript
133 lines • 5 kB
JavaScript
import { vec2 } from "gl-matrix";
import BatchRenderer from "../../renderer/batchRenderer";
import FluidRenderer from "../../renderer/fluidRenderer";
import Circle from "../../shapes/circle";
import Color from "../../utils/color";
import solveForces from "../solvers/dynamics/forces";
import kdTree from "./kdTree";
import Particle from "./particle";
export const MAX_FLUID_PARTICLES = 1000;
/**
* Simulates a fluid using SPH (Smooth Particle Hydrodynamics).
*
* @see [SPH Fluid Sim in Processing](https://hmbd.wordpress.com/2019/06/10/sph-fluid-simulation-in-processing/)
* @see [Processing Code](https://github.com/DanielsSim/hmbd/blob/master/Processing/SPH/Fluid.pde)
* @see [Particle-based Viscoelastic Fluid Sim](http://www.ligum.umontreal.ca/Clavet-2005-PVFS/pvfs.pdf)
*/
export default class Fluid {
/**
* Creates an {@link Fluid} with the given options.
*
* @param opts The options to setup the fluid with
*/
constructor(opts) {
this.particles = [];
this.debugShapes = new Map();
this.translate = vec2.create();
this.restDensity = opts.restDensity;
this.smoothingRadius = opts.smoothingRadius;
this.smoothingRadiusSqr = opts.smoothingRadius * opts.smoothingRadius;
this.stiffness = opts.stiffness;
this.stiffnessNear = opts.stiffnessNear;
this.particleRadius = opts.particleRadius;
this.maxParticles = Math.min(opts.maxParticles, MAX_FLUID_PARTICLES);
this.collisionGroup = opts.collisionGroup;
this.color = opts.color || new Color("#1D7BE3");
this.zIndex = opts.zIndex || 0;
this.renderThreshold = opts.renderThreshold || 2.3;
this.debug = opts.debug || false;
this.debugTex = opts.debugTex;
this.kdTree = new kdTree([]);
}
/**
* Adds a particle to the fluid at the given position.
*
* @param pos The position to create the particle at in world space.
* @returns Wether or not the particle was added
*/
addParticle(pos) {
if (this.particles.length >= this.maxParticles)
return false;
const particle = new Particle(this.particleRadius, this.restDensity / 10);
particle.setPosition(pos);
particle.filter.group = this.collisionGroup;
this.particles.push(particle);
if (this.physics) {
this.physics.addCollisionObj(particle);
}
return true;
}
/**
* Steps the fluid simulation forward in time.
*
* @param delta The time since the last update
*/
update(delta) {
const gravity = this.physics.getGravity() || vec2.create();
this.integrate(delta, gravity);
this.kdTree.build(this.particles);
for (const p of this.particles) {
p.findNeighbours(this.kdTree, this.smoothingRadius, this.smoothingRadiusSqr);
}
for (const p of this.particles) {
// p.neighbours.length = 0;
// for (const b of this.particles) {
// if (p === b) continue;
// const dist = vec2.dist(p.getPosition(), b.getPosition());
// if (dist > this.smoothingRadius) continue;
// p.neighbours.push(b);
// }
p.computeDoubleDensityRelaxation(this.smoothingRadius);
p.computePressure(this.stiffness, this.stiffnessNear, this.restDensity);
p.advancePosition(delta, this.smoothingRadius);
}
for (const p of this.particles) {
p.computeNewVelocity(delta, gravity);
}
if (this.debug)
this.debugRender();
}
/**
* Integrate any forces on the particle, including gravity, and integrate velocity.
*
* Also resets the particle's density from the last update.
*
* @param delta The time since the last update
*/
integrate(delta, gravity) {
for (const p of this.particles) {
vec2.copy(p.posPrev, p.getPosition());
// integrate forces
solveForces(p, delta, gravity);
// integrate velocity
p.translate(vec2.scale(this.translate, p.velocity, delta));
}
}
/**
* Queues the fluid to be renderer using the fluid renderer.
*/
render() {
FluidRenderer.queueFluid(this);
}
/**
* Renders the particles of the fluid as individual circles.
*/
debugRender() {
const circles = [];
for (const p of this.particles) {
let circle;
if (this.debugShapes.has(p)) {
circle = this.debugShapes.get(p);
}
else {
circle = new Circle(this.particleRadius);
circle.texture = this.debugTex;
this.debugShapes.set(p, circle);
}
circle.setPosition(p.getPosition());
circles.push(circle);
}
BatchRenderer.queueCircles(circles);
}
}
//# sourceMappingURL=fluid.js.map