150 lines
4.7 KiB
GDScript3
150 lines
4.7 KiB
GDScript3
|
@tool
|
||
|
extends Node
|
||
|
var generation_thread: Thread = Thread.new()
|
||
|
#### CHUNK GENERATION ####
|
||
|
@export_category("CHUNK GENERATION")
|
||
|
|
||
|
const CHUNK_SIZE := 256
|
||
|
const CHUNK_BOUNDS := 8
|
||
|
|
||
|
## How detailed the chunk should be. This could be made a const when debug phase is finished
|
||
|
@export_range(4,256,4) var chunk_resolution :=32:
|
||
|
set(new_resolution):
|
||
|
chunk_resolution = new_resolution
|
||
|
update_all_mesh()
|
||
|
|
||
|
#### WORLD GENERATION ####
|
||
|
@export_category("WORLD GENERATION")
|
||
|
@export_tool_button("Generate Chunks") var generate_chunk_callable = generate.bind(true)
|
||
|
@export var noise: FastNoiseLite:
|
||
|
set(new_noise):
|
||
|
noise = new_noise
|
||
|
update_all_mesh()
|
||
|
if noise:
|
||
|
noise.changed.connect(update_all_mesh) #change all thes meshes after eahc changes
|
||
|
@export_range(4.0,128.0,4.0) var height := 64.0:
|
||
|
set(new_height):
|
||
|
height = new_height
|
||
|
update_all_mesh()
|
||
|
|
||
|
var loaded_chunks := {}
|
||
|
|
||
|
|
||
|
# Called when the node enters the scene tree for the first time. ALSO IN EDITOR
|
||
|
func _ready() -> void:
|
||
|
generate(true)
|
||
|
|
||
|
|
||
|
func generate(_ForceRegen:bool=false):
|
||
|
print('Generation starts')
|
||
|
generate_chunks_around(Vector3(0,0,0), _ForceRegen)
|
||
|
|
||
|
|
||
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||
|
func _process(_delta: float) -> void:
|
||
|
pass
|
||
|
|
||
|
|
||
|
func generate_noise(_seed:float):
|
||
|
noise = FastNoiseLite.new()
|
||
|
|
||
|
|
||
|
# This function is better called in another thread, doing all the heavy process before adding it to the scene tree
|
||
|
func generate_chunk(position: Vector3, _ForceRegen:bool = false):
|
||
|
|
||
|
WorkerThreadPool.add_task(func():
|
||
|
var mesh_instance = MeshInstance3D.new()
|
||
|
update_mesh(mesh_instance)
|
||
|
mesh_instance.position = position
|
||
|
call_deferred("add_child", mesh_instance)
|
||
|
var chunk_coord = get_chunk_coord(position)
|
||
|
loaded_chunks[chunk_coord] = mesh_instance
|
||
|
print("chunk at : ",str(position),", coord ",str(chunk_coord), " loaded")
|
||
|
|
||
|
)
|
||
|
|
||
|
|
||
|
func get_height(x:float,y:float) -> float:
|
||
|
return noise.get_noise_2d(x,y) * height
|
||
|
|
||
|
|
||
|
func get_normal(x:float,y:float) -> Vector3:
|
||
|
var epsilon := CHUNK_SIZE / chunk_resolution
|
||
|
var normal := Vector3(
|
||
|
(get_height(x + epsilon,y) - get_height(x - epsilon, y))/(2.0 *epsilon),
|
||
|
1.0,
|
||
|
(get_height(x, y + epsilon) - get_height(x, y - epsilon))/(2.0 *epsilon)
|
||
|
|
||
|
)
|
||
|
return normal.normalized()
|
||
|
|
||
|
|
||
|
func update_all_mesh():
|
||
|
for chunk in loaded_chunks:
|
||
|
update_mesh(loaded_chunks[chunk])
|
||
|
|
||
|
|
||
|
func update_mesh(chunk_terrain_instance:MeshInstance3D):
|
||
|
var plane: PlaneMesh = PlaneMesh.new() #load("res://world_gen/new_plane_mesh.tres").duplicate()
|
||
|
plane.subdivide_depth = chunk_resolution
|
||
|
plane.subdivide_width = chunk_resolution
|
||
|
plane.size = Vector2(CHUNK_SIZE,CHUNK_SIZE)
|
||
|
|
||
|
var plane_arrays := plane.get_mesh_arrays()
|
||
|
var vertex_array: PackedVector3Array = plane_arrays[ArrayMesh.ARRAY_VERTEX]
|
||
|
var normal_array: PackedVector3Array = plane_arrays[ArrayMesh.ARRAY_NORMAL]
|
||
|
var tangent_array: PackedFloat32Array = plane_arrays[ArrayMesh.ARRAY_TANGENT]
|
||
|
|
||
|
for i:int in vertex_array.size():
|
||
|
var vertex := vertex_array[i]
|
||
|
# Convert local vertex coordinates to world coordinates
|
||
|
var world_x = chunk_terrain_instance.position.x + vertex.x
|
||
|
var world_z = chunk_terrain_instance.position.z + vertex.z
|
||
|
var normal := Vector3.UP
|
||
|
var tangent := Vector3.RIGHT
|
||
|
|
||
|
if noise:
|
||
|
vertex.y = get_height(world_x, world_z)
|
||
|
normal = get_normal(world_x, world_z)
|
||
|
tangent = normal.cross(Vector3.UP)
|
||
|
|
||
|
vertex_array[i] = vertex
|
||
|
normal_array[i] = normal
|
||
|
tangent_array[4*i] = tangent.x
|
||
|
tangent_array[4*i+1] = tangent.y
|
||
|
tangent_array[4*i+2] = tangent.z
|
||
|
tangent_array[4*i+3] = 1.0
|
||
|
|
||
|
var array_mesh := ArrayMesh.new()
|
||
|
array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES,plane_arrays)
|
||
|
chunk_terrain_instance.mesh = array_mesh
|
||
|
|
||
|
|
||
|
## Convert position to chunk coordinates
|
||
|
func get_chunk_coord(position:Vector3) -> Vector2i:
|
||
|
var coord_chunk_x = int(floor(position.x / CHUNK_SIZE))
|
||
|
var coord_chunk_z = int(floor(position.z / CHUNK_SIZE))
|
||
|
return Vector2i(coord_chunk_x,coord_chunk_z)
|
||
|
|
||
|
|
||
|
func generate_chunks_around(position:Vector3,ForceRegen:bool = false):
|
||
|
|
||
|
# Convert position to chunk coordinates
|
||
|
var position_to_coord := get_chunk_coord(position)
|
||
|
|
||
|
print("closest chunk : "+str(Vector2i(position_to_coord.x,position_to_coord.y)))
|
||
|
|
||
|
for x in range(position_to_coord.x - CHUNK_BOUNDS,position_to_coord.x + CHUNK_BOUNDS+1):
|
||
|
for z in range(position_to_coord.y - CHUNK_BOUNDS, position_to_coord.y + CHUNK_BOUNDS+1):
|
||
|
var chunk_coord := Vector2i(x,z)
|
||
|
var chunck_position := Vector3i(x * CHUNK_SIZE,0,z * CHUNK_SIZE)
|
||
|
print("trying to load chunk at coordinate : "+str(chunk_coord))
|
||
|
if ForceRegen:
|
||
|
loaded_chunks.clear()
|
||
|
for child in get_children():
|
||
|
child.queue_free()
|
||
|
|
||
|
if not loaded_chunks.has(chunk_coord):
|
||
|
print("generate chunk at : "+str(chunck_position))
|
||
|
generate_chunk(chunck_position)
|