chunk.gd 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. class_name Chunk
  2. extends StaticBody3D
  3. # These chunks are instanced and given data by VoxelWorld.
  4. # After that, chunks finish setting themselves up in the _ready() function.
  5. # If a chunk is changed, its "regenerate" method is called.
  6. const CHUNK_SIZE = 16 # Keep in sync with TerrainGenerator.
  7. const TEXTURE_SHEET_WIDTH = 8
  8. const CHUNK_LAST_INDEX = CHUNK_SIZE - 1
  9. const TEXTURE_TILE_SIZE = 1.0 / TEXTURE_SHEET_WIDTH
  10. var data = {}
  11. var chunk_position = Vector3i()
  12. var _thread
  13. @onready var voxel_world = get_parent()
  14. func _ready():
  15. transform.origin = Vector3(chunk_position * CHUNK_SIZE)
  16. name = str(chunk_position)
  17. if Settings.world_type == 0:
  18. data = TerrainGenerator.random_blocks()
  19. else:
  20. data = TerrainGenerator.flat(chunk_position)
  21. # We can only add colliders in the main thread due to physics limitations.
  22. _generate_chunk_collider()
  23. # However, we can use a thread for mesh generation.
  24. _thread = Thread.new()
  25. _thread.start(self._generate_chunk_mesh)
  26. func regenerate():
  27. # Clear out all old nodes first.
  28. for c in get_children():
  29. remove_child(c)
  30. c.queue_free()
  31. # Then generate new ones.
  32. _generate_chunk_collider()
  33. _generate_chunk_mesh()
  34. func _generate_chunk_collider():
  35. if data.is_empty():
  36. # Avoid errors caused by StaticBody3D not having colliders.
  37. _create_block_collider(Vector3.ZERO)
  38. collision_layer = 0
  39. collision_mask = 0
  40. return
  41. # For each block, generate a collider. Ensure collision layers are enabled.
  42. collision_layer = 0xFFFFF
  43. collision_mask = 0xFFFFF
  44. for block_position in data.keys():
  45. var block_id = data[block_position]
  46. if block_id != 27 and block_id != 28:
  47. _create_block_collider(block_position)
  48. func _generate_chunk_mesh():
  49. if data.is_empty():
  50. return
  51. var surface_tool = SurfaceTool.new()
  52. surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES)
  53. # For each block, add data to the SurfaceTool and generate a collider.
  54. for block_position in data.keys():
  55. var block_id = data[block_position]
  56. _draw_block_mesh(surface_tool, block_position, block_id)
  57. # Create the chunk's mesh from the SurfaceTool data.
  58. surface_tool.generate_normals()
  59. surface_tool.generate_tangents()
  60. surface_tool.index()
  61. var array_mesh = surface_tool.commit()
  62. var mi = MeshInstance3D.new()
  63. mi.mesh = array_mesh
  64. mi.material_override = preload("res://world/textures/material.tres")
  65. add_child.call_deferred(mi)
  66. func _draw_block_mesh(surface_tool, block_sub_position, block_id):
  67. var verts = calculate_block_verts(block_sub_position)
  68. var uvs = calculate_block_uvs(block_id)
  69. var top_uvs = uvs
  70. var bottom_uvs = uvs
  71. # Bush blocks get drawn in their own special way.
  72. if block_id == 27 or block_id == 28:
  73. _draw_block_face(surface_tool, [verts[2], verts[0], verts[7], verts[5]], uvs)
  74. _draw_block_face(surface_tool, [verts[7], verts[5], verts[2], verts[0]], uvs)
  75. _draw_block_face(surface_tool, [verts[3], verts[1], verts[6], verts[4]], uvs)
  76. _draw_block_face(surface_tool, [verts[6], verts[4], verts[3], verts[1]], uvs)
  77. return
  78. # Allow some blocks to have different top/bottom textures.
  79. if block_id == 3: # Grass.
  80. top_uvs = calculate_block_uvs(0)
  81. bottom_uvs = calculate_block_uvs(2)
  82. elif block_id == 5: # Furnace.
  83. top_uvs = calculate_block_uvs(31)
  84. bottom_uvs = top_uvs
  85. elif block_id == 12: # Log.
  86. top_uvs = calculate_block_uvs(30)
  87. bottom_uvs = top_uvs
  88. elif block_id == 19: # Bookshelf.
  89. top_uvs = calculate_block_uvs(4)
  90. bottom_uvs = top_uvs
  91. # Main rendering code for normal blocks.
  92. var other_block_position = block_sub_position + Vector3i.LEFT
  93. var other_block_id = 0
  94. if other_block_position.x == -1:
  95. other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
  96. elif data.has(other_block_position):
  97. other_block_id = data[other_block_position]
  98. if block_id != other_block_id and is_block_transparent(other_block_id):
  99. _draw_block_face(surface_tool, [verts[2], verts[0], verts[3], verts[1]], uvs)
  100. other_block_position = block_sub_position + Vector3i.RIGHT
  101. other_block_id = 0
  102. if other_block_position.x == CHUNK_SIZE:
  103. other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
  104. elif data.has(other_block_position):
  105. other_block_id = data[other_block_position]
  106. if block_id != other_block_id and is_block_transparent(other_block_id):
  107. _draw_block_face(surface_tool, [verts[7], verts[5], verts[6], verts[4]], uvs)
  108. other_block_position = block_sub_position + Vector3i.FORWARD
  109. other_block_id = 0
  110. if other_block_position.z == -1:
  111. other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
  112. elif data.has(other_block_position):
  113. other_block_id = data[other_block_position]
  114. if block_id != other_block_id and is_block_transparent(other_block_id):
  115. _draw_block_face(surface_tool, [verts[6], verts[4], verts[2], verts[0]], uvs)
  116. other_block_position = block_sub_position + Vector3i.BACK
  117. other_block_id = 0
  118. if other_block_position.z == CHUNK_SIZE:
  119. other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
  120. elif data.has(other_block_position):
  121. other_block_id = data[other_block_position]
  122. if block_id != other_block_id and is_block_transparent(other_block_id):
  123. _draw_block_face(surface_tool, [verts[3], verts[1], verts[7], verts[5]], uvs)
  124. other_block_position = block_sub_position + Vector3i.DOWN
  125. other_block_id = 0
  126. if other_block_position.y == -1:
  127. other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
  128. elif data.has(other_block_position):
  129. other_block_id = data[other_block_position]
  130. if block_id != other_block_id and is_block_transparent(other_block_id):
  131. _draw_block_face(surface_tool, [verts[4], verts[5], verts[0], verts[1]], bottom_uvs)
  132. other_block_position = block_sub_position + Vector3i.UP
  133. other_block_id = 0
  134. if other_block_position.y == CHUNK_SIZE:
  135. other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
  136. elif data.has(other_block_position):
  137. other_block_id = data[other_block_position]
  138. if block_id != other_block_id and is_block_transparent(other_block_id):
  139. _draw_block_face(surface_tool, [verts[2], verts[3], verts[6], verts[7]], top_uvs)
  140. func _draw_block_face(surface_tool: SurfaceTool, verts, uvs):
  141. surface_tool.set_uv(uvs[1]); surface_tool.add_vertex(verts[1])
  142. surface_tool.set_uv(uvs[2]); surface_tool.add_vertex(verts[2])
  143. surface_tool.set_uv(uvs[3]); surface_tool.add_vertex(verts[3])
  144. surface_tool.set_uv(uvs[2]); surface_tool.add_vertex(verts[2])
  145. surface_tool.set_uv(uvs[1]); surface_tool.add_vertex(verts[1])
  146. surface_tool.set_uv(uvs[0]); surface_tool.add_vertex(verts[0])
  147. func _create_block_collider(block_sub_position):
  148. var collider = CollisionShape3D.new()
  149. collider.shape = BoxShape3D.new()
  150. collider.shape.extents = Vector3.ONE / 2
  151. collider.transform.origin = Vector3(block_sub_position) + Vector3.ONE / 2
  152. add_child(collider)
  153. static func calculate_block_uvs(block_id):
  154. # This method only supports square texture sheets.
  155. var row = block_id / TEXTURE_SHEET_WIDTH
  156. var col = block_id % TEXTURE_SHEET_WIDTH
  157. return [
  158. # Godot 4 has a weird bug where there are seams at the edge
  159. # of the textures. Adding a margin of 0.01 "fixes" it.
  160. TEXTURE_TILE_SIZE * Vector2(col + 0.01, row + 0.01),
  161. TEXTURE_TILE_SIZE * Vector2(col + 0.01, row + 0.99),
  162. TEXTURE_TILE_SIZE * Vector2(col + 0.99, row + 0.01),
  163. TEXTURE_TILE_SIZE * Vector2(col + 0.99, row + 0.99),
  164. ]
  165. static func calculate_block_verts(block_position):
  166. return [
  167. Vector3(block_position.x, block_position.y, block_position.z),
  168. Vector3(block_position.x, block_position.y, block_position.z + 1),
  169. Vector3(block_position.x, block_position.y + 1, block_position.z),
  170. Vector3(block_position.x, block_position.y + 1, block_position.z + 1),
  171. Vector3(block_position.x + 1, block_position.y, block_position.z),
  172. Vector3(block_position.x + 1, block_position.y, block_position.z + 1),
  173. Vector3(block_position.x + 1, block_position.y + 1, block_position.z),
  174. Vector3(block_position.x + 1, block_position.y + 1, block_position.z + 1),
  175. ]
  176. static func is_block_transparent(block_id):
  177. return block_id == 0 or (block_id > 25 and block_id < 30)