124 lines
3.4 KiB
TypeScript
124 lines
3.4 KiB
TypeScript
|
|
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<Material, Mesh>()
|
||
|
|
const farMeshes = new Map<Material, Mesh>()
|
||
|
|
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<Material, Mesh>): 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<Material, Mesh>): 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 }
|
||
|
|
}
|
||
|
|
}
|