Files
gods-eye-view/src/data/trafficQueue.js
T

94 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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 };
}