Dynamics3D

class Dynamics3D<A : AgentLike> @JvmOverloads constructor(val space: ContinuousVolume<A>, var mass: (A) -> Double = { Defaults.unitMass }, maxSpeed: Double = Defaults.maxSpeed, minSpeed: Double = Defaults.minSpeed)(source)

3D analog of Dynamics. Continuous-time dynamics primitive for agents on a ContinuousVolume; owns the per-agent 3D velocity state and the list of Force3Ds contributing to motion. The step method performs the Euler integration step (sum forces → integrate velocity → compute candidate position) without applying it; the caller decides what to do with the candidate (wrap onto a 3D torus, reject if it enters a no-fly zone, clamp altitude to ground/ceiling, accept as-is).

Typical use (drone flight):

val dynamics = Dynamics3D<Drone>(airspace).apply {
addForce(desiredVelocity3D(speed = 5.0, tau = 0.5) { d, dyn ->
val p = dyn.space.positionOf(d) ?: return@desiredVelocity3D Point3D.ORIGIN
flow.directionAt(p)
})
addForce(peerRepulsion3D(radius = 3.0) { d -> 50.0 / d })
maxSpeed = 8.0
}

while (true) {
val (vNew, pNew) = dynamics.step(drone, dt = 0.1)
dynamics.setVelocity(drone, vNew)
airspace.moveTo(drone, clampToAirspace(pNew))
delay(0.1)
}

For the common "no boundary handling needed" case use runDynamics3D. For walls / no-fly zones / altitude clamps, write your own loop around step.

All Defaults and maxSpeed / minSpeed validation works exactly as in 2D Dynamics.

Parameters

space

the 3D projection that owns agent positions

mass

per-agent mass function. Default is unit mass for all.

Constructors

Link copied to clipboard
constructor(space: ContinuousVolume<A>, mass: (A) -> Double = { Defaults.unitMass }, maxSpeed: Double = Defaults.maxSpeed, minSpeed: Double = Defaults.minSpeed)

Types

Link copied to clipboard
object Defaults

Mutable global defaults for Dynamics3D. Mirrors Dynamics.Defaults.

Properties

Link copied to clipboard

Number of forces currently registered.

Link copied to clipboard
var mass: (A) -> Double
Link copied to clipboard

Hard upper bound on velocity magnitude after each integration step. Must be positive and >= minSpeed.

Link copied to clipboard

Lower bound — velocities below this magnitude are rescaled back up to minSpeed. A body at exactly zero velocity is given a deterministic heading (a pure function of its identity, so runs stay reproducible) and boosted to minSpeed too. Set to 0 to allow agents to stop. Must be non-negative and <= maxSpeed.

Link copied to clipboard

Functions

Link copied to clipboard
fun addForce(force: Force3D<A>)

Append force to the summed forces. Forces are evaluated in order.

Link copied to clipboard
fun setVelocity(agent: A, v: Point3D)

Set / update the velocity tracked for agent.

Link copied to clipboard
fun step(agent: A, dt: Double): Pair<Point3D, Point3D>

One Euler integration step for agent: sum registered forces, integrate velocity by (sumForce / mass) * dt, clamp to [minSpeed, maxSpeed], compute candidate position pos + vNew * dt.

Link copied to clipboard

Compute one Euler step for every agent in agents without applying any of them — a Jacobi (synchronous) update. Every step reads the current shared state, so the result is independent of the order of agents, unlike calling step and applying per agent in a loop (a Gauss-Seidel update that depends on iteration order). The caller applies the returned triples.

Link copied to clipboard
fun untrack(agent: A)

Stop tracking agent (called when an agent leaves the population).

Link copied to clipboard
fun velocityOf(agent: A): Point3D?

Get the current velocity tracked for agent, or null if not set.