1class_name Chunk
2extends StaticBody
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
7const CHUNK_SIZE = 16 # Keep in sync with TerrainGenerator.
8const TEXTURE_SHEET_WIDTH = 8
9
10const CHUNK_LAST_INDEX = CHUNK_SIZE - 1
11const TEXTURE_TILE_SIZE = 1.0 / TEXTURE_SHEET_WIDTH
12
13var data = {}
14var chunk_position = Vector3() # TODO: Vector3i
15
16var _thread
17
18onready var voxel_world = get_parent()
19
20
21func _ready():
22	transform.origin = chunk_position * CHUNK_SIZE
23	name = str(chunk_position)
24	if Settings.world_type == 0:
25		data = TerrainGenerator.random_blocks()
26	else:
27		data = TerrainGenerator.flat(chunk_position)
28
29	# We can only add colliders in the main thread due to physics limitations.
30	_generate_chunk_collider()
31	# However, we can use a thread for mesh generation.
32	_thread = Thread.new()
33	_thread.start(self, "_generate_chunk_mesh")
34
35
36func regenerate():
37	# Clear out all old nodes first.
38	for c in get_children():
39		remove_child(c)
40		c.queue_free()
41
42	# Then generate new ones.
43	_generate_chunk_collider()
44	_generate_chunk_mesh(0)
45
46
47func _generate_chunk_collider():
48	if data.empty():
49		# Avoid errors caused by StaticBody not having colliders.
50		_create_block_collider(Vector3.ZERO)
51		collision_layer = 0
52		collision_mask = 0
53		return
54
55	# For each block, generate a collider. Ensure collision layers are enabled.
56	collision_layer = 0xFFFFF
57	collision_mask = 0xFFFFF
58	for block_position in data.keys():
59		var block_id = data[block_position]
60		if block_id != 27 and block_id != 28:
61			_create_block_collider(block_position)
62
63
64func _generate_chunk_mesh(_this_argument_exists_due_to_bug_9924):
65	if data.empty():
66		return
67
68	var surface_tool = SurfaceTool.new()
69	surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES)
70
71	# For each block, add data to the SurfaceTool and generate a collider.
72	for block_position in data.keys():
73		var block_id = data[block_position]
74		_draw_block_mesh(surface_tool, block_position, block_id)
75
76	# Create the chunk's mesh from the SurfaceTool data.
77	surface_tool.generate_normals()
78	surface_tool.generate_tangents()
79	surface_tool.index()
80	var array_mesh = surface_tool.commit()
81	var mi = MeshInstance.new()
82	mi.mesh = array_mesh
83	mi.material_override = preload("res://world/textures/material.tres")
84	add_child(mi)
85
86
87func _draw_block_mesh(surface_tool, block_sub_position, block_id):
88	var verts = calculate_block_verts(block_sub_position)
89	var uvs = calculate_block_uvs(block_id)
90	var top_uvs = uvs
91	var bottom_uvs = uvs
92
93	# Bush blocks get drawn in their own special way.
94	if block_id == 27 or block_id == 28:
95		_draw_block_face(surface_tool, [verts[2], verts[0], verts[7], verts[5]], uvs)
96		_draw_block_face(surface_tool, [verts[7], verts[5], verts[2], verts[0]], uvs)
97		_draw_block_face(surface_tool, [verts[3], verts[1], verts[6], verts[4]], uvs)
98		_draw_block_face(surface_tool, [verts[6], verts[4], verts[3], verts[1]], uvs)
99		return
100
101	# Allow some blocks to have different top/bottom textures.
102	if block_id == 3: # Grass.
103		top_uvs = calculate_block_uvs(0)
104		bottom_uvs = calculate_block_uvs(2)
105	elif block_id == 5: # Furnace.
106		top_uvs = calculate_block_uvs(31)
107		bottom_uvs = top_uvs
108	elif block_id == 12: # Log.
109		top_uvs = calculate_block_uvs(30)
110		bottom_uvs = top_uvs
111	elif block_id == 19: # Bookshelf.
112		top_uvs = calculate_block_uvs(4)
113		bottom_uvs = top_uvs
114
115	# Main rendering code for normal blocks.
116	var other_block_position = block_sub_position + Vector3.LEFT
117	var other_block_id = 0
118	if other_block_position.x == -1:
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[2], verts[0], verts[3], verts[1]], uvs)
124
125	other_block_position = block_sub_position + Vector3.RIGHT
126	other_block_id = 0
127	if other_block_position.x == CHUNK_SIZE:
128		other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
129	elif data.has(other_block_position):
130		other_block_id = data[other_block_position]
131	if block_id != other_block_id and is_block_transparent(other_block_id):
132		_draw_block_face(surface_tool, [verts[7], verts[5], verts[6], verts[4]], uvs)
133
134	other_block_position = block_sub_position + Vector3.FORWARD
135	other_block_id = 0
136	if other_block_position.z == -1:
137		other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
138	elif data.has(other_block_position):
139		other_block_id = data[other_block_position]
140	if block_id != other_block_id and is_block_transparent(other_block_id):
141		_draw_block_face(surface_tool, [verts[6], verts[4], verts[2], verts[0]], uvs)
142
143	other_block_position = block_sub_position + Vector3.BACK
144	other_block_id = 0
145	if other_block_position.z == CHUNK_SIZE:
146		other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
147	elif data.has(other_block_position):
148		other_block_id = data[other_block_position]
149	if block_id != other_block_id and is_block_transparent(other_block_id):
150		_draw_block_face(surface_tool, [verts[3], verts[1], verts[7], verts[5]], uvs)
151
152	other_block_position = block_sub_position + Vector3.DOWN
153	other_block_id = 0
154	if other_block_position.y == -1:
155		other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
156	elif data.has(other_block_position):
157		other_block_id = data[other_block_position]
158	if block_id != other_block_id and is_block_transparent(other_block_id):
159		_draw_block_face(surface_tool, [verts[4], verts[5], verts[0], verts[1]], bottom_uvs)
160
161	other_block_position = block_sub_position + Vector3.UP
162	other_block_id = 0
163	if other_block_position.y == CHUNK_SIZE:
164		other_block_id = voxel_world.get_block_global_position(other_block_position + chunk_position * CHUNK_SIZE)
165	elif data.has(other_block_position):
166		other_block_id = data[other_block_position]
167	if block_id != other_block_id and is_block_transparent(other_block_id):
168		_draw_block_face(surface_tool, [verts[2], verts[3], verts[6], verts[7]], top_uvs)
169
170
171func _draw_block_face(surface_tool, verts, uvs):
172	surface_tool.add_uv(uvs[1]); surface_tool.add_vertex(verts[1])
173	surface_tool.add_uv(uvs[2]); surface_tool.add_vertex(verts[2])
174	surface_tool.add_uv(uvs[3]); surface_tool.add_vertex(verts[3])
175
176	surface_tool.add_uv(uvs[2]); surface_tool.add_vertex(verts[2])
177	surface_tool.add_uv(uvs[1]); surface_tool.add_vertex(verts[1])
178	surface_tool.add_uv(uvs[0]); surface_tool.add_vertex(verts[0])
179
180
181func _create_block_collider(block_sub_position):
182	var collider = CollisionShape.new()
183	collider.shape = BoxShape.new()
184	collider.shape.extents = Vector3.ONE / 2
185	collider.transform.origin = block_sub_position + Vector3.ONE / 2
186	add_child(collider)
187
188
189static func calculate_block_uvs(block_id):
190	# This method only supports square texture sheets.
191	var row = block_id / TEXTURE_SHEET_WIDTH
192	var col = block_id % TEXTURE_SHEET_WIDTH
193
194	return [
195		TEXTURE_TILE_SIZE * Vector2(col, row),
196		TEXTURE_TILE_SIZE * Vector2(col, row + 1),
197		TEXTURE_TILE_SIZE * Vector2(col + 1, row),
198		TEXTURE_TILE_SIZE * Vector2(col + 1, row + 1),
199	]
200
201
202static func calculate_block_verts(block_position):
203	return [
204		Vector3(block_position.x, block_position.y, block_position.z),
205		Vector3(block_position.x, block_position.y, block_position.z + 1),
206		Vector3(block_position.x, block_position.y + 1, block_position.z),
207		Vector3(block_position.x, block_position.y + 1, block_position.z + 1),
208		Vector3(block_position.x + 1, block_position.y, block_position.z),
209		Vector3(block_position.x + 1, block_position.y, block_position.z + 1),
210		Vector3(block_position.x + 1, block_position.y + 1, block_position.z),
211		Vector3(block_position.x + 1, block_position.y + 1, block_position.z + 1),
212	]
213
214
215static func is_block_transparent(block_id):
216	return block_id == 0 or (block_id > 25 and block_id < 30)
217