diff --git a/3d/voxel/player/player.gd b/3d/voxel/player/player.gd index 6be1de62..febcc3b7 100644 --- a/3d/voxel/player/player.gd +++ b/3d/voxel/player/player.gd @@ -19,7 +19,7 @@ var _selected_block := 6 @onready var raycast: RayCast3D = $Head/RayCast3D @onready var camera_attributes: CameraAttributes = $Head/Camera3D.attributes @onready var selected_block_texture: TextureRect = $SelectedBlock -@onready var voxel_world: Node = $"../VoxelWorld" +@onready var voxel_world: VoxelWorld = $"../VoxelWorld" @onready var crosshair: CenterContainer = $"../PauseMenu/Crosshair" @onready var aim_preview: MeshInstance3D = $AimPreview @onready var neutral_fov: float = camera.fov diff --git a/3d/voxel/project.godot b/3d/voxel/project.godot index 7e2afb37..fbb76103 100644 --- a/3d/voxel/project.godot +++ b/3d/voxel/project.godot @@ -155,7 +155,7 @@ pick_block={ [physics] common/physics_ticks_per_second=120 -3d/physics_engine="GodotPhysics3D" +3d/physics_engine="Jolt Physics" common/physics_interpolation=true [rendering] diff --git a/3d/voxel/world/chunk.gd b/3d/voxel/world/chunk.gd index 36cc84c5..fd21a3f4 100644 --- a/3d/voxel/world/chunk.gd +++ b/3d/voxel/world/chunk.gd @@ -4,23 +4,25 @@ extends StaticBody3D # After that, chunks finish setting themselves up in the _ready() function. # If a chunk is changed, its "regenerate" method is called. -const CHUNK_SIZE = 16 # Keep in sync with TerrainGenerator. -const TEXTURE_SHEET_WIDTH = 8 - -const CHUNK_LAST_INDEX = CHUNK_SIZE - 1 -const TEXTURE_TILE_SIZE = 1.0 / TEXTURE_SHEET_WIDTH +const CHUNK_SIZE := 16 # Keep in sync with TerrainGenerator. +const TEXTURE_SHEET_WIDTH := 8 +const CHUNK_LAST_INDEX := CHUNK_SIZE - 1 +const TEXTURE_TILE_SIZE := 1.0 / TEXTURE_SHEET_WIDTH +const CHUNK_EXTENTS := Vector3.ONE / 2.0 const DIRECTIONS: Array[Vector3i] = [Vector3i.LEFT, Vector3i.RIGHT, Vector3i.DOWN, Vector3i.UP, Vector3i.FORWARD, Vector3i.BACK] -var data := {} +var data: Dictionary[Vector3i, int] = {} var chunk_position := Vector3i() -var is_initial_mesh_generated: bool = false +var is_initial_mesh_generated := false +var mesh_task_id := 0 -var _thread: Thread +static var box_shape: BoxShape3D = null -@onready var voxel_world := get_parent() +@onready var voxel_world := get_parent() as VoxelWorld -func _ready() -> void: +func _init(pos: Vector3i) -> void: + chunk_position = pos transform.origin = Vector3(chunk_position * CHUNK_SIZE) name = str(chunk_position) if Settings.world_type == 0: @@ -32,17 +34,27 @@ func _ready() -> void: _generate_chunk_collider() +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + if mesh_task_id >= 1: + WorkerThreadPool.wait_for_task_completion(mesh_task_id) + mesh_task_id = 0 + + 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) + mesh_task_id = WorkerThreadPool.add_task(_generate_chunk_mesh, true) func regenerate() -> void: + # Making shape changes to bodies with many/complex shapes, while the body is in the scene tree, + # can be expensive when using Jolt Physics, so we temporarily remove it from the scene tree. + voxel_world.remove_child(self) + # Clear out all old nodes first. for c in get_children(): remove_child(c) @@ -52,18 +64,14 @@ func regenerate() -> void: _generate_chunk_collider() _generate_chunk_mesh() + voxel_world.add_child(self) + func _generate_chunk_collider() -> void: if data.is_empty(): - # Avoid errors caused by StaticBody3D not having colliders. - _create_block_collider(Vector3.ZERO) - collision_layer = 0 - collision_mask = 0 return - # For each block, generate a collider. Ensure collision layers are enabled. - collision_layer = 0xFFFFF - collision_mask = 0xFFFFF + # For each block, generate a collider. for block_position: Vector3i in data.keys(): var block_id: int = data[block_position] if block_id != 27 and block_id != 28: @@ -201,10 +209,13 @@ func _draw_block_face(surface_tool: SurfaceTool, verts: Array[Vector3], uvs: Arr func _create_block_collider(block_sub_position: Vector3) -> void: + if not box_shape: + box_shape = BoxShape3D.new() + box_shape.extents = CHUNK_EXTENTS + var collider := CollisionShape3D.new() - collider.shape = BoxShape3D.new() - collider.shape.extents = Vector3.ONE / 2 - collider.transform.origin = Vector3(block_sub_position) + Vector3.ONE / 2 + collider.shape = box_shape + collider.transform.origin = block_sub_position + CHUNK_EXTENTS add_child(collider) diff --git a/3d/voxel/world/terrain_generator.gd b/3d/voxel/world/terrain_generator.gd index 31344672..f7b08507 100644 --- a/3d/voxel/world/terrain_generator.gd +++ b/3d/voxel/world/terrain_generator.gd @@ -3,12 +3,12 @@ extends Resource const RANDOM_BLOCK_PROBABILITY = 0.015 -static func empty() -> Dictionary: +static func empty() -> Dictionary[Vector3i, int]: return {} -static func random_blocks() -> Dictionary: - var random_data := {} +static func random_blocks() -> Dictionary[Vector3i, int]: + var random_data: Dictionary[Vector3i, int] = {} for x in Chunk.CHUNK_SIZE: for y in Chunk.CHUNK_SIZE: for z in Chunk.CHUNK_SIZE: @@ -19,8 +19,8 @@ static func random_blocks() -> Dictionary: return random_data -static func flat(chunk_position: Vector3i) -> Dictionary: - var data := {} +static func flat(chunk_position: Vector3i) -> Dictionary[Vector3i, int]: + var data: Dictionary[Vector3i, int] = {} if chunk_position.y != -1: return data @@ -36,7 +36,7 @@ static func flat(chunk_position: Vector3i) -> Dictionary: # Used to create the project icon. -static func origin_grass(chunk_position: Vector3i) -> Dictionary: +static func origin_grass(chunk_position: Vector3i) -> Dictionary[Vector3i, int]: if chunk_position == Vector3i.ZERO: return { Vector3i.ZERO: 3 } diff --git a/3d/voxel/world/voxel_world.gd b/3d/voxel/world/voxel_world.gd index c3fe6563..9c43956d 100644 --- a/3d/voxel/world/voxel_world.gd +++ b/3d/voxel/world/voxel_world.gd @@ -1,3 +1,4 @@ +class_name VoxelWorld extends Node # This file manages the creation and deletion of Chunks. @@ -47,8 +48,7 @@ func _process(_delta: float) -> void: if _chunks.has(chunk_position): continue - var chunk := Chunk.new() - chunk.chunk_position = chunk_position + var chunk := Chunk.new(chunk_position) _chunks[chunk_position] = chunk add_child(chunk) chunk.try_initial_generate_mesh(_chunks) @@ -104,11 +104,6 @@ func set_block_global_position(block_global_position: Vector3i, block_id: int) - func clean_up() -> void: - for chunk_position_key: Vector3i in _chunks.keys(): - var thread: Thread = _chunks[chunk_position_key]._thread - if thread: - thread.wait_to_finish() - _chunks = {} set_process(false) @@ -129,9 +124,6 @@ func _delete_far_away_chunks(player_chunk: Vector3i) -> void: # Also take the opportunity to delete far away chunks. for chunk_position_key: Vector3i in _chunks.keys(): if Vector3(player_chunk).distance_to(Vector3(chunk_position_key)) > _delete_distance: - var thread: Thread = _chunks[chunk_position_key]._thread - if thread: - thread.wait_to_finish() _chunks[chunk_position_key].queue_free() _chunks.erase(chunk_position_key) deleted_this_frame += 1