import { STRIDE, type Mesh } from "../scene/Mesh" import type { Chunk, Bounds3 } from "./Chunk" import type { DrawGroup, Material } from "./Material" export type MeshBatch = { mesh: (material: Material) => Mesh use: (material: Material, mesh: Mesh) => void } export type ChunkItem = { position: { x: number; z: number } bakeNear: (batch: MeshBatch) => void bakeFar?: (batch: MeshBatch) => void } export type ChunkCell = { x0: number z0: number x1: number z1: number } export type ChunkBuilder = { minX: number minZ: number maxX: number maxZ: number columns: number rows: number bakeCell: (near: MeshBatch, far: MeshBatch, cell: ChunkCell) => void } export namespace ChunkBuilder { export function build(config: ChunkBuilder, items: ChunkItem[]): Chunk[] { const width = (config.maxX - config.minX) / config.columns const depth = (config.maxZ - config.minZ) / config.rows const chunks: Chunk[] = [] for (let column = 0; column < config.columns; column++) { const x0 = config.minX + column * width const x1 = x0 + width for (let row = 0; row < config.rows; row++) { const z0 = config.minZ + row * depth const z1 = z0 + depth const nearMeshes = new Map() const farMeshes = new Map() const near = batch(nearMeshes) const far = batch(farMeshes) config.bakeCell(near, far, { x0, z0, x1, z1 }) for (const item of items) { if (inCell(item.position, x0, z0, x1, z1)) { item.bakeNear(near) item.bakeFar?.(far) } } const nearGroups = groups(nearMeshes) const farGroups = groups(farMeshes) const box = bounds([...nearMeshes.values(), ...farMeshes.values()]) if (box !== null) { chunks.push({ ...box, near: nearGroups, far: farGroups }) } } } return chunks } function batch(meshes: Map): MeshBatch { return { mesh(material) { let mesh = meshes.get(material) if (mesh === undefined) { mesh = { verts: [], indices: [] } meshes.set(material, mesh) } return mesh }, use(material, mesh) { meshes.set(material, mesh) }, } } function groups(meshes: Map): DrawGroup[] { const result: DrawGroup[] = [] for (const [material, mesh] of meshes) { if (mesh.indices.length > 0) { result.push({ mesh, material }) } } return result } function inCell( position: { x: number; z: number }, x0: number, z0: number, x1: number, z1: number, ): boolean { return ( position.x >= x0 && position.x < x1 && position.z >= z0 && position.z < z1 ) } function bounds(meshes: Mesh[]): Bounds3 | null { let minX = Infinity let minY = Infinity let minZ = Infinity let maxX = -Infinity let maxY = -Infinity let maxZ = -Infinity for (const mesh of meshes) { for (let i = 0; i < mesh.verts.length; i += STRIDE) { minX = Math.min(minX, mesh.verts[i]) minY = Math.min(minY, mesh.verts[i + 1]) minZ = Math.min(minZ, mesh.verts[i + 2]) maxX = Math.max(maxX, mesh.verts[i]) maxY = Math.max(maxY, mesh.verts[i + 1]) maxZ = Math.max(maxZ, mesh.verts[i + 2]) } } return maxX < minX ? null : { minX, minY, minZ, maxX, maxY, maxZ } } }