From ed82078628679eda6ba92202e832f0a920dca71c Mon Sep 17 00:00:00 2001 From: Overside srl Date: Thu, 4 Jun 2026 11:01:24 +0200 Subject: [PATCH] fix lamppost wires --- core/biome_generator/biome_generator.gd | 201 ++++++++++++++++++------ tgcc/main_scene.tscn | 2 +- 2 files changed, 152 insertions(+), 51 deletions(-) diff --git a/core/biome_generator/biome_generator.gd b/core/biome_generator/biome_generator.gd index 97984b4..633047d 100644 --- a/core/biome_generator/biome_generator.gd +++ b/core/biome_generator/biome_generator.gd @@ -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: diff --git a/tgcc/main_scene.tscn b/tgcc/main_scene.tscn index a6ed9b3..cd264e2 100644 --- a/tgcc/main_scene.tscn +++ b/tgcc/main_scene.tscn @@ -200,7 +200,7 @@ compositor = SubResource("Compositor_mb5yv") rail_path = NodePath("../rail") biome_list = Array[ExtResource("6_jfe74")]([SubResource("Resource_3qrd0")]) entity_pool = ExtResource("34_a2cst") -entity_spawn_probability = 1.0 +entity_spawn_probability = 0.7 eye_line = 4 [node name="Terrain" type="MeshInstance3D" parent="." unique_id=219178561]