@tool class_name TerrainPropsData extends RefCounted ## Data structure containing terrain information for props placement. ## Generated by TerrainGenerator after terrain generation. var resolution: Vector2i = Vector2i(128, 128) var terrain_size: Vector2 = Vector2(200, 200) var heights: Array[float] = [] var biome_ids: Array[int] = [] var slope_values: Array[float] = [] var rail_distances: Array[float] = [] var station_distances: Array[float] = [] var mask_values: Array[Array] = [] func get_index(x: int, z: int) -> int: return z * resolution.x + x func get_uv(x: int, z: int) -> Vector2: return Vector2(float(x) / float(resolution.x - 1), float(z) / float(resolution.y - 1)) func get_world_position(x: int, z: int) -> Vector3: var half_x := terrain_size.x / 2.0 var half_z := terrain_size.y / 2.0 var step_x := terrain_size.x / float(resolution.x - 1) var step_z := terrain_size.y / float(resolution.y - 1) var idx := get_index(x, z) return Vector3( x * step_x - half_x, heights[idx] if idx < heights.size() else 0.0, z * step_z - half_z ) func get_height(x: int, z: int) -> float: var idx := get_index(x, z) if idx < 0 or idx >= heights.size(): return 0.0 return heights[idx] func get_biome_id(x: int, z: int) -> int: var idx := get_index(x, z) if idx < 0 or idx >= biome_ids.size(): return 0 return biome_ids[idx] func get_slope_limit(x: int, z: int) -> float: var idx := get_index(x, z) if idx < 0 or idx >= slope_values.size(): return 45.0 return slope_values[idx] func get_rail_distance(x: int, z: int) -> float: var idx := get_index(x, z) if idx < 0 or idx >= rail_distances.size(): return INF return rail_distances[idx] func get_station_distance(x: int, z: int) -> float: var idx := get_index(x, z) if idx < 0 or idx >= station_distances.size(): return INF return station_distances[idx] func get_mask_value(mask_index: int, x: int, z: int) -> float: if mask_index < 0 or mask_index >= mask_values.size(): return 0.0 var mask_arr: Array = mask_values[mask_index] var idx := get_index(x, z) if idx < 0 or idx >= mask_arr.size(): return 0.0 return mask_arr[idx] func get_calculated_slope(x: int, z: int) -> float: if x <= 0 or x >= resolution.x - 1 or z <= 0 or z >= resolution.y - 1: return 0.0 var step_x := terrain_size.x / float(resolution.x - 1) var step_z := terrain_size.y / float(resolution.y - 1) var h := get_height(x, z) var h_xm := get_height(x - 1, z) var h_xp := get_height(x + 1, z) var h_zm := get_height(x, z - 1) var h_zp := get_height(x, z + 1) var dx := (h_xp - h_xm) / (2.0 * step_x) var dz := (h_zp - h_zm) / (2.0 * step_z) return rad_to_deg(atan(sqrt(dx * dx + dz * dz))) func can_place_prop(x: int, z: int, rules: Dictionary) -> bool: var slope := get_calculated_slope(x, z) var rail_dist := get_rail_distance(x, z) var station_dist := get_station_distance(x, z) var biome := get_biome_id(x, z) if rules.has("max_slope") and slope > rules["max_slope"]: return false if rules.has("min_rail_distance") and rail_dist < rules["min_rail_distance"]: return false if rules.has("min_station_distance") and station_dist < rules["min_station_distance"]: return false if rules.has("allowed_biomes") and not biome in rules["allowed_biomes"]: return false if rules.has("excluded_biomes") and biome in rules["excluded_biomes"]: return false if rules.has("required_mask"): var mask_idx: int = rules["required_mask"] var threshold: float = rules.get("mask_threshold", 0.5) if get_mask_value(mask_idx, x, z) < threshold: return false if rules.has("excluded_mask"): var mask_idx: int = rules["excluded_mask"] var threshold: float = rules.get("exclude_threshold", 0.5) if get_mask_value(mask_idx, x, z) >= threshold: return false return true func get_density_multiplier(x: int, z: int, rules: Dictionary) -> float: var multiplier := 1.0 if rules.has("slope_density_curve"): var slope := get_calculated_slope(x, z) var curve: Curve = rules["slope_density_curve"] multiplier *= curve.sample(clampf(slope / 90.0, 0.0, 1.0)) if rules.has("rail_distance_curve"): var dist := get_rail_distance(x, z) var curve: Curve = rules["rail_distance_curve"] var max_dist: float = rules.get("rail_distance_max", 100.0) multiplier *= curve.sample(clampf(dist / max_dist, 0.0, 1.0)) if rules.has("density_mask"): var mask_idx: int = rules["density_mask"] multiplier *= get_mask_value(mask_idx, x, z) return multiplier