more editor stuff

This commit is contained in:
2026-07-01 15:58:59 -05:00
parent 38b24e1c3d
commit a2d0a12c1a
10 changed files with 3152 additions and 787 deletions
+4 -1
View File
@@ -52,7 +52,10 @@
<span id="zLevelLabel">0</span>
</div>
<div class="d-flex gap-3">
<div id="tilePalette" class="tile-palette"></div>
<div class="d-flex flex-column gap-2">
<button type="button" id="fillToggle" class="btn btn-sm btn-outline-secondary">Fill: Off</button>
<div id="tilePalette" class="tile-palette"></div>
</div>
<canvas id="gridCanvas" class="border tile-grid" width="512" height="512"></canvas>
</div>
+75 -2
View File
@@ -74,6 +74,7 @@
let zoomWorldH = 20;
let dirty = false;
let hoverTile = null;
let fillMode = false;
let mapRenderer = null;
let terrainMesh = null;
@@ -462,13 +463,63 @@
renderPreview();
}
// 4-connected flood fill of same-type tiles at the current Z-level,
// starting from (startX, startY), replacing them with selectedShape.
function floodFill(startX, startY) {
const targetType = tiles[ChunkTerrain.tileIndex(startX, startY, currentZLevel)];
if(targetType === selectedShape) return;
const stack = [[startX, startY]];
const visited = new Set();
while(stack.length) {
const [x, y] = stack.pop();
const key = `${x},${y}`;
if(visited.has(key)) continue;
if(x < 0 || x >= ChunkTerrain.CHUNK_WIDTH || y < 0 || y >= ChunkTerrain.CHUNK_HEIGHT) continue;
visited.add(key);
const idx = ChunkTerrain.tileIndex(x, y, currentZLevel);
if(tiles[idx] !== targetType) continue;
tiles[idx] = selectedShape;
stack.push([x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]);
}
}
function fillAt(e) {
const canvas = document.getElementById("gridCanvas");
const t = canvasToTile(e, canvas);
if(!t) return;
pushUndo();
floodFill(t.x, t.y);
markDirty();
renderGrid();
renderPreview();
}
function rebuildTerrainMesh() {
const floats = ChunkTerrain.buildTerrainVerts(tiles);
terrainMesh = floats.length ? mapRenderer.createMesh(floats) : null;
}
// Keeps the preview canvas's backing-store resolution matched to its
// actual displayed CSS size (times devicePixelRatio) - the canvas
// otherwise stays at its fixed width/height attributes (512x512) while
// CSS stretches it to fill the column, which upscales and blurs the
// WebGL output on wider or high-DPI screens.
function resizePreviewCanvas() {
const canvas = document.getElementById("previewCanvas");
const dpr = window.devicePixelRatio || 1;
const displayWidth = Math.max(1, Math.round(canvas.clientWidth * dpr));
const displayHeight = Math.max(1, Math.round(canvas.clientHeight * dpr));
if(canvas.width !== displayWidth || canvas.height !== displayHeight) {
canvas.width = displayWidth;
canvas.height = displayHeight;
}
}
async function renderPreview() {
if(!mapRenderer) return;
resizePreviewCanvas();
rebuildTerrainMesh();
const models = [];
@@ -604,6 +655,11 @@
let draggingMeshIndex = -1;
gridCanvas.addEventListener("mousedown", (e) => {
if(fillMode) {
fillAt(e);
return;
}
const hit = hitTestMesh(e, gridCanvas);
pushUndo();
if(hit !== -1) {
@@ -657,8 +713,18 @@
document.getElementById("navUp").addEventListener("click", () => goToChunk(coord.x, coord.y, coord.z + 1));
document.getElementById("navDown").addEventListener("click", () => goToChunk(coord.x, coord.y, coord.z - 1));
document.getElementById("btnSave").addEventListener("click", () => {
function runSave() {
saveChunk().catch((e) => updateStatus(`Save failed: ${e.message}`, true));
}
document.getElementById("btnSave").addEventListener("click", runSave);
document.getElementById("fillToggle").addEventListener("click", () => {
fillMode = !fillMode;
const btn = document.getElementById("fillToggle");
btn.textContent = fillMode ? "Fill: On" : "Fill: Off";
btn.classList.toggle("btn-secondary", fillMode);
btn.classList.toggle("btn-outline-secondary", !fillMode);
});
document.getElementById("btnAddMesh").addEventListener("click", () => {
@@ -683,10 +749,17 @@
renderPreview();
}, { passive: false });
window.addEventListener("resize", () => renderPreview());
window.addEventListener("keydown", (e) => {
if((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "z") {
if(!(e.ctrlKey || e.metaKey)) return;
const key = e.key.toLowerCase();
if(key === "z") {
e.preventDefault();
undo();
} else if(key === "s") {
e.preventDefault();
runSave();
}
});
}