Expand voxel gameplay, lighting, full-height streaming and world imports
MVP checks / mvp (push) Canceled after 0s
MVP checks / mvp (push) Canceled after 0s
Add shared Rust/WASM physics, worker meshing and diagnostics, 64-chunk full-height streaming, atlas texture support, and baseline world import. Document the current implementation and include the supplied in-game lobby screenshot.
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
import { key, isFullCube, unitBox } from "./math.js";
|
||||
import { getBlockFaceTextures, getFaceUV } from "./block-textures.js";
|
||||
import { createAmbientOcclusionSampler } from "./ambient-occlusion.js";
|
||||
|
||||
/** Shared terrain and actor format; all values become Float32 at upload. */
|
||||
export const MESH_VERTEX_STRIDE = 20;
|
||||
|
||||
export const faces = [
|
||||
{
|
||||
n: [1, 0, 0],
|
||||
v: [
|
||||
[1, 0, 1],
|
||||
[1, 0, 0],
|
||||
[1, 1, 0],
|
||||
[1, 1, 1],
|
||||
],
|
||||
},
|
||||
{
|
||||
n: [-1, 0, 0],
|
||||
v: [
|
||||
[0, 0, 0],
|
||||
[0, 0, 1],
|
||||
[0, 1, 1],
|
||||
[0, 1, 0],
|
||||
],
|
||||
},
|
||||
{
|
||||
n: [0, 1, 0],
|
||||
v: [
|
||||
[0, 1, 1],
|
||||
[1, 1, 1],
|
||||
[1, 1, 0],
|
||||
[0, 1, 0],
|
||||
],
|
||||
},
|
||||
{
|
||||
n: [0, -1, 0],
|
||||
v: [
|
||||
[0, 0, 0],
|
||||
[1, 0, 0],
|
||||
[1, 0, 1],
|
||||
[0, 0, 1],
|
||||
],
|
||||
},
|
||||
{
|
||||
n: [0, 0, 1],
|
||||
v: [
|
||||
[0, 0, 1],
|
||||
[1, 0, 1],
|
||||
[1, 1, 1],
|
||||
[0, 1, 1],
|
||||
],
|
||||
},
|
||||
{
|
||||
n: [0, 0, -1],
|
||||
v: [
|
||||
[1, 0, 0],
|
||||
[0, 0, 0],
|
||||
[0, 1, 0],
|
||||
[1, 1, 0],
|
||||
],
|
||||
},
|
||||
];
|
||||
export function boxVertices(
|
||||
out,
|
||||
pos,
|
||||
box,
|
||||
color,
|
||||
material = 0,
|
||||
skip = () => false,
|
||||
yaw = 0,
|
||||
opacity = 1,
|
||||
textures = null,
|
||||
state = "",
|
||||
ambient = null,
|
||||
emission = 0,
|
||||
blockLight = null,
|
||||
dynamicLight = false,
|
||||
origin = [0,0,0],
|
||||
) {
|
||||
for (let f = 0; f < 6; f++) {
|
||||
if (skip(faces[f].n)) continue;
|
||||
const face = faces[f],
|
||||
c = Math.cos(yaw),
|
||||
s = Math.sin(yaw),
|
||||
n = [
|
||||
face.n[0] * c - face.n[2] * s,
|
||||
face.n[1],
|
||||
face.n[0] * s + face.n[2] * c,
|
||||
];
|
||||
const light = ambient ? ambient(pos, box, face.n, face.v) : [1, 1, 1, 1];
|
||||
const propagated = !dynamicLight && blockLight ? blockLight.sampleFace(pos, box, face.n, face.v) : null;
|
||||
// Flip the diagonal so occlusion interpolation has no dark triangular seam.
|
||||
const indices = light[0] + light[2] > light[1] + light[3]
|
||||
? [0, 1, 3, 1, 2, 3] : [0, 1, 2, 0, 2, 3];
|
||||
for (const i of indices) {
|
||||
const v = face.v[i].map(
|
||||
(v, k) => box.min[k] + v * (box.max[k] - box.min[k]),
|
||||
);
|
||||
const point = [pos[0] + v[0] * c - v[2] * s, pos[1] + v[1], pos[2] + v[0] * s + v[2] * c];
|
||||
const local = dynamicLight && blockLight ? blockLight.sample(point.map((p, axis) => p + n[axis] * 0.02)) : null;
|
||||
out.push(
|
||||
point[0]-origin[0], point[1]-origin[1], point[2]-origin[2],
|
||||
...(textures?.[f]?.tint || color),
|
||||
...n,
|
||||
material,
|
||||
opacity,
|
||||
...getFaceUV(f, v, state, textures?.[f]),
|
||||
textures?.[f]?.layer ?? -1,
|
||||
light[i],
|
||||
emission,
|
||||
...(propagated?.block[i] || local?.block || [0, 0, 0]),
|
||||
propagated?.sky[i] ?? local?.sky ?? 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
export function materialType(state = "", effect) {
|
||||
if (effect === "bounce") return 5;
|
||||
if (/^minecraft:(water|bubble_column)(\[|$)/.test(state)) return 6;
|
||||
if (state.includes("grass_block")) return 1;
|
||||
if (state.includes("planks")) return 2;
|
||||
if (state.includes("brick")) return 3;
|
||||
if (/log|stem|wood/.test(state)) return 4;
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* Pure section meshing for both worker and synchronous fallback. World maps and
|
||||
* the light field are immutable for a build. A caller may reuse faceTextureCache
|
||||
* between sections; clear it whenever texture layers change.
|
||||
*/
|
||||
export function buildSectionMesh({
|
||||
id, blocks, materials, textureLayers = new Map(), blockLightField = null,
|
||||
faceTextureCache = new Map(), localCoordinates = false,
|
||||
}) {
|
||||
const faceTextures = (mat) => {
|
||||
if (!textureLayers.size) return null;
|
||||
if (!faceTextureCache.has(mat.state))
|
||||
faceTextureCache.set(mat.state, getBlockFaceTextures(mat.state, textureLayers));
|
||||
return faceTextureCache.get(mat.state);
|
||||
};
|
||||
const base = id.split(",").map((value) => Number(value) * 16),
|
||||
vertices = [], transparent = [],
|
||||
ambient = createAmbientOcclusionSampler(blocks, materials,
|
||||
(mat) => !mat || (mat.opacity ?? 1) >= 1 && !faceTextures(mat)?.some(face => face?.cutout));
|
||||
for (let x = base[0]; x < base[0] + 16; x++)
|
||||
for (let y = base[1]; y < base[1] + 16; y++)
|
||||
for (let z = base[2]; z < base[2] + 16; z++) {
|
||||
const pos = [x, y, z],
|
||||
block = blocks.getAt ? blocks.getAt(x,y,z) : blocks.get(key(pos));
|
||||
if (!block) continue;
|
||||
const mat = materials.get(block) || {
|
||||
color: [150, 152, 142],
|
||||
render: [unitBox],
|
||||
},
|
||||
color = mat.color.map((v) => v / 255),
|
||||
textures = faceTextures(mat),
|
||||
cutout = textures?.some((face) => face?.cutout),
|
||||
opacity = cutout ? 1 : (mat.opacity ?? 1),
|
||||
full = isFullCube(mat) && !cutout;
|
||||
for (const box of mat.render || [unitBox]) {
|
||||
boxVertices(
|
||||
opacity < 1 ? transparent : vertices,
|
||||
pos,
|
||||
box,
|
||||
color,
|
||||
materialType(mat.state, mat.effect),
|
||||
(n) => {
|
||||
if (!full) return false;
|
||||
const neighbor = materials.get(
|
||||
(blocks.getAt ? blocks.getAt(pos[0]+n[0],pos[1]+n[1],pos[2]+n[2]) : blocks.get(key(pos.map((v, i) => v + n[i])))),
|
||||
);
|
||||
return (
|
||||
isFullCube(neighbor) &&
|
||||
!faceTextures(neighbor)?.some((face) => face?.cutout)
|
||||
);
|
||||
},
|
||||
0,
|
||||
opacity,
|
||||
textures,
|
||||
mat.state,
|
||||
ambient,
|
||||
Math.max(0, Math.min(1, (mat.light || 0) / 15)),
|
||||
blockLightField, false, localCoordinates ? base : [0,0,0],
|
||||
);
|
||||
}
|
||||
}
|
||||
return { vertices: new Float32Array(vertices), transparent: new Float32Array(transparent), origin: localCoordinates ? base : [0,0,0] };
|
||||
}
|
||||
Reference in New Issue
Block a user