diff --git a/client/components/dimension.ts b/client/components/dimension.ts index 9a6a9b6..18ef69c 100644 --- a/client/components/dimension.ts +++ b/client/components/dimension.ts @@ -1,9 +1,10 @@ import { Component } from "$/common/ecs/mod.ts"; import { EverythingRegistry, TileRegistry } from "$/common/everything_registry.ts"; +import { Faces } from "../../common/constants.ts"; import { AssetManager } from "../assets.ts"; -import { ClientWorld } from "../client_world.ts"; import { generate_chunk } from "../generation.ts"; import { gl, Texture } from "../renderer/mod.ts"; +import { Camera } from "./camera.ts"; export interface Block { x: number; @@ -21,13 +22,11 @@ export const CHUNK_AREA = CHUNK_SIZE * CHUNK_SIZE; export interface Chunk { x: number; z: number; - blocks: Int32Array; + blocks: Int16Array; generated: boolean; dirty: boolean; - vertexBuffer?: WebGLBuffer; - vertexCount?: number; - vertexTransparentBuffer?: WebGLBuffer; - vertexTransparentCount?: number; + vertex_buffer?: WebGLBuffer; + vertex_count?: number; } export class Dimension extends Component { @@ -37,15 +36,19 @@ 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), dirty: true, generated: false }; + const chunk = { x, z, blocks: new Int16Array(CHUNK_AREA * CHUNK_HEIGHT), dirty: true, generated: false }; this.chunks.push(chunk); return chunk; } + get_chunk(x: number, z: number) { + return this.chunks.find((chunk) => chunk.x === x && chunk.z === z); + } + add_block(block: Block) { const block_chunk_x = Math.floor(block.x / CHUNK_SIZE); const block_chunk_z = Math.floor(block.z / CHUNK_SIZE); - let chunk = this.chunks.find((chunk) => chunk.x === block_chunk_x && chunk.z === block_chunk_z); + let chunk = this.get_chunk(block_chunk_x, block_chunk_z); if (!chunk) { chunk = this.add_chunk(block_chunk_x, block_chunk_z); } @@ -59,6 +62,7 @@ export class Dimension extends Component { const index = ly * CHUNK_SIZE * CHUNK_SIZE + lz * CHUNK_SIZE + lx; chunk.blocks[index] = nid; + chunk.dirty = true; if (block_info?.on_create) { block_info?.on_create(this, block); } @@ -68,9 +72,7 @@ export class Dimension extends Component { const chunk_x = Math.floor(x / CHUNK_SIZE); const chunk_z = Math.floor(z / CHUNK_SIZE); - const chunk = this.chunks.find( - (c) => c.x === chunk_x && c.z === chunk_z, - ); + const chunk = this.get_chunk(chunk_x, chunk_z); if (!chunk) { return -1; @@ -85,15 +87,49 @@ export class Dimension extends Component { return chunk.blocks[index]; } - delete_tile(_world: ClientWorld, _block: Block) { - // const index = this.blocks.indexOf(tile); - // if (index !== -1) { - // this.blocks.splice(index, 1); - // } + break_block(x: number, y: number, z: number) { + const block_chunk_x = Math.floor(x / CHUNK_SIZE); + const block_chunk_z = Math.floor(z / CHUNK_SIZE); + let chunk = this.get_chunk(block_chunk_x, block_chunk_z); + if (!chunk) { + chunk = this.add_chunk(block_chunk_x, block_chunk_z); + } - // const tile_info = EverythingRegistry.get("blocks", tile.id); - // if (tile_info?.on_delete) { - // tile_info?.on_delete(world, tile); + // const [nid, block_info] = EverythingRegistry.get_full("blocks", block.id)!; + + const lx = x - block_chunk_x * CHUNK_SIZE; + const lz = z - block_chunk_z * CHUNK_SIZE; + const ly = y; + + const index = ly * CHUNK_SIZE * CHUNK_SIZE + lz * CHUNK_SIZE + lx; + chunk.blocks[index] = 0; + chunk.dirty = true; + + if (lx === 0) { + const n = this.get_chunk(block_chunk_x - 1, block_chunk_z); + if (n) { + n.dirty = true; + } + } else if (lx === CHUNK_SIZE - 1) { + const n = this.get_chunk(block_chunk_x + 1, block_chunk_z); + if (n) { + n.dirty = true; + } + } + if (lz === 0) { + const n = this.get_chunk(block_chunk_x, block_chunk_z - 1); + if (n) { + n.dirty = true; + } + } else if (lz === CHUNK_SIZE - 1) { + const n = this.get_chunk(block_chunk_x, block_chunk_z + 1); + if (n) { + n.dirty = true; + } + } + + // if (block_info?.on_create) { + // block_info?.on_create(this, block); // } } @@ -109,7 +145,7 @@ export class Dimension extends Component { load_chunk(cx: number, cz: number) { generate_chunk(this, cx, cz); - const chunk = this.chunks.find((c) => c.x === cx && c.z === cz); + const chunk = this.get_chunk(cx, cz); if (chunk) { chunk.generated = true; chunk.dirty = true; @@ -133,7 +169,7 @@ export class Dimension extends Component { unload_chunk(cx: number, cz: number) { const chunk_i = this.chunks.findIndex((c) => c.x === cx && c.z === cz); if (chunk_i === -1) { - console.warn("Tried unloading a chunk that doesn't exist dumbass"); + console.warn("Tried unloading a chunk that doesn't exist"); return; } @@ -142,11 +178,76 @@ export class Dimension extends Component { } delete_chunk_mesh(chunk: Chunk) { - if (chunk.vertexBuffer) { - gl.deleteBuffer(chunk.vertexBuffer); - } - if (chunk.vertexTransparentBuffer) { - gl.deleteBuffer(chunk.vertexTransparentBuffer); + if (chunk.vertex_buffer) { + gl.deleteBuffer(chunk.vertex_buffer); } } + + get_looked_block( + dimension: Dimension, + camera: Camera, + max_distance = 6, + step = 0.05, + ): { x: number; y: number; z: number; block: number; face: Faces } | undefined { + const yaw = camera.yaw; + const pitch = camera.pitch; + + const cos_pitch = Math.cos(pitch); + const dx = -Math.sin(yaw) * cos_pitch; + const dy = Math.sin(pitch); + const dz = -Math.cos(yaw) * cos_pitch; + + let x = camera.x; + let y = camera.y; + let z = camera.z; + + let prev_bx = Math.floor(x); + let prev_by = Math.floor(y); + let prev_bz = Math.floor(z); + + let dist = 0; + + while (dist <= max_distance) { + x += dx * step; + y += dy * step; + z += dz * step; + dist += step; + + const bx = Math.floor(x); + const by = Math.floor(y); + const bz = Math.floor(z); + + if (bx === prev_bx && by === prev_by && bz === prev_bz) { + continue; + } + + const block = dimension.get_block(bx, by, bz); + + if (block && block !== 0) { + let face: Faces; + + if (bx > prev_bx) { + face = "west"; + } else if (bx < prev_bx) { + face = "east"; + } else if (by > prev_by) { + face = "bottom"; + } else if (by < prev_by) { + face = "top"; + } else if (bz > prev_bz) { + face = "north"; + } else { + face = "south"; + } + + return { x: bx, y: by, z: bz, face, block }; + } + + prev_bx = bx; + prev_by = by; + prev_bz = bz; + } + + return undefined; + } } diff --git a/client/renderer/models.ts b/client/renderer/models.ts index 969c168..4214fe1 100644 --- a/client/renderer/models.ts +++ b/client/renderer/models.ts @@ -1,7 +1,7 @@ import { Texture } from "./types.ts"; export function push_cube_to_mesh( - vertices: number[], + vertices: Float32Array, vertex_count: number, texture: Texture, x: number, diff --git a/client/systems/player_controls.ts b/client/systems/player_controls.ts index 6a878d9..9c11b7d 100644 --- a/client/systems/player_controls.ts +++ b/client/systems/player_controls.ts @@ -94,6 +94,29 @@ export class PlayerControlsSystem extends System { InputManager.set_mouse_grabbed(player_component.screens.length === 0); + const block = world.dimension.get_looked_block(world.dimension, camera); + if (block) { + if (InputManager.is_mouse_pressed(0)) { + world.dimension.break_block(block.x, block.y, block.z); + } else if (InputManager.is_mouse_pressed(2)) { + const FACE_OFFSETS = { + top: { x: 0, y: 1, z: 0 }, + bottom: { x: 0, y: -1, z: 0 }, + north: { x: 0, y: 0, z: -1 }, + south: { x: 0, y: 0, z: 1 }, + west: { x: -1, y: 0, z: 0 }, + east: { x: 1, y: 0, z: 0 }, + }; + const offset = FACE_OFFSETS[block.face]; + world.dimension.add_block({ + x: block.x + offset.x, + y: block.y + offset.y, + z: block.z + offset.z, + id: "bworld:dirt", + }); + } + } + if (player_component.screens.length === 0) { const player_inventory = player_component.player_inventory; const scroll = InputManager.get_wheel_delta(); diff --git a/client/systems/render_system.ts b/client/systems/render_system.ts index 6d8d38f..60c4856 100644 --- a/client/systems/render_system.ts +++ b/client/systems/render_system.ts @@ -7,7 +7,7 @@ import { Camera } from "$/client/components/camera.ts"; import { render_animated_sprite, render_sprite } from "./rendering/sprites.ts"; import { render_dimension } from "./rendering/dimension.ts"; -import { render_player_hotbar } from "./rendering/player.ts"; +import { render_player_crosshair, render_player_hotbar } from "./rendering/player.ts"; import { PlayerComponent } from "../player.ts"; import { begin_mode_3d, end_mode_3d } from "../renderer/core.ts"; @@ -52,6 +52,7 @@ export class RenderSystem extends System { const player_component = entity.get(PlayerComponent); if (player_component) { render_player_hotbar(player_component.player_inventory); + render_player_crosshair(); } } } diff --git a/client/systems/rendering/dimension.ts b/client/systems/rendering/dimension.ts index ecddf73..30d5b16 100644 --- a/client/systems/rendering/dimension.ts +++ b/client/systems/rendering/dimension.ts @@ -1,5 +1,5 @@ -import { distance_point_point, get_sprite_region } from "$/common/utils.ts"; -import { Chunk, CHUNK_SIZE, Dimension } from "$/client/components/dimension.ts"; +import { get_sprite_region } from "$/common/utils.ts"; +import { Chunk, CHUNK_AREA, CHUNK_SIZE, Dimension } from "$/client/components/dimension.ts"; import { TEXTURE_SIZE } from "$/common/constants.ts"; import { EverythingRegistry, TileRegistry } from "$/common/everything_registry.ts"; import { flush_buffer, gl, push_cube_to_mesh, set_current_texture } from "$/client/renderer/mod.ts"; @@ -12,52 +12,43 @@ export function render_dimension(dimension: Dimension, camera: Camera) { dimension.delete_chunk_mesh(chunk); chunk.dirty = false; make_chunk_mesh(chunk, dimension, camera); + // only generate one mesh per frame + break; } - render_chunk_opaque(chunk); } - gl.enable(gl.BLEND); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - gl.depthMask(false); for (const chunk of dimension.chunks) { - render_chunk_transparent(chunk); + render_chunk_opaque(chunk); } } function render_chunk_opaque(chunk: Chunk) { - if (!chunk.vertexBuffer || !chunk.vertexCount) { + if (!chunk.vertex_buffer || !chunk.vertex_count) { return; } - flush_buffer(chunk.vertexBuffer, chunk.vertexCount); -} - -function render_chunk_transparent(chunk: Chunk) { - if (!chunk.vertexTransparentBuffer || !chunk.vertexTransparentCount) { - return; - } - - flush_buffer(chunk.vertexTransparentBuffer, chunk.vertexTransparentCount); + flush_buffer(chunk.vertex_buffer, chunk.vertex_count); } function make_chunk_mesh(chunk: Chunk, dimension: Dimension, camera: Camera) { - const vertices: number[] = []; - const transparent_vertices: number[] = []; + let vertices = new Float32Array(CHUNK_AREA * 24 * 6 * 9); let count = 0; - let transparent_count = 0; - const transparent_blocks: [ - number, - number, - number, - number, - number, - boolean, - boolean, - boolean, - boolean, - boolean, - boolean, - ][] = []; const blocks_registry = EverythingRegistry.get_registry("blocks"); + + const show_face = (x: number, y: number, z: number) => { + const block = dimension.get_block(x, y, z); + if (block === -1) { + return false; + } + if (block === 0) { + return true; + } + const block_info = blocks_registry[block]; + if (block_info?.transparent) { + return true; + } + return false; + }; + for (let i = 0; i < chunk.blocks.length; i += 1) { const block_nid = chunk.blocks[i]; if (block_nid === 0) { @@ -80,20 +71,6 @@ function make_chunk_mesh(chunk: Chunk, dimension: Dimension, camera: Camera) { const wx = chunk.x * CHUNK_SIZE + x; const wz = chunk.z * CHUNK_SIZE + z; - const show_face = (x: number, y: number, z: number) => { - const block = dimension.get_block(x, y, z); - if (block === -1) { - return false; - } - if (block === 0) { - return true; - } - const block_info = blocks_registry[block]; - if (block_info?.transparent) { - return true; - } - return false; - }; const front = show_face(wx, y, wz + 1); const back = show_face(wx, y, wz - 1); const left = show_face(wx - 1, y, wz); @@ -103,101 +80,57 @@ function make_chunk_mesh(chunk: Chunk, dimension: Dimension, camera: Camera) { const region = get_sprite_region(texture_id); - if (tile_info.transparent) { - transparent_blocks.push([ - wx, - y, - wz, - region.x * TEXTURE_SIZE, - region.y * TEXTURE_SIZE, - front, - back, - left, - right, - top, - bottom, - ]); - } else { - count = push_cube_to_mesh( - vertices, - count, - dimension.image, - wx, - y, - wz, - 1, - 1, - 1, - region.x * TEXTURE_SIZE, - region.y * TEXTURE_SIZE, - TEXTURE_SIZE, - TEXTURE_SIZE, - 1, - 1, - 1, - 1, - front, - back, - left, - right, - top, - bottom, - ); - } - } - - transparent_blocks.sort((a, b) => { - const da = distance_point_point(a[0], a[1], a[2], camera.x, camera.y, camera.z); - const db = distance_point_point(b[0], b[1], b[2], camera.x, camera.y, camera.z); - - return da - db; - }); - - for (const block of transparent_blocks) { - transparent_count = push_cube_to_mesh( - transparent_vertices, - transparent_count, + vertices = ensure_capacity(vertices, count + (6 * 6 * 9)); + count = push_cube_to_mesh( + vertices, + count, dimension.image, - block[0], - block[1], - block[2], + wx, + y, + wz, 1, 1, 1, - block[3], - block[4], + region.x * TEXTURE_SIZE, + region.y * TEXTURE_SIZE, TEXTURE_SIZE, TEXTURE_SIZE, 1, 1, 1, 1, - block[5], - block[6], - block[7], - block[8], - block[9], - block[10], + front, + back, + left, + right, + top, + bottom, ); } - chunk.vertexBuffer = gl.createBuffer(); - chunk.vertexCount = count / 9; + chunk.vertex_buffer = gl.createBuffer(); + chunk.vertex_count = count / 9; - gl.bindBuffer(gl.ARRAY_BUFFER, chunk.vertexBuffer); + gl.bindBuffer(gl.ARRAY_BUFFER, chunk.vertex_buffer); gl.bufferData( gl.ARRAY_BUFFER, - new Float32Array(vertices), - gl.STATIC_DRAW, - ); - - chunk.vertexTransparentBuffer = gl.createBuffer(); - chunk.vertexTransparentCount = transparent_count / 9; - - gl.bindBuffer(gl.ARRAY_BUFFER, chunk.vertexTransparentBuffer); - gl.bufferData( - gl.ARRAY_BUFFER, - new Float32Array(transparent_vertices), + vertices.subarray(0, count), gl.STATIC_DRAW, ); } + +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; +} diff --git a/client/systems/rendering/player.ts b/client/systems/rendering/player.ts index 680b0e1..c797c27 100644 --- a/client/systems/rendering/player.ts +++ b/client/systems/rendering/player.ts @@ -2,7 +2,7 @@ import { SLOT_SIZE } from "$/common/constants.ts"; import { AssetManager } from "$/client/assets.ts"; import { PlayerInventory } from "../../inventory.ts"; import { draw_item, draw_nine_slice } from "./render_utils.ts"; -import { canvas, Texture } from "$/client/renderer/mod.ts"; +import { canvas, draw_rect_stroke, Texture } from "$/client/renderer/mod.ts"; const PADDING = 10; @@ -56,3 +56,14 @@ export function render_player_hotbar(player_inventory: PlayerInventory) { } } } + +export function render_player_crosshair() { + const CROSSHAIR_SIZE = 8; + draw_rect_stroke( + (canvas.width - CROSSHAIR_SIZE) / 2, + (canvas.height - CROSSHAIR_SIZE) / 2, + CROSSHAIR_SIZE, + CROSSHAIR_SIZE, + [0, 0, 0, 0.6], + ); +} diff --git a/client/systems/world_generation_system.ts b/client/systems/world_generation_system.ts index 3c5ee65..8663c0d 100644 --- a/client/systems/world_generation_system.ts +++ b/client/systems/world_generation_system.ts @@ -35,6 +35,7 @@ export class WorldGenerationSystem extends System { if (!maybe_chunk || !maybe_chunk.generated) { console.log("loading chunk", i, j); dimension.load_chunk(i, j); + // only generate one chunk per frame return; } } diff --git a/common/constants.ts b/common/constants.ts index 6027299..b9e1d14 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -10,3 +10,6 @@ export interface SpriteRegion { x: number; y: number; } + +export const faces = ["west", "east", "bottom", "top", "north", "south"] as const; +export type Faces = typeof faces[number];