90 lines
2.4 KiB
JavaScript
90 lines
2.4 KiB
JavaScript
(() => {
|
||
/**
|
||
* Runtime hooks for Speed / Sound map zones (MiR §4.2.6.8–9).
|
||
* Vector overlay only — consumed by motion controller / mission runner.
|
||
*/
|
||
const TYPES = {
|
||
speed: "speed",
|
||
sound: "sound",
|
||
};
|
||
|
||
const BEHAVIOR_TYPES = new Set([TYPES.speed, TYPES.sound]);
|
||
|
||
const SPEED_MIN = 0.1;
|
||
const SPEED_MAX = 1.5;
|
||
const DEFAULT_SPEED_MPS = 0.8;
|
||
|
||
function pointInPolygon(px, py, points) {
|
||
return window.MapPlannerZones?.pointInPolygon(px, py, points) || false;
|
||
}
|
||
|
||
function clampSpeed(value) {
|
||
const n = Number(value);
|
||
if (!Number.isFinite(n)) return DEFAULT_SPEED_MPS;
|
||
return Math.min(SPEED_MAX, Math.max(SPEED_MIN, n));
|
||
}
|
||
|
||
function isBehaviorZoneType(type) {
|
||
return BEHAVIOR_TYPES.has(type);
|
||
}
|
||
|
||
function filterBehaviorZones(zones) {
|
||
return (Array.isArray(zones) ? zones : []).filter((z) => isBehaviorZoneType(z?.type));
|
||
}
|
||
|
||
/** Topmost behavior zones containing image pixel (newest wins for overlaps). */
|
||
function zonesAtPoint(zones, px, py) {
|
||
const list = Array.isArray(zones) ? zones : [];
|
||
const hits = [];
|
||
for (let i = list.length - 1; i >= 0; i--) {
|
||
const z = list[i];
|
||
if (!isBehaviorZoneType(z?.type) || !z.points?.length) continue;
|
||
if (pointInPolygon(px, py, z.points)) hits.push(z);
|
||
}
|
||
return hits;
|
||
}
|
||
|
||
function getSpeedLimit(zones, px, py) {
|
||
const hit = zonesAtPoint(zones, px, py).find((z) => z.type === TYPES.speed);
|
||
if (!hit) return null;
|
||
return clampSpeed(hit.speed_mps);
|
||
}
|
||
|
||
function getSoundAtPoint(zones, px, py) {
|
||
const hit = zonesAtPoint(zones, px, py).find((z) => z.type === TYPES.sound);
|
||
if (!hit?.sound_id) return null;
|
||
return {
|
||
sound_id: hit.sound_id,
|
||
zone_id: hit.id,
|
||
};
|
||
}
|
||
|
||
function classifyPoint(zones, px, py) {
|
||
const hits = zonesAtPoint(zones, px, py);
|
||
const speedZone = hits.find((z) => z.type === TYPES.speed);
|
||
const soundZone = hits.find((z) => z.type === TYPES.sound);
|
||
return {
|
||
speed_mps: speedZone ? clampSpeed(speedZone.speed_mps) : null,
|
||
sound_id: soundZone?.sound_id || null,
|
||
speed_zone: speedZone || null,
|
||
sound_zone: soundZone || null,
|
||
zones: hits,
|
||
};
|
||
}
|
||
|
||
window.MapBehaviorZones = {
|
||
TYPES,
|
||
BEHAVIOR_TYPES,
|
||
SPEED_MIN,
|
||
SPEED_MAX,
|
||
DEFAULT_SPEED_MPS,
|
||
clampSpeed,
|
||
isBehaviorZoneType,
|
||
filterBehaviorZones,
|
||
zonesAtPoint,
|
||
getSpeedLimit,
|
||
getSoundAtPoint,
|
||
classifyPoint,
|
||
};
|
||
})();
|