fix wind!

This commit is contained in:
2026-04-21 12:40:59 +02:00
parent 7a1ac3b698
commit 9a26a4114b
48 changed files with 2376 additions and 234 deletions

View File

@@ -11,10 +11,15 @@ extends Node3D
@export var eye_line: int = 3
@export var district_scale: float = 0.05
@export_group("Lamppost")
@export var lamppost_wire_material: ShaderMaterial
@export_range(0.01, 1.0) var wire_thickness: float = 0.05
var board: Dictionary = {}
var last_pos_train: Vector2i = Vector2i(999999, 999999)
var noise_generator: FastNoiseLite
var altitude_generator: FastNoiseLite
var wire_connections: Dictionary = {}
var manual_biome: Biome = null
@@ -51,8 +56,7 @@ func _update_set_pieces() -> void:
var grid_z = roundi(sp.global_position.z / chunk_size)
local_biome = _get_procedural_biome_name(noise_generator.get_noise_2d(grid_x, grid_z))
print("👉 Analyze: '", sp.name, "' | Need: [", sp.exclusive_biome, "] | Current biome is: [", local_biome, "]")
print("Analyze: '", sp.name, "' | Need: [", sp.exclusive_biome, "] | Current biome is: [", local_biome, "]")
if local_biome == sp.exclusive_biome:
sp.show()
sp.process_mode = Node.PROCESS_MODE_INHERIT
@@ -60,23 +64,24 @@ func _update_set_pieces() -> void:
else:
sp.hide()
sp.process_mode = Node.PROCESS_MODE_DISABLED
print(" ❌ Temple is a invisible ghost.")
print(" ❌ Temple is an invisible ghost.")
func _destroy_and_regenrate_world() -> void:
#1. Turn off old temples
#Turn off old temples
_update_set_pieces()
#2. Destroy all models
#Destroy all models
for pos in board.keys():
var cella = board[pos]
if cella["tipo"] == "bioma":
if cella.has("nodo") and is_instance_valid(cella["nodo"]):
cella["nodo"].queue_free()
#3. Clean board
#Clean board and wire connections
board.clear()
wire_connections.clear()
#4. Create train
#Create train
last_pos_train = Vector2i(999999, 999999)
_train_radar()
last_pos_train = Vector2i(999999, 999999)
@@ -96,13 +101,22 @@ func _physics_process(_delta: float) -> void:
_clean_far_chunks(current_pos)
_train_radar()
func _generate_pieces_around_train(centro: Vector2i) -> void:
func collect_all_chunkinfo(root: Node, list: Array[Node]) -> void:
if root == null: return
if root.has_method("get_rotated_data") or "have_lamppost" in root:
list.append(root)
for child in root.get_children():
collect_all_chunkinfo(child, list)
func _generate_pieces_around_train(center: Vector2i) -> void:
for x in range(-eye_line, eye_line + 1):
for z in range(-eye_line, eye_line + 1):
var grid_pos = centro + Vector2i(x, z)
var grid_pos = center + Vector2i(x, z)
if not board.has(grid_pos):
var ce_obstacle = _register_cell_with_laser(grid_pos)
var ce_obstacle = _register_cell_with_ray(grid_pos)
if not ce_obstacle:
_add_compatible_biome(grid_pos)
@@ -123,7 +137,7 @@ func _get_procedural_biome_name(value: float) -> String:
var index = clamp(int(normalized_value * biome_list.size()), 0, biome_list.size() - 1)
return biome_list[index].name
func _register_cell_with_laser(grid_pos: Vector2i) -> bool:
func _register_cell_with_ray(grid_pos: Vector2i) -> bool:
if board.has(grid_pos): return true
var world_pos = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size)
@@ -136,28 +150,72 @@ func _register_cell_with_laser(grid_pos: Vector2i) -> bool:
if result.size() > 0:
var collider = result["collider"]
var current_node = collider
var root_chunk = collider
var info_list: Array[Node] = []
#Find parent node
while root_chunk != null and root_chunk != get_tree().root:
collect_all_chunkinfo(root_chunk, info_list)
if info_list.size() > 0:
break
root_chunk = root_chunk.get_parent()
var right_info_node = null
#Find closest chunk to the ray
if info_list.size() > 0:
var min_distance = 999999.0
var center_cell_pos = Vector3(world_pos.x, 0, world_pos.z)
for info in info_list:
if info is Node3D:
var pos_info = Vector3(info.global_position.x, 0, info.global_position.z)
var dist = center_cell_pos.distance_to(pos_info)
if dist < min_distance:
min_distance = dist
right_info_node = info
#If no node is found use first element
if right_info_node == null and info_list.size() > 0:
right_info_node = info_list[0]
var exit_found = {"north": false, "est": false, "south": false, "west": false}
var height_found = {"north": 0, "est": 0, "south": 0, "west": 0}
var have_lamppost = false
while current_node != null and current_node != get_tree().root:
if current_node.has_method("get_rotated_data"):
var rotation_steps = roundi(rad_to_deg(current_node.global_rotation.y) / -90.0)
var data = current_node.get_rotated_data(rotation_steps)
#Get node info
if right_info_node != null:
if right_info_node.has_method("get_rotated_data"):
var rotation_steps = roundi(rad_to_deg(right_info_node.global_rotation.y) / -90.0)
var data = right_info_node.get_rotated_data(rotation_steps)
exit_found = data["connections"]
height_found = data["heights"]
break
current_node = current_node.get_parent()
if "have_lamppost" in right_info_node:
have_lamppost = right_info_node.have_lamppost
#Add piece to the grid
board[grid_pos] = {
"type": "obstacle",
"exit": exit_found,
"heights": height_found,
"node": root_chunk,
"info": right_info_node,
"have_lamppost": have_lamppost
}
if have_lamppost:
_connect_lamppost_wires(grid_pos)
board[grid_pos] = {"type": "obstacle", "exit": exit_found, "heights": height_found}
return true
return false
func _add_compatible_biome(grid_pos: Vector2i) -> void:
var req_conn_north = _needed_connections(grid_pos + Vector2i(0, -1), "south")
var req_conn_est = _needed_connections(grid_pos + Vector2i(1, 0), "west")
var req_conn_south = _needed_connections(grid_pos + Vector2i(0, 1), "north")
var req_conn_west = _needed_connections(grid_pos + Vector2i(-1, 0), "est")
var req_conn_north = _needed_connection(grid_pos + Vector2i(0, -1), "south")
var req_conn_est = _needed_connection(grid_pos + Vector2i(1, 0), "west")
var req_conn_south = _needed_connection(grid_pos + Vector2i(0, 1), "north")
var req_conn_west = _needed_connection(grid_pos + Vector2i(-1, 0), "est")
var req_height_north = _needed_heights(grid_pos + Vector2i(0, -1), "south")
var req_height_est = _needed_heights(grid_pos + Vector2i(1, 0), "west")
@@ -172,8 +230,11 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void:
for scene in zone_catalogue:
var test_chunk = scene.instantiate()
if test_chunk.has_method("get_rotated_data"):
var test_list: Array[Node] = []
collect_all_chunkinfo(test_chunk, test_list)
var info_test = test_list[0] if test_list.size() > 0 else null
if info_test != null and info_test.has_method("get_rotated_data"):
for rot in range(4):
var data = test_chunk.get_rotated_data(rot)
var u_conn = data["connections"]
@@ -221,30 +282,53 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void:
new_chunk.position = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size)
new_chunk.rotation.y = choise.rotation * (-PI / 2.0)
board[grid_pos] = {"type": "biome", "exit": choise.data["connections"], "heights": choise.data["heights"], "nodo": new_chunk}
else:
push_warning("⚠️ One piece miss for position " + str(grid_pos) + "!")
var new_list: Array[Node] = []
collect_all_chunkinfo(new_chunk, new_list)
var info_new = new_list[0] if new_list.size() > 0 else null
var fmt_alt = func(req): return str(req) if req != -1 else "Free"
#var fmt_conn = func(req): return "Yes" if req == 1 else ("No" if req == 0 else "Free")
print("We need missed piece -> North: ", fmt_alt.call(req_height_north), " | Est: ", fmt_alt.call(req_height_est), " | South: ", fmt_alt.call(req_height_south), " | West: ", fmt_alt.call(req_height_west))
var have_lamppost = false
if info_new != null and "have_lamppost" in new_chunk:
have_lamppost = info_new.have_lamppost
board[grid_pos] = {
"type": "bioma",
"exit": choise.data["connections"],
"heights": choise.data["heights"],
"node": new_chunk,
"info": info_new,
"have_lamppost": have_lamppost
}
if have_lamppost:
_connect_lamppost_wires(grid_pos)
else:
var backup = zone_catalogue[0].instantiate()
add_child(backup)
backup.position = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size)
var backup_list: Array[Node] = []
collect_all_chunkinfo(backup, backup_list)
var info_backup = backup_list[0] if backup_list.size() > 0 else null
var safe_heights = {
"north": req_height_north if req_height_north != -1 else height_target,
"est": req_height_est if req_height_est != -1 else height_target,
"south": req_height_south if req_height_south != -1 else height_target,
"west": req_height_west if req_height_west != -1 else height_target
}
board[grid_pos] = {"type": "biome", "exit": {"north":false, "est":false, "south":false, "west":false}, "heights": safe_heights, "node": backup}
board[grid_pos] = {
"type": "biome",
"exit": {"north":false, "est":false, "south":false, "west":false},
"heights": safe_heights,
"node": backup,
"info": info_backup,
"have_lamppost": false
}
func _needed_connections(near_pos: Vector2i, side_needed: String) -> int:
func _needed_connection(near_pos: Vector2i, side_needed: String) -> int:
if not board.has(near_pos):
_register_cell_with_laser(near_pos)
_register_cell_with_ray(near_pos)
if board.has(near_pos) and board[near_pos].has("exit"):
return 1 if board[near_pos]["exit"][side_needed] else 0
return -1
@@ -267,7 +351,9 @@ func _train_radar() -> void:
var current_progress = rail_path.train_progress
var total_length = rail_path.curve.get_baked_length()
#var exchange_distance = 0.0
#var next_biome = ""
for step in range(1, 31):
var next_progress = wrapf(current_progress + (step * chunk_size), 0.0, total_length)
var next_local_pos = rail_path.curve.sample_baked(next_progress, true)
@@ -275,11 +361,11 @@ func _train_radar() -> void:
var f_x = roundi(next_global_pos.x / chunk_size)
var f_z = roundi(next_global_pos.z / chunk_size)
var next_biome = _get_procedural_biome_name(noise_generator.get_noise_2d(f_x, f_z))
var future_biome = _get_procedural_biome_name(noise_generator.get_noise_2d(f_x, f_z))
if next_biome != current_biome:
#distanza_cambio = step * chunk_size
#prossimo_bioma = next_biome
if future_biome != current_biome:
#exchange_distance = step * chunk_size
#next_biome = future_biome
break
func _clean_far_chunks(centro_attuale: Vector2i) -> void:
@@ -300,3 +386,186 @@ func _clean_far_chunks(centro_attuale: Vector2i) -> void:
for pos in cells_to_remove:
board.erase(pos)
func _get_lampposts(info_node: Node, side: String) -> Array[Node3D]:
var lampposts: Array[Node3D] = []
if side == "sx":
if "connection_left" in info_node and info_node.connection_left != null:
for p in info_node.connection_left:
if is_instance_valid(p): lampposts.append(p)
#if lampposts.is_empty() and "nodo_attacco_sx" in nodo_info and is_instance_valid(nodo_info.get("nodo_attacco_sx")):
# lampposts.append(nodo_info.get("nodo_attacco_sx"))
else:
if "connection_right" in info_node and info_node.connection_right != null:
for p in info_node.connection_right:
if is_instance_valid(p): lampposts.append(p)
#if lampposts.is_empty() and "nodo_attacco_dx" in nodo_info and is_instance_valid(nodo_info.get("nodo_attacco_dx")):
# lampposts.append(nodo_info.get("nodo_attacco_dx"))
return lampposts
func _connect_lamppost_wires(new_board_pos: Vector2i) -> void:
var new_cell = board[new_board_pos]
var new_root = new_cell["node"]
var new_info = new_cell["info"]
if new_info == null: return
if new_info is Node3D: new_info.force_update_transform()
var new_sx = _get_lampposts(new_info, "sx")
var new_dx = _get_lampposts(new_info, "dx")
if new_sx.is_empty() or new_dx.is_empty(): return
var new_id = new_root.get_instance_id()
if not wire_connections.has(new_id): wire_connections[new_id] = 0
if wire_connections[new_id] >= 2: return
var p_sx_my_best = null; var p_dx_my_best = null
var p_sx_your_best = null; var p_dx_your_best = null
var best_closest_to_root = null
var best_distance = 999999.0
var ray_research = 3
for x in range(-ray_research, ray_research + 1):
for z in range(-ray_research, ray_research + 1):
if x == 0 and z == 0: continue
var closest_post = new_board_pos + Vector2i(x, z)
if board.has(closest_post) and board[closest_post].get("have_lamppost", false):
var closest_root = board[closest_post]["node"]
var closest_info = board[closest_post]["info"]
if not is_instance_valid(closest_root) or closest_root == new_root or closest_info == null: continue
var closest_id = closest_root.get_instance_id()
var closest_connections = wire_connections.get(closest_id, 0)
if closest_connections >= 2: continue
if closest_info is Node3D: closest_info.force_update_transform()
var closest_sx = _get_lampposts(closest_info, "sx")
var closest_dx = _get_lampposts(closest_info, "dx")
if closest_sx.is_empty() or closest_dx.is_empty(): continue
for i_m in range(new_sx.size()):
for i_t in range(closest_sx.size()):
var my_c = (new_sx[i_m].global_position + new_dx[i_m].global_position) / 2.0
var your_c = (closest_sx[i_t].global_position + closest_dx[i_t].global_position) / 2.0
var dist = my_c.distance_to(your_c)
if dist < best_distance:
best_distance = dist
best_closest_to_root = closest_root
p_sx_my_best = new_sx[i_m]
p_dx_my_best = new_dx[i_m]
p_sx_your_best = closest_sx[i_t]
p_dx_your_best = closest_dx[i_t]
if best_closest_to_root == null:
for x in range(-ray_research, ray_research + 1):
for z in range(-ray_research, ray_research + 1):
if x == 0 and z == 0: continue
var closest_pos = new_board_pos + Vector2i(x, z)
if board.has(closest_pos) and board[closest_pos].get("have_lamppost", false):
var closest_root = board[closest_pos]["node"]
var closest_info = board[closest_pos]["info"]
if not is_instance_valid(closest_root) or closest_root == new_root or closest_info == null: continue
if closest_info is Node3D: closest_info.force_update_transform()
var closest_sx = _get_lampposts(closest_info, "sx")
var closest_dx = _get_lampposts(closest_info, "dx")
if closest_sx.is_empty() or closest_dx.is_empty(): continue
for i_m in range(new_sx.size()):
for i_t in range(closest_sx.size()):
var c_mio = (new_sx[i_m].global_position + new_dx[i_m].global_position) / 2.0
var c_tuo = (closest_sx[i_t].global_position + closest_dx[i_t].global_position) / 2.0
var dist = c_mio.distance_to(c_tuo)
if dist < best_distance:
best_distance = dist
best_closest_to_root = closest_root
p_sx_my_best = new_sx[i_m]
p_dx_my_best = new_dx[i_m]
p_sx_your_best = closest_sx[i_t]
p_dx_your_best = closest_dx[i_t]
var max_dist = chunk_size * 8.0
if best_closest_to_root != null and best_distance < max_dist:
var dist_streight = p_sx_my_best.global_position.distance_to(p_sx_your_best.global_position) + p_dx_my_best.global_position.distance_to(p_dx_your_best.global_position)
var dist_cross = p_sx_my_best.global_position.distance_to(p_dx_your_best.global_position) + p_dx_my_best.global_position.distance_to(p_sx_your_best.global_position)
if dist_streight <= dist_cross:
_draw_parable(p_sx_my_best.global_position, p_sx_your_best.global_position, new_root)
_draw_parable(p_dx_my_best.global_position, p_dx_your_best.global_position, new_root)
else:
_draw_parable(p_sx_my_best.global_position, p_dx_your_best.global_position, new_root)
_draw_parable(p_dx_my_best.global_position, p_sx_your_best.global_position, new_root)
wire_connections[new_id] += 1
var closest_id = best_closest_to_root.get_instance_id()
if not wire_connections.has(closest_id): wire_connections[closest_id] = 0
wire_connections[closest_id] += 1
func _draw_parable(p1: Vector3, p2: Vector3, parent: Node3D) -> void:
var segments = 15
var lowering = 1.5
var curve_points = []
var sag_factors = []
for i in range(segments + 1):
var t = float(i) / float(segments)
var global_post = p1.lerp(p2, t)
var gravity = lowering * (1.0 - pow(2.0 * t - 1.0, 2.0))
global_post.y -= gravity
curve_points.append(parent.to_local(global_post))
sag_factors.append(sin(t * PI))
var st = SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
var lati = 4
var ray = wire_thickness / 2.0
for i in range(curve_points.size() - 1):
var p_current = curve_points[i]
var p_next = curve_points[i+1]
var sag_currnet = sag_factors[i]
var sag_next = sag_factors[i+1]
var dir = (p_next - p_current).normalized()
var up = Vector3.UP
var right = dir.cross(up).normalized()
if right.length_squared() < 0.01:
right = dir.cross(Vector3.RIGHT).normalized()
up = right.cross(dir).normalized()
for s in range(lati):
var ang1 = (float(s) / lati) * TAU
var ang2 = (float((s + 1) % lati) / lati) * TAU
var offset1 = (right * cos(ang1) + up * sin(ang1)) * ray
var offset2 = (right * cos(ang2) + up * sin(ang2)) * ray
st.set_color(Color(sag_currnet, 0, 0, 1))
st.add_vertex(p_current + offset1)
st.set_color(Color(sag_currnet, 0, 0, 1))
st.add_vertex(p_current + offset2)
st.set_color(Color(sag_next, 0, 0, 1))
st.add_vertex(p_next + offset1)
st.set_color(Color(sag_currnet, 0, 0, 1))
st.add_vertex(p_current + offset2)
st.set_color(Color(sag_next, 0, 0, 1))
st.add_vertex(p_next + offset2)
st.set_color(Color(sag_next, 0, 0, 1))
st.add_vertex(p_next + offset1)
var wire_node = MeshInstance3D.new()
wire_node.mesh = st.commit()
if lamppost_wire_material: wire_node.material_override = lamppost_wire_material
parent.add_child(wire_node)

