From 9db229dde42a4947366f8756273babc71ee7e5f2 Mon Sep 17 00:00:00 2001 From: paulaboks Date: Fri, 13 Mar 2026 09:30:41 -0300 Subject: [PATCH] Mesh chunks --- client/components/dimension.ts | 5 +- client/game.ts | 2 +- client/renderer/core.ts | 44 +++++++++-- client/renderer/models.ts | 104 +++++++++++++++----------- client/renderer/textures.ts | 2 + client/systems/rendering/dimension.ts | 38 +++++++++- 6 files changed, 141 insertions(+), 54 deletions(-) diff --git a/client/components/dimension.ts b/client/components/dimension.ts index 347699d..6ff2158 100644 --- a/client/components/dimension.ts +++ b/client/components/dimension.ts @@ -21,6 +21,9 @@ export interface Chunk { x: number; z: number; blocks: Int32Array; + vertexBuffer?: WebGLBuffer; + vertexCount?: number; + dirty: boolean; } export class Dimension extends Component { @@ -30,7 +33,7 @@ export class Dimension extends Component { tick_timer = 0; add_chunk(x: number, z: number) { - const chunk = { x, z, blocks: new Int32Array(CHUNK_AREA * CHUNK_HEIGHT) }; + const chunk = { x, z, blocks: new Int32Array(CHUNK_AREA * CHUNK_HEIGHT), dirty: true }; this.chunks.push(chunk); return chunk; } diff --git a/client/game.ts b/client/game.ts index 24d59b9..16b4e05 100644 --- a/client/game.ts +++ b/client/game.ts @@ -16,7 +16,7 @@ export function start_game(world: ClientWorld) { world.dimension = new Dimension(); dimension.add(world.dimension); world.add_entity(dimension); - generate(world, dimension.get(Dimension)!, 32, 32); + generate(world, dimension.get(Dimension)!, 16 * 8, 16 * 8); create_player(world); diff --git a/client/renderer/core.ts b/client/renderer/core.ts index 02ab01d..4483000 100644 --- a/client/renderer/core.ts +++ b/client/renderer/core.ts @@ -9,13 +9,14 @@ const VERTS_PER_SPRITE = 6; const FLOATS_PER_VERT = 9; let program: WebGLProgram; -let buffer: WebGLBuffer; +let main_buffer: WebGLBuffer; const vertex_data = new Float32Array(MAX_SPRITES * VERTS_PER_SPRITE * FLOATS_PER_VERT); let vert_index = 0; let pos_loc: GLint; let uv_loc: GLint; +let color_loc: GLint; let texture_loc: WebGLUniformLocation | null; let mvp_loc: WebGLUniformLocation | null; let col_diffuse_loc: WebGLUniformLocation | null; @@ -75,8 +76,8 @@ export function init_window(canvas_element: HTMLCanvasElement) { program = create_program(vertex_src, fragment_src); - buffer = gl.createBuffer()!; - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + main_buffer = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, main_buffer); const stride = FLOATS_PER_VERT * 4; @@ -88,7 +89,7 @@ export function init_window(canvas_element: HTMLCanvasElement) { gl.enableVertexAttribArray(uv_loc); gl.vertexAttribPointer(uv_loc, 2, gl.FLOAT, false, stride, 12); - const color_loc = gl.getAttribLocation(program, "vertexColor"); + color_loc = gl.getAttribLocation(program, "vertexColor"); gl.enableVertexAttribArray(color_loc); gl.vertexAttribPointer(color_loc, 4, gl.FLOAT, false, stride, 20); @@ -153,6 +154,9 @@ export function begin_mode_3d(new_camera: Camera) { gl.enable(gl.DEPTH_TEST); gl.enable(gl.CULL_FACE); + gl.depthMask(true); + gl.enable(gl.POLYGON_OFFSET_FILL); + gl.polygonOffset(1, 1); update_camera(); } @@ -167,6 +171,8 @@ export function end_mode_3d() { mode3d = false; gl.disable(gl.DEPTH_TEST); gl.disable(gl.CULL_FACE); + gl.depthMask(false); + gl.disable(gl.POLYGON_OFFSET_FILL); } export function clear_background(r: number, g: number, b: number, a = 1) { @@ -179,7 +185,7 @@ export function flush_batch() { return; } - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bindBuffer(gl.ARRAY_BUFFER, main_buffer); gl.bufferData(gl.ARRAY_BUFFER, vertex_data.subarray(0, vert_index), gl.STREAM_DRAW); if (mode3d) { @@ -188,6 +194,11 @@ export function flush_batch() { gl.uniformMatrix4fv(mvp_loc, false, ortho); } + const stride = FLOATS_PER_VERT * 4; + gl.vertexAttribPointer(pos_loc, 3, gl.FLOAT, false, stride, 0); + gl.vertexAttribPointer(uv_loc, 2, gl.FLOAT, false, stride, 12); + gl.vertexAttribPointer(color_loc, 4, gl.FLOAT, false, stride, 20); + gl.uniform4f(col_diffuse_loc, 1, 1, 1, 1); gl.activeTexture(gl.TEXTURE0); @@ -199,6 +210,29 @@ export function flush_batch() { vert_index = 0; } +export function flush_buffer(buffer: WebGLBuffer, draw_count: number) { + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + + if (mode3d) { + gl.uniformMatrix4fv(mvp_loc, false, mvp); + } else { + gl.uniformMatrix4fv(mvp_loc, false, ortho); + } + + const stride = FLOATS_PER_VERT * 4; + gl.vertexAttribPointer(pos_loc, 3, gl.FLOAT, false, stride, 0); + gl.vertexAttribPointer(uv_loc, 2, gl.FLOAT, false, stride, 12); + gl.vertexAttribPointer(color_loc, 4, gl.FLOAT, false, stride, 20); + + gl.uniform4f(col_diffuse_loc, 1, 1, 1, 1); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, current_texture); + gl.uniform1i(texture_loc, 0); + + gl.drawArrays(gl.TRIANGLES, 0, draw_count); +} + export function resize_canvas() { const width = self.innerWidth; const height = self.innerHeight; diff --git a/client/renderer/models.ts b/client/renderer/models.ts index 3a55aeb..969c168 100644 --- a/client/renderer/models.ts +++ b/client/renderer/models.ts @@ -1,7 +1,8 @@ -import { flush_batch, get_current_texture, push_vertex, set_current_texture } from "./core.ts"; import { Texture } from "./types.ts"; -export function push_cube( +export function push_cube_to_mesh( + vertices: number[], + vertex_count: number, texture: Texture, x: number, y: number, @@ -24,11 +25,6 @@ export function push_cube( top = true, bottom = true, ) { - if (get_current_texture() !== texture.tex) { - flush_batch(); - set_current_texture(texture.tex); - } - const x2 = x + w; const y2 = y + h; const z2 = z + d; @@ -38,63 +34,85 @@ export function push_cube( const u1 = (sx + sw) / texture.width; const v1 = (sy + sh) / texture.height; + let i = vertex_count; + + const push = (px: number, py: number, pz: number, u: number, vv: number) => { + vertices[i++] = px; + vertices[i++] = py; + vertices[i++] = pz; + vertices[i++] = u; + vertices[i++] = vv; + vertices[i++] = r; + vertices[i++] = g; + vertices[i++] = b; + vertices[i++] = a; + }; + // front (+z) if (front) { - push_vertex(x, y, z2, u0, v0, r, g, b, a); - push_vertex(x2, y, z2, u1, v0, r, g, b, a); - push_vertex(x, y2, z2, u0, v1, r, g, b, a); - push_vertex(x2, y, z2, u1, v0, r, g, b, a); - push_vertex(x2, y2, z2, u1, v1, r, g, b, a); - push_vertex(x, y2, z2, u0, v1, r, g, b, a); + push(x, y, z2, u0, v1); + push(x2, y, z2, u1, v1); + push(x2, y2, z2, u1, v0); + + push(x, y, z2, u0, v1); + push(x2, y2, z2, u1, v0); + push(x, y2, z2, u0, v0); } // back (-z) if (back) { - push_vertex(x2, y, z, u0, v0, r, g, b, a); - push_vertex(x, y, z, u1, v0, r, g, b, a); - push_vertex(x2, y2, z, u0, v1, r, g, b, a); - push_vertex(x, y, z, u1, v0, r, g, b, a); - push_vertex(x, y2, z, u1, v1, r, g, b, a); - push_vertex(x2, y2, z, u0, v1, r, g, b, a); + push(x2, y, z, u0, v1); + push(x, y, z, u1, v1); + push(x, y2, z, u1, v0); + + push(x2, y, z, u0, v1); + push(x, y2, z, u1, v0); + push(x2, y2, z, u0, v0); } // left (-x) if (left) { - push_vertex(x, y, z, u0, v0, r, g, b, a); - push_vertex(x, y, z2, u1, v0, r, g, b, a); - push_vertex(x, y2, z, u0, v1, r, g, b, a); - push_vertex(x, y, z2, u1, v0, r, g, b, a); - push_vertex(x, y2, z2, u1, v1, r, g, b, a); - push_vertex(x, y2, z, u0, v1, r, g, b, a); + push(x, y, z, u0, v1); + push(x, y, z2, u1, v1); + push(x, y2, z2, u1, v0); + + push(x, y, z, u0, v1); + push(x, y2, z2, u1, v0); + push(x, y2, z, u0, v0); } // right (+x) if (right) { - push_vertex(x2, y, z2, u0, v0, r, g, b, a); - push_vertex(x2, y, z, u1, v0, r, g, b, a); - push_vertex(x2, y2, z2, u0, v1, r, g, b, a); - push_vertex(x2, y, z, u1, v0, r, g, b, a); - push_vertex(x2, y2, z, u1, v1, r, g, b, a); - push_vertex(x2, y2, z2, u0, v1, r, g, b, a); + push(x2, y, z2, u0, v1); + push(x2, y, z, u1, v1); + push(x2, y2, z, u1, v0); + + push(x2, y, z2, u0, v1); + push(x2, y2, z, u1, v0); + push(x2, y2, z2, u0, v0); } // top (+y) if (top) { - push_vertex(x, y2, z2, u0, v0, r, g, b, a); - push_vertex(x2, y2, z2, u1, v0, r, g, b, a); - push_vertex(x, y2, z, u0, v1, r, g, b, a); - push_vertex(x2, y2, z2, u1, v0, r, g, b, a); - push_vertex(x2, y2, z, u1, v1, r, g, b, a); - push_vertex(x, y2, z, u0, v1, r, g, b, a); + push(x, y2, z2, u0, v1); + push(x2, y2, z2, u1, v1); + push(x2, y2, z, u1, v0); + + push(x, y2, z2, u0, v1); + push(x2, y2, z, u1, v0); + push(x, y2, z, u0, v0); } // bottom (-y) if (bottom) { - push_vertex(x, y, z, u0, v0, r, g, b, a); - push_vertex(x2, y, z, u1, v0, r, g, b, a); - push_vertex(x, y, z2, u0, v1, r, g, b, a); - push_vertex(x2, y, z, u1, v0, r, g, b, a); - push_vertex(x2, y, z2, u1, v1, r, g, b, a); - push_vertex(x, y, z2, u0, v1, r, g, b, a); + push(x, y, z, u0, v1); + push(x2, y, z, u1, v1); + push(x2, y, z2, u1, v0); + + push(x, y, z, u0, v1); + push(x2, y, z2, u1, v0); + push(x, y, z2, u0, v0); } + + return i; } diff --git a/client/renderer/textures.ts b/client/renderer/textures.ts index 8ffd184..9f60f8d 100644 --- a/client/renderer/textures.ts +++ b/client/renderer/textures.ts @@ -21,6 +21,8 @@ export function load_texture(image: HTMLImageElement): Texture { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); return { tex: texture, diff --git a/client/systems/rendering/dimension.ts b/client/systems/rendering/dimension.ts index 91896ad..af702d3 100644 --- a/client/systems/rendering/dimension.ts +++ b/client/systems/rendering/dimension.ts @@ -2,15 +2,33 @@ import { get_sprite_region } from "$/common/utils.ts"; import { Chunk, CHUNK_SIDE_SIZE, Dimension } from "$/client/components/dimension.ts"; import { TEXTURE_SIZE } from "$/common/constants.ts"; import { EverythingRegistry, TileRegistry } from "$/common/everything_registry.ts"; -import { push_cube } from "$/client/renderer/mod.ts"; +import { flush_buffer, gl, push_cube_to_mesh, set_current_texture } from "$/client/renderer/mod.ts"; export function render_dimension(dimension: Dimension /*, camera: Camera*/) { + set_current_texture(dimension.image.tex); for (const chunk of dimension.chunks) { - render_chunk(chunk, dimension); + if (chunk.dirty) { + if (chunk.vertexBuffer) { + gl.deleteBuffer(chunk.vertexBuffer); + } + chunk.dirty = false; + make_chunk_mesh(chunk, dimension); + } + render_chunk(chunk); } } -function render_chunk(chunk: Chunk, dimension: Dimension) { +function render_chunk(chunk: Chunk) { + if (!chunk.vertexBuffer || !chunk.vertexCount) { + return; + } + + flush_buffer(chunk.vertexBuffer, chunk.vertexCount); +} + +function make_chunk_mesh(chunk: Chunk, dimension: Dimension) { + const vertices: number[] = []; + let count = 0; const blocks_registry = EverythingRegistry.get_registry("blocks"); for (let i = 0; i < chunk.blocks.length; i += 1) { const block_nid = chunk.blocks[i]; @@ -40,7 +58,9 @@ function render_chunk(chunk: Chunk, dimension: Dimension) { const region = get_sprite_region(texture_id); - push_cube( + count = push_cube_to_mesh( + vertices, + count, dimension.image, wx, y, @@ -64,4 +84,14 @@ function render_chunk(chunk: Chunk, dimension: Dimension) { bottom, ); } + + chunk.vertexBuffer = gl.createBuffer(); + chunk.vertexCount = count / 9; + + gl.bindBuffer(gl.ARRAY_BUFFER, chunk.vertexBuffer); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array(vertices), + gl.STATIC_DRAW, + ); }