// 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; 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, 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, TILE_SHAPE_RAMP_NORTHEAST_INNER, TILE_SHAPE_RAMP_NORTHWEST_INNER, TILE_SHAPE_RAMP_SOUTHEAST_INNER, TILE_SHAPE_RAMP_SOUTHWEST_INNER, RAMP_CORNERS, tileIndex, buildTerrainVerts, }); })();