fixed measurement issue
This commit is contained in:
+209
-78
@@ -48,8 +48,12 @@ const alphaShapeCache = new Map();
|
|||||||
|
|
||||||
Hooks.on("renderTokenConfig", injectAurasTab);
|
Hooks.on("renderTokenConfig", injectAurasTab);
|
||||||
Hooks.on("renderPrototypeTokenConfig", injectAurasTab);
|
Hooks.on("renderPrototypeTokenConfig", injectAurasTab);
|
||||||
Hooks.on("preUpdateToken", (_token, changes) => parseAurasInChanges(changes, AURAS_FLAG_PATH));
|
Hooks.on("preUpdateToken", (_token, changes) =>
|
||||||
Hooks.on("preUpdateActor", (_actor, changes) => parseAurasInChanges(changes, `prototypeToken.${AURAS_FLAG_PATH}`));
|
parseAurasInChanges(changes, AURAS_FLAG_PATH),
|
||||||
|
);
|
||||||
|
Hooks.on("preUpdateActor", (_actor, changes) =>
|
||||||
|
parseAurasInChanges(changes, `prototypeToken.${AURAS_FLAG_PATH}`),
|
||||||
|
);
|
||||||
Hooks.on("canvasReady", scheduleAuraHighlightRefresh);
|
Hooks.on("canvasReady", scheduleAuraHighlightRefresh);
|
||||||
Hooks.on("canvasTearDown", clearAuraHighlights);
|
Hooks.on("canvasTearDown", clearAuraHighlights);
|
||||||
Hooks.on("controlToken", scheduleAuraHighlightRefresh);
|
Hooks.on("controlToken", scheduleAuraHighlightRefresh);
|
||||||
@@ -67,7 +71,9 @@ function injectAurasTab(app, element) {
|
|||||||
const tabsNav = findTabsNav(root);
|
const tabsNav = findTabsNav(root);
|
||||||
const existingTab = findExistingTab(root, tabsNav);
|
const existingTab = findExistingTab(root, tabsNav);
|
||||||
if (!tabsNav || !existingTab) {
|
if (!tabsNav || !existingTab) {
|
||||||
console.warn(`${MODULE_ID} | Could not find token config tabs to add Auras tab.`);
|
console.warn(
|
||||||
|
`${MODULE_ID} | Could not find token config tabs to add Auras tab.`,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,17 +93,22 @@ function getRootElement(element) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findTabsNav(root) {
|
function findTabsNav(root) {
|
||||||
return root.querySelector(`nav.tabs[data-group], .tabs[data-group], nav.tabs, .tabs`);
|
return root.querySelector(
|
||||||
|
`nav.tabs[data-group], .tabs[data-group], nav.tabs, .tabs`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findExistingTab(root, tabsNav) {
|
function findExistingTab(root, tabsNav) {
|
||||||
const group = tabsNav?.dataset.group;
|
const group = tabsNav?.dataset.group;
|
||||||
const selector = group ? `.tab[data-group="${group}"][data-tab]` : `.tab[data-tab]`;
|
const selector = group
|
||||||
|
? `.tab[data-group="${group}"][data-tab]`
|
||||||
|
: `.tab[data-tab]`;
|
||||||
return root.querySelector(selector) || root.querySelector(`.tab[data-tab]`);
|
return root.querySelector(selector) || root.querySelector(`.tab[data-tab]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSavedAuras(app) {
|
function getSavedAuras(app) {
|
||||||
const document = app.document ?? app.object ?? app.token ?? app.actor?.prototypeToken;
|
const document =
|
||||||
|
app.document ?? app.object ?? app.token ?? app.actor?.prototypeToken;
|
||||||
return normalizeAuras(parseAuras(document?.getFlag?.(MODULE_ID, "auras")));
|
return normalizeAuras(parseAuras(document?.getFlag?.(MODULE_ID, "auras")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +144,9 @@ function createAurasTabPanel(group, auras) {
|
|||||||
|
|
||||||
function activateAurasControls(root, group, panel) {
|
function activateAurasControls(root, group, panel) {
|
||||||
const form = root.querySelector("form");
|
const form = root.querySelector("form");
|
||||||
const aurasTabButton = root.querySelector(`[data-tab="${AURAS_TAB}"][data-group="${group}"]`);
|
const aurasTabButton = root.querySelector(
|
||||||
|
`[data-tab="${AURAS_TAB}"][data-group="${group}"]`,
|
||||||
|
);
|
||||||
|
|
||||||
aurasTabButton?.addEventListener("click", (event) => {
|
aurasTabButton?.addEventListener("click", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -141,7 +154,9 @@ function activateAurasControls(root, group, panel) {
|
|||||||
activateAurasTab(root, group);
|
activateAurasTab(root, group);
|
||||||
});
|
});
|
||||||
|
|
||||||
panel.querySelector("[data-action='add-aura']")?.addEventListener("click", () => {
|
panel
|
||||||
|
.querySelector("[data-action='add-aura']")
|
||||||
|
?.addEventListener("click", () => {
|
||||||
const auras = readAuras(panel);
|
const auras = readAuras(panel);
|
||||||
auras.push(createDefaultAura(auras.length));
|
auras.push(createDefaultAura(auras.length));
|
||||||
renderAuras(panel, auras);
|
renderAuras(panel, auras);
|
||||||
@@ -157,10 +172,15 @@ function activateAurasControls(root, group, panel) {
|
|||||||
|
|
||||||
const auraItem = deleteButton.closest("[data-aura-id]");
|
const auraItem = deleteButton.closest("[data-aura-id]");
|
||||||
const id = auraItem?.dataset.auraId;
|
const id = auraItem?.dataset.auraId;
|
||||||
renderAuras(panel, readAuras(panel).filter((aura) => aura.id !== id));
|
renderAuras(
|
||||||
|
panel,
|
||||||
|
readAuras(panel).filter((aura) => aura.id !== id),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
form?.addEventListener("submit", () => syncAurasField(panel), { capture: true });
|
form?.addEventListener("submit", () => syncAurasField(panel), {
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAuras(panel, auras) {
|
function renderAuras(panel, auras) {
|
||||||
@@ -169,7 +189,9 @@ function renderAuras(panel, auras) {
|
|||||||
const empty = panel.querySelector("[data-empty]");
|
const empty = panel.querySelector("[data-empty]");
|
||||||
if (!list || !empty) return;
|
if (!list || !empty) return;
|
||||||
|
|
||||||
list.innerHTML = normalizedAuras.map((aura, index) => renderAura(aura, index)).join("");
|
list.innerHTML = normalizedAuras
|
||||||
|
.map((aura, index) => renderAura(aura, index))
|
||||||
|
.join("");
|
||||||
empty.hidden = normalizedAuras.length > 0;
|
empty.hidden = normalizedAuras.length > 0;
|
||||||
syncAurasField(panel);
|
syncAurasField(panel);
|
||||||
}
|
}
|
||||||
@@ -237,8 +259,12 @@ function readAuras(panel) {
|
|||||||
name: item.querySelector("[data-aura-field='name']")?.value?.trim() || "",
|
name: item.querySelector("[data-aura-field='name']")?.value?.trim() || "",
|
||||||
range: Number(item.querySelector("[data-aura-field='range']")?.value) || 0,
|
range: Number(item.querySelector("[data-aura-field='range']")?.value) || 0,
|
||||||
color: item.querySelector("[data-aura-field='color']")?.value || "#ff0000",
|
color: item.querySelector("[data-aura-field='color']")?.value || "#ff0000",
|
||||||
from: normalizeMeasurementMode(item.querySelector("[data-aura-field='from']")?.value),
|
from: normalizeMeasurementMode(
|
||||||
to: normalizeMeasurementMode(item.querySelector("[data-aura-field='to']")?.value),
|
item.querySelector("[data-aura-field='from']")?.value,
|
||||||
|
),
|
||||||
|
to: normalizeMeasurementMode(
|
||||||
|
item.querySelector("[data-aura-field='to']")?.value,
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,12 +341,16 @@ function refreshAuraHighlights() {
|
|||||||
|
|
||||||
clearAuraHighlights();
|
clearAuraHighlights();
|
||||||
|
|
||||||
const sourceTokens = activeCanvas.tokens.controlled.filter((token) => getTokenAuras(token).length > 0);
|
const sourceTokens = activeCanvas.tokens.controlled.filter(
|
||||||
|
(token) => getTokenAuras(token).length > 0,
|
||||||
|
);
|
||||||
if (sourceTokens.length === 0) return;
|
if (sourceTokens.length === 0) return;
|
||||||
|
|
||||||
const highlights = collectAuraHighlights(sourceTokens);
|
const highlights = collectAuraHighlights(sourceTokens);
|
||||||
for (const [targetId, targetHighlights] of highlights) {
|
for (const [targetId, targetHighlights] of highlights) {
|
||||||
const target = activeCanvas.tokens.placeables.find((token) => token.id === targetId);
|
const target = activeCanvas.tokens.placeables.find(
|
||||||
|
(token) => token.id === targetId,
|
||||||
|
);
|
||||||
if (!target) continue;
|
if (!target) continue;
|
||||||
highlightToken(target, targetHighlights);
|
highlightToken(target, targetHighlights);
|
||||||
}
|
}
|
||||||
@@ -339,16 +369,18 @@ function clearAuraHighlights() {
|
|||||||
|
|
||||||
const activeCanvas = globalThis.canvas;
|
const activeCanvas = globalThis.canvas;
|
||||||
const parent = activeCanvas?.interface ?? activeCanvas?.tokens;
|
const parent = activeCanvas?.interface ?? activeCanvas?.tokens;
|
||||||
const container = parent?.getChildByName?.(HIGHLIGHT_CONTAINER_NAME)
|
const container =
|
||||||
?? parent?.children?.find((child) => child.name === HIGHLIGHT_CONTAINER_NAME);
|
parent?.getChildByName?.(HIGHLIGHT_CONTAINER_NAME) ??
|
||||||
|
parent?.children?.find((child) => child.name === HIGHLIGHT_CONTAINER_NAME);
|
||||||
if (container) clearHighlightContainer(container);
|
if (container) clearHighlightContainer(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHighlightContainer() {
|
function getHighlightContainer() {
|
||||||
const activeCanvas = globalThis.canvas;
|
const activeCanvas = globalThis.canvas;
|
||||||
const parent = activeCanvas.interface ?? activeCanvas.tokens;
|
const parent = activeCanvas.interface ?? activeCanvas.tokens;
|
||||||
let container = parent.getChildByName?.(HIGHLIGHT_CONTAINER_NAME)
|
let container =
|
||||||
?? parent.children?.find((child) => child.name === HIGHLIGHT_CONTAINER_NAME);
|
parent.getChildByName?.(HIGHLIGHT_CONTAINER_NAME) ??
|
||||||
|
parent.children?.find((child) => child.name === HIGHLIGHT_CONTAINER_NAME);
|
||||||
if (container) return container;
|
if (container) return container;
|
||||||
|
|
||||||
container = new PIXI.Container();
|
container = new PIXI.Container();
|
||||||
@@ -387,7 +419,9 @@ function collectAuraHighlights(sourceTokens) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTokenAuras(token) {
|
function getTokenAuras(token) {
|
||||||
return normalizeAuras(parseAuras(token.document?.getFlag?.(MODULE_ID, "auras")));
|
return normalizeAuras(
|
||||||
|
parseAuras(token.document?.getFlag?.(MODULE_ID, "auras")),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTokenInAura(source, target, aura) {
|
function isTokenInAura(source, target, aura) {
|
||||||
@@ -396,21 +430,28 @@ function isTokenInAura(source, target, aura) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function measureCenterDistance(source, target) {
|
function measureCenterDistance(source, target) {
|
||||||
return Math.hypot(source.center.x - target.center.x, source.center.y - target.center.y);
|
return Math.hypot(
|
||||||
|
source.center.x - target.center.x,
|
||||||
|
source.center.y - target.center.y,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function measureAuraDistance(source, target, aura) {
|
function measureAuraDistance(source, target, aura) {
|
||||||
const sourcePoints = getMeasurementPoints(source, aura.from);
|
const sourcePoints = getMeasurementPoints(source, aura.from);
|
||||||
const targetPoints = getMeasurementPoints(target, aura.to);
|
const targetPoints = getMeasurementPoints(target, aura.to);
|
||||||
if (sourcePoints.length === 0 || targetPoints.length === 0) return measureBoundsDistance(source, target, aura);
|
if (sourcePoints.length === 0 || targetPoints.length === 0)
|
||||||
|
return measureBoundsDistance(source, target, aura);
|
||||||
|
|
||||||
return measurePointSetDistance(sourcePoints, targetPoints);
|
return measurePointSetDistance(sourcePoints, targetPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
function measureBoundsDistance(source, target, aura) {
|
function measureBoundsDistance(source, target, aura) {
|
||||||
if (aura.from === "center" && aura.to === "center") return measureCenterDistance(source, target);
|
if (aura.from === "center" && aura.to === "center")
|
||||||
if (aura.from === "center") return measurePointToBoundsDistance(source.center, target.bounds);
|
return measureCenterDistance(source, target);
|
||||||
if (aura.to === "center") return measurePointToBoundsDistance(target.center, source.bounds);
|
if (aura.from === "center")
|
||||||
|
return measurePointToBoundsDistance(source.center, target.bounds);
|
||||||
|
if (aura.to === "center")
|
||||||
|
return measurePointToBoundsDistance(target.center, source.bounds);
|
||||||
return measureBoundsEdgeDistance(source, target);
|
return measureBoundsEdgeDistance(source, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,8 +461,16 @@ function measureBoundsEdgeDistance(source, target) {
|
|||||||
|
|
||||||
const sourceBounds = source.bounds;
|
const sourceBounds = source.bounds;
|
||||||
const targetBounds = target.bounds;
|
const targetBounds = target.bounds;
|
||||||
const dx = Math.max(sourceBounds.x - targetBounds.right, targetBounds.x - sourceBounds.right, 0);
|
const dx = Math.max(
|
||||||
const dy = Math.max(sourceBounds.y - targetBounds.bottom, targetBounds.y - sourceBounds.bottom, 0);
|
sourceBounds.x - targetBounds.right,
|
||||||
|
targetBounds.x - sourceBounds.right,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const dy = Math.max(
|
||||||
|
sourceBounds.y - targetBounds.bottom,
|
||||||
|
targetBounds.y - sourceBounds.bottom,
|
||||||
|
0,
|
||||||
|
);
|
||||||
return Math.hypot(dx, dy);
|
return Math.hypot(dx, dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,8 +502,9 @@ function measurePointSetDistance(sourcePoints, targetPoints) {
|
|||||||
for (const targetPoint of targetPoints) {
|
for (const targetPoint of targetPoints) {
|
||||||
const dx = sourcePoint.x - targetPoint.x;
|
const dx = sourcePoint.x - targetPoint.x;
|
||||||
const dy = sourcePoint.y - targetPoint.y;
|
const dy = sourcePoint.y - targetPoint.y;
|
||||||
const distanceSquared = (dx * dx) + (dy * dy);
|
const distanceSquared = dx * dx + dy * dy;
|
||||||
if (distanceSquared < minDistanceSquared) minDistanceSquared = distanceSquared;
|
if (distanceSquared < minDistanceSquared)
|
||||||
|
minDistanceSquared = distanceSquared;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,17 +523,23 @@ function getTokenAlphaEdgePoints(token) {
|
|||||||
function getAlphaPointMapper(displayObject, token) {
|
function getAlphaPointMapper(displayObject, token) {
|
||||||
const localBounds = displayObject?.getLocalBounds?.();
|
const localBounds = displayObject?.getLocalBounds?.();
|
||||||
const transform = displayObject?.worldTransform;
|
const transform = displayObject?.worldTransform;
|
||||||
if (localBounds && transform?.apply) {
|
const stageTransform = globalThis.canvas?.stage?.worldTransform;
|
||||||
return (point) => transform.apply(new PIXI.Point(
|
|
||||||
localBounds.x + (point.x * localBounds.width),
|
if (localBounds && transform?.apply && stageTransform?.applyInverse) {
|
||||||
localBounds.y + (point.y * localBounds.height),
|
return (point) => {
|
||||||
));
|
const localPoint = {
|
||||||
|
x: localBounds.x + point.x * localBounds.width,
|
||||||
|
y: localBounds.y + point.y * localBounds.height,
|
||||||
|
};
|
||||||
|
const worldPoint = transform.apply(localPoint);
|
||||||
|
return stageTransform.applyInverse(worldPoint);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const bounds = token.bounds;
|
const bounds = token.bounds;
|
||||||
return (point) => ({
|
return (point) => ({
|
||||||
x: bounds.x + (point.x * bounds.width),
|
x: bounds.x + point.x * bounds.width,
|
||||||
y: bounds.y + (point.y * bounds.height),
|
y: bounds.y + point.y * bounds.height,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,11 +558,12 @@ function getTextureAlphaEdgePoints(displayObject) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTextureSource(texture) {
|
function getTextureSource(texture) {
|
||||||
const source = texture?.source?.resource?.source
|
const source =
|
||||||
?? texture?.source?.resource
|
texture?.source?.resource?.source ??
|
||||||
?? texture?.baseTexture?.resource?.source
|
texture?.source?.resource ??
|
||||||
?? texture?.baseTexture?.resource
|
texture?.baseTexture?.resource?.source ??
|
||||||
?? null;
|
texture?.baseTexture?.resource ??
|
||||||
|
null;
|
||||||
|
|
||||||
return isCanvasImageSource(source) ? source : null;
|
return isCanvasImageSource(source) ? source : null;
|
||||||
}
|
}
|
||||||
@@ -525,8 +582,18 @@ function isCanvasImageSource(source) {
|
|||||||
|
|
||||||
function getTextureFrame(texture, source) {
|
function getTextureFrame(texture, source) {
|
||||||
const frame = texture.frame ?? texture._frame;
|
const frame = texture.frame ?? texture._frame;
|
||||||
const width = frame?.width ?? texture.width ?? source.videoWidth ?? source.naturalWidth ?? source.width;
|
const width =
|
||||||
const height = frame?.height ?? texture.height ?? source.videoHeight ?? source.naturalHeight ?? source.height;
|
frame?.width ??
|
||||||
|
texture.width ??
|
||||||
|
source.videoWidth ??
|
||||||
|
source.naturalWidth ??
|
||||||
|
source.width;
|
||||||
|
const height =
|
||||||
|
frame?.height ??
|
||||||
|
texture.height ??
|
||||||
|
source.videoHeight ??
|
||||||
|
source.naturalHeight ??
|
||||||
|
source.height;
|
||||||
return {
|
return {
|
||||||
x: frame?.x ?? 0,
|
x: frame?.x ?? 0,
|
||||||
y: frame?.y ?? 0,
|
y: frame?.y ?? 0,
|
||||||
@@ -536,12 +603,13 @@ function getTextureFrame(texture, source) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAlphaShapeCacheKey(texture, frame) {
|
function getAlphaShapeCacheKey(texture, frame) {
|
||||||
const textureId = texture.cacheId
|
const textureId =
|
||||||
?? texture.source?.uid
|
texture.cacheId ??
|
||||||
?? texture.baseTexture?.uid
|
texture.source?.uid ??
|
||||||
?? texture.baseTexture?.cacheId
|
texture.baseTexture?.uid ??
|
||||||
?? texture.uid
|
texture.baseTexture?.cacheId ??
|
||||||
?? "texture";
|
texture.uid ??
|
||||||
|
"texture";
|
||||||
return `${textureId}:${frame.x}:${frame.y}:${frame.width}:${frame.height}:${ALPHA_SAMPLE_SIZE}:${HIGHLIGHT_ALPHA_THRESHOLD}`;
|
return `${textureId}:${frame.x}:${frame.y}:${frame.width}:${frame.height}:${ALPHA_SAMPLE_SIZE}:${HIGHLIGHT_ALPHA_THRESHOLD}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,10 +623,25 @@ function sampleTextureAlphaEdges(source, frame) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
context.drawImage(source, frame.x, frame.y, frame.width, frame.height, 0, 0, canvas.width, canvas.height);
|
context.drawImage(
|
||||||
return getAlphaEdgeSamplePoints(context.getImageData(0, 0, canvas.width, canvas.height));
|
source,
|
||||||
|
frame.x,
|
||||||
|
frame.y,
|
||||||
|
frame.width,
|
||||||
|
frame.height,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
canvas.width,
|
||||||
|
canvas.height,
|
||||||
|
);
|
||||||
|
return getAlphaEdgeSamplePoints(
|
||||||
|
context.getImageData(0, 0, canvas.width, canvas.height),
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`${MODULE_ID} | Could not sample token texture alpha for aura distance. Falling back to token bounds.`, error);
|
console.warn(
|
||||||
|
`${MODULE_ID} | Could not sample token texture alpha for aura distance. Falling back to token bounds.`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -572,14 +655,18 @@ function getAlphaEdgeSamplePoints(imageData) {
|
|||||||
for (let y = 0; y < height; y += 1) {
|
for (let y = 0; y < height; y += 1) {
|
||||||
for (let x = 0; x < width; x += 1) {
|
for (let x = 0; x < width; x += 1) {
|
||||||
const index = y * width + x;
|
const index = y * width + x;
|
||||||
opaquePixels[index] = data[(index * 4) + 3] >= alphaThreshold ? 1 : 0;
|
opaquePixels[index] = data[index * 4 + 3] >= alphaThreshold ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let y = 0; y < height; y += 1) {
|
for (let y = 0; y < height; y += 1) {
|
||||||
for (let x = 0; x < width; x += 1) {
|
for (let x = 0; x < width; x += 1) {
|
||||||
const index = y * width + x;
|
const index = y * width + x;
|
||||||
if (!opaquePixels[index] || !isAlphaEdgePixel(opaquePixels, width, height, x, y)) continue;
|
if (
|
||||||
|
!opaquePixels[index] ||
|
||||||
|
!isAlphaEdgePixel(opaquePixels, width, height, x, y)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
points.push({
|
points.push({
|
||||||
x: (x + 0.5) / width,
|
x: (x + 0.5) / width,
|
||||||
@@ -598,8 +685,14 @@ function isAlphaEdgePixel(opaquePixels, width, height, x, y) {
|
|||||||
|
|
||||||
const neighborX = x + dx;
|
const neighborX = x + dx;
|
||||||
const neighborY = y + dy;
|
const neighborY = y + dy;
|
||||||
if (neighborX < 0 || neighborY < 0 || neighborX >= width || neighborY >= height) return true;
|
if (
|
||||||
if (!opaquePixels[(neighborY * width) + neighborX]) return true;
|
neighborX < 0 ||
|
||||||
|
neighborY < 0 ||
|
||||||
|
neighborX >= width ||
|
||||||
|
neighborY >= height
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
if (!opaquePixels[neighborY * width + neighborX]) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,7 +701,9 @@ function isAlphaEdgePixel(opaquePixels, width, height, x, y) {
|
|||||||
|
|
||||||
function highlightToken(token, highlights) {
|
function highlightToken(token, highlights) {
|
||||||
const target = getTokenFilterTarget(token);
|
const target = getTokenFilterTarget(token);
|
||||||
const filters = highlights.map((aura) => createOutlineFilter(aura.color)).filter(Boolean);
|
const filters = highlights
|
||||||
|
.map((aura) => createOutlineFilter(aura.color))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
if (target && filters.length > 0) {
|
if (target && filters.length > 0) {
|
||||||
applyHighlightFilters(target, filters);
|
applyHighlightFilters(target, filters);
|
||||||
@@ -619,10 +714,12 @@ function highlightToken(token, highlights) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTokenFilterTarget(token) {
|
function getTokenFilterTarget(token) {
|
||||||
return token.mesh
|
return (
|
||||||
?? token.icon
|
token.mesh ??
|
||||||
?? token.children?.find((child) => child.texture)
|
token.icon ??
|
||||||
?? null;
|
token.children?.find((child) => child.texture) ??
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyHighlightFilters(target, filters) {
|
function applyHighlightFilters(target, filters) {
|
||||||
@@ -638,17 +735,21 @@ function applyHighlightFilters(target, filters) {
|
|||||||
|
|
||||||
function removeHighlightFilters(target) {
|
function removeHighlightFilters(target) {
|
||||||
const filters = target.filters ?? [];
|
const filters = target.filters ?? [];
|
||||||
const remainingFilters = filters.filter((filter) => !filter?.[HIGHLIGHT_FILTER_MARKER]);
|
const remainingFilters = filters.filter(
|
||||||
|
(filter) => !filter?.[HIGHLIGHT_FILTER_MARKER],
|
||||||
|
);
|
||||||
target.filters = remainingFilters.length > 0 ? remainingFilters : null;
|
target.filters = remainingFilters.length > 0 ? remainingFilters : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createOutlineFilter(color) {
|
function createOutlineFilter(color) {
|
||||||
const colorNumber = colorToNumber(color);
|
const colorNumber = colorToNumber(color);
|
||||||
const pixiOutlineFilter = globalThis.PIXI?.filters?.OutlineFilter;
|
const pixiOutlineFilter = globalThis.PIXI?.filters?.OutlineFilter;
|
||||||
if (pixiOutlineFilter) return new pixiOutlineFilter(HIGHLIGHT_LINE_WIDTH, colorNumber, 0.2);
|
if (pixiOutlineFilter)
|
||||||
|
return new pixiOutlineFilter(HIGHLIGHT_LINE_WIDTH, colorNumber, 0.2);
|
||||||
|
|
||||||
const outlineOverlayFilter = globalThis.OutlineOverlayFilter
|
const outlineOverlayFilter =
|
||||||
?? globalThis.foundry?.canvas?.rendering?.filters?.OutlineOverlayFilter;
|
globalThis.OutlineOverlayFilter ??
|
||||||
|
globalThis.foundry?.canvas?.rendering?.filters?.OutlineOverlayFilter;
|
||||||
if (outlineOverlayFilter?.create) {
|
if (outlineOverlayFilter?.create) {
|
||||||
const filter = outlineOverlayFilter.create({
|
const filter = outlineOverlayFilter.create({
|
||||||
outlineColor: colorToRgbArray(colorNumber),
|
outlineColor: colorToRgbArray(colorNumber),
|
||||||
@@ -665,8 +766,9 @@ function createOutlineFilter(color) {
|
|||||||
const alphaOutlineFilter = createAlphaOutlineFilter(colorNumber);
|
const alphaOutlineFilter = createAlphaOutlineFilter(colorNumber);
|
||||||
if (alphaOutlineFilter) return alphaOutlineFilter;
|
if (alphaOutlineFilter) return alphaOutlineFilter;
|
||||||
|
|
||||||
const glowOverlayFilter = globalThis.GlowOverlayFilter
|
const glowOverlayFilter =
|
||||||
?? globalThis.foundry?.canvas?.rendering?.filters?.GlowOverlayFilter;
|
globalThis.GlowOverlayFilter ??
|
||||||
|
globalThis.foundry?.canvas?.rendering?.filters?.GlowOverlayFilter;
|
||||||
if (glowOverlayFilter?.create) {
|
if (glowOverlayFilter?.create) {
|
||||||
const filter = glowOverlayFilter.create({
|
const filter = glowOverlayFilter.create({
|
||||||
glowColor: colorToRgbArray(colorNumber),
|
glowColor: colorToRgbArray(colorNumber),
|
||||||
@@ -689,11 +791,17 @@ function createAlphaOutlineFilter(color) {
|
|||||||
thickness: HIGHLIGHT_LINE_WIDTH,
|
thickness: HIGHLIGHT_LINE_WIDTH,
|
||||||
alphaThreshold: HIGHLIGHT_ALPHA_THRESHOLD,
|
alphaThreshold: HIGHLIGHT_ALPHA_THRESHOLD,
|
||||||
};
|
};
|
||||||
const baseFilter = globalThis.foundry?.canvas?.rendering?.filters?.AbstractBaseFilter ?? globalThis.PIXI?.Filter;
|
const baseFilter =
|
||||||
|
globalThis.foundry?.canvas?.rendering?.filters?.AbstractBaseFilter ??
|
||||||
|
globalThis.PIXI?.Filter;
|
||||||
if (!baseFilter) return null;
|
if (!baseFilter) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const filter = new baseFilter(undefined, ALPHA_OUTLINE_FRAGMENT_SHADER, uniforms);
|
const filter = new baseFilter(
|
||||||
|
undefined,
|
||||||
|
ALPHA_OUTLINE_FRAGMENT_SHADER,
|
||||||
|
uniforms,
|
||||||
|
);
|
||||||
filter.padding = HIGHLIGHT_LINE_WIDTH;
|
filter.padding = HIGHLIGHT_LINE_WIDTH;
|
||||||
return filter;
|
return filter;
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
@@ -707,15 +815,27 @@ function drawFallbackTokenHighlights(container, token, highlights) {
|
|||||||
const graphic = new PIXI.Graphics();
|
const graphic = new PIXI.Graphics();
|
||||||
|
|
||||||
graphic.lineStyle(HIGHLIGHT_LINE_WIDTH, colorToNumber(aura.color), 0.9);
|
graphic.lineStyle(HIGHLIGHT_LINE_WIDTH, colorToNumber(aura.color), 0.9);
|
||||||
graphic.drawRoundedRect(bounds.x, bounds.y, bounds.width, bounds.height, 6 + (index * 2));
|
graphic.drawRoundedRect(
|
||||||
|
bounds.x,
|
||||||
|
bounds.y,
|
||||||
|
bounds.width,
|
||||||
|
bounds.height,
|
||||||
|
6 + index * 2,
|
||||||
|
);
|
||||||
container.addChild(graphic);
|
container.addChild(graphic);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPixelsPerSceneUnit() {
|
function getPixelsPerSceneUnit() {
|
||||||
const activeCanvas = globalThis.canvas;
|
const activeCanvas = globalThis.canvas;
|
||||||
const gridSize = activeCanvas.grid?.size ?? activeCanvas.dimensions?.size ?? 100;
|
const gridSize =
|
||||||
const gridDistance = Number(activeCanvas.scene?.grid?.distance ?? activeCanvas.dimensions?.distance ?? 1) || 1;
|
activeCanvas.grid?.size ?? activeCanvas.dimensions?.size ?? 100;
|
||||||
|
const gridDistance =
|
||||||
|
Number(
|
||||||
|
activeCanvas.scene?.grid?.distance ??
|
||||||
|
activeCanvas.dimensions?.distance ??
|
||||||
|
1,
|
||||||
|
) || 1;
|
||||||
return gridSize / gridDistance;
|
return gridSize / gridDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,11 +855,15 @@ function colorToRgbArray(color) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function activateAurasTab(root, group) {
|
function activateAurasTab(root, group) {
|
||||||
for (const tab of root.querySelectorAll(`.tab[data-group="${group}"], .tab[data-tab]`)) {
|
for (const tab of root.querySelectorAll(
|
||||||
|
`.tab[data-group="${group}"], .tab[data-tab]`,
|
||||||
|
)) {
|
||||||
tab.classList.toggle("active", tab.dataset.tab === AURAS_TAB);
|
tab.classList.toggle("active", tab.dataset.tab === AURAS_TAB);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const item of root.querySelectorAll(`[data-group="${group}"][data-tab], .tabs [data-tab]`)) {
|
for (const item of root.querySelectorAll(
|
||||||
|
`[data-group="${group}"][data-tab], .tabs [data-tab]`,
|
||||||
|
)) {
|
||||||
item.classList.toggle("active", item.dataset.tab === AURAS_TAB);
|
item.classList.toggle("active", item.dataset.tab === AURAS_TAB);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -749,15 +873,22 @@ function localize(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function randomId() {
|
function randomId() {
|
||||||
return globalThis.foundry?.utils?.randomID?.() ?? Math.random().toString(36).slice(2);
|
return (
|
||||||
|
globalThis.foundry?.utils?.randomID?.() ??
|
||||||
|
Math.random().toString(36).slice(2)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(value) {
|
function escapeHtml(value) {
|
||||||
return String(value).replace(/[&<>"']/g, (character) => ({
|
return String(value).replace(
|
||||||
|
/[&<>"']/g,
|
||||||
|
(character) =>
|
||||||
|
({
|
||||||
"&": "&",
|
"&": "&",
|
||||||
"<": "<",
|
"<": "<",
|
||||||
">": ">",
|
">": ">",
|
||||||
"\"": """,
|
'"': """,
|
||||||
"'": "'",
|
"'": "'",
|
||||||
}[character]));
|
})[character],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user