bone-forge/world_gen/world_generator.gd
2025-06-27 14:07:21 +02:00

149 lines
4.7 KiB
GDScript

@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)