View File

@@ -1,6 +1,8 @@
[gd_scene format=3 uid="uid://ujv2f1l4d2ps"]
[ext_resource type="Script" uid="uid://ct2k25kslaxe5" path="res://core/biome_generator/biome_generator.gd" id="1_c7ilo"]
[ext_resource type="Material" uid="uid://b8p1sjxisi522" path="res://core/biome_generator/wires.tres" id="2_w42pm"]
[node name="biome_generator" type="Node3D" unique_id=1861369341]
script = ExtResource("1_c7ilo")
lamppost_wire_material = ExtResource("2_w42pm")

View File

@@ -12,12 +12,17 @@ class_name ChunkInfo
@export var south: bool = false
@export var west: bool = false
@export_group("Maring heights (level 0, 1, 2)")
@export_group("Margin heights (level 0, 1, 2)")
@export var height_north: int = 0
@export var height_est: int = 0
@export var height_south: int = 0
@export var height_west: int = 0
@export_group("Lamppost")
@export var have_lamppost: bool = false
@export var connection_left: Array[Node3D]
@export var connection_right: Array[Node3D]
func _ready() -> void:
if exclusive_biome != "":
add_to_group("set_pieces")

View File

@@ -0,0 +1,27 @@
shader_type spatial;
render_mode blend_mix, depth_draw_opaque, cull_disabled;
//Wire color
uniform vec4 albedo_color : source_color = vec4(0.1, 0.1, 0.1, 1.0);
global uniform float global_wind_strength;
global uniform float global_wind_speed;
void vertex() {
//COLOR.r is the drawing. 0 -> lampost (no move); 1.0 is the center of the wire
float movement = COLOR.r;
//Create a wave (based on position and time to be random)
float wind_wave = 0.0;
if (global_wind_speed > 0.0 && global_wind_strength > 0.0) {
wind_wave = sin(TIME * global_wind_speed + NODE_POSITION_WORLD.x * 0.5) * global_wind_strength;
}
VERTEX.x += wind_wave * 0.1 * movement;
VERTEX.z += wind_wave * 0.05 * movement;
}
void fragment() {
ALBEDO = albedo_color.rgb;
ROUGHNESS = 0.9;
}

View File

@@ -0,0 +1 @@
uid://du8cu413q5scy

View File

@@ -0,0 +1,7 @@
[gd_resource type="ShaderMaterial" format=3 uid="uid://b8p1sjxisi522"]
[ext_resource type="Shader" uid="uid://du8cu413q5scy" path="res://core/biome_generator/wires.gdshader" id="1_6saq1"]
[resource]
render_priority = 0
shader = ExtResource("1_6saq1")