mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 16:46:35 +00:00
Add contested drawing controls to TerritoryLayer and TerritoryWebGLRenderer
- Introduced UI elements for enabling contested drawing and selecting contest patterns (blueNoise, checkerboard, bayer4x4) in TerritoryLayer. - Updated TerritoryWebGLRenderer to handle contest pattern modes and integrate them into the rendering logic. - Enhanced shader logic to support new contest pattern modes for improved visual representation of contested territories. - Added functionality to synchronize contest state and pattern mode between the UI and renderer.
This commit is contained in:
@@ -89,6 +89,8 @@ export class TerritoryLayer implements Layer {
|
||||
private tickIntervalEmaMs = 0;
|
||||
private readonly TICK_INTERVAL_EMA_ALPHA = 0.2;
|
||||
private smoothingDebugUi: HTMLDivElement | null = null;
|
||||
private contestedPatternMode: "blueNoise" | "checkerboard" | "bayer4x4" =
|
||||
"blueNoise";
|
||||
|
||||
constructor(
|
||||
private game: GameView,
|
||||
@@ -570,6 +572,80 @@ export class TerritoryLayer implements Layer {
|
||||
modeRow.appendChild(modeSelect);
|
||||
root.appendChild(modeRow);
|
||||
|
||||
// Contested drawing controls
|
||||
const contestedRow = document.createElement("label");
|
||||
contestedRow.style.display = "flex";
|
||||
contestedRow.style.alignItems = "center";
|
||||
contestedRow.style.gap = "6px";
|
||||
contestedRow.style.marginBottom = "6px";
|
||||
|
||||
const contestedCheckbox = document.createElement("input");
|
||||
contestedCheckbox.type = "checkbox";
|
||||
contestedCheckbox.checked = this.contestEnabled;
|
||||
contestedCheckbox.addEventListener("change", () => {
|
||||
const enabled = contestedCheckbox.checked;
|
||||
this.contestEnabled = enabled;
|
||||
this.contestTileCount = 0;
|
||||
this.contestActive = false;
|
||||
if (enabled) {
|
||||
this.ensureContestScratch();
|
||||
this.syncContestStateToRenderer();
|
||||
} else {
|
||||
this.contestComponents.clear();
|
||||
}
|
||||
this.territoryRenderer?.setContestEnabled(enabled);
|
||||
this.territoryRenderer?.markAllDirty();
|
||||
});
|
||||
|
||||
const contestedText = document.createElement("span");
|
||||
contestedText.textContent = "contested draw";
|
||||
contestedRow.appendChild(contestedCheckbox);
|
||||
contestedRow.appendChild(contestedText);
|
||||
root.appendChild(contestedRow);
|
||||
|
||||
const contestedModeRow = document.createElement("label");
|
||||
contestedModeRow.style.display = "flex";
|
||||
contestedModeRow.style.alignItems = "center";
|
||||
contestedModeRow.style.gap = "6px";
|
||||
contestedModeRow.style.marginBottom = "0px";
|
||||
|
||||
const contestedModeText = document.createElement("span");
|
||||
contestedModeText.textContent = "contested pattern:";
|
||||
|
||||
const contestedModeSelect = document.createElement("select");
|
||||
contestedModeSelect.style.font = "12px monospace";
|
||||
contestedModeSelect.style.background = "rgba(0,0,0,0.35)";
|
||||
contestedModeSelect.style.color = "rgba(255,255,255,0.92)";
|
||||
contestedModeSelect.style.border = "1px solid rgba(255,255,255,0.2)";
|
||||
contestedModeSelect.style.borderRadius = "4px";
|
||||
contestedModeSelect.style.padding = "2px 4px";
|
||||
|
||||
const contestedModes: Array<"blueNoise" | "checkerboard" | "bayer4x4"> = [
|
||||
"blueNoise",
|
||||
"checkerboard",
|
||||
"bayer4x4",
|
||||
];
|
||||
for (const m of contestedModes) {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = m;
|
||||
opt.textContent = m;
|
||||
contestedModeSelect.appendChild(opt);
|
||||
}
|
||||
contestedModeSelect.value = this.contestedPatternMode;
|
||||
contestedModeSelect.addEventListener("change", () => {
|
||||
const v = contestedModeSelect.value as
|
||||
| "blueNoise"
|
||||
| "checkerboard"
|
||||
| "bayer4x4";
|
||||
this.contestedPatternMode = v;
|
||||
this.territoryRenderer?.setContestPatternMode(v);
|
||||
this.territoryRenderer?.markAllDirty();
|
||||
});
|
||||
|
||||
contestedModeRow.appendChild(contestedModeText);
|
||||
contestedModeRow.appendChild(contestedModeSelect);
|
||||
root.appendChild(contestedModeRow);
|
||||
|
||||
document.body.appendChild(root);
|
||||
this.smoothingDebugUi = root;
|
||||
}
|
||||
@@ -646,6 +722,7 @@ export class TerritoryLayer implements Layer {
|
||||
|
||||
this.territoryRenderer = renderer;
|
||||
this.territoryRenderer.setContestEnabled(this.contestEnabled);
|
||||
this.territoryRenderer.setContestPatternMode(this.contestedPatternMode);
|
||||
this.territoryRenderer.setAlternativeView(this.alternativeView);
|
||||
this.territoryRenderer.markAllDirty();
|
||||
this.territoryRenderer.refreshPalette();
|
||||
@@ -1372,6 +1449,7 @@ export class TerritoryLayer implements Layer {
|
||||
`smoothPrereq: prevCopy ${stats.prevStateCopySupported ? "yes" : "no"}`,
|
||||
`jfa: ${jfaStatus} dirty ${stats.jfaDirty ? "yes" : "no"}`,
|
||||
`contests: ${this.contestEnabled ? "on" : "off"} comps ${this.contestComponents.size}`,
|
||||
`contestPattern: ${this.contestedPatternMode}`,
|
||||
`contestTiles: ${this.contestTileCount}`,
|
||||
`contestTicks: ${this.contestDurationTicks}`,
|
||||
`hovered: ${stats.hoveredPlayerId}`,
|
||||
|
||||
@@ -28,6 +28,7 @@ export class TerritoryWebGLRenderer {
|
||||
public readonly canvas: HTMLCanvasElement;
|
||||
|
||||
private contestEnabled = false;
|
||||
private contestPatternMode: 0 | 1 | 2 = 0; // 0=blueNoise(strength), 1=checkerboard(50/50), 2=bayer4x4(strength)
|
||||
|
||||
private readonly gl: WebGL2RenderingContext | null;
|
||||
private readonly program: WebGLProgram | null;
|
||||
@@ -94,6 +95,7 @@ export class TerritoryWebGLRenderer {
|
||||
relations: WebGLUniformLocation | null;
|
||||
patterns: WebGLUniformLocation | null;
|
||||
contestEnabled: WebGLUniformLocation | null;
|
||||
contestPatternMode: WebGLUniformLocation | null;
|
||||
contestOwners: WebGLUniformLocation | null;
|
||||
contestIds: WebGLUniformLocation | null;
|
||||
contestTimes: WebGLUniformLocation | null;
|
||||
@@ -258,6 +260,7 @@ export class TerritoryWebGLRenderer {
|
||||
relations: null,
|
||||
patterns: null,
|
||||
contestEnabled: null,
|
||||
contestPatternMode: null,
|
||||
contestOwners: null,
|
||||
contestIds: null,
|
||||
contestTimes: null,
|
||||
@@ -351,6 +354,7 @@ export class TerritoryWebGLRenderer {
|
||||
relations: null,
|
||||
patterns: null,
|
||||
contestEnabled: null,
|
||||
contestPatternMode: null,
|
||||
contestOwners: null,
|
||||
contestIds: null,
|
||||
contestTimes: null,
|
||||
@@ -445,6 +449,10 @@ export class TerritoryWebGLRenderer {
|
||||
relations: gl.getUniformLocation(this.program, "u_relations"),
|
||||
patterns: gl.getUniformLocation(this.program, "u_patterns"),
|
||||
contestEnabled: gl.getUniformLocation(this.program, "u_contestEnabled"),
|
||||
contestPatternMode: gl.getUniformLocation(
|
||||
this.program,
|
||||
"u_contestPatternMode",
|
||||
),
|
||||
contestOwners: gl.getUniformLocation(this.program, "u_contestOwners"),
|
||||
contestIds: gl.getUniformLocation(this.program, "u_contestIds"),
|
||||
contestTimes: gl.getUniformLocation(this.program, "u_contestTimes"),
|
||||
@@ -1284,6 +1292,12 @@ export class TerritoryWebGLRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
setContestPatternMode(mode: "blueNoise" | "checkerboard" | "bayer4x4") {
|
||||
if (mode === "checkerboard") this.contestPatternMode = 1;
|
||||
else if (mode === "bayer4x4") this.contestPatternMode = 2;
|
||||
else this.contestPatternMode = 0;
|
||||
}
|
||||
|
||||
markTile(tile: TileRef) {
|
||||
if (this.needsFullUpload) {
|
||||
return;
|
||||
@@ -1757,6 +1771,9 @@ export class TerritoryWebGLRenderer {
|
||||
if (this.uniforms.contestEnabled) {
|
||||
gl.uniform1i(this.uniforms.contestEnabled, this.contestEnabled ? 1 : 0);
|
||||
}
|
||||
if (this.uniforms.contestPatternMode) {
|
||||
gl.uniform1i(this.uniforms.contestPatternMode, this.contestPatternMode);
|
||||
}
|
||||
if (this.uniforms.contestNow) {
|
||||
gl.uniform1i(this.uniforms.contestNow, this.contestNow);
|
||||
}
|
||||
@@ -2713,6 +2730,7 @@ export class TerritoryWebGLRenderer {
|
||||
uniform usampler2D u_relations;
|
||||
uniform usampler2D u_patterns;
|
||||
uniform bool u_contestEnabled;
|
||||
uniform int u_contestPatternMode; // 0=blueNoise(strength), 1=checkerboard(50/50), 2=bayer4x4(strength)
|
||||
uniform usampler2D u_contestOwners;
|
||||
uniform usampler2D u_contestIds;
|
||||
uniform usampler2D u_contestTimes;
|
||||
@@ -2934,6 +2952,47 @@ export class TerritoryWebGLRenderer {
|
||||
return fract(52.9829189 * x);
|
||||
}
|
||||
|
||||
float bayer4x4(ivec2 texCoord) {
|
||||
// Classic 4x4 Bayer matrix values 0..15 mapped to (0.5/16 .. 15.5/16)
|
||||
int x = texCoord.x & 3;
|
||||
int y = texCoord.y & 3;
|
||||
int idx = (y << 2) | x;
|
||||
int v = 0;
|
||||
// Row-major:
|
||||
// 0 8 2 10
|
||||
// 12 4 14 6
|
||||
// 3 11 1 9
|
||||
// 15 7 13 5
|
||||
if (idx == 0) v = 0;
|
||||
else if (idx == 1) v = 8;
|
||||
else if (idx == 2) v = 2;
|
||||
else if (idx == 3) v = 10;
|
||||
else if (idx == 4) v = 12;
|
||||
else if (idx == 5) v = 4;
|
||||
else if (idx == 6) v = 14;
|
||||
else if (idx == 7) v = 6;
|
||||
else if (idx == 8) v = 3;
|
||||
else if (idx == 9) v = 11;
|
||||
else if (idx == 10) v = 1;
|
||||
else if (idx == 11) v = 9;
|
||||
else if (idx == 12) v = 15;
|
||||
else if (idx == 13) v = 7;
|
||||
else if (idx == 14) v = 13;
|
||||
else v = 5;
|
||||
return (float(v) + 0.5) / 16.0;
|
||||
}
|
||||
|
||||
bool contestPickAttacker(ivec2 texCoord, float strength) {
|
||||
if (u_contestPatternMode == 1) {
|
||||
// Checkerboard is always 50/50 (ignores strength)
|
||||
return ((texCoord.x + texCoord.y) & 1) == 0;
|
||||
}
|
||||
if (u_contestPatternMode == 2) {
|
||||
return bayer4x4(texCoord) < strength;
|
||||
}
|
||||
return blueNoise(texCoord) < strength;
|
||||
}
|
||||
|
||||
uint relationCode(uint owner, uint other) {
|
||||
if (owner == 0u || other == 0u) {
|
||||
return 0u;
|
||||
@@ -3193,8 +3252,8 @@ export class TerritoryWebGLRenderer {
|
||||
defenderBase = defenderColor.rgb;
|
||||
}
|
||||
float strength = contestStrength(contestId);
|
||||
float noise = blueNoise(texCoord);
|
||||
vec3 contestColor = noise < strength ? latestOwnerBase : defenderBase;
|
||||
bool pickAttacker = contestPickAttacker(texCoord, strength);
|
||||
vec3 contestColor = pickAttacker ? latestOwnerBase : defenderBase;
|
||||
// Blend contested fill on top of terrain
|
||||
color = mix(baseTerrainColor, contestColor, u_alpha);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user