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
text/typescript
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))
}
}