import { AgentSet, CellGrid } from '../structures' import Vector2 from '../numbers/Vector2' import Agent, {type AgentConstructor } from './Agent' import Cell from './Cell' export interface ParticleConstructor extends AgentConstructor { initialVelocity: [number, number] mass?: number } export default abstract class Particle extends Agent { public acceleration: Vector2 public velocity: Vector2 public mass: number constructor(opts: ParticleConstructor) { super(opts) this.metabolism = 0 this.acceleration = new Vector2([0, 0]) this.velocity = new Vector2(opts.initialVelocity) this.mass = opts.mass || 1 } public get kineticEnergy(): number { return 0.5 * this.mass * this.velocity.magnitude ** 2 } public get momentum(): Vector2 { return this.velocity.scale(this.mass) } /** * Applies an acceleration to the particle proportionate to the net gravitational attraction of all other particles in the given set. */ public resolveGravitation(world: CellGrid, particles: AgentSet): void { this.acceleration = new Vector2([0, 0]) particles.forEach((other) => { this.resolveGravitationPair(world, other) }) } public resolveCollisions( world: CellGrid, particles: AgentSet, options: { particle?: boolean } = { particle: true } ): void { if (world.boundaryCondition === 'finite') { this.resolveBoundaryCollision(world) } if (options.particle) { particles.forEach((neighbor) => { this.resolveParticleCollision(neighbor) }) } } private resolveParticleCollision(other: T): void { if (this.id === other.id) { return } const nextPosition = this.position.add(this.velocity) const otherNextPosition = other.position.add(other.velocity) const distance = nextPosition.subtract(otherNextPosition).magnitude if (distance < (this.radius / 2) + (other.radius / 2)) { const normal = this.position.subtract(other.position).normal const relativeVelocity = this.velocity.subtract(other.velocity) const impulse = (2 * this.mass * other.mass) / (this.mass + other.mass) * relativeVelocity.dot(normal) this.velocity = this.velocity.subtract(normal.scale(impulse / this.mass)) other.velocity = other.velocity.add(normal.scale(impulse / other.mass)) } } private resolveBoundaryCollision(world: CellGrid): void { if (this.position.x - this.radius < 0) { this.position.x = this.radius this.velocity.x *= -1 } else if (this.position.x + this.radius >= world.width) { this.position.x = world.width - this.radius this.velocity.x *= -1 } if (this.position.y - this.radius < 0) { this.position.y = this.radius this.velocity.y *= -1 } else if (this.position.y + this.radius >= world.height) { this.position.y = world.height - this.radius this.velocity.y *= -1 } } private resolveGravitationPair(world: CellGrid, other: U): void { if (this.id === other.id) { return } const dxy = this.position.subtract(other.position) const force = dxy.scale( - 1e-4 * this.mass * other.mass / this.distanceTo(world, other) ** 2) this.acceleration = this.acceleration.add(force.scale(1 / this.mass)) other.acceleration = other.acceleration.subtract(force.scale(1 / other.mass).scale(-1)) } }