Explorar o código

Fix block face checks and explicitly define normals in voxel demo (#1182)

Aaron Franke hai 4 meses
pai
achega
d169ec17e2

+ 1 - 1
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.

+ 2 - 2
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()
 
 

+ 1 - 1
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"]

+ 0 - 1
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]

+ 5 - 5
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):

+ 66 - 43
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])

+ 12 - 7
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