voxel_world.gd 4.7 KB

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