mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
94 lines
3.5 KiB
JavaScript
94 lines
3.5 KiB
JavaScript
/**
|
||
* @file Queue-spawn helpers for jammed roads — platoon position math only.
|
||
* Cesium-free so bumper-to-bumper spacing is unit-testable; the traffic
|
||
* layer maps the returned distances onto Cartesian3 waypoints at spawn time.
|
||
*
|
||
* Only ever used for live-flow jam-bucket roads (jamViz 'density'/'both');
|
||
* the keyless simulation never calls into this module.
|
||
*
|
||
* @module data/trafficQueue
|
||
*/
|
||
|
||
/** @const {number} Smallest platoon (cars per queue cluster). */
|
||
const PLATOON_MIN = 4;
|
||
/** @const {number} Random extra cars per platoon (0–4 → sizes 4–8). */
|
||
const PLATOON_SPREAD = 5;
|
||
/** @const {number} Meters — minimum bumper-to-bumper gap inside a platoon. */
|
||
const GAP_MIN_M = 6;
|
||
/** @const {number} Meters — random extra gap (6–12 m total). */
|
||
const GAP_SPREAD_M = 6;
|
||
|
||
/**
|
||
* Generate `count` along-road distances clustered into bumper-to-bumper
|
||
* platoons: each platoon anchors at a random distance and trails 4–8 cars
|
||
* behind it at 6–12 m gaps, wrapping around the road start. Platoons are
|
||
* returned as separate arrays so the caller can give each queue one shared
|
||
* travel direction.
|
||
*
|
||
* @param {number} totalLen - Road length in meters.
|
||
* @param {number} count - Dots to place across all platoons.
|
||
* @param {() => number} [rng=Math.random] - Uniform [0,1) source (injectable for tests).
|
||
* @returns {number[][]} Platoons of distances in [0, totalLen); total length
|
||
* across platoons is `count` (empty on degenerate input).
|
||
*/
|
||
export function queuePlatoons(totalLen, count, rng = Math.random) {
|
||
const platoons = [];
|
||
if (!Number.isFinite(totalLen) || totalLen <= 0 || !(count > 0))
|
||
return platoons;
|
||
|
||
let placed = 0;
|
||
while (placed < count) {
|
||
const platoonSize = Math.min(
|
||
count - placed,
|
||
PLATOON_MIN + Math.floor(rng() * PLATOON_SPREAD),
|
||
);
|
||
const anchor = rng() * totalLen;
|
||
const platoon = [];
|
||
let trail = 0;
|
||
for (let j = 0; j < platoonSize; j++) {
|
||
if (j > 0) trail += GAP_MIN_M + rng() * GAP_SPREAD_M;
|
||
const s = anchor - trail;
|
||
platoon.push(((s % totalLen) + totalLen) % totalLen);
|
||
}
|
||
platoons.push(platoon);
|
||
placed += platoonSize;
|
||
}
|
||
return platoons;
|
||
}
|
||
|
||
/**
|
||
* Flat variant of {@link queuePlatoons} — platoon structure discarded.
|
||
*
|
||
* @param {number} totalLen - Road length in meters.
|
||
* @param {number} count - Dots to place.
|
||
* @param {() => number} [rng=Math.random] - Uniform [0,1) source (injectable for tests).
|
||
* @returns {number[]} Distances in [0, totalLen), length `count` (or empty on
|
||
* degenerate input).
|
||
*/
|
||
export function queueDistances(totalLen, count, rng = Math.random) {
|
||
return queuePlatoons(totalLen, count, rng).flat();
|
||
}
|
||
|
||
/**
|
||
* Map an along-road distance to its containing segment and parametric t.
|
||
* Distances beyond the road clamp to the far end; zero-length segments are
|
||
* skipped. A distance landing exactly on a segment boundary resolves to the
|
||
* earlier segment at t=1 (the animator treats t≥1 as a crossing).
|
||
*
|
||
* @param {number[]} segmentDist - Per-segment lengths in meters.
|
||
* @param {number} s - Along-road distance in meters.
|
||
* @returns {{segIdx:number, t:number}} Segment index + parametric position.
|
||
*/
|
||
export function locateAlongRoad(segmentDist, s) {
|
||
let remaining = Math.max(0, s);
|
||
const lastIdx = segmentDist.length - 1;
|
||
for (let k = 0; k <= lastIdx; k++) {
|
||
const d = segmentDist[k] || 0;
|
||
if ((d > 0 && remaining <= d) || k === lastIdx) {
|
||
return { segIdx: k, t: d > 0 ? Math.min(1, remaining / d) : 0 };
|
||
}
|
||
remaining -= d;
|
||
}
|
||
return { segIdx: 0, t: 0 };
|
||
}
|