UNPKG

@dominicstop/utils

Version:

Yet another event emitter written in typescript.

164 lines (121 loc) 4.12 kB
import { AnyBoxedShape, Vector2D } from "../geometry"; import { SomeShapeParticle } from "./SomeShapeParticle"; export type ParticleInit< ParticleShape extends AnyBoxedShape > = { id: string; position: Vector2D; mass: number; initialVelocity?: Vector2D; shape: ParticleShape; }; export class Particle< ParticleShape extends AnyBoxedShape > implements SomeShapeParticle<ParticleShape> { id: string; private _position!: Vector2D; previousPosition: Vector2D; velocity: Vector2D; acceleration: Vector2D; mass: number; inverseMass: number; shape: ParticleShape; isStatic: boolean; get position(): Vector2D { return this._position; }; set position(newValue: Vector2D) { this._position = newValue; this.shape.center = newValue.asPoint; }; constructor(args: ParticleInit<ParticleShape>) { this.shape = args.shape; this.id = args.id; this.position = args.position.clone(); this.previousPosition = args.position.clone(); this.mass = args.mass; this.inverseMass = (args.mass === 0 ? 0 : 1 / args.mass ); this.velocity = args.initialVelocity ?? Vector2D.zero; this.acceleration = Vector2D.zero; this.isStatic = args.mass === 0; }; clone() { const clone = new Particle<ParticleShape>({ id: this.id, position: this.position.clone(), shape: this.shape.clone() as ParticleShape, mass: this.mass, initialVelocity: this.velocity.clone(), }); clone.previousPosition = this.previousPosition.clone(); return clone; }; applyForce(force: Vector2D): void { if(this.isStatic) return; const accelerationFromForce = force.multipliedByScalar(this.inverseMass); this.acceleration = this.acceleration.addedWithOther(accelerationFromForce); }; update(deltaTime: number): void { if (this.isStatic) return; this.previousPosition = this.position.clone(); // get change in velocity due to acceleration over the time step. const velocityDelta = this.acceleration.multipliedByScalar(deltaTime); this.velocity = this.velocity.addedWithOther(velocityDelta); // get change in position due to velocity over the time step. const positionDelta = this.velocity.multipliedByScalar(deltaTime); this.position = this.position.addedWithOther(positionDelta); }; resetAcceleration(): void { this.acceleration = Vector2D.zero; }; getSpeed(): number { return this.velocity.magnitude; }; getKineticEnergy(): number { return 0.5 * this.mass * this.getSpeed() ** 2; }; setPosition(newPosition: Vector2D): void { this.previousPosition = this.position.clone(); this.position = newPosition; }; setVelocity(newVelocity: Vector2D): void { this.velocity = newVelocity; }; setMass(newMass: number): void { this.mass = newMass; this.inverseMass = ((newMass === 0) ? 0 : 1 / newMass ); this.isStatic = newMass === 0; }; isCollidingWithOther(other: Particle<ParticleShape>): boolean { return this.shape.isCollidingWithOther(other.shape); }; computeDistanceToOther(other: Particle<ParticleShape>): number { return this.shape.computeDistanceToOther(other.shape); }; computeOverlapVectorWith(other: Particle<ParticleShape>): Vector2D { if (!this.isCollidingWithOther(other)) { return Vector2D.zero; } // Vector from other to this const direction = this.position.subtractedWithOther(other.position).normalized; // overlap depth (negative distance means overlap) const distance = this.computeDistanceToOther(other); const overlapDepth = -distance; return direction.multipliedByScalar(overlapDepth); } checkIsAtRest(): boolean { const restThreshold = 1e-5; return this.velocity.magnitude < restThreshold; }; isEdgeToEdgeWithOther(other: Particle<ParticleShape>): boolean { const distance = this.computeDistanceToOther(other); const epsilon = 1e-8; // Small tolerance for floating-point precision return Math.abs(distance) < epsilon && !this.isCollidingWithOther(other); } };