160 lines
6.2 KiB
JavaScript
160 lines
6.2 KiB
JavaScript
// 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;
|
|
|
|
// World-space Z distance of one Z-layer/story, in world-float space.
|
|
// Must match WORLD_LAYER_HEIGHT in src/dusk/rpg/overworld/worldpos.h.
|
|
const WORLD_LAYER_HEIGHT = Math.SQRT1_2;
|
|
|
|
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;
|
|
const TILE_SHAPE_RAMP_NORTHEAST_INNER = 10;
|
|
const TILE_SHAPE_RAMP_NORTHWEST_INNER = 11;
|
|
const TILE_SHAPE_RAMP_SOUTHEAST_INNER = 12;
|
|
const TILE_SHAPE_RAMP_SOUTHWEST_INNER = 13;
|
|
|
|
// 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 outer-corner ramps: only the named corner raised, rest at 0.
|
|
// Diagonal inner-corner ramps: only the corner opposite the named one is
|
|
// lowered to 0, the rest (including the named corner) stay raised at 1.
|
|
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],
|
|
[TILE_SHAPE_RAMP_NORTHEAST_INNER]: [0.0, 1.0, 1.0, 1.0],
|
|
[TILE_SHAPE_RAMP_NORTHWEST_INNER]: [1.0, 0.0, 1.0, 1.0],
|
|
[TILE_SHAPE_RAMP_SOUTHEAST_INNER]: [1.0, 1.0, 1.0, 0.0],
|
|
[TILE_SHAPE_RAMP_SOUTHWEST_INNER]: [1.0, 1.0, 0.0, 1.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) * WORLD_LAYER_HEIGHT,
|
|
(z + se) * WORLD_LAYER_HEIGHT,
|
|
(z + ne) * WORLD_LAYER_HEIGHT,
|
|
(z + nw) * WORLD_LAYER_HEIGHT
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return new Float32Array(out);
|
|
}
|
|
|
|
// Four edges (SW-SE, SE-NE, NE-NW, NW-SW) of a tile's quad as line
|
|
// segments, lifted slightly above the terrain surface to avoid
|
|
// z-fighting - same corner-height convention as pushTileQuad().
|
|
function pushTileGridLines(out, fx, fy, swZ, seZ, neZ, nwZ) {
|
|
const eps = 0.01;
|
|
const sw = [fx, fy, swZ + eps];
|
|
const se = [fx + 1, fy, seZ + eps];
|
|
const ne = [fx + 1, fy + 1, neZ + eps];
|
|
const nw = [fx, fy + 1, nwZ + eps];
|
|
const edges = [sw, se, se, ne, ne, nw, nw, sw];
|
|
for(const [x, y, z] of edges) out.push(0, 0, x, y, z);
|
|
}
|
|
|
|
// Generate grid-outline line segments for every non-null tile, following
|
|
// the same per-corner heights as buildTerrainVerts() so lines hug ramps
|
|
// instead of cutting through them. Returns a Float32Array of interleaved
|
|
// [u, v, x, y, z] vertices meant to be drawn with gl.LINES (pairs).
|
|
function buildGridLines(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 [sw, se, ne, nw] = corners;
|
|
pushTileGridLines(
|
|
out, x, y,
|
|
(z + sw) * WORLD_LAYER_HEIGHT,
|
|
(z + se) * WORLD_LAYER_HEIGHT,
|
|
(z + ne) * WORLD_LAYER_HEIGHT,
|
|
(z + nw) * WORLD_LAYER_HEIGHT
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return new Float32Array(out);
|
|
}
|
|
|
|
return Object.freeze({
|
|
CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_COUNT, WORLD_LAYER_HEIGHT,
|
|
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,
|
|
TILE_SHAPE_RAMP_NORTHEAST_INNER, TILE_SHAPE_RAMP_NORTHWEST_INNER,
|
|
TILE_SHAPE_RAMP_SOUTHEAST_INNER, TILE_SHAPE_RAMP_SOUTHWEST_INNER,
|
|
RAMP_CORNERS, tileIndex, buildTerrainVerts, buildGridLines,
|
|
});
|
|
})();
|