diff --git a/3d/voxel/menu/debug.gd b/3d/voxel/menu/debug.gd index b1cd138c..28072392 100644 --- a/3d/voxel/menu/debug.gd +++ b/3d/voxel/menu/debug.gd @@ -12,7 +12,7 @@ func _process(_delta: float) -> void: text += "\nEffective render distance: " + str(voxel_world.effective_render_distance) text += "\nLooking: " + _cardinal_string_from_radians(player.transform.basis.get_euler().y) text += "\nMemory: " + "%3.0f" % (OS.get_static_memory_usage() / 1048576.0) + " MiB" - text += "\nFPS: " + str(Engine.get_frames_per_second()) + text += "\nFPS: " + String.num_uint64(Engine.get_frames_per_second()) # Avoids the problem of showing more digits than needed or available. diff --git a/3d/voxel/menu/options/option_buttons.gd b/3d/voxel/menu/options/option_buttons.gd index df391596..f507ea14 100644 --- a/3d/voxel/menu/options/option_buttons.gd +++ b/3d/voxel/menu/options/option_buttons.gd @@ -7,13 +7,13 @@ extends Control func _ready() -> void: render_distance_slider.value = Settings.render_distance - render_distance_label.text = "Render distance: " + str(Settings.render_distance) + render_distance_label.text = "Render distance: " + String.num_int64(Settings.render_distance) fog_checkbox.button_pressed = Settings.fog_enabled func _on_RenderDistanceSlider_value_changed(value: float) -> void: Settings.render_distance = int(value) - render_distance_label.text = "Render distance: " + str(value) + render_distance_label.text = "Render distance: " + String.num_int64(Settings.render_distance) Settings.save_settings() diff --git a/3d/voxel/player/player.tscn b/3d/voxel/player/player.tscn index 3a0d0f42..65229b37 100644 --- a/3d/voxel/player/player.tscn +++ b/3d/voxel/player/player.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=6 format=3 uid="uid://1s4asqpay67m"] -[ext_resource type="Script" path="res://player/player.gd" id="1"] +[ext_resource type="Script" uid="uid://rm45k07vw817" path="res://player/player.gd" id="1"] [ext_resource type="Texture2D" uid="uid://d3f34krqfgdjd" path="res://world/textures/texture_sheet.png" id="2"] [sub_resource type="CylinderShape3D" id="1"] diff --git a/3d/voxel/project.godot b/3d/voxel/project.godot index ee8e05fc..b8708988 100644 --- a/3d/voxel/project.godot +++ b/3d/voxel/project.godot @@ -147,7 +147,6 @@ pick_block={ [physics] common/physics_ticks_per_second=120 -3d/physics_engine="Bullet" 3d/default_gravity=20.0 [rendering] diff --git a/3d/voxel/settings.gd b/3d/voxel/settings.gd index 263e68b1..fcb5403d 100644 --- a/3d/voxel/settings.gd +++ b/3d/voxel/settings.gd @@ -1,12 +1,12 @@ extends Node -var render_distance := 7 -var fog_enabled := true +var render_distance: int = 7 +var fog_enabled: bool = true -var fog_distance := 32.0 # Not saved, only used during runtime. -var world_type := 0 # Not saved, only used during runtime. +var fog_distance: float = 32.0 # Not saved, only used during runtime. +var world_type: int = 0 # Not saved, only used during runtime. -var _save_path := "user://settings.json" +var _save_path: String = "user://settings.json" func _enter_tree() -> void: if FileAccess.file_exists(_save_path): diff --git a/3d/voxel/world/chunk.gd b/3d/voxel/world/chunk.gd index ce94e519..36cc84c5 100644 --- a/3d/voxel/world/chunk.gd +++ b/3d/voxel/world/chunk.gd @@ -9,14 +9,17 @@ const TEXTURE_SHEET_WIDTH = 8 const CHUNK_LAST_INDEX = CHUNK_SIZE - 1 const TEXTURE_TILE_SIZE = 1.0 / TEXTURE_SHEET_WIDTH +const DIRECTIONS: Array[Vector3i] = [Vector3i.LEFT, Vector3i.RIGHT, Vector3i.DOWN, Vector3i.UP, Vector3i.FORWARD, Vector3i.BACK] var data := {} var chunk_position := Vector3i() +var is_initial_mesh_generated: bool = false var _thread: Thread @onready var voxel_world := get_parent() + func _ready() -> void: transform.origin = Vector3(chunk_position * CHUNK_SIZE) name = str(chunk_position) @@ -27,7 +30,14 @@ func _ready() -> void: # We can only add colliders in the main thread due to physics limitations. _generate_chunk_collider() - # However, we can use a thread for mesh generation. + + +func try_initial_generate_mesh(all_chunks: Dictionary[Vector3i, Chunk]) -> void: + # We can use a thread for mesh generation. + for dir in DIRECTIONS: + if not all_chunks.has(chunk_position + dir): + return + is_initial_mesh_generated = true _thread = Thread.new() _thread.start(_generate_chunk_mesh) @@ -73,7 +83,6 @@ func _generate_chunk_mesh() -> void: _draw_block_mesh(surface_tool, block_position, block_id) # Create the chunk's mesh from the SurfaceTool data. - surface_tool.generate_normals() surface_tool.generate_tangents() surface_tool.index() var array_mesh := surface_tool.commit() @@ -91,10 +100,10 @@ func _draw_block_mesh(surface_tool: SurfaceTool, block_sub_position: Vector3i, b # Bush blocks get drawn in their own special way. if block_id == 27 or block_id == 28: - _draw_block_face(surface_tool, [verts[2], verts[0], verts[7], verts[5]], uvs) - _draw_block_face(surface_tool, [verts[7], verts[5], verts[2], verts[0]], uvs) - _draw_block_face(surface_tool, [verts[3], verts[1], verts[6], verts[4]], uvs) - _draw_block_face(surface_tool, [verts[6], verts[4], verts[3], verts[1]], uvs) + _draw_block_face(surface_tool, [verts[2], verts[0], verts[7], verts[5]], uvs, Vector3(-1, 0, 1).normalized()) + _draw_block_face(surface_tool, [verts[7], verts[5], verts[2], verts[0]], uvs, Vector3(1, 0, -1).normalized()) + _draw_block_face(surface_tool, [verts[3], verts[1], verts[6], verts[4]], uvs, Vector3(1, 0, 1).normalized()) + _draw_block_face(surface_tool, [verts[6], verts[4], verts[3], verts[1]], uvs, Vector3(-1, 0, -1).normalized()) return # Allow some blocks to have different top/bottom textures. @@ -112,62 +121,76 @@ func _draw_block_mesh(surface_tool: SurfaceTool, block_sub_position: Vector3i, b bottom_uvs = top_uvs # Main rendering code for normal blocks. - var other_block_position := block_sub_position + Vector3i.LEFT + #var other_block_position := block_sub_position var other_block_id := 0 - if other_block_position.x == -1: - other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE) - elif data.has(other_block_position): - other_block_id = data[other_block_position] + if block_sub_position.x == 0: + var other_sub_pos: Vector3i = Vector3i(15, block_sub_position.y, block_sub_position.z) + other_block_id = voxel_world.get_block_in_chunk(chunk_position + Vector3i.LEFT, other_sub_pos) + else: + var other_block_sub_pos: Vector3i = block_sub_position + Vector3i.LEFT + if data.has(other_block_sub_pos): + other_block_id = data[other_block_sub_pos] if block_id != other_block_id and Chunk.is_block_transparent(other_block_id): - _draw_block_face(surface_tool, [verts[2], verts[0], verts[3], verts[1]], uvs) + _draw_block_face(surface_tool, [verts[2], verts[0], verts[3], verts[1]], uvs, Vector3.LEFT) - other_block_position = block_sub_position + Vector3i.RIGHT other_block_id = 0 - if other_block_position.x == CHUNK_SIZE: - other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE) - elif data.has(other_block_position): - other_block_id = data[other_block_position] + if block_sub_position.x == CHUNK_SIZE - 1: + var other_sub_pos: Vector3i = Vector3i(0, block_sub_position.y, block_sub_position.z) + other_block_id = voxel_world.get_block_in_chunk(chunk_position + Vector3i.RIGHT, other_sub_pos) + else: + var other_block_sub_pos: Vector3i = block_sub_position + Vector3i.RIGHT + if data.has(other_block_sub_pos): + other_block_id = data[other_block_sub_pos] if block_id != other_block_id and Chunk.is_block_transparent(other_block_id): - _draw_block_face(surface_tool, [verts[7], verts[5], verts[6], verts[4]], uvs) + _draw_block_face(surface_tool, [verts[7], verts[5], verts[6], verts[4]], uvs, Vector3.RIGHT) - other_block_position = block_sub_position + Vector3i.FORWARD other_block_id = 0 - if other_block_position.z == -1: - other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE) - elif data.has(other_block_position): - other_block_id = data[other_block_position] + if block_sub_position.z == 0: + var other_sub_pos: Vector3i = Vector3i(block_sub_position.x, block_sub_position.y, CHUNK_SIZE - 1) + other_block_id = voxel_world.get_block_in_chunk(chunk_position + Vector3i.FORWARD, other_sub_pos) + else: + var other_block_sub_pos: Vector3i = block_sub_position + Vector3i.FORWARD + if data.has(other_block_sub_pos): + other_block_id = data[other_block_sub_pos] if block_id != other_block_id and Chunk.is_block_transparent(other_block_id): - _draw_block_face(surface_tool, [verts[6], verts[4], verts[2], verts[0]], uvs) + _draw_block_face(surface_tool, [verts[6], verts[4], verts[2], verts[0]], uvs, Vector3.FORWARD) - other_block_position = block_sub_position + Vector3i.BACK other_block_id = 0 - if other_block_position.z == CHUNK_SIZE: - other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE) - elif data.has(other_block_position): - other_block_id = data[other_block_position] + if block_sub_position.z == CHUNK_SIZE - 1: + var other_sub_pos: Vector3i = Vector3i(block_sub_position.x, block_sub_position.y, 0) + other_block_id = voxel_world.get_block_in_chunk(chunk_position + Vector3i.BACK, other_sub_pos) + else: + var other_block_sub_pos: Vector3i = block_sub_position + Vector3i.BACK + if data.has(other_block_sub_pos): + other_block_id = data[other_block_sub_pos] if block_id != other_block_id and Chunk.is_block_transparent(other_block_id): - _draw_block_face(surface_tool, [verts[3], verts[1], verts[7], verts[5]], uvs) + _draw_block_face(surface_tool, [verts[3], verts[1], verts[7], verts[5]], uvs, Vector3.BACK) - other_block_position = block_sub_position + Vector3i.DOWN other_block_id = 0 - if other_block_position.y == -1: - other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE) - elif data.has(other_block_position): - other_block_id = data[other_block_position] + if block_sub_position.y == 0: + var other_sub_pos: Vector3i = Vector3i(block_sub_position.x, CHUNK_SIZE - 1, block_sub_position.z) + other_block_id = voxel_world.get_block_in_chunk(chunk_position + Vector3i.DOWN, other_sub_pos) + else: + var other_block_sub_pos: Vector3i = block_sub_position + Vector3i.DOWN + if data.has(other_block_sub_pos): + other_block_id = data[other_block_sub_pos] if block_id != other_block_id and Chunk.is_block_transparent(other_block_id): - _draw_block_face(surface_tool, [verts[4], verts[5], verts[0], verts[1]], bottom_uvs) + _draw_block_face(surface_tool, [verts[4], verts[5], verts[0], verts[1]], bottom_uvs, Vector3.DOWN) - other_block_position = block_sub_position + Vector3i.UP other_block_id = 0 - if other_block_position.y == CHUNK_SIZE: - other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE) - elif data.has(other_block_position): - other_block_id = data[other_block_position] + if block_sub_position.y == CHUNK_SIZE - 1: + var other_sub_pos: Vector3i = Vector3i(block_sub_position.x, 0, block_sub_position.z) + other_block_id = voxel_world.get_block_in_chunk(chunk_position + Vector3i.UP, other_sub_pos) + else: + var other_block_sub_pos: Vector3i = block_sub_position + Vector3i.UP + if data.has(other_block_sub_pos): + other_block_id = data[other_block_sub_pos] if block_id != other_block_id and Chunk.is_block_transparent(other_block_id): - _draw_block_face(surface_tool, [verts[2], verts[3], verts[6], verts[7]], top_uvs) + _draw_block_face(surface_tool, [verts[2], verts[3], verts[6], verts[7]], top_uvs, Vector3.UP) -func _draw_block_face(surface_tool: SurfaceTool, verts: Array[Vector3], uvs: Array[Vector2]) -> void: +func _draw_block_face(surface_tool: SurfaceTool, verts: Array[Vector3], uvs: Array[Vector2], normal: Vector3) -> void: + surface_tool.set_normal(normal) surface_tool.set_uv(uvs[1]); surface_tool.add_vertex(verts[1]) surface_tool.set_uv(uvs[2]); surface_tool.add_vertex(verts[2]) surface_tool.set_uv(uvs[3]); surface_tool.add_vertex(verts[3]) diff --git a/3d/voxel/world/voxel_world.gd b/3d/voxel/world/voxel_world.gd index 4e817602..c3fe6563 100644 --- a/3d/voxel/world/voxel_world.gd +++ b/3d/voxel/world/voxel_world.gd @@ -3,6 +3,7 @@ extends Node const CHUNK_MIDPOINT = Vector3(0.5, 0.5, 0.5) * Chunk.CHUNK_SIZE const CHUNK_END_SIZE = Chunk.CHUNK_SIZE - 1 +const DIRECTIONS: Array[Vector3i] = [Vector3i.LEFT, Vector3i.RIGHT, Vector3i.DOWN, Vector3i.UP, Vector3i.FORWARD, Vector3i.BACK] var render_distance: int: set(value): @@ -16,7 +17,7 @@ var _old_player_chunk := Vector3i() var _generating := true var _deleting := false -var _chunks := {} +var _chunks: Dictionary[Vector3i, Chunk] = {} @onready var player: CharacterBody3D = $"../Player" @@ -50,6 +51,13 @@ func _process(_delta: float) -> void: chunk.chunk_position = chunk_position _chunks[chunk_position] = chunk add_child(chunk) + chunk.try_initial_generate_mesh(_chunks) + for dir in DIRECTIONS: + var neighbor: Chunk = _chunks.get(chunk_position + dir) + if neighbor != null and not neighbor.is_initial_mesh_generated: + neighbor.try_initial_generate_mesh(_chunks) + # Generate at most one chunk per frame in terms of data/colliders. + # Mesh generation is threaded so it's ok that the above may generate multiple meshes. return # If we didn't generate any chunks (and therefore didn't return), what next? @@ -61,14 +69,11 @@ func _process(_delta: float) -> void: _generating = false -func get_block_global_position(block_global_position: Vector3i) -> int: - var chunk_position := Vector3i((block_global_position / Chunk.CHUNK_SIZE)) +func get_block_in_chunk(chunk_position: Vector3i, block_sub_position: Vector3i) -> int: if _chunks.has(chunk_position): var chunk: Chunk = _chunks[chunk_position] - var sub_position := Vector3i(Vector3(block_global_position).posmod(Chunk.CHUNK_SIZE)) - if chunk.data.has(sub_position): - return chunk.data[sub_position] - + if chunk.data.has(block_sub_position): + return chunk.data[block_sub_position] return 0