optimized the measurment calculations

This commit is contained in:
grimsace
2026-05-07 15:44:43 -05:00
parent 64d0d29a7c
commit cf536e18d6
+157 -85
View File
@@ -443,18 +443,47 @@ function clearHighlightContainer(container) {
// Find which tokens are within range of which auras // Find which tokens are within range of which auras
function collectAuraHighlights(sourceTokens) { function collectAuraHighlights(sourceTokens) {
const highlights = new Map(); const highlights = new Map();
const tokens = globalThis.canvas.tokens.placeables;
const pixelsPerUnit = getPixelsPerSceneUnit();
// Cache token-specific data for the duration of this refresh
const tokenCache = new Map();
for (const source of sourceTokens) { for (const source of sourceTokens) {
const auras = getTokenAuras(source); const auras = getTokenAuras(source);
for (const target of globalThis.canvas.tokens.placeables) { if (auras.length === 0) continue;
const sourceData = getCachedTokenData(source, tokenCache, pixelsPerUnit);
for (const target of tokens) {
if (target === source || target.document.hidden) continue; if (target === source || target.document.hidden) continue;
for (const aura of auras) { const targetData = getCachedTokenData(target, tokenCache, pixelsPerUnit);
if (!isTokenInAura(source, target, aura)) continue; const dz = Math.abs(sourceData.elevation - targetData.elevation);
const dz2 = dz * dz;
const targetHighlights = highlights.get(target.id) ?? []; for (const aura of auras) {
targetHighlights.push(aura); const rangePixels = aura.range * pixelsPerUnit;
highlights.set(target.id, targetHighlights); const rangePixels2 = rangePixels * rangePixels;
// Broad phase: check if elevation difference alone exceeds range
if (dz2 > rangePixels2) continue;
if (
isTokenInAuraCached(
source,
target,
aura,
sourceData,
targetData,
dz2,
rangePixels2,
)
) {
const targetHighlights = highlights.get(target.id) ?? [];
targetHighlights.push(aura);
highlights.set(target.id, targetHighlights);
}
} }
} }
} }
@@ -462,106 +491,149 @@ function collectAuraHighlights(sourceTokens) {
return highlights; return highlights;
} }
function getCachedTokenData(token, cache, pixelsPerUnit) {
let data = cache.get(token.id);
if (data) return data;
data = {
center: token.center,
bounds: token.bounds,
elevation: (token.document?.elevation ?? 0) * pixelsPerUnit,
alphaPoints: null, // Lazy loaded
};
cache.set(token.id, data);
return data;
}
function getTokenAuras(token) { function getTokenAuras(token) {
return normalizeAuras( return normalizeAuras(
parseAuras(token.document?.getFlag?.(MODULE_ID, "auras")), parseAuras(token.document?.getFlag?.(MODULE_ID, "auras")),
); );
} }
function isTokenInAura(source, target, aura) { function isTokenInAuraCached(
const rangePixels = aura.range * getPixelsPerSceneUnit(); source,
return measureAuraDistance(source, target, aura) <= rangePixels; target,
} aura,
sourceData,
targetData,
dz2,
rangePixels2,
) {
// Broad phase: Bounding box check
const sourceBounds = sourceData.bounds;
const targetBounds = targetData.bounds;
const auraRange = aura.range * getPixelsPerSceneUnit();
// 3D Distance (Euclidean) from center to center const dxBox = Math.max(
function measureCenterDistance(source, target) {
const dx = source.center.x - target.center.x;
const dy = source.center.y - target.center.y;
const dz = getElevationOffset(source, target);
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
// Main distance entry point that handles edge vs center modes
function measureAuraDistance(source, target, aura) {
const sourcePoints = getMeasurementPoints(source, aura.from);
const targetPoints = getMeasurementPoints(target, aura.to);
if (sourcePoints.length === 0 || targetPoints.length === 0)
return measureBoundsDistance(source, target, aura);
return measurePointSetDistance(sourcePoints, targetPoints, source, target);
}
// Fallback distance calculation using token bounds
function measureBoundsDistance(source, target, aura) {
const dz = getElevationOffset(source, target);
let d2d = 0;
if (aura.from === "center" && aura.to === "center")
return measureCenterDistance(source, target);
if (aura.from === "center")
d2d = measurePointToBoundsDistance(source.center, target.bounds);
else if (aura.to === "center")
d2d = measurePointToBoundsDistance(target.center, source.bounds);
else d2d = measureBoundsEdgeDistance(source, target);
return Math.sqrt(d2d * d2d + dz * dz);
}
function measureBoundsEdgeDistance(source, target) {
const alphaDistance = measureAlphaEdgeDistance(source, target);
if (Number.isFinite(alphaDistance)) return alphaDistance;
const sourceBounds = source.bounds;
const targetBounds = target.bounds;
const dx = Math.max(
sourceBounds.x - targetBounds.right, sourceBounds.x - targetBounds.right,
targetBounds.x - sourceBounds.right, targetBounds.x - sourceBounds.right,
0, 0,
); );
const dy = Math.max( const dyBox = Math.max(
sourceBounds.y - targetBounds.bottom, sourceBounds.y - targetBounds.bottom,
targetBounds.y - sourceBounds.bottom, targetBounds.y - sourceBounds.bottom,
0, 0,
); );
return Math.hypot(dx, dy);
if (dxBox * dxBox + dyBox * dyBox + dz2 > rangePixels2) return false;
const d2d2 = measureAuraDistanceSquared(
source,
target,
aura,
sourceData,
targetData,
);
return d2d2 + dz2 <= rangePixels2;
} }
function measurePointToBoundsDistance(point, bounds) { // Distance from center to center (squared)
const dx = Math.max(bounds.x - point.x, point.x - bounds.right, 0); function measureCenterDistance2(sourceData, targetData) {
const dy = Math.max(bounds.y - point.y, point.y - bounds.bottom, 0); const dx = sourceData.center.x - targetData.center.x;
return Math.hypot(dx, dy); const dy = sourceData.center.y - targetData.center.y;
return dx * dx + dy * dy;
} }
function measureAlphaEdgeDistance(source, target) { // Main distance entry point (squared)
const sourcePoints = getTokenAlphaEdgePoints(source); function measureAuraDistanceSquared(
const targetPoints = getTokenAlphaEdgePoints(target); source,
if (sourcePoints.length === 0 || targetPoints.length === 0) return Number.NaN; target,
aura,
return measurePointSetDistance(sourcePoints, targetPoints, source, target); sourceData,
} targetData,
) {
function getMeasurementPoints(token, mode) { if (aura.from === "center" && aura.to === "center") {
if (mode === "center") return [token.center]; return measureCenterDistance2(sourceData, targetData);
const alphaPoints = getTokenAlphaEdgePoints(token);
if (alphaPoints.length > 0) return alphaPoints;
return [];
}
// Find the minimum 3D distance between two sets of points
function measurePointSetDistance(sourcePoints, targetPoints, source, target) {
let minDistanceSquared = Infinity;
for (const sourcePoint of sourcePoints) {
for (const targetPoint of targetPoints) {
const dx = sourcePoint.x - targetPoint.x;
const dy = sourcePoint.y - targetPoint.y;
const distanceSquared = dx * dx + dy * dy;
if (distanceSquared < minDistanceSquared)
minDistanceSquared = distanceSquared;
}
} }
const dz = getElevationOffset(source, target); const sourcePoints = getMeasurementPointsCached(
return Math.sqrt(minDistanceSquared + dz * dz); source,
aura.from,
sourceData,
);
const targetPoints = getMeasurementPointsCached(target, aura.to, targetData);
if (sourcePoints.length === 0 || targetPoints.length === 0) {
return measureBoundsDistance2(sourceData, targetData, aura);
}
return measurePointSetDistance2(sourcePoints, targetPoints);
}
// Fallback distance calculation using token bounds (squared)
function measureBoundsDistance2(sourceData, targetData, aura) {
if (aura.from === "center")
return measurePointToBoundsDistance2(sourceData.center, targetData.bounds);
if (aura.to === "center")
return measurePointToBoundsDistance2(targetData.center, sourceData.bounds);
const dx = Math.max(
sourceData.bounds.x - targetData.bounds.right,
targetData.bounds.x - sourceData.bounds.right,
0,
);
const dy = Math.max(
sourceData.bounds.y - targetData.bounds.bottom,
targetData.bounds.y - sourceData.bounds.bottom,
0,
);
return dx * dx + dy * dy;
}
function measurePointToBoundsDistance2(point, bounds) {
const dx = Math.max(bounds.x - point.x, point.x - bounds.right, 0);
const dy = Math.max(bounds.y - point.y, point.y - bounds.bottom, 0);
return dx * dx + dy * dy;
}
function getMeasurementPointsCached(token, mode, tokenData) {
if (mode === "center") return [tokenData.center];
if (tokenData.alphaPoints === null) {
tokenData.alphaPoints = getTokenAlphaEdgePoints(token);
}
return tokenData.alphaPoints;
}
// Find the minimum distance squared between two sets of points
function measurePointSetDistance2(sourcePoints, targetPoints) {
let minDistanceSquared = Infinity;
for (let i = 0, lenS = sourcePoints.length; i < lenS; i++) {
const sP = sourcePoints[i];
for (let j = 0, lenT = targetPoints.length; j < lenT; j++) {
const tP = targetPoints[j];
const dx = sP.x - tP.x;
const dy = sP.y - tP.y;
const d2 = dx * dx + dy * dy;
if (d2 < minDistanceSquared) {
minDistanceSquared = d2;
// Optimization: Distance can't be less than 0
if (minDistanceSquared === 0) return 0;
}
}
}
return minDistanceSquared;
} }
// Convert elevation difference to pixel distance // Convert elevation difference to pixel distance