voxel_world.gd 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. extends Node
  2. # This file manages the creation and deletion of Chunks.
  3. const CHUNK_MIDPOINT = Vector3(0.5, 0.5, 0.5) * Chunk.CHUNK_SIZE
  4. const CHUNK_END_SIZE = Chunk.CHUNK_SIZE - 1
  5. var render_distance: int:
  6. set(value):
  7. render_distance = value
  8. _delete_distance = value + 2
  9. var _delete_distance := 0
  10. var effective_render_distance := 0
  11. var _old_player_chunk := Vector3i()
  12. var _generating := true
  13. var _deleting := false
  14. var _chunks := {}
  15. @onready var player: CharacterBody3D = $"../Player"
  16. func _process(_delta: float) -> void:
  17. render_distance = Settings.render_distance
  18. var player_chunk := Vector3i((player.transform.origin / Chunk.CHUNK_SIZE).round())
  19. if _deleting or player_chunk != _old_player_chunk:
  20. _delete_far_away_chunks(player_chunk)
  21. _generating = true
  22. if not _generating:
  23. return
  24. # Try to generate chunks ahead of time based on where the player is moving.
  25. @warning_ignore("integer_division")
  26. player_chunk.y += roundi(clampf(player.velocity.y, -render_distance / 4, render_distance / 4))
  27. # Check existing chunks within range. If it doesn't exist, create it.
  28. for x in range(player_chunk.x - effective_render_distance, player_chunk.x + effective_render_distance):
  29. for y in range(player_chunk.y - effective_render_distance, player_chunk.y + effective_render_distance):
  30. for z in range(player_chunk.z - effective_render_distance, player_chunk.z + effective_render_distance):
  31. var chunk_position := Vector3i(x, y, z)
  32. if Vector3(player_chunk).distance_to(Vector3(chunk_position)) > render_distance:
  33. continue
  34. if _chunks.has(chunk_position):
  35. continue
  36. var chunk := Chunk.new()
  37. chunk.chunk_position = chunk_position
  38. _chunks[chunk_position] = chunk
  39. add_child(chunk)
  40. return
  41. # If we didn't generate any chunks (and therefore didn't return), what next?
  42. if effective_render_distance < render_distance:
  43. # We can move on to the next stage by increasing the effective distance.
  44. effective_render_distance += 1
  45. else:
  46. # Effective render distance is maxed out, done generating.
  47. _generating = false
  48. func get_block_global_position(block_global_position: Vector3i) -> int:
  49. var chunk_position := Vector3i((block_global_position / Chunk.CHUNK_SIZE))
  50. if _chunks.has(chunk_position):
  51. var chunk: Chunk = _chunks[chunk_position]
  52. var sub_position := Vector3i(Vector3(block_global_position).posmod(Chunk.CHUNK_SIZE))
  53. if chunk.data.has(sub_position):
  54. return chunk.data[sub_position]
  55. return 0
  56. func set_block_global_position(block_global_position: Vector3i, block_id: int) -> void:
  57. var chunk_position := Vector3i((Vector3(block_global_position) / Chunk.CHUNK_SIZE).floor())
  58. var chunk: Chunk = _chunks[chunk_position]
  59. var sub_position := Vector3i(Vector3(block_global_position).posmod(Chunk.CHUNK_SIZE))
  60. if block_id == 0:
  61. chunk.data.erase(sub_position)
  62. else:
  63. chunk.data[sub_position] = block_id
  64. chunk.regenerate()
  65. # We also might need to regenerate some neighboring chunks.
  66. if Chunk.is_block_transparent(block_id):
  67. if sub_position.x == 0:
  68. _chunks[chunk_position + Vector3i.LEFT].regenerate()
  69. elif sub_position.x == CHUNK_END_SIZE:
  70. _chunks[chunk_position + Vector3i.RIGHT].regenerate()
  71. if sub_position.z == 0:
  72. _chunks[chunk_position + Vector3i.FORWARD].regenerate()
  73. elif sub_position.z == CHUNK_END_SIZE:
  74. _chunks[chunk_position + Vector3i.BACK].regenerate()
  75. if sub_position.y == 0:
  76. _chunks[chunk_position + Vector3i.DOWN].regenerate()
  77. elif sub_position.y == CHUNK_END_SIZE:
  78. _chunks[chunk_position + Vector3i.UP].regenerate()
  79. func clean_up() -> void:
  80. for chunk_position_key: Vector3i in _chunks.keys():
  81. var thread: Thread = _chunks[chunk_position_key]._thread
  82. if thread:
  83. thread.wait_to_finish()
  84. _chunks = {}
  85. set_process(false)
  86. for c in get_children():
  87. c.free()
  88. func _delete_far_away_chunks(player_chunk: Vector3i) -> void:
  89. _old_player_chunk = player_chunk
  90. # If we need to delete chunks, give the new chunk system a chance to catch up.
  91. effective_render_distance = maxi(1, effective_render_distance - 1)
  92. var deleted_this_frame := 0
  93. # We should delete old chunks more aggressively if moving fast.
  94. # An easy way to calculate this is by using the effective render distance.
  95. # The specific values in this formula are arbitrary and from experimentation.
  96. var max_deletions := clampi(2 * (render_distance - effective_render_distance), 2, 8)
  97. # Also take the opportunity to delete far away chunks.
  98. for chunk_position_key: Vector3i in _chunks.keys():
  99. if Vector3(player_chunk).distance_to(Vector3(chunk_position_key)) > _delete_distance:
  100. var thread: Thread = _chunks[chunk_position_key]._thread
  101. if thread:
  102. thread.wait_to_finish()
  103. _chunks[chunk_position_key].queue_free()
  104. _chunks.erase(chunk_position_key)
  105. deleted_this_frame += 1
  106. # Limit the amount of deletions per frame to avoid lag spikes.
  107. if deleted_this_frame > max_deletions:
  108. # Continue deleting next frame.
  109. _deleting = true
  110. return
  111. # We're done deleting.
  112. _deleting = false