editor first pass
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
"use strict";
|
||||
|
||||
// ChunkTerrain - JS port of the terrain-quad generation performed by
|
||||
// tools/asset/chunk/__main__.py (build_terrain_verts / _tile_quad /
|
||||
// _RAMP_CORNERS) so the editor's 3D preview can show the same geometry the
|
||||
// Python compiler will bake into the chunk's terrain mesh, without waiting
|
||||
// for a compile pass. Vertex layout matches DMF: interleaved [u, v, x, y, z].
|
||||
|
||||
const ChunkTerrain = (() => {
|
||||
const CHUNK_WIDTH = 16;
|
||||
const CHUNK_HEIGHT = 16;
|
||||
const CHUNK_DEPTH = 4;
|
||||
const TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH;
|
||||
|
||||
const TILE_SHAPE_NULL = 0;
|
||||
const TILE_SHAPE_GROUND = 1;
|
||||
const TILE_SHAPE_RAMP_NORTH = 2;
|
||||
const TILE_SHAPE_RAMP_EAST = 3;
|
||||
const TILE_SHAPE_RAMP_SOUTH = 4;
|
||||
const TILE_SHAPE_RAMP_WEST = 5;
|
||||
const TILE_SHAPE_RAMP_NORTHEAST = 6;
|
||||
const TILE_SHAPE_RAMP_NORTHWEST = 7;
|
||||
const TILE_SHAPE_RAMP_SOUTHEAST = 8;
|
||||
const TILE_SHAPE_RAMP_SOUTHWEST = 9;
|
||||
|
||||
// Per-shape corner heights as [sw, se, ne, nw] offsets from tile base Z.
|
||||
// NORTH=+Y, EAST=+X. Cardinal ramps: low face at 0, high face at 1.
|
||||
// Diagonal ramps: opposite corner at 0/1, adjacent corners at 0.5.
|
||||
const RAMP_CORNERS = {
|
||||
[TILE_SHAPE_GROUND]: [0.0, 0.0, 0.0, 0.0],
|
||||
[TILE_SHAPE_RAMP_NORTH]: [0.0, 0.0, 1.0, 1.0],
|
||||
[TILE_SHAPE_RAMP_SOUTH]: [1.0, 1.0, 0.0, 0.0],
|
||||
[TILE_SHAPE_RAMP_EAST]: [0.0, 1.0, 1.0, 0.0],
|
||||
[TILE_SHAPE_RAMP_WEST]: [1.0, 0.0, 0.0, 1.0],
|
||||
[TILE_SHAPE_RAMP_NORTHEAST]: [0.0, 0.0, 1.0, 0.0],
|
||||
[TILE_SHAPE_RAMP_NORTHWEST]: [0.0, 0.0, 0.0, 1.0],
|
||||
[TILE_SHAPE_RAMP_SOUTHEAST]: [0.0, 1.0, 0.0, 0.0],
|
||||
[TILE_SHAPE_RAMP_SOUTHWEST]: [1.0, 0.0, 0.0, 0.0],
|
||||
};
|
||||
|
||||
function tileIndex(x, y, z) {
|
||||
return x + y * CHUNK_WIDTH + z * CHUNK_WIDTH * CHUNK_HEIGHT;
|
||||
}
|
||||
|
||||
// Six vertices (2 CCW triangles) for a quad with per-corner Z heights.
|
||||
// Corner order: SW=(fx,fy), SE=(fx+1,fy), NE=(fx+1,fy+1), NW=(fx,fy+1).
|
||||
function pushTileQuad(out, u0, u1, v0, v1, fx, fy, swZ, seZ, neZ, nwZ) {
|
||||
out.push(
|
||||
u0, v0, fx, fy, swZ,
|
||||
u1, v0, fx + 1, fy, seZ,
|
||||
u1, v1, fx + 1, fy + 1, neZ,
|
||||
u0, v0, fx, fy, swZ,
|
||||
u1, v1, fx + 1, fy + 1, neZ,
|
||||
u0, v1, fx, fy + 1, nwZ,
|
||||
);
|
||||
}
|
||||
|
||||
// Generate terrain quads for every non-null tile in a flat tiles array
|
||||
// (length TILE_COUNT, indexed via tileIndex). Returns a Float32Array of
|
||||
// interleaved [u, v, x, y, z] vertices, matching DMF.VERTEX_FLOATS.
|
||||
function buildTerrainVerts(tiles) {
|
||||
const out = [];
|
||||
for(let z = 0; z < CHUNK_DEPTH; z++) {
|
||||
for(let y = 0; y < CHUNK_HEIGHT; y++) {
|
||||
for(let x = 0; x < CHUNK_WIDTH; x++) {
|
||||
const type = tiles[tileIndex(x, y, z)] || TILE_SHAPE_NULL;
|
||||
const corners = RAMP_CORNERS[type];
|
||||
if(!corners) continue;
|
||||
|
||||
const u0 = x / CHUNK_WIDTH;
|
||||
const u1 = (x + 1) / CHUNK_WIDTH;
|
||||
const v0 = y / CHUNK_HEIGHT;
|
||||
const v1 = (y + 1) / CHUNK_HEIGHT;
|
||||
const [sw, se, ne, nw] = corners;
|
||||
|
||||
pushTileQuad(out, u0, u1, v0, v1, x, y, z + sw, z + se, z + ne, z + nw);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Float32Array(out);
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_COUNT,
|
||||
TILE_SHAPE_NULL, TILE_SHAPE_GROUND,
|
||||
TILE_SHAPE_RAMP_NORTH, TILE_SHAPE_RAMP_EAST,
|
||||
TILE_SHAPE_RAMP_SOUTH, TILE_SHAPE_RAMP_WEST,
|
||||
TILE_SHAPE_RAMP_NORTHEAST, TILE_SHAPE_RAMP_NORTHWEST,
|
||||
TILE_SHAPE_RAMP_SOUTHEAST, TILE_SHAPE_RAMP_SOUTHWEST,
|
||||
RAMP_CORNERS, tileIndex, buildTerrainVerts,
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
"use strict";
|
||||
|
||||
// DMF - Dusk Mesh Format
|
||||
//
|
||||
// Header (12 bytes):
|
||||
// [0-3] "DMF\0" magic
|
||||
// [4-7] uint32 version (little-endian)
|
||||
// [8-11] uint32 vertCount (little-endian)
|
||||
//
|
||||
// Followed by vertCount vertices, each 20 bytes:
|
||||
// [0-7] float32[2] uv (little-endian)
|
||||
// [8-19] float32[3] pos (little-endian)
|
||||
|
||||
const DMF = (() => {
|
||||
const MAGIC = [0x44, 0x4d, 0x46, 0x00]; // "DMF\0"
|
||||
const VERSION = 1;
|
||||
const HEADER_SIZE = 12;
|
||||
const VERTEX_FLOATS = 5; // uv[2] + pos[3]
|
||||
const VERTEX_SIZE = VERTEX_FLOATS * 4;
|
||||
|
||||
// Decode a DMF ArrayBuffer into { version, vertexCount, floats }, where
|
||||
// floats is a Float32Array of tightly interleaved [u, v, x, y, z] per
|
||||
// vertex - ready to hand straight to gl.bufferData().
|
||||
function decode(buffer) {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
if(bytes.length < HEADER_SIZE) throw new Error("File too small to be a valid DMF");
|
||||
if(
|
||||
bytes[0] !== MAGIC[0] || bytes[1] !== MAGIC[1] ||
|
||||
bytes[2] !== MAGIC[2] || bytes[3] !== MAGIC[3]
|
||||
) {
|
||||
throw new Error("Invalid DMF magic bytes - not a DMF file");
|
||||
}
|
||||
|
||||
const version = view.getUint32(4, true);
|
||||
if(version !== VERSION) {
|
||||
throw new Error(`Unsupported DMF version: ${version}`);
|
||||
}
|
||||
|
||||
const vertexCount = view.getUint32(8, true);
|
||||
const expected = HEADER_SIZE + vertexCount * VERTEX_SIZE;
|
||||
if(bytes.length < expected) {
|
||||
throw new Error(`DMF vertex data truncated (expected ${expected} bytes, got ${bytes.length})`);
|
||||
}
|
||||
|
||||
const floats = new Float32Array(vertexCount * VERTEX_FLOATS);
|
||||
for(let i = 0; i < floats.length; i++) {
|
||||
floats[i] = view.getFloat32(HEADER_SIZE + i * 4, true);
|
||||
}
|
||||
|
||||
return { version, vertexCount, floats };
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
decode,
|
||||
VERSION, HEADER_SIZE, VERTEX_FLOATS, VERTEX_SIZE,
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user