UNPKG

agentscape

Version:

Agentscape is a library for creating agent-based simulations. It provides a simple API for defining agents and their behavior, and for defining the environment in which the agents interact. Agentscape is designed to be flexible and extensible, allowing

109 lines (89 loc) 3.83 kB
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<T extends Particle>(world: CellGrid<Cell>, particles: AgentSet<T>): void { this.acceleration = new Vector2([0, 0]) particles.forEach((other) => { this.resolveGravitationPair(world, other) }) } public resolveCollisions<U extends Cell, T extends Particle>( world: CellGrid<U>, particles: AgentSet<T>, options: { particle?: boolean } = { particle: true } ): void { if (world.boundaryCondition === 'finite') { this.resolveBoundaryCollision(world) } if (options.particle) { particles.forEach((neighbor) => { this.resolveParticleCollision(neighbor) }) } } private resolveParticleCollision<T extends Particle>(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<U extends Cell>(world: CellGrid<U>): 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<U extends Particle>(world: CellGrid<Cell>, 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)) } }