32 lines
1.5 KiB
JavaScript
32 lines
1.5 KiB
JavaScript
|
|
"use strict";
|
||
|
|
|
||
|
|
// Layer: physics/projection-bridge
|
||
|
|
// Shared tiny helpers for positional projection used by links, mechanical
|
||
|
|
// bodies, and circle/circle collision code.
|
||
|
|
(function (global) {
|
||
|
|
function num(v, fallback = 0) { const n = Number(v); return Number.isFinite(n) ? n : fallback; }
|
||
|
|
|
||
|
|
function markSpatialDirty(worldRef, item, reason = "projection", threshold = 0.35, minInterval = 0.065) {
|
||
|
|
const world = worldRef || item?.world || null;
|
||
|
|
if (!world || !item) return false;
|
||
|
|
const now = Number(world.time || 0) || 0;
|
||
|
|
const x = num(item.x), y = num(item.y), r = num(item.r || item.radius || 0);
|
||
|
|
const lastX = Number(item._lastProjectionSpatialDirtyX);
|
||
|
|
const lastY = Number(item._lastProjectionSpatialDirtyY);
|
||
|
|
const lastR = Number(item._lastProjectionSpatialDirtyR);
|
||
|
|
const missing = !Number.isFinite(lastX) || !Number.isFinite(lastY) || !Number.isFinite(lastR);
|
||
|
|
const moved = missing || Math.hypot(x - lastX, y - lastY) > threshold || Math.abs(r - lastR) > threshold;
|
||
|
|
const due = now >= Number(item._nextProjectionSpatialDirtyAt || 0);
|
||
|
|
if (!moved && !due) return false;
|
||
|
|
item._lastProjectionSpatialDirtyX = x;
|
||
|
|
item._lastProjectionSpatialDirtyY = y;
|
||
|
|
item._lastProjectionSpatialDirtyR = r;
|
||
|
|
item._nextProjectionSpatialDirtyAt = now + Math.max(0.016, num(minInterval, 0.065));
|
||
|
|
world.markSpatialDirty?.(reason);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
global.TarinaiPhysicsProjectionSystem = Object.freeze({
|
||
|
|
markSpatialDirty,
|
||
|
|
});
|
||
|
|
})(typeof window !== "undefined" ? window : globalThis);
|