@dominicstop/utils
Version:
Yet another event emitter written in typescript.
139 lines (109 loc) • 3.53 kB
text/typescript
import { Rect } from "src/geometry";
import { CircleParticle } from "./ParticleShapes";
import { PhysicsEngine } from "./PhysicsEngine";
import { SomeOfflineSimulation } from "./SomeOfflineSimulation";
import { SomeParticleForce } from "./SomeParticleForce";
import { SomeSystemForce } from "./SomeSystemForce";
import { CentralAttractionForce } from "./CentralAttractionForce";
export class CirclePackingOfflineSimulation
implements SomeOfflineSimulation<CircleParticle>
{
engine: PhysicsEngine<CircleParticle>;
isRunning: boolean = false;
shouldLog = false;
private _totalSimulatedTime: number = 0;
private _iterationCount: number = 0;
readonly fixedDeltaTimeSeconds: number;
minIterations: number = 50;
maxIterations: number = 250;
onPreStep: ((deltaTime: number) => void) | null = null;
onPostStep: ((deltaTime: number) => void) | null = null;
onUpdate: (() => void) | null = null;
get totalSimulatedTime(): number {
return this._totalSimulatedTime;
}
get iterationCount(): number {
return this._iterationCount;
};
get frameTimeMS(): number {
return this.minIterations * 1000;
};
constructor(args: {
frame: Rect;
centralAttractionStrength?: number;
timeStep?: number
}) {
this.fixedDeltaTimeSeconds = args.timeStep ?? (1 / 60);
const engine = new PhysicsEngine<CircleParticle>();
this.engine = engine;
const centerForce = new CentralAttractionForce(
args.frame.center.asVector,
args.centralAttractionStrength ?? 8,
true
);
engine.addForce(centerForce);
}
run() {
this.isRunning = true;
this._iterationCount = 0;
while (true) {
const didReachMaxIterations =
this._iterationCount >= this.maxIterations;
if(!this.isRunning || didReachMaxIterations){
this.isRunning = false;
break;
};
this.step();
this._iterationCount++;
this.onUpdate?.();
const didReachMinIterations =
this._iterationCount > this.minIterations;
const shouldStop =
didReachMinIterations
&& this.isStable();
if (shouldStop) {
this.isRunning = false;
}
}
};
step() {
this.onPreStep?.(this.fixedDeltaTimeSeconds);
this.engine.update(this.fixedDeltaTimeSeconds);
this.onPostStep?.(this.fixedDeltaTimeSeconds);
this._totalSimulatedTime += this.fixedDeltaTimeSeconds;
if(this.shouldLog){
console.log({
'isRunning': this.isRunning,
'iterationCount': this._iterationCount,
'totalSimulatedTime': this._totalSimulatedTime,
'getTotalKineticEnergy': this.engine.getTotalKineticEnergy(),
'isKineticEnergyNegligible': this.isKineticEnergyNegligible(),
'checkIfAllParticlesAtRest': this.engine.checkIfAllParticlesAtRest(),
'this.isStable': this.isStable(),
});
this.engine.logState();
};
};
reset() {
this.isRunning = false;
this.engine.clear();
this._totalSimulatedTime = 0;
this._iterationCount = 0;
};
addParticle(particle: CircleParticle) {
this.engine.addParticle(particle);
};
addForce(force: SomeParticleForce | SomeSystemForce) {
this.engine.addForce(force);
};
isKineticEnergyNegligible(): boolean {
const energyThreshold = 0.01;
return this.engine.getTotalKineticEnergy() < energyThreshold;
};
isStable(): boolean {
return (
this.isKineticEnergyNegligible()
|| this.engine.checkIfAllParticlesAtRest()
);
};
};