mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-30 04:59:40 +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 tickIntervalEmaMs = 0;
|
||||||
private readonly TICK_INTERVAL_EMA_ALPHA = 0.2;
|
private readonly TICK_INTERVAL_EMA_ALPHA = 0.2;
|
||||||
private smoothingDebugUi: HTMLDivElement | null = null;
|
private smoothingDebugUi: HTMLDivElement | null = null;
|
||||||
|
private contestedPatternMode: "blueNoise" | "checkerboard" | "bayer4x4" =
|
||||||
|
"blueNoise";
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private game: GameView,
|
private game: GameView,
|
||||||
@@ -570,6 +572,80 @@ export class TerritoryLayer implements Layer {
|
|||||||
modeRow.appendChild(modeSelect);
|
modeRow.appendChild(modeSelect);
|
||||||
root.appendChild(modeRow);
|
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);
|
document.body.appendChild(root);
|
||||||
this.smoothingDebugUi = root;
|
this.smoothingDebugUi = root;
|
||||||
}
|
}
|
||||||
@@ -646,6 +722,7 @@ export class TerritoryLayer implements Layer {
|
|||||||
|
|
||||||
this.territoryRenderer = renderer;
|
this.territoryRenderer = renderer;
|
||||||
this.territoryRenderer.setContestEnabled(this.contestEnabled);
|
this.territoryRenderer.setContestEnabled(this.contestEnabled);
|
||||||
|
this.territoryRenderer.setContestPatternMode(this.contestedPatternMode);
|
||||||
this.territoryRenderer.setAlternativeView(this.alternativeView);
|
this.territoryRenderer.setAlternativeView(this.alternativeView);
|
||||||
this.territoryRenderer.markAllDirty();
|
this.territoryRenderer.markAllDirty();
|
||||||
this.territoryRenderer.refreshPalette();
|
this.territoryRenderer.refreshPalette();
|
||||||
@@ -1372,6 +1449,7 @@ export class TerritoryLayer implements Layer {
|
|||||||
`smoothPrereq: prevCopy ${stats.prevStateCopySupported ? "yes" : "no"}`,
|
`smoothPrereq: prevCopy ${stats.prevStateCopySupported ? "yes" : "no"}`,
|
||||||
`jfa: ${jfaStatus} dirty ${stats.jfaDirty ? "yes" : "no"}`,
|
`jfa: ${jfaStatus} dirty ${stats.jfaDirty ? "yes" : "no"}`,
|
||||||
`contests: ${this.contestEnabled ? "on" : "off"} comps ${this.contestComponents.size}`,
|
`contests: ${this.contestEnabled ? "on" : "off"} comps ${this.contestComponents.size}`,
|
||||||
|
`contestPattern: ${this.contestedPatternMode}`,
|
||||||
`contestTiles: ${this.contestTileCount}`,
|
`contestTiles: ${this.contestTileCount}`,
|
||||||
`contestTicks: ${this.contestDurationTicks}`,
|
`contestTicks: ${this.contestDurationTicks}`,
|
||||||
`hovered: ${stats.hoveredPlayerId}`,
|
`hovered: ${stats.hoveredPlayerId}`,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export class TerritoryWebGLRenderer {
|
|||||||
public readonly canvas: HTMLCanvasElement;
|
public readonly canvas: HTMLCanvasElement;
|
||||||
|
|
||||||
private contestEnabled = false;
|
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 gl: WebGL2RenderingContext | null;
|
||||||
private readonly program: WebGLProgram | null;
|
private readonly program: WebGLProgram | null;
|
||||||
@@ -94,6 +95,7 @@ export class TerritoryWebGLRenderer {
|
|||||||
relations: WebGLUniformLocation | null;
|
relations: WebGLUniformLocation | null;
|
||||||
patterns: WebGLUniformLocation | null;
|
patterns: WebGLUniformLocation | null;
|
||||||
contestEnabled: WebGLUniformLocation | null;
|
contestEnabled: WebGLUniformLocation | null;
|
||||||
|
contestPatternMode: WebGLUniformLocation | null;
|
||||||
contestOwners: WebGLUniformLocation | null;
|
contestOwners: WebGLUniformLocation | null;
|
||||||
contestIds: WebGLUniformLocation | null;
|
contestIds: WebGLUniformLocation | null;
|
||||||
contestTimes: WebGLUniformLocation | null;
|
contestTimes: WebGLUniformLocation | null;
|
||||||
@@ -258,6 +260,7 @@ export class TerritoryWebGLRenderer {
|
|||||||
relations: null,
|
relations: null,
|
||||||
patterns: null,
|
patterns: null,
|
||||||
contestEnabled: null,
|
contestEnabled: null,
|
||||||
|
contestPatternMode: null,
|
||||||
contestOwners: null,
|
contestOwners: null,
|
||||||
contestIds: null,
|
contestIds: null,
|
||||||
contestTimes: null,
|
contestTimes: null,
|
||||||
@@ -351,6 +354,7 @@ export class TerritoryWebGLRenderer {
|
|||||||
relations: null,
|
relations: null,
|
||||||
patterns: null,
|
patterns: null,
|
||||||
contestEnabled: null,
|
contestEnabled: null,
|
||||||
|
contestPatternMode: null,
|
||||||
contestOwners: null,
|
contestOwners: null,
|
||||||
contestIds: null,
|
contestIds: null,
|
||||||
contestTimes: null,
|
contestTimes: null,
|
||||||
@@ -445,6 +449,10 @@ export class TerritoryWebGLRenderer {
|
|||||||
relations: gl.getUniformLocation(this.program, "u_relations"),
|
relations: gl.getUniformLocation(this.program, "u_relations"),
|
||||||
patterns: gl.getUniformLocation(this.program, "u_patterns"),
|
patterns: gl.getUniformLocation(this.program, "u_patterns"),
|
||||||
contestEnabled: gl.getUniformLocation(this.program, "u_contestEnabled"),
|
contestEnabled: gl.getUniformLocation(this.program, "u_contestEnabled"),
|
||||||
|
contestPatternMode: gl.getUniformLocation(
|
||||||
|
this.program,
|
||||||
|
"u_contestPatternMode",
|
||||||
|
),
|
||||||
contestOwners: gl.getUniformLocation(this.program, "u_contestOwners"),
|
contestOwners: gl.getUniformLocation(this.program, "u_contestOwners"),
|
||||||
contestIds: gl.getUniformLocation(this.program, "u_contestIds"),
|
contestIds: gl.getUniformLocation(this.program, "u_contestIds"),
|
||||||
contestTimes: gl.getUniformLocation(this.program, "u_contestTimes"),
|
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) {
|
markTile(tile: TileRef) {
|
||||||
if (this.needsFullUpload) {
|
if (this.needsFullUpload) {
|
||||||
return;
|
return;
|
||||||
@@ -1757,6 +1771,9 @@ export class TerritoryWebGLRenderer {
|
|||||||
if (this.uniforms.contestEnabled) {
|
if (this.uniforms.contestEnabled) {
|
||||||
gl.uniform1i(this.uniforms.contestEnabled, this.contestEnabled ? 1 : 0);
|
gl.uniform1i(this.uniforms.contestEnabled, this.contestEnabled ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
if (this.uniforms.contestPatternMode) {
|
||||||
|
gl.uniform1i(this.uniforms.contestPatternMode, this.contestPatternMode);
|
||||||
|
}
|
||||||
if (this.uniforms.contestNow) {
|
if (this.uniforms.contestNow) {
|
||||||
gl.uniform1i(this.uniforms.contestNow, this.contestNow);
|
gl.uniform1i(this.uniforms.contestNow, this.contestNow);
|
||||||
}
|
}
|
||||||
@@ -2713,6 +2730,7 @@ export class TerritoryWebGLRenderer {
|
|||||||
uniform usampler2D u_relations;
|
uniform usampler2D u_relations;
|
||||||
uniform usampler2D u_patterns;
|
uniform usampler2D u_patterns;
|
||||||
uniform bool u_contestEnabled;
|
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_contestOwners;
|
||||||
uniform usampler2D u_contestIds;
|
uniform usampler2D u_contestIds;
|
||||||
uniform usampler2D u_contestTimes;
|
uniform usampler2D u_contestTimes;
|
||||||
@@ -2934,6 +2952,47 @@ export class TerritoryWebGLRenderer {
|
|||||||
return fract(52.9829189 * x);
|
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) {
|
uint relationCode(uint owner, uint other) {
|
||||||
if (owner == 0u || other == 0u) {
|
if (owner == 0u || other == 0u) {
|
||||||
return 0u;
|
return 0u;
|
||||||
@@ -3193,8 +3252,8 @@ export class TerritoryWebGLRenderer {
|
|||||||
defenderBase = defenderColor.rgb;
|
defenderBase = defenderColor.rgb;
|
||||||
}
|
}
|
||||||
float strength = contestStrength(contestId);
|
float strength = contestStrength(contestId);
|
||||||
float noise = blueNoise(texCoord);
|
bool pickAttacker = contestPickAttacker(texCoord, strength);
|
||||||
vec3 contestColor = noise < strength ? latestOwnerBase : defenderBase;
|
vec3 contestColor = pickAttacker ? latestOwnerBase : defenderBase;
|
||||||
// Blend contested fill on top of terrain
|
// Blend contested fill on top of terrain
|
||||||
color = mix(baseTerrainColor, contestColor, u_alpha);
|
color = mix(baseTerrainColor, contestColor, u_alpha);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user