ContinuousProjection

class ContinuousProjection<A : AgentLike> @JvmOverloads constructor(val context: AgentModel.Context<A>, val xRange: ClosedRange<Double>, val yRange: ClosedRange<Double>, val torus: Boolean = false, val cellSize: Double = run { val xSize = xRange.endInclusive - xRange.start val ySize = yRange.endInclusive - yRange.start val smaller = minOf(xSize, ySize) if (smaller > 0.0) smaller / Defaults.cellSizeDivisor else 1.0 }, val name: String = "continuous") : Projection<A> (source)

A 2D Euclidean projection: each agent in the context has an optional Point2D position. The projection tracks positions in a map keyed by agent reference; positions are set explicitly via placeAt / moveTo from user code.

Spatial queries are accelerated by an internal uniform spatial hash: the plane is partitioned into square cells of cellSize units, and agents are bucketed into the cell their position falls into. within scans only the cells overlapping the query disk rather than every agent; nearest does the same via progressive radius doubling. Insertion / move / remove are O(1) amortized.

The default cellSize is min(xSize, ySize) / 10.0 — ten cells along the smaller dimension. Tune for your model: cell size roughly equal to typical query radius gives near-optimal performance. Smaller cells cost more bookkeeping but tighter query bounding boxes; larger cells cost more agents-per-cell to filter.

Boundaries: positions are not enforced to be inside xRange / yRange — the ranges describe the projection's logical domain but placeAt / moveTo accept any coordinates. When torus is true, distance computes wrapped distance using the ranges as the torus dimensions, and the spatial hash wraps bucket coordinates accordingly.

Parameters

context

the context whose agents this projection positions

xRange

logical x bounds (used for torus wrapping)

yRange

logical y bounds (used for torus wrapping)

torus

if true, distances wrap at the bounds

cellSize

side length of each spatial-hash cell. Defaults to min(xSize, ySize) / 10.0, or 1.0 for degenerate ranges. Must be positive.

name

display name

Constructors

Link copied to clipboard
constructor(context: AgentModel.Context<A>, xRange: ClosedRange<Double>, yRange: ClosedRange<Double>, torus: Boolean = false, cellSize: Double = run { val xSize = xRange.endInclusive - xRange.start val ySize = yRange.endInclusive - yRange.start val smaller = minOf(xSize, ySize) if (smaller > 0.0) smaller / Defaults.cellSizeDivisor else 1.0 }, name: String = "continuous")

Types

Link copied to clipboard
object Defaults

Mutable global defaults for ContinuousProjection internals.

Properties

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

Maximum occupancy of any single bucket. Diagnostic for cellSize tuning.

Link copied to clipboard
open override val name: String
Link copied to clipboard

Number of non-empty buckets. Indicates how well-distributed agents are across the spatial hash. Pure diagnostic — useful for tuning cellSize on a model with known typical population.

Link copied to clipboard
val size: Int

Number of agents currently placed in this projection.

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

Functions

Link copied to clipboard

Construct a ProjectionSpatialModel over this projection. Each call creates a new spatial model; locations created by one spatial model are not valid in another. Store the result and reuse it across all MovableResource / move / transport calls that need to share coordinates with this projection.

Link copied to clipboard
fun delta(from: Point2D, to: Point2D): Point2D

Signed shortest-direction delta from from to to under this projection's geometry. For a non-torus projection this is the trivial component-wise subtraction to - from. For a torus, each component picks whichever wrap (positive or negative) is shorter — so a boid at x = 99 querying a peer at x = 1 in a 100-wide torus gets dx = +2, not -98.

Link copied to clipboard
fun distance(a: A, b: A): Double

Distance between two agents. Returns Double.NaN if either agent has no assigned position. Uses Euclidean distance, or wrapped Euclidean if torus is true.

Distance between two points under this projection's metric (Euclidean, with torus wrap if enabled).

Link copied to clipboard
fun moveTo(agent: A, point: Point2D)

fun moveTo(agent: A, x: Double, y: Double)

Update agent's position. Identical to placeAt.

Link copied to clipboard
fun nearest(center: Point2D, k: Int): List<A>

The k nearest agents to center, ordered by distance. Implemented as a progressive radius doubling on top of within: start with a small radius (one cell), double until enough candidates are found, then trim to k. Bounded scanning rather than the O(n) linear sort the previous implementation used.

Link copied to clipboard
fun neighborsOf(agent: A, radius: Double): List<A>

All agents within radius of agent's position (excluding the agent itself), ordered by distance.

Link copied to clipboard
open override fun onAgentLeft(agent: A)

Called by the AgentModel.Context when agent leaves. Default no-op — projections that track per-agent state (positions, edges) typically override to drop their bookkeeping for the departing agent.

Link copied to clipboard
fun placeAt(agent: A, point: Point2D)

fun placeAt(agent: A, x: Double, y: Double)

Place agent at the given coordinates. Idempotent — equivalent to moveTo.

Link copied to clipboard
fun positionOf(agent: A): Point2D?

Return the position of agent, or null if the agent has no assigned position (never placed, or has left the context).

Link copied to clipboard
fun within(center: Point2D, radius: Double): List<A>

All agents whose position is within radius of center, ordered by distance. Includes any agent exactly at center. Uses the spatial hash: only buckets overlapping the query disk are scanned.