fix lamppost wires

This commit is contained in:
2026-06-04 11:01:24 +02:00
parent 584579afb8
commit ed82078628
2 changed files with 152 additions and 51 deletions

View File

@@ -39,9 +39,12 @@ const RIVER_NEIGHBOUR_OFFSETS: Dictionary = {
}
@export_group("Generation")
@export var chunk_generation_frame_budget_usec: int = 1500 #2 ms/frame to generate chunks
@export var chunk_cleanup_frame_budget_usec: int = 1000 #1 ms/frame per cleanup
@export var lampost_wire_frame_budget_usec: int = 1000 #1 ms/frame per i fili dei lampioni
#ms/frame to generate chunks
@export var chunk_generation_frame_budget_usec: int = 1500
#ms/frame per cleanup
@export var chunk_cleanup_frame_budget_usec: int = 1000
#ms/frame per i fili dei lampioni
@export var lampost_wire_frame_budget_usec: int = 1000
#(about 4 ms of work for frame)
#Se compaiono chunk troppo lentamente davanti al treno:
#aumentare generazione a 3000 o 4000
@@ -53,32 +56,51 @@ const RIVER_NEIGHBOUR_OFFSETS: Dictionary = {
#abbassare cleanup a 500
@export_group("Rails")
@export var rail_path: Path3D #rail path
##rail path
@export var rail_path: Path3D
##list of railways to choose from
@export var railway_pool: Resource
@export_group("Biomes")
@export var biome_list: Array[Biome] #list of scenes for the biome
##list of scenes for the biome
@export var biome_list: Array[Biome]
@export_group("Entities")
##list of entities to choose from
@export var entity_pool: Resource
##propability of entity spawn (e.g. entity_spawn_probability 0.6 * spawn_chance 1.0 = 60%)
@export_range(0.0, 1.0, 0.01) var entity_spawn_probability: float = 0.6
##max spawnable entities per chunk
@export_range(0, 20, 1) var max_entities_per_chunk: int = 3
@export_group("Grid and Area")
@export var chunk_size: float = 20.0 #size of a cell; default 20 (global position is defined dividing x and z for this value)
@export var eye_line: int = 3 #cells to be considerated around the train (e.g. 3 => a square of cells from -3 to 3 around train cell)
@export var district_scale: float = 0.05 #noise value to distribuite biome; low values create large area, high values create biome with more variants
##size of a cell; default 20 (global position is defined dividing x and z for this value)
@export var chunk_size: float = 20.0
##cells to be considerated around the train (e.g. 3 => a square of cells from -3 to 3 around train cell)
@export var eye_line: int = 3
##noise value to distribuite biome; low values create large area, high values create biome with more variants
@export var district_scale: float = 0.05
@export_group("Lamppost")
##lampost wire material
@export var lamppost_wire_material: ShaderMaterial
##lampost wire thickness
@export_range(0.01, 1.0) var wire_thickness: float = 0.05
@export var lamppost_dist_factor: int = 10 #max distance of connections
##max distance of connections
@export var lamppost_dist_factor: int = 10
##max cells to search around a lamppost chunk
@export_range(1, 6, 1) var lamppost_search_radius_cells: int = 3
##max allowed distance between lamppost pairs
@export var lamppost_max_wire_distance: float = 60.0
##if true wire connections blocked by colliders are discarded
@export var lamppost_obstacle_check_enabled: bool = true
var board: Dictionary = {}
var last_pos_train: Vector2i = Vector2i(999999, 999999)
var noise_generator: FastNoiseLite
var altitude_generator: FastNoiseLite
var wire_connections: Dictionary = {}
var wire_connection_pairs: Dictionary = {}
var chunk_candidate_cache: Dictionary = {} #node cache (metadata)
var prop_candidate_cache: Dictionary = {}
var entity_candidate_cache: Dictionary = {}
@@ -165,6 +187,7 @@ func _destroy_and_regenrate_world() -> void:
#Clean board and wire connections
board.clear()
wire_connections.clear()
wire_connection_pairs.clear()
#Create train
last_pos_train = Vector2i(999999, 999999)
@@ -542,6 +565,18 @@ func _queue_lamppost_wire_connection(grid_pos: Vector2i) -> void:
pending_wire_lookup[grid_pos] = true
pending_wire_cells.append(grid_pos)
func _queue_lamppost_wire_connections_around(grid_pos: Vector2i) -> void:
_queue_lamppost_wire_connection(grid_pos)
for x in range(-lamppost_search_radius_cells, lamppost_search_radius_cells + 1):
for z in range(-lamppost_search_radius_cells, lamppost_search_radius_cells + 1):
if x == 0 and z == 0:
continue
var near_pos = grid_pos + Vector2i(x, z)
if board.has(near_pos) and board[near_pos].get("have_lamppost", false):
_queue_lamppost_wire_connection(near_pos)
func _drain_wire_queue() -> void:
var processed: int = 0
var start_usec: int = Time.get_ticks_usec()
@@ -716,15 +751,11 @@ func _get_prop_scene_uniqueness(scene: PackedScene) -> int:
prop_candidate_cache[key] = uniqueness
return uniqueness
func _get_marker_prop_uniqueness(marker: Node) -> int:
var uniqueness = _get_prop_uniqueness_from_info(marker)
if uniqueness == -1:
return 0
return uniqueness
func _pick_weighted_prop_scene(marker: Node) -> PackedScene:
var candidates = []
var fallback_uniqueness = _get_marker_prop_uniqueness(marker)
var fallback_uniqueness = _get_prop_uniqueness_from_info(marker)
if fallback_uniqueness == -1:
fallback_uniqueness = 0
for prop_scene in marker.available_props:
if prop_scene == null:
@@ -991,7 +1022,7 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool:
}
if have_lamppost:
_queue_lamppost_wire_connection(grid_pos)
_queue_lamppost_wire_connections_around(grid_pos)
_spawn_entities_for_static_obstacle(root_chunk, grid_pos)
@@ -1100,7 +1131,8 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void:
have_lamppost = info_new.have_lamppost
var river_flow_direction = _calculate_river_flow_direction(grid_pos, choise.data["river_connections"])
_apply_river_flow_direction(new_chunk, river_flow_direction)
if !river_flow_direction.is_zero_approx():
_set_river_flow_direction_recursive(new_chunk, river_flow_direction.normalized())
board[grid_pos] = {
"type": "biome",
@@ -1115,7 +1147,7 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void:
}
if have_lamppost:
_queue_lamppost_wire_connection(grid_pos)
_queue_lamppost_wire_connections_around(grid_pos)
else:
var backup_scene = _pick_backup_scene(zone_catalogue, grid_pos)
@@ -1231,11 +1263,6 @@ func _get_average_river_side_direction(sides: Array[String]) -> Vector2:
return Vector2.ZERO
return direction / float(sides.size())
func _apply_river_flow_direction(root: Node, flow_direction: Vector2) -> void:
if flow_direction.is_zero_approx():
return
_set_river_flow_direction_recursive(root, flow_direction.normalized())
func _set_river_flow_direction_recursive(node: Node, flow_direction: Vector2) -> void:
if node is MeshInstance3D and node.name.begins_with("Water_F"):
var mesh_instance := node as MeshInstance3D
@@ -1275,6 +1302,13 @@ func _get_lampposts(info_node: Node, side: String) -> Array[Node3D]:
if is_instance_valid(p): lampposts.append(p)
return lampposts
func _get_wire_connection_pair_key(root_a: Node, root_b: Node) -> String:
var id_a: int = root_a.get_instance_id()
var id_b: int = root_b.get_instance_id()
if id_a < id_b:
return str(id_a, ":", id_b)
return str(id_b, ":", id_a)
func _connect_lamppost_wires(new_board_pos: Vector2i) -> void:
var new_cell = board[new_board_pos]
var new_root = new_cell["node"]
@@ -1292,12 +1326,11 @@ func _connect_lamppost_wires(new_board_pos: Vector2i) -> void:
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_candidate: Dictionary = {}
var best_distance = 999999.0
var ray_research = 6
var ray_research: int = lamppost_search_radius_cells
var max_dist: float = minf(lamppost_max_wire_distance, chunk_size * lamppost_dist_factor)
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
@@ -1311,9 +1344,10 @@ func _connect_lamppost_wires(new_board_pos: Vector2i) -> void:
var closest_id = closest_root.get_instance_id()
var closest_connections = wire_connections.get(closest_id, 0)
var pair_key = _get_wire_connection_pair_key(new_root, closest_root)
if wire_connection_pairs.has(pair_key): continue
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")
@@ -1324,32 +1358,99 @@ func _connect_lamppost_wires(new_board_pos: Vector2i) -> void:
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 > max_dist:
continue
var p_sx_my = new_sx[i_m]
var p_dx_my = new_dx[i_m]
var p_sx_your = closest_sx[i_t]
var p_dx_your = closest_dx[i_t]
var dist_streight = p_sx_my.global_position.distance_to(p_sx_your.global_position) + p_dx_my.global_position.distance_to(p_dx_your.global_position)
var dist_cross = p_sx_my.global_position.distance_to(p_dx_your.global_position) + p_dx_my.global_position.distance_to(p_sx_your.global_position)
var use_cross = dist_cross < dist_streight
if use_cross:
if _is_wire_path_blocked(p_sx_my.global_position, p_dx_your.global_position, [new_root, closest_root]):
continue
if _is_wire_path_blocked(p_dx_my.global_position, p_sx_your.global_position, [new_root, closest_root]):
continue
else:
if _is_wire_path_blocked(p_sx_my.global_position, p_sx_your.global_position, [new_root, closest_root]):
continue
if _is_wire_path_blocked(p_dx_my.global_position, p_dx_your.global_position, [new_root, closest_root]):
continue
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 * lamppost_dist_factor
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)
best_candidate = {
"closest_root": closest_root,
"pair_key": pair_key,
"p_sx_my": p_sx_my,
"p_dx_my": p_dx_my,
"p_sx_your": p_sx_your,
"p_dx_your": p_dx_your,
"use_cross": use_cross,
}
if not best_candidate.is_empty():
if best_candidate["use_cross"]:
_draw_parable(best_candidate["p_sx_my"].global_position, best_candidate["p_dx_your"].global_position, new_root)
_draw_parable(best_candidate["p_dx_my"].global_position, best_candidate["p_sx_your"].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)
_draw_parable(best_candidate["p_sx_my"].global_position, best_candidate["p_sx_your"].global_position, new_root)
_draw_parable(best_candidate["p_dx_my"].global_position, best_candidate["p_dx_your"].global_position, new_root)
wire_connections[new_id] += 1
var closest_id = best_closest_to_root.get_instance_id()
wire_connection_pairs[best_candidate["pair_key"]] = true
var closest_id = best_candidate["closest_root"].get_instance_id()
if not wire_connections.has(closest_id): wire_connections[closest_id] = 0
wire_connections[closest_id] += 1
if wire_connections[new_id] < 2:
_queue_lamppost_wire_connection(new_board_pos)
func _get_wire_sample_point(p1: Vector3, p2: Vector3, t: float) -> Vector3:
var lowering: float = 1.5
var point: Vector3 = p1.lerp(p2, t)
var gravity: float = lowering * (1.0 - pow(2.0 * t - 1.0, 2.0))
point.y -= gravity
return point
func _collect_wire_excluded_rids(root: Node, excluded_rids: Array[RID]) -> void:
if root == null:
return
var collision_object = root as CollisionObject3D
if collision_object != null:
excluded_rids.append(collision_object.get_rid())
for child in root.get_children():
_collect_wire_excluded_rids(child, excluded_rids)
func _get_wire_excluded_rids(excluded_roots: Array) -> Array[RID]:
var excluded_rids: Array[RID] = []
for root in excluded_roots:
_collect_wire_excluded_rids(root, excluded_rids)
return excluded_rids
func _is_wire_path_blocked(p1: Vector3, p2: Vector3, excluded_roots: Array = []) -> bool:
if not lamppost_obstacle_check_enabled:
return false
var space_state = get_world_3d().direct_space_state
var excluded_rids: Array[RID] = _get_wire_excluded_rids(excluded_roots)
var previous_point: Vector3 = _get_wire_sample_point(p1, p2, 0.08)
var segments: int = 12
for i in range(1, segments + 1):
var t: float = lerpf(0.08, 0.92, float(i) / float(segments))
var next_point: Vector3 = _get_wire_sample_point(p1, p2, t)
var query = PhysicsRayQueryParameters3D.create(previous_point, next_point)
query.exclude = excluded_rids
query.collide_with_areas = true
var result = space_state.intersect_ray(query)
if not result.is_empty():
return true
previous_point = next_point
return false
#draw lamppost wires
func _draw_parable(p1: Vector3, p2: Vector3, parent: Node3D) -> void: