diff --git a/core/biome_generator/biome_generator.gd b/core/biome_generator/biome_generator.gd index 5bd7c45..e2d73f8 100644 --- a/core/biome_generator/biome_generator.gd +++ b/core/biome_generator/biome_generator.gd @@ -7,6 +7,8 @@ const CHUNK_TYPE_CURVED_TRACK: int = 2 const CHUNK_GENERATION_STEPS_PER_FRAME: int = 2 const CHUNK_CLEANUP_STEPS_PER_FRAME: int = 4 const LAMPPOST_WIRE_STEPS_PER_FRAME: int = 1 + +const MAX_CHUNK_UNIQUENESS: int = 5 const RAILWAY_SCENE_DIRECTORY: String = "res://tgcc/chunk/railway/scene" const RIVER_SIDE_ORDER: Array[String] = ["north", "est", "south", "west"] const RIVER_DIRECTIONS: Dictionary = { @@ -23,33 +25,32 @@ const RIVER_NEIGHBOUR_OFFSETS: Dictionary = { } @export_group("Rails") -@export var rail_path: Path3D +@export var rail_path: Path3D #rail path @export_group("Biomes") -@export var biome_list: Array[Biome] +@export var biome_list: Array[Biome] #list of scenes for the biome @export_group("Grid and Area") -@export var chunk_size: float = 20.0 -@export var eye_line: int = 3 -@export var district_scale: float = 0.05 +@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 @export_group("Lamppost") @export var lamppost_wire_material: ShaderMaterial @export_range(0.01, 1.0) var wire_thickness: float = 0.05 -@export var lamppost_dist_factor: int = 10 +@export var lamppost_dist_factor: int = 10 #max distance of connections var board: Dictionary = {} var last_pos_train: Vector2i = Vector2i(999999, 999999) var noise_generator: FastNoiseLite var altitude_generator: FastNoiseLite var wire_connections: Dictionary = {} -var chunk_candidate_cache: Dictionary = {} +var chunk_candidate_cache: Dictionary = {} #node cache (metadata) var pending_generation_cells: Array[Vector2i] = [] var pending_cleanup_cells: Array[Vector2i] = [] var pending_wire_cells: Array[Vector2i] = [] var pending_wire_lookup: Dictionary = {} -var pending_radar_update: bool = false -var rail_chunk_catalogue: Dictionary = {} +var rail_chunk_catalogue: Dictionary = {} #rails chunk list var manual_biome: Biome = null @@ -58,7 +59,8 @@ func _ready() -> void: #connect events UIEvents.update_rail_chunks.connect(_update_rail_chunks) - + + #noise generator for biome and altitute noise_generator = FastNoiseLite.new() noise_generator.noise_type = FastNoiseLite.TYPE_PERLIN noise_generator.seed = randi() @@ -69,8 +71,10 @@ func _ready() -> void: altitude_generator.seed = randi() altitude_generator.frequency = district_scale * 0.5 + #fill cache with available chunk _warm_chunk_candidate_cache() _warm_rail_chunk_catalogue() + #set unique pieces _update_set_pieces() func _update_set_pieces() -> void: @@ -125,16 +129,13 @@ func _destroy_and_regenrate_world() -> void: var train_pos = rail_path.train_instance.global_position var current_pos = Vector2i(roundi(train_pos.x / chunk_size), roundi(train_pos.z / chunk_size)) _refresh_world(current_pos) - pending_radar_update = true func _process(_delta: float) -> void: + #based on constant values the queue are evalutated by frame and not all together _drain_cleanup_queue() _drain_generation_queue() _drain_wire_queue() - if pending_radar_update: - pending_radar_update = false - func _physics_process(_delta: float) -> void: if rail_path == null or rail_path.train_instance == null: return @@ -146,7 +147,6 @@ func _physics_process(_delta: float) -> void: if current_pos != last_pos_train: last_pos_train = current_pos _refresh_world(current_pos) - pending_radar_update = true func collect_all_chunkinfo(root: Node, list: Array[Node]) -> void: if root == null: return @@ -157,6 +157,15 @@ func collect_all_chunkinfo(root: Node, list: Array[Node]) -> void: for child in root.get_children(): collect_all_chunkinfo(child, list) +func collect_all_propinfo(root: Node, list: Array[Node]) -> void: + if root == null: return + + if "available_props" in root: + list.append(root) + + for child in root.get_children(): + collect_all_propinfo(child, list) + func _warm_chunk_candidate_cache() -> void: var unique_scenes: Dictionary = {} @@ -239,6 +248,7 @@ func _get_chunk_scene_metadata(scene: PackedScene) -> Dictionary: "info_path": info_path, "rotations": rotations, "has_lamppost": "have_lamppost" in info_node and info_node.have_lamppost, + "uniqueness": _get_chunk_uniqueness_from_info(info_node), } preview_chunk.queue_free() chunk_candidate_cache[key] = metadata @@ -273,7 +283,6 @@ func _clear_pending_world_work() -> void: pending_cleanup_cells.clear() pending_wire_cells.clear() pending_wire_lookup.clear() - pending_radar_update = false func _collect_replaceable_rail_chunks(root: Node, result: Array[Node3D]) -> void: if root == null: @@ -470,6 +479,78 @@ 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 _get_chunk_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 0 + +func _has_nearby_unique_chunk(grid_pos: Vector2i, uniqueness: int) -> bool: + if uniqueness <= 0: + return false + + for nearby_pos in board.keys(): + var nearby_uniqueness = int(board[nearby_pos].get("uniqueness", 0)) + if nearby_uniqueness <= 0: + continue + + var min_distance = maxi(uniqueness, nearby_uniqueness) + var dist_x = abs(nearby_pos.x - grid_pos.x) + var dist_z = abs(nearby_pos.y - grid_pos.y) + if dist_x <= min_distance and dist_z <= min_distance: + return true + return false + +func _get_uniqueness_pick_weight(uniqueness: int) -> float: + return 1.0 / pow(float(uniqueness + 1), 2.0) + +func _pick_weighted_candidate(candidates: Array) -> Dictionary: + var total_weight = 0.0 + for candidate in candidates: + total_weight += candidate.weight + + if total_weight <= 0.0: + return candidates.pick_random() + + var target_weight = randf() * total_weight + var current_weight = 0.0 + for candidate in candidates: + current_weight += candidate.weight + if current_weight >= target_weight: + return candidate + return candidates.back() + +func _pick_backup_scene(zone_catalogue: Array[PackedScene], grid_pos: Vector2i) -> PackedScene: + for scene in zone_catalogue: + var metadata = _get_chunk_scene_metadata(scene) + var uniqueness = int(metadata.get("uniqueness", 0)) + if not _has_nearby_unique_chunk(grid_pos, uniqueness): + return scene + return zone_catalogue[0] + +func _spawn_props_for_chunk(root: Node) -> void: + var prop_markers: Array[Node] = [] + collect_all_propinfo(root, prop_markers) + + for marker in prop_markers: + _spawn_prop_for_marker(marker) + +func _spawn_prop_for_marker(marker: Node) -> void: + if marker.available_props.is_empty(): + return + + var prop_scene = marker.available_props.pick_random() + if prop_scene == null: + return + + var prop_instance = prop_scene.instantiate() + var prop_node = prop_instance as Node3D + if prop_node == null: + prop_instance.queue_free() + return + + marker.add_child(prop_node) + prop_node.transform = Transform3D.IDENTITY + func _register_cell_with_ray(grid_pos: Vector2i) -> bool: if board.has(grid_pos): return true @@ -517,6 +598,7 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool: var river_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 + var uniqueness = 0 #Get node info if right_info_node != null: @@ -530,6 +612,8 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool: if "have_lamppost" in right_info_node: have_lamppost = right_info_node.have_lamppost + uniqueness = _get_chunk_uniqueness_from_info(right_info_node) + #Add piece to the grid board[grid_pos] = { "type": "obstacle", @@ -538,7 +622,8 @@ func _register_cell_with_ray(grid_pos: Vector2i) -> bool: "heights": height_found, "node": root_chunk, "info": right_info_node, - "have_lamppost": have_lamppost + "have_lamppost": have_lamppost, + "uniqueness": uniqueness } if have_lamppost: @@ -579,6 +664,9 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: var u_conn = data["connections"] var u_river_conn = data["river_connections"] var u_height = data["heights"] + var uniqueness = int(metadata.get("uniqueness", 0)) + if _has_nearby_unique_chunk(grid_pos, uniqueness): + continue var match_conn_n = (req_conn_north == -1) or ((req_conn_north == 1) == u_conn["north"]) var match_conn_e = (req_conn_est == -1) or ((req_conn_est == 1) == u_conn["est"]) @@ -607,7 +695,14 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: if req_height_south == -1 and u_height["south"] == height_target: score += 1 if req_height_west == -1 and u_height["west"] == height_target: score += 1 - valid_candidates.append({"scene": scene, "rotation": rot, "data": data, "score": score}) + valid_candidates.append({ + "scene": scene, + "rotation": rot, + "data": data, + "score": score, + "weight": _get_uniqueness_pick_weight(uniqueness), + "uniqueness": uniqueness + }) if valid_candidates.size() > 0: var max_score = -1 @@ -618,11 +713,12 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: for c in valid_candidates: if c.score == max_score: best_candidate.append(c) - var choise = best_candidate.pick_random() + var choise = _pick_weighted_candidate(best_candidate) var new_chunk = choise.scene.instantiate() new_chunk.position = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size) new_chunk.rotation.y = choise.rotation * (-PI / 2.0) add_child(new_chunk) + _spawn_props_for_chunk(new_chunk) var info_new = _get_cached_chunk_info(new_chunk, choise.scene) @@ -641,18 +737,22 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: "heights": choise.data["heights"], "node": new_chunk, "info": info_new, - "have_lamppost": have_lamppost + "have_lamppost": have_lamppost, + "uniqueness": choise.uniqueness } if have_lamppost: _queue_lamppost_wire_connection(grid_pos) else: - var backup = zone_catalogue[0].instantiate() + var backup_scene = _pick_backup_scene(zone_catalogue, grid_pos) + var backup = backup_scene.instantiate() backup.position = Vector3(grid_pos.x * chunk_size, 0, grid_pos.y * chunk_size) add_child(backup) + _spawn_props_for_chunk(backup) - var info_backup = _get_cached_chunk_info(backup, zone_catalogue[0]) + var info_backup = _get_cached_chunk_info(backup, backup_scene) + var backup_uniqueness = _get_chunk_uniqueness_from_info(info_backup) var safe_heights = { "north": req_height_north if req_height_north != -1 else height_target, @@ -668,7 +768,8 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: "heights": safe_heights, "node": backup, "info": info_backup, - "have_lamppost": false + "have_lamppost": false, + "uniqueness": backup_uniqueness } func _calculate_river_flow_direction(grid_pos: Vector2i, river_connections: Dictionary) -> Vector2: diff --git a/core/biome_generator/chunk_info.gd b/core/biome_generator/chunk_info.gd index 74b6629..5f9bad6 100644 --- a/core/biome_generator/chunk_info.gd +++ b/core/biome_generator/chunk_info.gd @@ -5,6 +5,7 @@ class_name ChunkInfo @export_group("Set Piece Rules (Unique pieces)") @export var exclusive_biome: String = "" # Es: "Forest" +@export_range(0, 5, 1) var uniqueness: int = 0 #0=common; 5=unique @export_group("Base exit") @export var north: bool = false diff --git a/core/biome_generator/prop_info.gd b/core/biome_generator/prop_info.gd new file mode 100644 index 0000000..a6d033a --- /dev/null +++ b/core/biome_generator/prop_info.gd @@ -0,0 +1,4 @@ +extends Marker3D +class_name PropInfo + +@export var available_props: Array[PackedScene] diff --git a/core/biome_generator/prop_info.gd.uid b/core/biome_generator/prop_info.gd.uid new file mode 100644 index 0000000..2702100 --- /dev/null +++ b/core/biome_generator/prop_info.gd.uid @@ -0,0 +1 @@ +uid://dg6ngy4pmtsyc diff --git a/tgcc/chunk/countryside/scene/chunk_country_corner_01.tscn b/tgcc/chunk/countryside/scene/chunk_country_corner_01.tscn index bce639f..23e32c5 100644 --- a/tgcc/chunk/countryside/scene/chunk_country_corner_01.tscn +++ b/tgcc/chunk/countryside/scene/chunk_country_corner_01.tscn @@ -4,8 +4,10 @@ [ext_resource type="Script" uid="uid://dg2h4kbqe8j3m" path="res://core/biome_generator/chunk_info.gd" id="2_4d433"] [ext_resource type="Material" uid="uid://blqelpjvdv23j" path="res://tgcc/chunk/material/grassflat_chunk.tres" id="2_ewqv4"] [ext_resource type="Material" uid="uid://4xhpd6lust7w" path="res://tgcc/chunk/material/path_chunk.tres" id="3_4d433"] +[ext_resource type="Script" uid="uid://dg6ngy4pmtsyc" path="res://core/biome_generator/prop_info.gd" id="3_ckjyx"] [ext_resource type="Material" uid="uid://ce0bdk3mx2xao" path="res://tgcc/chunk/material/water_chunk.tres" id="4_r06ll"] [ext_resource type="Material" uid="uid://fnjxocmx16b7" path="res://tgcc/chunk/prop/grass/grass_chunk.tres" id="5_sddh0"] +[ext_resource type="PackedScene" uid="uid://d12t04rs47jq3" path="res://tgcc/chunk/prop/tree/tree_01.tscn" id="5_y2fk3"] [ext_resource type="Material" uid="uid://jygb1hcokks5" path="res://tgcc/chunk/prop/grass/grass_bank.tres" id="6_ckjyx"] [ext_resource type="Material" uid="uid://bjrb33qwp1p43" path="res://tgcc/chunk/prop/grass/grass_chunk_weeds.tres" id="7_y2fk3"] [ext_resource type="Material" uid="uid://0x17mj2v807r" path="res://tgcc/chunk/prop/grass/grass_chunk_weeds2.tres" id="8_vtfy7"] @@ -52,22 +54,27 @@ have_lamppost = true connection_left = [NodePath("PaloLuce/sx")] connection_right = [NodePath("PaloLuce/dx")] -[node name="Argini_001" parent="." index="0" unique_id=439223507] +[node name="Prop1" type="Marker3D" parent="." index="0" unique_id=1846012620] +transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 2.1078086, 4.588761, 4.5396976) +script = ExtResource("3_ckjyx") +available_props = Array[PackedScene]([ExtResource("10_r06ll"), ExtResource("5_y2fk3")]) + +[node name="Argini_001" parent="." index="1" unique_id=1474838784] surface_material_override/0 = ExtResource("2_ewqv4") -[node name="Chunk_005" parent="." index="1" unique_id=1955568282] +[node name="Chunk_005" parent="." index="2" unique_id=844192036] surface_material_override/0 = ExtResource("2_ewqv4") -[node name="Chunk_036" parent="." index="2" unique_id=1808067169 groups=["weather_node"]] +[node name="Chunk_036" parent="." index="3" unique_id=1584066508 groups=["weather_node"]] surface_material_override/0 = ExtResource("3_4d433") -[node name="Chunk_037" parent="." index="3" unique_id=1338393481 groups=["weather_node"]] +[node name="Chunk_037" parent="." index="4" unique_id=1100757609 groups=["weather_node"]] surface_material_override/0 = ExtResource("3_4d433") -[node name="Water_003" parent="." index="4" unique_id=1175879696] +[node name="Water_003" parent="." index="5" unique_id=1047599796] surface_material_override/0 = ExtResource("4_r06ll") -[node name="grass" type="Node3D" parent="." index="5" unique_id=924191951 groups=["weather_vegetables_node", "wind_node"]] +[node name="grass" type="Node3D" parent="." index="6" unique_id=924191951 groups=["weather_vegetables_node", "wind_node"]] [node name="grass_plane" type="MeshInstance3D" parent="grass" index="0" unique_id=1701437548] transform = Transform3D(1, 0, 0, 0, -4.371139e-08, -1, 0, 1, -4.371139e-08, 0, 0, 0) @@ -98,7 +105,7 @@ material_override = ExtResource("8_vtfy7") cast_shadow = 0 multimesh = SubResource("MultiMesh_sddh0") -[node name="rice" type="Node3D" parent="." index="6" unique_id=318026795 groups=["weather_vegetables_node", "wind_node"]] +[node name="rice" type="Node3D" parent="." index="7" unique_id=318026795 groups=["weather_vegetables_node", "wind_node"]] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -86.08408, 0, 0) [node name="rice_plane61" type="MeshInstance3D" parent="rice" index="0" unique_id=1796062490] @@ -911,7 +918,7 @@ cast_shadow = 0 mesh = SubResource("PlaneMesh_oviqx") surface_material_override/0 = ExtResource("7_y2fk3") -[node name="PaloLuce" parent="." index="7" unique_id=1607500596 instance=ExtResource("10_r06ll")] +[node name="PaloLuce" parent="." index="8" unique_id=1607500596 instance=ExtResource("10_r06ll")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4629593, 0, -1.4456635) [editable path="PaloLuce"]