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