diff --git a/core/biome_generator/biome_generator.gd b/core/biome_generator/biome_generator.gd index e2d73f8..4e50efb 100644 --- a/core/biome_generator/biome_generator.gd +++ b/core/biome_generator/biome_generator.gd @@ -46,6 +46,7 @@ var noise_generator: FastNoiseLite var altitude_generator: FastNoiseLite var wire_connections: Dictionary = {} var chunk_candidate_cache: Dictionary = {} #node cache (metadata) +var prop_candidate_cache: Dictionary = {} var pending_generation_cells: Array[Vector2i] = [] var pending_cleanup_cells: Array[Vector2i] = [] var pending_wire_cells: Array[Vector2i] = [] @@ -89,7 +90,7 @@ func _update_set_pieces() -> void: var local_biome = "" if manual_biome != null: - local_biome = manual_biome.nome + local_biome = manual_biome.name else: var grid_x = roundi(sp.global_position.x / chunk_size) var grid_z = roundi(sp.global_position.z / chunk_size) @@ -115,7 +116,7 @@ func _destroy_and_regenrate_world() -> void: #Destroy all models for pos in board.keys(): var cella = board[pos] - if cella["type"] != "obstacle": + if not _is_persistent_obstacle(cella): if cella.has("node") and is_instance_valid(cella["node"]): cella["node"].queue_free() @@ -349,6 +350,8 @@ func _replace_rail_chunk_instance(chunk_root: Node3D) -> bool: chunk_root.queue_free() return true +#rebuild the scene catalogue from res://tgcc/chunk/railway/scene, +#than serach on the current scene which chunks can be changed and for each one chose a new scene (the same kind) func _refresh_rail_chunks() -> void: print("update rail chunks") _warm_rail_chunk_catalogue() @@ -376,6 +379,9 @@ func _refresh_world(center: Vector2i) -> void: _rebuild_generation_queue(center) _rebuild_cleanup_queue(center) +#check cells around the train by rings: +#first the center, then borders with distance = 1, the distance = 2 and so to eye_line. +#Use maxi(abs(x), abs(z)) to know the border of the ring. Cells closest to the train have more priority func _rebuild_generation_queue(center: Vector2i) -> void: pending_generation_cells.clear() @@ -390,13 +396,16 @@ func _rebuild_generation_queue(center: Vector2i) -> void: continue pending_generation_cells.append(grid_pos) +#For each cells add to cleanup queue the cells too far +#Use eye_line plus a margin value to hide chunks not instantly when go out of the eye line +#Persistent obstacle cells are not deleted (rails or set_pieces) func _rebuild_cleanup_queue(center: Vector2i) -> void: pending_cleanup_cells.clear() var safe_margin = 2 for grid_pos in board.keys(): var cell = board[grid_pos] - if cell["type"] == "obstacle": + if _is_persistent_obstacle(cell): continue var dist_x = abs(grid_pos.x - center.x) @@ -424,7 +433,7 @@ func _drain_cleanup_queue() -> void: continue var cell = board[grid_pos] - if cell["type"] == "obstacle": + if _is_persistent_obstacle(cell): continue if cell.has("node") and is_instance_valid(cell["node"]): @@ -462,6 +471,8 @@ func _generate_pieces_around_train(center: Vector2i) -> void: if not ce_obstacle: _add_compatible_biome(grid_pos) +#Decice which catalogue of chunks use for a cell. If manual_biome is set use always it +#Otherwise read noise_generator.get_noise_2d to give an index for biome_list func _choose_catalogue_by_cell(grid_pos: Vector2i) -> Array[PackedScene]: if manual_biome != null: return manual_biome.available_chunks @@ -484,6 +495,14 @@ func _get_chunk_uniqueness_from_info(info_node: Node) -> int: return clampi(info_node.uniqueness, 0, MAX_CHUNK_UNIQUENESS) return 0 +func _get_prop_uniqueness_from_info(info_node: Node) -> int: + if info_node != null and "uniqueness" in info_node: + return clampi(info_node.uniqueness, 0, MAX_CHUNK_UNIQUENESS) + return -1 + +func _is_persistent_obstacle(cell: Dictionary) -> bool: + return cell.get("type", "") == "obstacle" and cell.get("persistent", true) + func _has_nearby_unique_chunk(grid_pos: Vector2i, uniqueness: int) -> bool: if uniqueness <= 0: return false @@ -527,6 +546,61 @@ func _pick_backup_scene(zone_catalogue: Array[PackedScene], grid_pos: Vector2i) return scene return zone_catalogue[0] +func _get_prop_scene_cache_key(scene: PackedScene) -> String: + if scene == null: + return "" + if scene.resource_path != "": + return scene.resource_path + return "prop_scene_%s" % scene.get_instance_id() + +func _get_prop_scene_uniqueness(scene: PackedScene) -> int: + if scene == null: + return -1 + + var key = _get_prop_scene_cache_key(scene) + if prop_candidate_cache.has(key): + return prop_candidate_cache[key] + + var preview_prop = scene.instantiate() + var uniqueness = _get_prop_uniqueness_from_info(preview_prop) + if uniqueness == -1: + var prop_info_list: Array[Node] = [] + collect_all_propinfo(preview_prop, prop_info_list) + if not prop_info_list.is_empty(): + uniqueness = _get_prop_uniqueness_from_info(prop_info_list[0]) + preview_prop.queue_free() + + 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) + + for prop_scene in marker.available_props: + if prop_scene == null: + continue + + var uniqueness = _get_prop_scene_uniqueness(prop_scene) + if uniqueness == -1: + uniqueness = fallback_uniqueness + + candidates.append({ + "scene": prop_scene, + "weight": _get_uniqueness_pick_weight(uniqueness), + "uniqueness": uniqueness + }) + + if candidates.is_empty(): + return null + return _pick_weighted_candidate(candidates).scene + func _spawn_props_for_chunk(root: Node) -> void: var prop_markers: Array[Node] = [] collect_all_propinfo(root, prop_markers) @@ -538,7 +612,7 @@ func _spawn_prop_for_marker(marker: Node) -> void: if marker.available_props.is_empty(): return - var prop_scene = marker.available_props.pick_random() + var prop_scene = _pick_weighted_prop_scene(marker) if prop_scene == null: return @@ -551,6 +625,8 @@ func _spawn_prop_for_marker(marker: Node) -> void: marker.add_child(prop_node) prop_node.transform = Transform3D.IDENTITY +#Using a vertical raycast from the top to the bottom at the center of the cell +#If there is a collision search for a new chunk node func _register_cell_with_ray(grid_pos: Vector2i) -> bool: if board.has(grid_pos): return true @@ -623,7 +699,8 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool: "node": root_chunk, "info": right_info_node, "have_lamppost": have_lamppost, - "uniqueness": uniqueness + "uniqueness": uniqueness, + "persistent": true } if have_lamppost: @@ -632,6 +709,11 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool: return true return false +#For the cell to fill check the contrains for the four neighbors: +#If the neighbor at north have and exit on south then new chunk should have exit to north (the same for east and ovest) +#Contrains: 1 -> connection is mandatory; 0 -> no connection; -1 -> no contrain because there is no neighbor or it already exits +#When generator know the contrains take all chunks and try all different rotations +#A candidate is valid only if all contrains are correct func _add_compatible_biome(grid_pos: Vector2i) -> void: var req_conn_north = _needed_connection(grid_pos + Vector2i(0, -1), "south") var req_conn_est = _needed_connection(grid_pos + Vector2i(1, 0), "west") @@ -730,7 +812,7 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: _apply_river_flow_direction(new_chunk, river_flow_direction) board[grid_pos] = { - "type": "bioma", + "type": "biome", "exit": choise.data["connections"], "river_exit": choise.data["river_connections"], "river_flow_direction": river_flow_direction, @@ -772,6 +854,8 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: "uniqueness": backup_uniqueness } +#Check if a neighbors have a river direction: if yes try to continue it, +#otherwise create a direction based on connections func _calculate_river_flow_direction(grid_pos: Vector2i, river_connections: Dictionary) -> Vector2: var connected_sides: Array[String] = [] for side in RIVER_SIDE_ORDER: @@ -1006,7 +1090,7 @@ func _connect_lamppost_wires(new_board_pos: Vector2i) -> void: if not wire_connections.has(closest_id): wire_connections[closest_id] = 0 wire_connections[closest_id] += 1 -#draw lamppost +#draw lamppost wires func _draw_parable(p1: Vector3, p2: Vector3, parent: Node3D) -> void: var segments = 15 var lowering = 1.5 diff --git a/core/biome_generator/prop_info.gd b/core/biome_generator/prop_info.gd index a6d033a..72e2962 100644 --- a/core/biome_generator/prop_info.gd +++ b/core/biome_generator/prop_info.gd @@ -2,3 +2,4 @@ extends Marker3D class_name PropInfo @export var available_props: Array[PackedScene] +@export_range(0, 5, 1) var uniqueness: int = 0 #0=common; 5=unique