Refactor TerritoryLayer and TerritoryWebGLRenderer for improved smoothing management

- Removed redundant smooth state handling in TerritoryLayer, replacing it with a snapshot mechanism for smoother transitions.
- Updated rendering logic in TerritoryWebGLRenderer to utilize framebuffers for state management, enhancing performance and visual fidelity.
- Adjusted shader logic to accommodate new smoothing conditions and removed obsolete properties related to smooth state tracking.
This commit is contained in:
scamiv
2026-01-10 18:45:50 +01:00
parent 3beb0bc6b7
commit 95f81c9543
2 changed files with 128 additions and 263 deletions
+18 -66
View File
@@ -67,9 +67,7 @@ export class TerritoryLayer implements Layer {
private smoothMaxDistance = 12;
private smoothActive = false;
private smoothStartMs = 0;
private smoothTiles: TileRef[] = [];
private smoothActiveMask: Uint8Array | null = null;
private smoothPrevOwners: Uint16Array | null = null;
private smoothSnapshotPending = false;
constructor(
private game: GameView,
@@ -100,8 +98,15 @@ export class TerritoryLayer implements Layer {
this.refreshPaletteIfNeeded();
this.game.recentlyUpdatedTiles().forEach((t) => this.markTile(t));
this.applySmoothChanges(this.game.recentlyUpdatedOwnerTiles(), now);
this.applyContestChanges(this.game.recentlyUpdatedOwnerTiles(), now);
const ownerUpdates = this.game.recentlyUpdatedOwnerTiles();
if (ownerUpdates.length > 0) {
if (this.territoryRenderer) {
this.smoothSnapshotPending = true;
}
this.smoothStartMs = now;
this.smoothActive = true;
}
this.applyContestChanges(ownerUpdates, now);
const updates = this.game.updatesSinceLastTick();
// Detect alliance mutations
@@ -364,7 +369,6 @@ export class TerritoryLayer implements Layer {
this.configureRenderers();
this.ensureContestScratch();
this.syncContestStateToRenderer();
this.ensureSmoothScratch();
this.syncSmoothStateToRenderer();
// Add a second canvas for highlights
@@ -426,6 +430,10 @@ export class TerritoryLayer implements Layer {
return;
}
const now = this.nowMs();
if (this.smoothSnapshotPending) {
this.territoryRenderer.snapshotStateForSmoothing();
this.smoothSnapshotPending = false;
}
this.updateSmoothState(now);
this.updateContestState(now);
@@ -534,66 +542,19 @@ export class TerritoryLayer implements Layer {
}
}
private ensureSmoothScratch() {
const size = this.game.width() * this.game.height();
if (!this.smoothActiveMask || this.smoothActiveMask.length !== size) {
this.smoothActiveMask = new Uint8Array(size);
this.smoothPrevOwners = new Uint16Array(size);
this.smoothTiles = [];
this.smoothActive = false;
this.smoothStartMs = 0;
}
}
private applySmoothChanges(
changes: Array<{ tile: TileRef; previousOwner: number; newOwner: number }>,
now: number,
) {
if (!this.territoryRenderer || changes.length === 0) {
return;
}
this.ensureSmoothScratch();
this.smoothStartMs = now;
this.smoothActive = true;
this.territoryRenderer.setSmoothEnabled(true);
this.territoryRenderer.setSmoothMaxDistance(this.smoothMaxDistance);
for (const change of changes) {
if (change.newOwner === change.previousOwner) {
continue;
}
const tile = change.tile;
this.smoothPrevOwners![tile] = change.previousOwner;
if (this.smoothActiveMask![tile] === 0) {
this.smoothActiveMask![tile] = 1;
this.smoothTiles.push(tile);
}
this.territoryRenderer.setSmoothTile(tile, change.previousOwner);
}
}
private updateSmoothState(now: number) {
if (!this.territoryRenderer) {
return;
}
this.ensureSmoothScratch();
let progress = 1;
if (this.smoothActive) {
const elapsed = now - this.smoothStartMs;
progress = Math.max(0, Math.min(1, elapsed / this.smoothDurationMs));
if (progress >= 1) {
for (const tile of this.smoothTiles) {
if (this.smoothActiveMask![tile] === 0) {
continue;
}
this.smoothActiveMask![tile] = 0;
this.smoothPrevOwners![tile] = 0;
this.territoryRenderer.clearSmoothTile(tile, this.game.ownerID(tile));
}
this.smoothTiles = [];
this.smoothActive = false;
}
}
this.territoryRenderer.setSmoothMaxDistance(this.smoothMaxDistance);
this.territoryRenderer.setSmoothProgress(progress);
this.territoryRenderer.setSmoothEnabled(this.smoothActive);
}
@@ -933,12 +894,11 @@ export class TerritoryLayer implements Layer {
}
private syncSmoothStateToRenderer() {
if (!this.territoryRenderer || !this.smoothActiveMask) {
if (!this.territoryRenderer) {
return;
}
this.territoryRenderer.setSmoothMaxDistance(this.smoothMaxDistance);
if (this.smoothActive) {
this.territoryRenderer.setSmoothEnabled(true);
this.territoryRenderer.setSmoothMaxDistance(this.smoothMaxDistance);
const now = this.nowMs();
const elapsed = now - this.smoothStartMs;
const progress = Math.max(
@@ -946,15 +906,7 @@ export class TerritoryLayer implements Layer {
Math.min(1, elapsed / this.smoothDurationMs),
);
this.territoryRenderer.setSmoothProgress(progress);
for (const tile of this.smoothTiles) {
if (this.smoothActiveMask[tile] === 0) {
continue;
}
this.territoryRenderer.setSmoothTile(
tile,
this.smoothPrevOwners![tile],
);
}
this.territoryRenderer.setSmoothEnabled(true);
} else {
this.territoryRenderer.setSmoothEnabled(false);
this.territoryRenderer.setSmoothProgress(1);
@@ -39,7 +39,8 @@ export class TerritoryWebGLRenderer {
private readonly contestIdsTexture: WebGLTexture | null;
private readonly contestTimesTexture: WebGLTexture | null;
private readonly prevOwnerTexture: WebGLTexture | null;
private readonly changeMaskTexture: WebGLTexture | null;
private readonly stateFramebuffer: WebGLFramebuffer | null;
private readonly prevStateFramebuffer: WebGLFramebuffer | null;
private readonly jfaTextureA: WebGLTexture | null;
private readonly jfaTextureB: WebGLTexture | null;
private readonly jfaFramebufferA: WebGLFramebuffer | null;
@@ -67,7 +68,6 @@ export class TerritoryWebGLRenderer {
contestNow: WebGLUniformLocation | null;
contestDuration: WebGLUniformLocation | null;
prevOwner: WebGLUniformLocation | null;
changeMask: WebGLUniformLocation | null;
jfaSeeds: WebGLUniformLocation | null;
smoothProgress: WebGLUniformLocation | null;
smoothMaxDistance: WebGLUniformLocation | null;
@@ -94,30 +94,27 @@ export class TerritoryWebGLRenderer {
private contestOwnersState: Uint16Array;
private contestIdsState: Uint16Array;
private contestTimesState: Uint16Array;
private smoothPrevOwnerState: Uint16Array;
private smoothChangeMaskState: Uint8Array;
private readonly dirtyRows: Map<number, DirtySpan> = new Map();
private readonly contestDirtyRows: Map<number, DirtySpan> = new Map();
private readonly smoothDirtyRows: Map<number, DirtySpan> = new Map();
private needsFullUpload = true;
private needsContestFullUpload = true;
private needsContestTimesUpload = true;
private needsSmoothFullUpload = true;
private alternativeView = false;
private paletteWidth = 0;
private hoverHighlightStrength = 0.7;
private hoverHighlightStrength = 0.3;
private hoverHighlightColor: [number, number, number] = [1, 1, 1];
private hoverPulseStrength = 0.25;
private hoverPulseSpeed = Math.PI * 2;
private hoveredPlayerId = -1;
private animationStartTime = Date.now();
private contestNow = 0;
private contestDurationMs = 5000;
private contestDurationMs = 0;
private smoothProgress = 1;
private smoothMaxDistance = 12;
private smoothEnabled = false;
private smoothEnabled = true;
private jfaSupported = false;
private jfaDirty = false;
private prevStateCopySupported = false;
private jfaSteps: number[] = [];
private jfaResultIsA = true;
private readonly userSettings = new UserSettings();
@@ -136,11 +133,6 @@ export class TerritoryWebGLRenderer {
this.contestOwnersState = new Uint16Array(state.length * 2);
this.contestIdsState = new Uint16Array(state.length);
this.contestTimesState = new Uint16Array(1);
this.smoothPrevOwnerState = new Uint16Array(state.length);
for (let i = 0; i < state.length; i++) {
this.smoothPrevOwnerState[i] = state[i] & 0x0fff;
}
this.smoothChangeMaskState = new Uint8Array(state.length);
this.gl = this.canvas.getContext("webgl2", {
premultipliedAlpha: true,
@@ -160,7 +152,8 @@ export class TerritoryWebGLRenderer {
this.contestIdsTexture = null;
this.contestTimesTexture = null;
this.prevOwnerTexture = null;
this.changeMaskTexture = null;
this.stateFramebuffer = null;
this.prevStateFramebuffer = null;
this.jfaTextureA = null;
this.jfaTextureB = null;
this.jfaFramebufferA = null;
@@ -181,7 +174,6 @@ export class TerritoryWebGLRenderer {
contestNow: null,
contestDuration: null,
prevOwner: null,
changeMask: null,
jfaSeeds: null,
smoothProgress: null,
smoothMaxDistance: null,
@@ -219,7 +211,8 @@ export class TerritoryWebGLRenderer {
this.contestIdsTexture = null;
this.contestTimesTexture = null;
this.prevOwnerTexture = null;
this.changeMaskTexture = null;
this.stateFramebuffer = null;
this.prevStateFramebuffer = null;
this.jfaTextureA = null;
this.jfaTextureB = null;
this.jfaFramebufferA = null;
@@ -240,7 +233,6 @@ export class TerritoryWebGLRenderer {
contestNow: null,
contestDuration: null,
prevOwner: null,
changeMask: null,
jfaSeeds: null,
smoothProgress: null,
smoothMaxDistance: null,
@@ -305,7 +297,6 @@ export class TerritoryWebGLRenderer {
"u_contestDurationMs",
),
prevOwner: gl.getUniformLocation(this.program, "u_prevOwner"),
changeMask: gl.getUniformLocation(this.program, "u_changeMask"),
jfaSeeds: gl.getUniformLocation(this.program, "u_jfaSeeds"),
smoothProgress: gl.getUniformLocation(this.program, "u_smoothProgress"),
smoothMaxDistance: gl.getUniformLocation(
@@ -375,7 +366,8 @@ export class TerritoryWebGLRenderer {
this.contestIdsTexture = gl.createTexture();
this.contestTimesTexture = gl.createTexture();
this.prevOwnerTexture = gl.createTexture();
this.changeMaskTexture = gl.createTexture();
this.stateFramebuffer = gl.createFramebuffer();
this.prevStateFramebuffer = gl.createFramebuffer();
this.jfaTextureA = this.jfaSupported ? gl.createTexture() : null;
this.jfaTextureB = this.jfaSupported ? gl.createTexture() : null;
this.jfaFramebufferA = this.jfaSupported ? gl.createFramebuffer() : null;
@@ -475,27 +467,38 @@ export class TerritoryWebGLRenderer {
0,
gl.RED_INTEGER,
gl.UNSIGNED_SHORT,
this.smoothPrevOwnerState,
this.state,
);
gl.activeTexture(gl.TEXTURE8);
gl.bindTexture(gl.TEXTURE_2D, this.changeMaskTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.R8UI,
this.canvas.width,
this.canvas.height,
0,
gl.RED_INTEGER,
gl.UNSIGNED_BYTE,
this.smoothChangeMaskState,
);
if (
this.stateFramebuffer &&
this.prevStateFramebuffer &&
this.stateTexture &&
this.prevOwnerTexture
) {
gl.bindFramebuffer(gl.FRAMEBUFFER, this.stateFramebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
this.stateTexture,
0,
);
const stateStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
gl.bindFramebuffer(gl.FRAMEBUFFER, this.prevStateFramebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
this.prevOwnerTexture,
0,
);
const prevStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
this.prevStateCopySupported =
stateStatus === gl.FRAMEBUFFER_COMPLETE &&
prevStatus === gl.FRAMEBUFFER_COMPLETE;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}
if (
this.jfaSupported &&
@@ -571,8 +574,7 @@ export class TerritoryWebGLRenderer {
gl.uniform1i(this.uniforms.contestIds, 5);
gl.uniform1i(this.uniforms.contestTimes, 6);
gl.uniform1i(this.uniforms.prevOwner, 7);
gl.uniform1i(this.uniforms.changeMask, 8);
gl.uniform1i(this.uniforms.jfaSeeds, 9);
gl.uniform1i(this.uniforms.jfaSeeds, 8);
if (this.uniforms.resolution) {
gl.uniform2f(
@@ -677,7 +679,7 @@ export class TerritoryWebGLRenderer {
}
if (this.jfaSupported && this.jfaTextureA && this.jfaTextureB) {
gl.activeTexture(gl.TEXTURE9);
gl.activeTexture(gl.TEXTURE8);
gl.bindTexture(
gl.TEXTURE_2D,
this.jfaResultIsA ? this.jfaTextureA : this.jfaTextureB,
@@ -836,41 +838,32 @@ export class TerritoryWebGLRenderer {
this.contestDurationMs = Math.max(1, durationMs);
}
setSmoothTile(tile: TileRef, previousOwner: number) {
this.smoothPrevOwnerState[tile] = previousOwner & 0xffff;
this.smoothChangeMaskState[tile] = 1;
if (this.needsSmoothFullUpload) {
this.jfaDirty = true;
snapshotStateForSmoothing() {
if (
!this.gl ||
!this.prevStateCopySupported ||
!this.stateFramebuffer ||
!this.prevStateFramebuffer
) {
return;
}
const x = tile % this.canvas.width;
const y = Math.floor(tile / this.canvas.width);
const span = this.smoothDirtyRows.get(y);
if (span === undefined) {
this.smoothDirtyRows.set(y, { minX: x, maxX: x });
} else {
span.minX = Math.min(span.minX, x);
span.maxX = Math.max(span.maxX, x);
}
this.jfaDirty = true;
}
clearSmoothTile(tile: TileRef, currentOwner: number) {
this.smoothPrevOwnerState[tile] = currentOwner & 0xffff;
this.smoothChangeMaskState[tile] = 0;
if (this.needsSmoothFullUpload) {
this.jfaDirty = true;
return;
}
const x = tile % this.canvas.width;
const y = Math.floor(tile / this.canvas.width);
const span = this.smoothDirtyRows.get(y);
if (span === undefined) {
this.smoothDirtyRows.set(y, { minX: x, maxX: x });
} else {
span.minX = Math.min(span.minX, x);
span.maxX = Math.max(span.maxX, x);
}
const gl = this.gl;
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.stateFramebuffer);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.prevStateFramebuffer);
gl.blitFramebuffer(
0,
0,
this.canvas.width,
this.canvas.height,
0,
0,
this.canvas.width,
this.canvas.height,
gl.COLOR_BUFFER_BIT,
gl.NEAREST,
);
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
this.jfaDirty = true;
}
@@ -883,7 +876,8 @@ export class TerritoryWebGLRenderer {
}
setSmoothEnabled(enabled: boolean) {
this.smoothEnabled = enabled && this.jfaSupported;
this.smoothEnabled =
enabled && this.jfaSupported && this.prevStateCopySupported;
}
markAllDirty() {
@@ -892,8 +886,6 @@ export class TerritoryWebGLRenderer {
this.needsContestFullUpload = true;
this.needsContestTimesUpload = true;
this.contestDirtyRows.clear();
this.needsSmoothFullUpload = true;
this.smoothDirtyRows.clear();
this.jfaDirty = true;
}
@@ -928,10 +920,6 @@ export class TerritoryWebGLRenderer {
uploadContestTimesSpan,
);
const uploadSmoothSpan = FrameProfiler.start();
this.uploadSmoothTextures();
FrameProfiler.end("TerritoryWebGLRenderer:uploadSmooth", uploadSmoothSpan);
if (this.jfaSupported && this.smoothEnabled) {
this.updateJfa();
}
@@ -1166,107 +1154,6 @@ export class TerritoryWebGLRenderer {
return { rows: 1, bytes };
}
private uploadSmoothTextures(): { rows: number; bytes: number } {
if (!this.gl || !this.prevOwnerTexture || !this.changeMaskTexture) {
return { rows: 0, bytes: 0 };
}
const gl = this.gl;
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
const bytesPerOwner = Uint16Array.BYTES_PER_ELEMENT;
const bytesPerMask = Uint8Array.BYTES_PER_ELEMENT;
let rowsUploaded = 0;
let bytesUploaded = 0;
if (this.needsSmoothFullUpload) {
gl.activeTexture(gl.TEXTURE7);
gl.bindTexture(gl.TEXTURE_2D, this.prevOwnerTexture);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.R16UI,
this.canvas.width,
this.canvas.height,
0,
gl.RED_INTEGER,
gl.UNSIGNED_SHORT,
this.smoothPrevOwnerState,
);
gl.activeTexture(gl.TEXTURE8);
gl.bindTexture(gl.TEXTURE_2D, this.changeMaskTexture);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.R8UI,
this.canvas.width,
this.canvas.height,
0,
gl.RED_INTEGER,
gl.UNSIGNED_BYTE,
this.smoothChangeMaskState,
);
this.needsSmoothFullUpload = false;
this.smoothDirtyRows.clear();
rowsUploaded = this.canvas.height;
bytesUploaded =
this.canvas.width * this.canvas.height * (bytesPerOwner + bytesPerMask);
return { rows: rowsUploaded, bytes: bytesUploaded };
}
if (this.smoothDirtyRows.size === 0) {
return { rows: 0, bytes: 0 };
}
for (const [y, span] of this.smoothDirtyRows) {
const width = span.maxX - span.minX + 1;
const ownerOffset = y * this.canvas.width + span.minX;
const ownerSlice = this.smoothPrevOwnerState.subarray(
ownerOffset,
ownerOffset + width,
);
gl.activeTexture(gl.TEXTURE7);
gl.bindTexture(gl.TEXTURE_2D, this.prevOwnerTexture);
gl.texSubImage2D(
gl.TEXTURE_2D,
0,
span.minX,
y,
width,
1,
gl.RED_INTEGER,
gl.UNSIGNED_SHORT,
ownerSlice,
);
const maskOffset = y * this.canvas.width + span.minX;
const maskSlice = this.smoothChangeMaskState.subarray(
maskOffset,
maskOffset + width,
);
gl.activeTexture(gl.TEXTURE8);
gl.bindTexture(gl.TEXTURE_2D, this.changeMaskTexture);
gl.texSubImage2D(
gl.TEXTURE_2D,
0,
span.minX,
y,
width,
1,
gl.RED_INTEGER,
gl.UNSIGNED_BYTE,
maskSlice,
);
rowsUploaded++;
bytesUploaded += width * (bytesPerOwner + bytesPerMask);
}
this.smoothDirtyRows.clear();
return { rows: rowsUploaded, bytes: bytesUploaded };
}
private updateJfa() {
if (
!this.gl ||
@@ -1559,7 +1446,7 @@ export class TerritoryWebGLRenderer {
ivec2(0, 0),
ivec2(int(u_resolution.x) - 1, int(u_resolution.y) - 1)
);
return texelFetch(u_prevOwner, clamped, 0).r;
return texelFetch(u_prevOwner, clamped, 0).r & 0xFFFu;
}
void main() {
@@ -1732,7 +1619,6 @@ export class TerritoryWebGLRenderer {
uniform int u_contestNow;
uniform float u_contestDurationMs;
uniform usampler2D u_prevOwner;
uniform usampler2D u_changeMask;
uniform sampler2D u_jfaSeeds;
uniform float u_smoothProgress;
uniform float u_smoothMaxDistance;
@@ -1775,15 +1661,6 @@ export class TerritoryWebGLRenderer {
return texelFetch(u_prevOwner, clamped, 0).r & 0xFFFu;
}
uint changeMaskAtTex(ivec2 texCoord) {
ivec2 clamped = clamp(
texCoord,
ivec2(0, 0),
ivec2(int(u_resolution.x) - 1, int(u_resolution.y) - 1)
);
return texelFetch(u_changeMask, clamped, 0).r;
}
vec2 jfaSeedAtTex(ivec2 texCoord) {
ivec2 clamped = clamp(
texCoord,
@@ -1904,6 +1781,8 @@ export class TerritoryWebGLRenderer {
bool hasFriendlyRelation = false;
bool hasEmbargoRelation = false;
bool pushedBorder = false;
bool attackerFriendlyRelation = false;
bool attackerEmbargoRelation = false;
bool regainedBorder = false;
uint nOwner = ownerAtTex(texCoord + ivec2(1, 0));
@@ -1920,6 +1799,11 @@ export class TerritoryWebGLRenderer {
bool nAttackerEver = sameComponent && ((nContestRaw & CONTEST_ATTACKER_EVER) != 0u);
if (attackerEver && !nAttackerEver) {
pushedBorder = true;
if (attacker != 0u && nOwner != 0u && nOwner != attacker) {
uint rel = relationCode(attacker, nOwner);
attackerEmbargoRelation = attackerEmbargoRelation || isEmbargo(rel);
attackerFriendlyRelation = attackerFriendlyRelation || isFriendly(rel);
}
}
if (sameComponent && owner == defender && nOwner == attacker) {
regainedBorder = true;
@@ -1940,6 +1824,11 @@ export class TerritoryWebGLRenderer {
bool nAttackerEver = sameComponent && ((nContestRaw & CONTEST_ATTACKER_EVER) != 0u);
if (attackerEver && !nAttackerEver) {
pushedBorder = true;
if (attacker != 0u && nOwner != 0u && nOwner != attacker) {
uint rel = relationCode(attacker, nOwner);
attackerEmbargoRelation = attackerEmbargoRelation || isEmbargo(rel);
attackerFriendlyRelation = attackerFriendlyRelation || isFriendly(rel);
}
}
if (sameComponent && owner == defender && nOwner == attacker) {
regainedBorder = true;
@@ -1960,6 +1849,11 @@ export class TerritoryWebGLRenderer {
bool nAttackerEver = sameComponent && ((nContestRaw & CONTEST_ATTACKER_EVER) != 0u);
if (attackerEver && !nAttackerEver) {
pushedBorder = true;
if (attacker != 0u && nOwner != 0u && nOwner != attacker) {
uint rel = relationCode(attacker, nOwner);
attackerEmbargoRelation = attackerEmbargoRelation || isEmbargo(rel);
attackerFriendlyRelation = attackerFriendlyRelation || isFriendly(rel);
}
}
if (sameComponent && owner == defender && nOwner == attacker) {
regainedBorder = true;
@@ -1980,6 +1874,11 @@ export class TerritoryWebGLRenderer {
bool nAttackerEver = sameComponent && ((nContestRaw & CONTEST_ATTACKER_EVER) != 0u);
if (attackerEver && !nAttackerEver) {
pushedBorder = true;
if (attacker != 0u && nOwner != 0u && nOwner != attacker) {
uint rel = relationCode(attacker, nOwner);
attackerEmbargoRelation = attackerEmbargoRelation || isEmbargo(rel);
attackerFriendlyRelation = attackerFriendlyRelation || isFriendly(rel);
}
}
if (sameComponent && owner == defender && nOwner == attacker) {
regainedBorder = true;
@@ -2084,7 +1983,21 @@ export class TerritoryWebGLRenderer {
ivec2(int(attacker) * 2 + 1, 0),
0
);
attackerBorderColor = applyDefended(attackerBorder.rgb, isDefended, texCoord);
vec3 attackerColor = attackerBorder.rgb;
const float ATTACKER_BORDER_TINT_RATIO = 0.35;
const vec3 ATTACKER_FRIENDLY_TINT_TARGET = vec3(0.0, 1.0, 0.0);
const vec3 ATTACKER_EMBARGO_TINT_TARGET = vec3(1.0, 0.0, 0.0);
if (attackerFriendlyRelation) {
attackerColor = attackerColor * (1.0 - ATTACKER_BORDER_TINT_RATIO) +
ATTACKER_FRIENDLY_TINT_TARGET * ATTACKER_BORDER_TINT_RATIO;
}
if (attackerEmbargoRelation) {
attackerColor = attackerColor * (1.0 - ATTACKER_BORDER_TINT_RATIO) +
ATTACKER_EMBARGO_TINT_TARGET * ATTACKER_BORDER_TINT_RATIO;
}
attackerBorderColor = applyDefended(attackerColor, isDefended, texCoord);
attackerBorderAlpha = attackerBorder.a;
}
@@ -2113,14 +2026,14 @@ export class TerritoryWebGLRenderer {
}
}
uint oldOwner = prevOwnerAtTex(texCoord);
bool smoothActive = u_smoothEnabled &&
u_smoothProgress < 1.0 &&
!u_alternativeView &&
!contested &&
changeMaskAtTex(texCoord) != 0u;
oldOwner != owner;
if (smoothActive) {
uint oldOwner = prevOwnerAtTex(texCoord);
bool oldIsBorder = false;
bool oldFriendlyRelation = false;
bool oldEmbargoRelation = false;