Files
MineGeo/renderer/mesh-buffers.mjs

133 lines
4.5 KiB
JavaScript

// Numeric section keys and one bit per opaque voxel avoid millions of coordinate
// strings. Bounds checks prevent key aliasing outside the indexed sections.
export class OpaqueIndex {
constructor(bounds, minY, maxY) {
this.minX = Math.floor(bounds.minX / 16);
this.maxX = Math.floor(bounds.maxX / 16);
this.minZ = Math.floor(bounds.minZ / 16);
this.maxZ = Math.floor(bounds.maxZ / 16);
this.minY = Math.floor(minY / 16);
this.maxY = Math.floor(maxY / 16);
this.width = this.maxX - this.minX + 1;
this.depth = this.maxZ - this.minZ + 1;
this.sections = new Map();
}
key(x, y, z) {
const cx = Math.floor(x / 16),
cy = Math.floor(y / 16),
cz = Math.floor(z / 16);
if (
cx < this.minX ||
cx > this.maxX ||
cz < this.minZ ||
cz > this.maxZ ||
cy < this.minY ||
cy > this.maxY
)
return -1;
return (
cx -
this.minX +
(cz - this.minZ) * this.width +
(cy - this.minY) * this.width * this.depth
);
}
add(x, y, z) {
const key = this.key(x, y, z);
if (key < 0) throw new Error("Terrain block outside indexed bounds");
let section = this.sections.get(key);
if (!section) {
section = new Uint32Array(128);
this.sections.set(key, section);
}
const bit = (x & 15) | ((z & 15) << 4) | ((y & 15) << 8);
section[bit >>> 5] |= 1 << (bit & 31);
}
has(x, y, z) {
const key = this.key(x, y, z);
if (key < 0) return false;
const section = this.sections.get(key);
if (!section) return false;
const bit = (x & 15) | ((z & 15) << 4) | ((y & 15) << 8);
return (section[bit >>> 5] & (1 << (bit & 31))) !== 0;
}
clear() {
this.sections.clear();
}
}
// Each buffer is bounded, keeps 16-bit triangle indices, and writes directly
// into typed arrays. No per-vertex objects or growable JS number arrays.
export class QuadBuffer {
constructor(maxQuads = 4096) {
this.maxQuads = maxQuads;
this.capacity = Math.min(32, maxQuads);
this.count = 0;
this.positions = new Float32Array(this.capacity * 12);
this.normals = new Float32Array(this.capacity * 12);
this.uvs = new Float32Array(this.capacity * 8);
this.indices = new Uint16Array(this.capacity * 6);
this.min = [Infinity, Infinity, Infinity];
this.max = [-Infinity, -Infinity, -Infinity];
}
get full() {
return this.count === this.maxQuads;
}
add(face, x, y, z) {
if (this.full) throw new Error("Geometry batch is full");
if (this.count === this.capacity) {
this.capacity = Math.min(this.maxQuads, this.capacity * 2);
for (const [key, components, Type] of [
["positions", 12, Float32Array],
["normals", 12, Float32Array],
["uvs", 8, Float32Array],
["indices", 6, Uint16Array],
]) {
const next = new Type(this.capacity * components);
next.set(this[key]);
this[key] = next;
}
}
const offset = this.count * 12;
for (let i = 0; i < 12; i += 3) {
this.positions[offset + i] = face.positions[i] + x;
this.positions[offset + i + 1] = face.positions[i + 1] + y;
this.positions[offset + i + 2] = face.positions[i + 2] + z;
// Bounds must enclose the actual Float32 positions, including rounding
// at large Minecraft coordinates, or frustum culling can cut off faces.
const px = this.positions[offset + i],
py = this.positions[offset + i + 1],
pz = this.positions[offset + i + 2];
this.normals[offset + i] = face.normal[0];
this.normals[offset + i + 1] = face.normal[1];
this.normals[offset + i + 2] = face.normal[2];
if (px < this.min[0]) this.min[0] = px;
if (py < this.min[1]) this.min[1] = py;
if (pz < this.min[2]) this.min[2] = pz;
if (px > this.max[0]) this.max[0] = px;
if (py > this.max[1]) this.max[1] = py;
if (pz > this.max[2]) this.max[2] = pz;
}
this.uvs.set(face.uvs, this.count * 8);
const base = this.count * 4,
index = this.count * 6;
this.indices[index] = base;
this.indices[index + 1] = base + 1;
this.indices[index + 2] = base + 2;
this.indices[index + 3] = base + 2;
this.indices[index + 4] = base + 1;
this.indices[index + 5] = base + 3;
this.count++;
}
finish() {
return {
positions: this.positions.slice(0, this.count * 12),
normals: this.normals.slice(0, this.count * 12),
uvs: this.uvs.slice(0, this.count * 8),
indices: this.indices.slice(0, this.count * 6),
min: this.min,
max: this.max,
};
}
}