From 44cbf2e6dcec08d62e15f124e7a02c92ce0f47e8 Mon Sep 17 00:00:00 2001 From: snipercup <50166150+snipercup@users.noreply.github.com> Date: Tue, 2 Apr 2024 21:57:21 +0200 Subject: [PATCH 1/5] Move the level mesh generation code to separate function --- Scripts/Chunk.gd | 93 ++++++++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 35 deletions(-) diff --git a/Scripts/Chunk.gd b/Scripts/Chunk.gd index 4bdcf0cb..a400cac7 100644 --- a/Scripts/Chunk.gd +++ b/Scripts/Chunk.gd @@ -44,6 +44,7 @@ var navigation_mesh: NavigationMesh = NavigationMesh.new() var source_geometry_data: NavigationMeshSourceGeometryData3D var generation_task: int var chunk_mesh_body: StaticBody3D +var atlas_output: Dictionary signal chunk_unloaded(chunkdata: Dictionary) # The chunk is fully unloaded # Signals that the chunk is partly loaded and the next chunk can start loading @@ -682,43 +683,11 @@ func prepare_mesh_data(arrays: Array, blocks_at_same_y: Array, block_uv_map: Dic # - Colliders func generate_chunk_mesh(): # Create the atlas and get the atlas texture - var atlas_output = create_atlas() - var atlas_texture = atlas_output.atlas_texture - var block_uv_map = atlas_output.block_uv_map + atlas_output = create_atlas() for level_index in range(MAX_LEVELS): - # Find blocks at the current y level - var y_level = level_index - 10 - var blocks_at_same_y = find_blocks_at_y_level(y_level) # Adjust based on the level indexing - if blocks_at_same_y.size() > 0: - # Prepare mesh data for this level - var arrays = [] - arrays.resize(ArrayMesh.ARRAY_MAX) - prepare_mesh_data(arrays, blocks_at_same_y, block_uv_map) - - # Create a MeshInstance3D for each level with the prepared mesh data - var mesh = ArrayMesh.new() - mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) - - # Apply the shared atlas texture to a StandardMaterial3D - var material = StandardMaterial3D.new() - material.albedo_texture = atlas_texture - - # Create and configure the mesh instance for the level - var mesh_instance = MeshInstance3D.new() - mesh_instance.mesh = mesh - mesh.surface_set_material(0, material) - - # Add the MeshInstance3D to the appropriate level node - # Assuming you have a structure where each level is a child of the chunk - # You need to adjust this part to fit your node structure - var level_node = ChunkLevel.new() - # We don't set the y position of the chunklevel because that would mess up the mesh placement - # since the mesh will enhirit the position of the chunklevel. So instead we just save the - # y_level to the level node. The purpose of this is to allow hiding levels above the player - level_node.y = y_level - level_node.add_child(mesh_instance) - add_child.call_deferred(level_node) # Add the level node to the chunk + var y_level = level_index - 10 # Calculate the y-level offset if needed + generate_chunk_mesh_for_level(y_level) # Create the static body for collision chunk_mesh_body = StaticBody3D.new() @@ -923,3 +892,57 @@ func get_block_rotation(shape: String, tilerotation: int = 0) -> int: # Previously saved chunks will not have id in the data and it returns false func is_new_chunk() -> bool: return chunk_data.has("id") + + +# Called when the player builds a new block on the map +# We update the block_positions to include the new block +# We have to update te chunk mesh and the navigationmesh +# We also need to add a collider for the new block +func add_block(block_id: String, block_position: Vector3): + # Generate a key for the new block position + var block_key = "%s,%s,%s" % [block_position.x, block_position.y, block_position.z] + + # Update block_positions with the new block + block_positions[block_key] = { + "id": block_id, + "rotation": 0, # Default rotation of 0; implement rotation later + } + + # Regenerate mesh and update navigation and collision for the affected level + generate_chunk_mesh_for_level(int(block_position.y)) + update_navigation_mesh() + # You might want to call a specific function to update colliders if necessary + + +# Adjusted to accept atlas data directly +func generate_chunk_mesh_for_level(y_level: int): + var blocks_at_same_y = find_blocks_at_y_level(y_level) + if blocks_at_same_y.size() > 0: + # Use the passed atlas data + var atlas_texture = atlas_output["atlas_texture"] + var block_uv_map = atlas_output["block_uv_map"] + var arrays = [] + arrays.resize(ArrayMesh.ARRAY_MAX) + prepare_mesh_data(arrays, blocks_at_same_y, block_uv_map) + + # Create a MeshInstance3D for each level with the prepared mesh data + var mesh = ArrayMesh.new() + mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) + + # Apply the shared atlas texture to a StandardMaterial3D + var material = StandardMaterial3D.new() + material.albedo_texture = atlas_texture + + # Create and configure the mesh instance for the level + var mesh_instance = MeshInstance3D.new() + mesh_instance.mesh = mesh + mesh.surface_set_material(0, material) + + # Add the MeshInstance3D to the appropriate level node + var level_node = ChunkLevel.new() + # We don't set the y position of the chunklevel because that would mess up the mesh placement + # since the mesh will enhirit the position of the chunklevel. So instead we just save the + # y_level to the level node. The purpose of this is to allow hiding levels above the player + level_node.y = y_level + level_node.add_child(mesh_instance) + add_child.call_deferred(level_node) # Add the level node to the chunk From 6587f2517e3d1338c1a87a7d4fc9edef3bd99c70 Mon Sep 17 00:00:00 2001 From: snipercup <50166150+snipercup@users.noreply.github.com> Date: Tue, 2 Apr 2024 22:15:00 +0200 Subject: [PATCH 2/5] check for existing level node --- Scripts/Chunk.gd | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/Scripts/Chunk.gd b/Scripts/Chunk.gd index a400cac7..59f37392 100644 --- a/Scripts/Chunk.gd +++ b/Scripts/Chunk.gd @@ -42,9 +42,10 @@ var mypos: Vector3 # The position in 3d space. Expect y to be 0 var navigation_region: NavigationRegion3D var navigation_mesh: NavigationMesh = NavigationMesh.new() var source_geometry_data: NavigationMeshSourceGeometryData3D -var generation_task: int -var chunk_mesh_body: StaticBody3D -var atlas_output: Dictionary +var chunk_mesh_body: StaticBody3D # The staticbody that will visualize the chunk mesh +var atlas_output: Dictionary # An atlas texture that combines all textures of this chunk's blocks +var level_nodes: Dictionary = {} # Keeps track of level nodes by their y_level + signal chunk_unloaded(chunkdata: Dictionary) # The chunk is fully unloaded # Signals that the chunk is partly loaded and the next chunk can start loading @@ -938,11 +939,21 @@ func generate_chunk_mesh_for_level(y_level: int): mesh_instance.mesh = mesh mesh.surface_set_material(0, material) - # Add the MeshInstance3D to the appropriate level node - var level_node = ChunkLevel.new() - # We don't set the y position of the chunklevel because that would mess up the mesh placement - # since the mesh will enhirit the position of the chunklevel. So instead we just save the - # y_level to the level node. The purpose of this is to allow hiding levels above the player - level_node.y = y_level - level_node.add_child(mesh_instance) - add_child.call_deferred(level_node) # Add the level node to the chunk + # Check if a level node exists for this y_level + if level_nodes.has(y_level): + var existing_level_node = level_nodes[y_level] + # Replace the old mesh instance with the new one + existing_level_node.remove_child(existing_level_node.get_child(0)) # Assumes only one child + existing_level_node.add_child(mesh_instance) + else: + # Create a new level node + var level_node = ChunkLevel.new() + # We don't set the y position of the chunklevel because that would mess up the mesh placement + # since the mesh will enhirit the position of the chunklevel. So instead we just save the + # y_level to the level node. The purpose of this is to allow hiding levels above the player + level_node.y = y_level + level_node.name = "Level_" + str(y_level) + level_node.add_child(mesh_instance) + add_child.call_deferred(level_node) # Add the level node to the chunk + # Store the reference to the new level node + level_nodes[y_level] = level_node From 86ea7bf61ecebc8b356b5bac204c57217875614a Mon Sep 17 00:00:00 2001 From: snipercup <50166150+snipercup@users.noreply.github.com> Date: Tue, 2 Apr 2024 22:31:50 +0200 Subject: [PATCH 3/5] cleanup, add collider --- Scripts/Chunk.gd | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Scripts/Chunk.gd b/Scripts/Chunk.gd index 59f37392..c0abc447 100644 --- a/Scripts/Chunk.gd +++ b/Scripts/Chunk.gd @@ -698,7 +698,7 @@ func generate_chunk_mesh(): # Set collision mask to layer 1 chunk_mesh_body.collision_mask = 1 # Layer 1 is 1 add_child.call_deferred(chunk_mesh_body) - create_colliders(chunk_mesh_body) + create_colliders() update_navigation_mesh() #generate_chunk_mesh_finished.call_deferred() @@ -804,7 +804,7 @@ func setup_slope(blockrotation: int, pos: Vector3, verts, uvs, normals, indices, # Coroutine for creating colliders with non-blocking delays -func create_colliders(static_body: StaticBody3D) -> void: +func create_colliders() -> void: var total_blocks = block_positions.size() # Ensure we at least get 1 to avoid division by zero. Aim for a maximum of 15 steps. var delay_every_n_blocks = max(1, total_blocks / 15) @@ -816,7 +816,7 @@ func create_colliders(static_body: StaticBody3D) -> void: var block_data = block_positions[key] var block_shape = block_data.get("shape", "cube") var block_rotation = block_data.get("rotation", 0) - static_body.add_child.call_deferred(_create_block_collider(block_pos, block_shape, block_rotation)) + chunk_mesh_body.add_child.call_deferred(_create_block_collider(block_pos, block_shape, block_rotation)) block_counter += 1 # Check if it's time to delay @@ -903,16 +903,19 @@ func add_block(block_id: String, block_position: Vector3): # Generate a key for the new block position var block_key = "%s,%s,%s" % [block_position.x, block_position.y, block_position.z] - # Update block_positions with the new block + # Update block_positions with the new block data block_positions[block_key] = { "id": block_id, - "rotation": 0, # Default rotation of 0; implement rotation later + "rotation": 0, # Assume default rotation; adjust if necessary } - # Regenerate mesh and update navigation and collision for the affected level + # Regenerate mesh for the affected level generate_chunk_mesh_for_level(int(block_position.y)) update_navigation_mesh() - # You might want to call a specific function to update colliders if necessary + + # Create and add a new collider for the block + var new_collider = _create_block_collider(block_position, "cube", 0) # Cube shape; rotation 0 + chunk_mesh_body.add_child.call_deferred(new_collider) # Adjusted to accept atlas data directly From fed47c0f0a1065ca315617fbe0fed4b23c045348 Mon Sep 17 00:00:00 2001 From: snipercup <50166150+snipercup@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:58:06 +0200 Subject: [PATCH 4/5] Blocks have shadows again, update navigation mesh --- Scripts/Chunk.gd | 158 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 122 insertions(+), 36 deletions(-) diff --git a/Scripts/Chunk.gd b/Scripts/Chunk.gd index c0abc447..307f26b9 100644 --- a/Scripts/Chunk.gd +++ b/Scripts/Chunk.gd @@ -82,6 +82,7 @@ func initialize_chunk_data(): func generate_new_chunk(): block_positions = create_block_position_dictionary_new_arraymesh() await Helper.task_manager.create_task(generate_chunk_mesh).completed + await Helper.task_manager.create_task(update_all_navigation_data).completed processed_level_data = process_level_data() await Helper.task_manager.create_task(add_furnitures_to_new_block).completed await Helper.task_manager.create_task(add_block_mobs).completed @@ -143,6 +144,7 @@ func create_block_position_dictionary_new_arraymesh() -> Dictionary: func generate_saved_chunk() -> void: block_positions = chunk_data.block_positions await Helper.task_manager.create_task(generate_chunk_mesh).completed + await Helper.task_manager.create_task(update_all_navigation_data).completed for item: Dictionary in chunk_data.items: add_item_to_map(item) @@ -661,9 +663,6 @@ func prepare_mesh_data(arrays: Array, blocks_at_same_y: Array, block_uv_map: Dic block_data["rotation"] = blockrotation else: # Rotation has been previously saved so we can use that blockrotation = block_data.rotation - # After calculating and adding vertices to the mesh arrays - # Call add_mesh_to_navigation_data for each block - add_mesh_to_navigation_data(poslocal, blockrotation, blockshape) if blockshape == "cube": setup_cube(pos, blockrotation, verts, uvs, normals, indices, top_face_uv) @@ -699,39 +698,8 @@ func generate_chunk_mesh(): chunk_mesh_body.collision_mask = 1 # Layer 1 is 1 add_child.call_deferred(chunk_mesh_body) create_colliders() - - update_navigation_mesh() - #generate_chunk_mesh_finished.call_deferred() -func setup_cube(pos: Vector3, blockrotation: int, verts, uvs, normals, indices, top_face_uv): - # Assume a block size for the calculations - var half_block = 0.5 - var top_verts = [ - Vector3(-half_block, half_block, -half_block) + pos, - Vector3(half_block, half_block, -half_block) + pos, - Vector3(half_block, half_block, half_block) + pos, - Vector3(-half_block, half_block, half_block) + pos - ] - - var rotated_top_verts = [] - for vertex in top_verts: - rotated_top_verts.append(rotate_vertex_y(vertex - pos, blockrotation) + pos) - - verts.append_array(rotated_top_verts) - uvs.append_array(top_face_uv) - - # Normals for the top face - for _i in range(4): - normals.append(Vector3(0, 1, 0)) - - # Indices for the top face - var base_index = verts.size() - 4 - indices.append_array([ - base_index, base_index + 1, base_index + 2, - base_index, base_index + 2, base_index + 3 - ]) - # Function to find all blocks on the same y level func find_blocks_at_y_level(y_level: int) -> Array: @@ -911,8 +879,11 @@ func add_block(block_id: String, block_position: Vector3): # Regenerate mesh for the affected level generate_chunk_mesh_for_level(int(block_position.y)) - update_navigation_mesh() - + # Update the navigation data based on all blocks + # We can't do this for a specific level, since we have 1 navigationmesh + # If we had multiple navigationmeshes, we could create one per level + await Helper.task_manager.create_task(update_all_navigation_data).completed + # Create and add a new collider for the block var new_collider = _create_block_collider(block_position, "cube", 0) # Cube shape; rotation 0 chunk_mesh_body.add_child.call_deferred(new_collider) @@ -960,3 +931,118 @@ func generate_chunk_mesh_for_level(y_level: int): add_child.call_deferred(level_node) # Add the level node to the chunk # Store the reference to the new level node level_nodes[y_level] = level_node + + +func update_all_navigation_data(): + for key in block_positions.keys(): + var block_data: Dictionary = block_positions[key] + var pos_array = key.split(",") + var block_position = Vector3(float(pos_array[0]), float(pos_array[1]), float(pos_array[2])) + var block_rotation = block_data.rotation + var block_shape = block_data.get("shape", "cube") + #var block_shape = Gamedata.get_data_by_id(Gamedata.data.tiles, block_data.id).shape + + add_mesh_to_navigation_data(block_position, block_rotation, block_shape) + update_navigation_mesh() + + +func setup_cube(pos: Vector3, blockrotation: int, verts, uvs, normals, indices, top_face_uv): + var half_block = 0.5 + + # Top face vertices + var top_verts = [ + Vector3(-half_block, half_block, -half_block) + pos, # top-left-front + Vector3(half_block, half_block, -half_block) + pos, # top-right-front + Vector3(half_block, half_block, half_block) + pos, # top-right-back + Vector3(-half_block, half_block, half_block) + pos # top-left-back + ] + + # Left face vertices + var left_verts = [ + Vector3(-half_block, half_block, -half_block) + pos, # top-left-front + Vector3(-half_block, half_block, half_block) + pos, # top-left-back + Vector3(-half_block, -half_block, half_block) + pos, # bottom-left-back + Vector3(-half_block, -half_block, -half_block) + pos # bottom-left-front + ] + + # Right face vertices (x value set to half_block for all, adjusted order) + var right_verts = [ + Vector3(half_block, half_block, half_block) + pos, # top-right-back + Vector3(half_block, half_block, -half_block) + pos, # top-right-front + Vector3(half_block, -half_block, -half_block) + pos, # bottom-right-front + Vector3(half_block, -half_block, half_block) + pos # bottom-right-back + ] + + # Front face vertices (z value set to -half_block for all, adjusted order) + var front_verts = [ + Vector3(half_block, half_block, -half_block) + pos, # top-right-front + Vector3(-half_block, half_block, -half_block) + pos, # top-left-front + Vector3(-half_block, -half_block, -half_block) + pos, # bottom-left-front + Vector3(half_block, -half_block, -half_block) + pos # bottom-right-front + ] + + # Back face vertices (z value set to half_block for all) + var back_verts = [ + Vector3(-half_block, half_block, half_block) + pos, # top-left-back + Vector3(half_block, half_block, half_block) + pos, # top-right-back + Vector3(half_block, -half_block, half_block) + pos, # bottom-right-back + Vector3(-half_block, -half_block, half_block) + pos # bottom-left-back + ] + + # Rotate only the top-face vertices by blockrotation around the Y axis at position + var rotated_top_verts = [] + for vertex in top_verts: + rotated_top_verts.append(rotate_vertex_y(vertex - pos, blockrotation) + pos) + + # Add vertices to arrays + verts.append_array(rotated_top_verts) + verts.append_array(left_verts) + verts.append_array(right_verts) + verts.append_array(front_verts) # Front face + verts.append_array(back_verts) # back face + + # Append UVs for each face + uvs.append_array(top_face_uv) # Assuming top_face_uv is already defined + uvs.append_array(top_face_uv) # We won't see the left face, so we can just apply the top face uvs + uvs.append_array(top_face_uv) # We won't see the right face, so we can just apply the top face uvs + uvs.append_array(top_face_uv) # We won't see the front face, so we can just apply the top face uvs + uvs.append_array(top_face_uv) # We won't see the back face, so we can just apply the top face uvs + + # Add normals (assuming flat shading and orthogonal faces) + for _i in range(4): + normals.append(Vector3(0, 1, 0)) # Top face + for _i in range(4): + normals.append(Vector3(-1, 0, 0)) # west-facing face + for _i in range(4): + normals.append(Vector3(1, 0, 0)) # east-facing face + for _i in range(4): + normals.append(Vector3(0, 0, -1)) # north-facing face + for _i in range(4): + normals.append(Vector3(0, 0, 1)) # south-facing face + + # Add indices for top, left, and right faces + var top_base_index = verts.size() - 20 + var left_base_index = verts.size() - 16 + var right_base_index = verts.size() - 12 + var front_base_index = verts.size() - 8 + var back_base_index = verts.size() - 4 + indices.append_array([ + top_base_index, top_base_index + 1, top_base_index + 2, + top_base_index, top_base_index + 2, top_base_index + 3 + ]) + indices.append_array([ + left_base_index, left_base_index + 1, left_base_index + 2, + left_base_index, left_base_index + 2, left_base_index + 3 + ]) + indices.append_array([ + right_base_index, right_base_index + 1, right_base_index + 2, + right_base_index, right_base_index + 2, right_base_index + 3 + ]) + indices.append_array([ + front_base_index, front_base_index + 1, front_base_index + 2, + front_base_index, front_base_index + 2, front_base_index + 3 + ]) + indices.append_array([ + back_base_index, back_base_index + 1, back_base_index + 2, + back_base_index, back_base_index + 2, back_base_index + 3 + ]) From c4ae469555f9a993aca8e95e3b441b268f50f358 Mon Sep 17 00:00:00 2001 From: snipercup <50166150+snipercup@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:10:53 +0200 Subject: [PATCH 5/5] Add comments --- Scripts/Chunk.gd | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Scripts/Chunk.gd b/Scripts/Chunk.gd index 307f26b9..940293bd 100644 --- a/Scripts/Chunk.gd +++ b/Scripts/Chunk.gd @@ -933,6 +933,9 @@ func generate_chunk_mesh_for_level(y_level: int): level_nodes[y_level] = level_node +# Rebuilds the navigationmesh for all blocks in the chunk +# We can't do this for a specific level, since we have 1 navigationmesh +# If we had multiple navigationmeshes, we could create one per level, which is more optimized func update_all_navigation_data(): for key in block_positions.keys(): var block_data: Dictionary = block_positions[key] @@ -946,6 +949,22 @@ func update_all_navigation_data(): update_navigation_mesh() +# Creates the vertices for a mesh that makes up a cube +# Couldn't get it to work with a for-loop, so every side is explicitly defined +# TODO: instead of making all the faces, only add them if there is no neighboring cube +# We can do this by checking the neighbor: + # Directions corresponding to the faces + #var directions = [ + #Vector3(0, 0, -1), # Front + #Vector3(1, 0, 0), # Right + #Vector3(0, 0, 1), # Back + #Vector3(-1, 0, 0), # Left + #Vector3(0, 1, 0), # Top + #] + #var neighbor_pos = pos + directions[i] + #var neighbor_key = "%s,%s,%s" % [neighbor_pos.x, neighbor_pos.y, neighbor_pos.z] + #if not block_positions.has(neighbor_key): # Check if there is no block at the neighbor position +# If it does not have a neighbor, we would add the face. func setup_cube(pos: Vector3, blockrotation: int, verts, uvs, normals, indices, top_face_uv): var half_block = 0.5