Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve aura rendering for tokens with multiple auras #17288

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions src/module/canvas/token/aura/map.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as R from "remeda";
import { TokenPF2e } from "../object.ts";
import { AuraRenderer } from "./renderer.ts";
import { createTestPolygons } from "./util.ts";

export class AuraRenderers extends Map<string, AuraRenderer> {
readonly token: TokenPF2e;
Expand Down Expand Up @@ -77,17 +79,48 @@ export class AuraRenderers extends Map<string, AuraRenderer> {
}

if (showBordersHighlights && (this.token.hover || this.token.layer.highlightObjects)) {
const { highlightId } = this;
const highlightId = this.highlightId;
const highlight =
canvas.interface.grid.highlightLayers[highlightId] ??
canvas.interface.grid.addHighlightLayer(highlightId);
highlight.clear();
for (const aura of this.values()) {
// Reuse the largest aura's polygons for multiple auras
if (this.size > 1) {
const testPolygons = this.#createGroupedPolygons();
for (const aura of this.values()) {
const polygons = testPolygons[aura.restrictionType];
if (polygons.length > 0) {
aura.polygons = polygons;
}
}
}
// Always draw highlight from largest to smallest radius
for (const aura of [...this.values()].sort((a, b) => a.radius - b.radius)) {
aura.highlight();
}
}
}

/** Create test polygons for the largest aura grouped by wall restriction type */
#createGroupedPolygons(): TestPolygons {
const polygons: TestPolygons = {
move: [],
sight: [],
sound: [],
};
const grouped = R.groupBy(
[...this.values()].sort((a, b) => a.radius - b.radius),
(r) => r.restrictionType,
);
for (const [type, renderers] of R.entries(grouped)) {
if (polygons[type].length > 0) continue;
const renderer = renderers.at(0);
if (!renderer) continue;
polygons[type] = createTestPolygons(renderer);
}
return polygons;
}

/** Deallocate the aura's GPU memory before removing from map */
override delete(key: string): boolean {
const aura = this.get(key);
Expand Down Expand Up @@ -118,3 +151,5 @@ export class AuraRenderers extends Map<string, AuraRenderer> {
canvas.interface.grid.destroyHighlightLayer(this.highlightId);
}
}

type TestPolygons = Record<AuraRenderer["restrictionType"], ClockwiseSweepPolygon[]>;
9 changes: 8 additions & 1 deletion src/module/canvas/token/aura/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TokenAuraData } from "@scene/token-document/aura/index.ts";
import { isVideoFilePath } from "@util";
import type { EffectAreaSquare } from "../../effect-area-square.ts";
import type { TokenPF2e } from "../index.ts";
import { getAreaSquares } from "./util.ts";
import { getAreaSquares, getAuraRestrictionType } from "./util.ts";

