/// import type { BlockRegistry } from "$/common/everything_registry.ts"; import type { SpriteRegion } from "$/common/constants.ts"; import type { Texture } from "../renderer/types.ts"; import type { FromChunkWorker, ToChunkWorker } from "./chunk_messages.ts"; import { generate_chunk } from "../generation.ts"; const pad = 0.5; function push_vertex( vertices: Float32Array, i: number, px: number, py: number, pz: number, u: number, v: number, r: number, g: number, b: number, a: number, ) { vertices[i++] = px; vertices[i++] = py; vertices[i++] = pz; vertices[i++] = u; vertices[i++] = v; vertices[i++] = r; vertices[i++] = g; vertices[i++] = b; vertices[i++] = a; return i; } export function push_front_face( vertices: Float32Array, i: number, texture: Texture, x: number, y: number, z: number, sx: number, sy: number, sw: number, sh: number, r = 1, g = 1, b = 1, a = 1, ) { const x2 = x + 1; const y2 = y + 1; const z2 = z + 1; const u0 = (sx + pad) / texture.width; const v0 = (sy + pad) / texture.height; const u1 = (sx + sw - pad) / texture.width; const v1 = (sy + sh - pad) / texture.height; i = push_vertex(vertices, i, x, y, z2, u0, v1, r, g, b, a); i = push_vertex(vertices, i, x2, y, z2, u1, v1, r, g, b, a); i = push_vertex(vertices, i, x2, y2, z2, u1, v0, r, g, b, a); i = push_vertex(vertices, i, x, y, z2, u0, v1, r, g, b, a); i = push_vertex(vertices, i, x2, y2, z2, u1, v0, r, g, b, a); i = push_vertex(vertices, i, x, y2, z2, u0, v0, r, g, b, a); return i; } export function push_back_face( vertices: Float32Array, i: number, texture: Texture, x: number, y: number, z: number, sx: number, sy: number, sw: number, sh: number, r = 1, g = 1, b = 1, a = 1, ) { const x2 = x + 1; const y2 = y + 1; const u0 = (sx + pad) / texture.width; const v0 = (sy + pad) / texture.height; const u1 = (sx + sw - pad) / texture.width; const v1 = (sy + sh - pad) / texture.height; i = push_vertex(vertices, i, x2, y, z, u0, v1, r, g, b, a); i = push_vertex(vertices, i, x, y, z, u1, v1, r, g, b, a); i = push_vertex(vertices, i, x, y2, z, u1, v0, r, g, b, a); i = push_vertex(vertices, i, x2, y, z, u0, v1, r, g, b, a); i = push_vertex(vertices, i, x, y2, z, u1, v0, r, g, b, a); i = push_vertex(vertices, i, x2, y2, z, u0, v0, r, g, b, a); return i; } export function push_left_face( vertices: Float32Array, i: number, texture: Texture, x: number, y: number, z: number, sx: number, sy: number, sw: number, sh: number, r = 1, g = 1, b = 1, a = 1, ) { const y2 = y + 1; const z2 = z + 1; const u0 = (sx + pad) / texture.width; const v0 = (sy + pad) / texture.height; const u1 = (sx + sw - pad) / texture.width; const v1 = (sy + sh - pad) / texture.height; i = push_vertex(vertices, i, x, y, z, u0, v1, r, g, b, a); i = push_vertex(vertices, i, x, y, z2, u1, v1, r, g, b, a); i = push_vertex(vertices, i, x, y2, z2, u1, v0, r, g, b, a); i = push_vertex(vertices, i, x, y, z, u0, v1, r, g, b, a); i = push_vertex(vertices, i, x, y2, z2, u1, v0, r, g, b, a); i = push_vertex(vertices, i, x, y2, z, u0, v0, r, g, b, a); return i; } export function push_right_face( vertices: Float32Array, i: number, texture: Texture, x: number, y: number, z: number, sx: number, sy: number, sw: number, sh: number, r = 1, g = 1, b = 1, a = 1, ) { const x2 = x + 1; const y2 = y + 1; const z2 = z + 1; const u0 = (sx + pad) / texture.width; const v0 = (sy + pad) / texture.height; const u1 = (sx + sw - pad) / texture.width; const v1 = (sy + sh - pad) / texture.height; i = push_vertex(vertices, i, x2, y, z2, u0, v1, r, g, b, a); i = push_vertex(vertices, i, x2, y, z, u1, v1, r, g, b, a); i = push_vertex(vertices, i, x2, y2, z, u1, v0, r, g, b, a); i = push_vertex(vertices, i, x2, y, z2, u0, v1, r, g, b, a); i = push_vertex(vertices, i, x2, y2, z, u1, v0, r, g, b, a); i = push_vertex(vertices, i, x2, y2, z2, u0, v0, r, g, b, a); return i; } export function push_top_face( vertices: Float32Array, i: number, texture: Texture, x: number, y: number, z: number, sx: number, sy: number, sw: number, sh: number, r = 1, g = 1, b = 1, a = 1, ) { const x2 = x + 1; const z2 = z + 1; const y2 = y + 1; const u0 = (sx + pad) / texture.width; const v0 = (sy + pad) / texture.height; const u1 = (sx + sw - pad) / texture.width; const v1 = (sy + sh - pad) / texture.height; i = push_vertex(vertices, i, x, y2, z2, u0, v1, r, g, b, a); i = push_vertex(vertices, i, x2, y2, z2, u1, v1, r, g, b, a); i = push_vertex(vertices, i, x2, y2, z, u1, v0, r, g, b, a); i = push_vertex(vertices, i, x, y2, z2, u0, v1, r, g, b, a); i = push_vertex(vertices, i, x2, y2, z, u1, v0, r, g, b, a); i = push_vertex(vertices, i, x, y2, z, u0, v0, r, g, b, a); return i; } export function push_bottom_face( vertices: Float32Array, i: number, texture: Texture, x: number, y: number, z: number, sx: number, sy: number, sw: number, sh: number, r = 1, g = 1, b = 1, a = 1, ) { const x2 = x + 1; const z2 = z + 1; const u0 = (sx + pad) / texture.width; const v0 = (sy + pad) / texture.height; const u1 = (sx + sw - pad) / texture.width; const v1 = (sy + sh - pad) / texture.height; i = push_vertex(vertices, i, x, y, z, u0, v1, r, g, b, a); i = push_vertex(vertices, i, x2, y, z, u1, v1, r, g, b, a); i = push_vertex(vertices, i, x2, y, z2, u1, v0, r, g, b, a); i = push_vertex(vertices, i, x, y, z, u0, v1, r, g, b, a); i = push_vertex(vertices, i, x2, y, z2, u1, v0, r, g, b, a); i = push_vertex(vertices, i, x, y, z2, u0, v0, r, g, b, a); return i; } type TexturesInfo = Record; const CHUNK_SIZE = 16; const CHUNK_HEIGHT = 128; const TEXTURE_SIZE = 16; const FACE_PUSHING_FUNCTIONS = { top: push_top_face, bottom: push_bottom_face, front: push_front_face, back: push_back_face, left: push_left_face, right: push_right_face, } as const; let blocks_registry: BlockRegistry[] = []; let block_ids: Record = {}; let textures_info: TexturesInfo = {}; let image: Texture; self.onmessage = (event: MessageEvent) => { const message = event.data; switch (message.type) { case "init": blocks_registry = message.blocks_registry; block_ids = message.block_ids; textures_info = message.textures_info; image = message.image as Texture; break; case "generate": generate(message.chunk_x, message.chunk_z, message.seed); break; case "mesh": { const [opaque_vertices, opaque_count, transparent_vertices, transparent_count] = make_chunk_mesh( message.chunk_x, message.chunk_z, message.padded_chunk, blocks_registry, textures_info, image, ); post( { type: "meshed", chunk_x: message.chunk_x, chunk_z: message.chunk_z, version: message.version, opaque_vertices, opaque_count, transparent_vertices, transparent_count, }, [opaque_vertices.buffer, transparent_vertices.buffer], ); break; } } }; function post(message: FromChunkWorker, transfer: Transferable[]) { self.postMessage(message, transfer); } function generate(chunk_x: number, chunk_z: number, seed: string) { const blocks = new Uint32Array(CHUNK_SIZE * CHUNK_SIZE * CHUNK_HEIGHT); const spills: number[] = []; generate_chunk( { add_block(block) { const nid = block_ids[block.id]; if (nid === undefined || block.y < 0 || block.y >= CHUNK_HEIGHT) { return; } const block_chunk_x = Math.floor(block.x / CHUNK_SIZE); const block_chunk_z = Math.floor(block.z / CHUNK_SIZE); if (block_chunk_x !== chunk_x || block_chunk_z !== chunk_z) { spills.push(block.x, block.y, block.z, nid); return; } const lx = block.x - chunk_x * CHUNK_SIZE; const lz = block.z - chunk_z * CHUNK_SIZE; blocks[block.y * CHUNK_SIZE * CHUNK_SIZE + lz * CHUNK_SIZE + lx] = nid; }, }, chunk_x, chunk_z, seed, ); const spills_array = new Int32Array(spills); post({ type: "generated", chunk_x, chunk_z, blocks, spills: spills_array }, [ blocks.buffer, spills_array.buffer, ]); } function make_chunk_mesh( chunk_x: number, chunk_z: number, padded_chunk: Uint32Array, blocks_registry: BlockRegistry[], textures_info: TexturesInfo, image: Texture, ): [Float32Array, number, Float32Array, number] { let opaque_vertices = new Float32Array(2048); let opaque_count = 0; let transparent_vertices = new Float32Array(2048); let transparent_count = 0; const size = CHUNK_SIZE + 2; const layer = size * size; const padded_index = (x: number, y: number, z: number) => { return y * layer + z * size + x; }; const show_face = (block: number) => { if (block === 0) return true; const info = blocks_registry[block]; return info?.transparent ?? false; }; for (let y = 0; y < CHUNK_HEIGHT; y++) { for (let z = 0; z < CHUNK_SIZE; z++) { for (let x = 0; x < CHUNK_SIZE; x++) { const px = x + 1; const pz = z + 1; const block_nid = padded_chunk[padded_index(px, y, pz)]; if (block_nid === 0) continue; const block_info = blocks_registry[block_nid]; const texture_ids = { top: "bworld:missing", bottom: "bworld:missing", front: "bworld:missing", back: "bworld:missing", left: "bworld:missing", right: "bworld:missing", }; const textures = block_info.textures; if (!textures) throw new Error(`no textures for ${block_nid}`); if (typeof textures === "string") { texture_ids.top = textures; texture_ids.bottom = textures; texture_ids.front = textures; texture_ids.back = textures; texture_ids.left = textures; texture_ids.right = textures; } else if ("top" in textures && "bottom" in textures && "side" in textures) { texture_ids.top = textures.top; texture_ids.bottom = textures.bottom; texture_ids.front = textures.side; texture_ids.back = textures.side; texture_ids.left = textures.side; texture_ids.right = textures.side; } else if ("front" in textures && "side" in textures) { texture_ids.top = textures.side; texture_ids.bottom = textures.side; texture_ids.front = textures.front; texture_ids.back = textures.side; texture_ids.left = textures.side; texture_ids.right = textures.side; } const wx = chunk_x * CHUNK_SIZE + x; const wz = chunk_z * CHUNK_SIZE + z; const faces = { front: show_face(padded_chunk[padded_index(px, y, pz + 1)]), back: show_face(padded_chunk[padded_index(px, y, pz - 1)]), left: show_face(padded_chunk[padded_index(px - 1, y, pz)]), right: show_face(padded_chunk[padded_index(px + 1, y, pz)]), top: show_face(padded_chunk[padded_index(px, y + 1, pz)]), bottom: show_face(padded_chunk[padded_index(px, y - 1, pz)]), } as const; for (const side of ["front", "back", "left", "right", "top", "bottom"] as const) { if (faces[side]) { const region = textures_info[texture_ids[side]]; if (block_info.transparent) { transparent_vertices = ensure_capacity( transparent_vertices, transparent_count + (6 * 6 * 9), ); transparent_count = FACE_PUSHING_FUNCTIONS[side]( transparent_vertices, transparent_count, image, wx, y, wz, region.x * TEXTURE_SIZE, region.y * TEXTURE_SIZE, TEXTURE_SIZE, TEXTURE_SIZE, 1, 1, 1, block_info.alpha ?? 1, ); } else { opaque_vertices = ensure_capacity(opaque_vertices, opaque_count + (6 * 6 * 9)); opaque_count = FACE_PUSHING_FUNCTIONS[side]( opaque_vertices, opaque_count, image, wx, y, wz, region.x * TEXTURE_SIZE, region.y * TEXTURE_SIZE, TEXTURE_SIZE, TEXTURE_SIZE, 1, 1, 1, 1, ); } } } } } } return [opaque_vertices, opaque_count, transparent_vertices, transparent_count]; } function ensure_capacity( buffer: Float32Array, required: number, ) { if (required <= buffer.length) return buffer; let new_length = buffer.length; while (new_length < required) { new_length *= 2; } const new_buffer = new Float32Array(new_length); new_buffer.set(buffer); return new_buffer; }