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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
{
"mesh": "meshes/chunks/chunk_2_0_0_0.dmf",
"color": [
255,
255,
255,
255
],
"texture": "tiles.png"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -52,7 +52,10 @@
<span id="zLevelLabel">0</span> <span id="zLevelLabel">0</span>
</div> </div>
<div class="d-flex gap-3"> <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> <canvas id="gridCanvas" class="border tile-grid" width="512" height="512"></canvas>
</div> </div>
+75 -2
View File
@@ -74,6 +74,7 @@
let zoomWorldH = 20; let zoomWorldH = 20;
let dirty = false; let dirty = false;
let hoverTile = null; let hoverTile = null;
let fillMode = false;
let mapRenderer = null; let mapRenderer = null;
let terrainMesh = null; let terrainMesh = null;
@@ -462,13 +463,63 @@
renderPreview(); 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() { function rebuildTerrainMesh() {
const floats = ChunkTerrain.buildTerrainVerts(tiles); const floats = ChunkTerrain.buildTerrainVerts(tiles);
terrainMesh = floats.length ? mapRenderer.createMesh(floats) : null; 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() { async function renderPreview() {
if(!mapRenderer) return; if(!mapRenderer) return;
resizePreviewCanvas();
rebuildTerrainMesh(); rebuildTerrainMesh();
const models = []; const models = [];
@@ -604,6 +655,11 @@
let draggingMeshIndex = -1; let draggingMeshIndex = -1;
gridCanvas.addEventListener("mousedown", (e) => { gridCanvas.addEventListener("mousedown", (e) => {
if(fillMode) {
fillAt(e);
return;
}
const hit = hitTestMesh(e, gridCanvas); const hit = hitTestMesh(e, gridCanvas);
pushUndo(); pushUndo();
if(hit !== -1) { if(hit !== -1) {
@@ -657,8 +713,18 @@
document.getElementById("navUp").addEventListener("click", () => goToChunk(coord.x, coord.y, coord.z + 1)); 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("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)); 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", () => { document.getElementById("btnAddMesh").addEventListener("click", () => {
@@ -683,10 +749,17 @@
renderPreview(); renderPreview();
}, { passive: false }); }, { passive: false });
window.addEventListener("resize", () => renderPreview());
window.addEventListener("keydown", (e) => { 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(); e.preventDefault();
undo(); undo();
} else if(key === "s") {
e.preventDefault();
runSave();
} }
}); });
} }
+12 -6
View File
@@ -10,6 +10,7 @@
#include "util/memory.h" #include "util/memory.h"
#include "display/spritebatch/spritebatch.h" #include "display/spritebatch/spritebatch.h"
#include "display/text/text.h" #include "display/text/text.h"
#include "display/screen/screen.h"
#include "assert/assert.h" #include "assert/assert.h"
uisettings_t UI_SETTINGS; uisettings_t UI_SETTINGS;
@@ -75,15 +76,20 @@ errorret_t uiSettingsInit(void) {
errorret_t uiSettingsDraw(void) { errorret_t uiSettingsDraw(void) {
if(!uiMenuIsActive(&UI_SETTINGS.menu)) errorOk(); if(!uiMenuIsActive(&UI_SETTINGS.menu)) errorOk();
uint8_t rows = UI_SETTINGS_ITEM_COUNT; const float_t width = 300.0f;
const float_t height = 300.0f;
const float_t x = (float_t)SCREEN.scanX +
((float_t)SCREEN.scanWidth - width) * 0.5f;
const float_t y = (float_t)SCREEN.scanY +
((float_t)SCREEN.scanHeight - height) * 0.5f;
errorChain(uiFrameDraw(0.0f, 0.0f, 300.0f, 300)); errorChain(uiFrameDraw(x, y, width, height));
errorChain(uiMenuDraw( errorChain(uiMenuDraw(
&UI_SETTINGS.menu, &UI_SETTINGS.menu,
UI_FRAME_BORDER_WIDTH, x + UI_FRAME_BORDER_WIDTH,
UI_FRAME_BORDER_HEIGHT, y + UI_FRAME_BORDER_HEIGHT,
300.0f - (UI_FRAME_BORDER_WIDTH * 2), width - (UI_FRAME_BORDER_WIDTH * 2),
300.0f - (UI_FRAME_BORDER_HEIGHT * 2) height - (UI_FRAME_BORDER_HEIGHT * 2)
)); ));
errorChain(spriteBatchFlush()); errorChain(spriteBatchFlush());