optimized the measurment calculations
This commit is contained in:
+157
-85
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user