/** Visual rendering of auras emanated by a token's actor */
class AuraRenderer extends PIXI.Graphics implements TokenAuraData {
Expand Down Expand Up @@ -32,6 +32,12 @@ class AuraRenderer extends PIXI.Graphics implements TokenAuraData {

textureContainer: PIXI.Graphics | null = null;

/** An optional set of predefined polygons used to test square inclusion */
polygons?: ClockwiseSweepPolygon[];

/** The wall restriction type used for this aura */
restrictionType: Exclude<WallRestrictionType, "light">;

constructor(params: AuraRendererParams) {
super();

Expand All @@ -42,6 +48,7 @@ class AuraRenderer extends PIXI.Graphics implements TokenAuraData {
this.radiusPixels =
0.5 * this.token.mechanicalBounds.width + (this.radius / canvas.dimensions.distance) * canvas.grid.size;
this.traits = params.traits;
this.restrictionType = getAuraRestrictionType(this);
this.addChild(this.border);
}

Expand Down
65 changes: 39 additions & 26 deletions src/module/canvas/token/aura/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { measureDistanceCuboid } from "@module/canvas/helpers.ts";
import { TokenDocumentPF2e } from "@scene";
import type BaseEffectSource from "types/foundry/client-esm/canvas/sources/base-effect-source.d.ts";
import type { TokenPF2e } from "../index.ts";
import type { AuraRenderer } from "./renderer.ts";

export function getAreaSquares(data: GetAreaSquaresParams): EffectAreaSquare[] {
function getAreaSquares(data: GetAreaSquaresParams): EffectAreaSquare[] {
if (!canvas.ready) return [];
const squareWidth = canvas.dimensions.size;
const rowCount = Math.ceil(data.bounds.width / squareWidth);
Expand All @@ -28,12 +29,31 @@ export function getAreaSquares(data: GetAreaSquaresParams): EffectAreaSquare[] {
};

const topLeftSquare = new EffectAreaSquare(data.bounds.x, data.bounds.y, squareWidth, squareWidth);
const collisionType =
data.traits?.includes("visual") && !data.traits.includes("auditory")
? "sight"
: data.traits?.includes("auditory") && !data.traits.includes("visual")
? "sound"
: "move";
const tokenBounds = data.token.mechanicalBounds;
const testPolygons = data.polygons ?? createTestPolygons(data);

return emptyVector
.reduce(
(squares: EffectAreaSquare[][]) => {
const lastSquare = squares.at(-1)?.at(-1) ?? { x: NaN, y: NaN };
const column = genColumn(
new EffectAreaSquare(lastSquare.x + squareWidth, topLeftSquare.y, squareWidth, squareWidth),
);
squares.push(column);
return squares;
},
[genColumn(topLeftSquare)],
)
.flat()
.filter((s) => measureDistanceCuboid(tokenBounds, s) <= data.radius)
.map((square) => {
square.active = testPolygons.some((c) => c.contains(square.center.x, square.center.y));
return square;
});
}

function createTestPolygons(data: GetAreaSquaresParams): ClockwiseSweepPolygon[] {
const collisionType = data.restrictionType ?? getAuraRestrictionType(data);

const tokenBounds = data.token.mechanicalBounds;
const tokenCenter = data.token.center;
Expand Down Expand Up @@ -61,37 +81,30 @@ export function getAreaSquares(data: GetAreaSquaresParams): EffectAreaSquare[] {
return new PointSource({ object: tokenObject });
})();

const tokenCenterPolygons = tokenCenters.map((c) =>
return tokenCenters.map((c) =>
CONFIG.Canvas.polygonBackends[collisionType].create(c, {
type: collisionType,
source: pointSource,
boundaryShape: [data.bounds],
}),
);
}

return emptyVector
.reduce(
(squares: EffectAreaSquare[][]) => {
const lastSquare = squares.at(-1)?.at(-1) ?? { x: NaN, y: NaN };
const column = genColumn(
new EffectAreaSquare(lastSquare.x + squareWidth, topLeftSquare.y, squareWidth, squareWidth),
);
squares.push(column);
return squares;
},
[genColumn(topLeftSquare)],
)
.flat()
.filter((s) => measureDistanceCuboid(tokenBounds, s) <= data.radius)
.map((square) => {
square.active = tokenCenterPolygons.some((c) => c.contains(square.center.x, square.center.y));
return square;
});
function getAuraRestrictionType(data: GetAreaSquaresParams): AuraRenderer["restrictionType"] {
return data.traits?.includes("visual") && !data.traits.includes("auditory")
? "sight"
: data.traits?.includes("auditory") && !data.traits.includes("visual")
? "sound"
: "move";
}

interface GetAreaSquaresParams {
bounds: PIXI.Rectangle;
polygons?: ClockwiseSweepPolygon[];
restrictionType?: AuraRenderer["restrictionType"];
radius: number;
token: TokenPF2e | TokenDocumentPF2e;
traits?: string[];
}

export { createTestPolygons, getAreaSquares, getAuraRestrictionType };
2 changes: 1 addition & 1 deletion src/module/canvas/token/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ class TokenPF2e<TDocument extends TokenDocumentPF2e = TokenDocumentPF2e> extends
): void {
super._onUpdate(changed, operation, userId);

if (changed.width) {
if (changed.width || changed.flags?.pf2e?.linkToActorSize !== undefined) {
if (this.animation) {
this.animation.then(() => {
this.auras.reset();
Expand Down
Loading