Skip to content

Commit

Permalink
Improve aura rendering for tokens with multiple auras
Browse files Browse the repository at this point in the history
  • Loading branch information
In3luki committed Nov 12, 2024
1 parent a2d07c7 commit 28f901d
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 27 deletions.
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[]>;
12 changes: 12 additions & 0 deletions src/module/canvas/token/aura/renderer.ts
Original file line number Diff line number Diff line change
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[] | null = null;

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

constructor(params: AuraRendererParams) {
super();

Expand All @@ -42,6 +48,12 @@ 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 =
this.traits.includes("visual") && !this.traits.includes("auditory")
? "sight"
: this.traits.includes("auditory") && !this.traits.includes("visual")
? "sound"
: "move";
this.addChild(this.border);
}

Expand Down
58 changes: 34 additions & 24 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,13 +29,37 @@ export function getAreaSquares(data: GetAreaSquaresParams): EffectAreaSquare[] {
};

const topLeftSquare = new EffectAreaSquare(data.bounds.x, data.bounds.y, squareWidth, squareWidth);
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.traits?.includes("visual") && !data.traits.includes("auditory")
data.restrictionType ??
(data.traits?.includes("visual") && !data.traits.includes("auditory")
? "sight"
: data.traits?.includes("auditory") && !data.traits.includes("visual")
? "sound"
: "move";

: "move");
const tokenBounds = data.token.mechanicalBounds;
const tokenCenter = data.token.center;
const tokenCenters = [
Expand All @@ -61,37 +86,22 @@ 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;
});
}

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

export { createTestPolygons, getAreaSquares };
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

0 comments on commit 28f901d

Please sign in to comment.