Improve tree gathering and rain occlusion
This commit is contained in:
parent
a7c71213cc
commit
3608be41ab
8 changed files with 165 additions and 2 deletions
|
|
@ -12,6 +12,9 @@ const GeneratedLakePool: FishPool = preload(
|
|||
const GeneratedRiverPool: FishPool = preload(
|
||||
"res://fish/pools/generated_river_pool.tres"
|
||||
)
|
||||
const PrecipitationOcclusionType = preload(
|
||||
"res://world/environment/precipitation_occlusion.gd"
|
||||
)
|
||||
const FIRST_SEED := 13001
|
||||
const SECOND_SEED := 13002
|
||||
const RIVER_FALLBACK_SEED := 13012
|
||||
|
|
@ -502,6 +505,12 @@ func _validate_generated_region(
|
|||
assert(PROCEDURAL_PROP_IDS.has(prop_id))
|
||||
var definition := region.get_prop_catalog().definition_for_id(prop_id)
|
||||
assert(definition != null)
|
||||
if definition.procedural_group in [&"grass_tree", &"sand_tree"]:
|
||||
assert(
|
||||
_has_precipitation_occlusion_layer(child),
|
||||
"Precipitation occlusion layer missing from %s (%s)."
|
||||
% [child.name, prop_id],
|
||||
)
|
||||
var biome_id := StringName(
|
||||
child.get_meta(&"terrain_biome_id", &"")
|
||||
)
|
||||
|
|
@ -967,10 +976,12 @@ func _validate_prop_catalog(region: GeneratedWorldRegion) -> void:
|
|||
assert(pine != null)
|
||||
assert(pine.minimum_visual_scale < pine.maximum_visual_scale)
|
||||
assert(pine.material_variants.is_empty())
|
||||
assert(not pine.has_gatherable_surface())
|
||||
var large_pine := catalog.definition_for_id(&"prop_pine_large")
|
||||
assert(large_pine != null)
|
||||
assert(large_pine.minimum_visual_scale < large_pine.maximum_visual_scale)
|
||||
assert(large_pine.material_variants.is_empty())
|
||||
assert(not large_pine.has_gatherable_surface())
|
||||
var palm := catalog.definition_for_id(&"prop_palm")
|
||||
assert(palm != null)
|
||||
assert(is_equal_approx(palm.minimum_visual_scale, 0.5))
|
||||
|
|
@ -1180,6 +1191,21 @@ func _find_mesh_instance(root_node: Node) -> MeshInstance3D:
|
|||
return null
|
||||
|
||||
|
||||
func _has_precipitation_occlusion_layer(root_node: Node) -> bool:
|
||||
var mesh_instance := root_node as MeshInstance3D
|
||||
if (
|
||||
mesh_instance != null
|
||||
and mesh_instance.get_layer_mask_value(
|
||||
PrecipitationOcclusionType.RENDER_LAYER_NUMBER
|
||||
)
|
||||
):
|
||||
return true
|
||||
for child: Node in root_node.get_children():
|
||||
if _has_precipitation_occlusion_layer(child):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _has_material_variant_override(
|
||||
root_node: Node,
|
||||
variants: Array[Material],
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ const WorldTimeServiceType = preload("res://world/world_time_service.gd")
|
|||
const WorldTimeVisualControllerType = preload(
|
||||
"res://world/world_time_visual_controller.gd"
|
||||
)
|
||||
const PrecipitationOcclusionType = preload(
|
||||
"res://world/environment/precipitation_occlusion.gd"
|
||||
)
|
||||
const FishingContextType = preload("res://fishing/fishing_context.gd")
|
||||
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
||||
const FishAvailabilityType = preload("res://fish/fish_availability.gd")
|
||||
|
|
@ -526,6 +529,14 @@ func _validate_weather_presentation() -> void:
|
|||
))
|
||||
var rain_material := rain.process_material as ParticleProcessMaterial
|
||||
assert(rain_material != null)
|
||||
assert(
|
||||
rain_material.collision_mode
|
||||
== ParticleProcessMaterial.COLLISION_HIDE_ON_CONTACT
|
||||
)
|
||||
assert(is_equal_approx(
|
||||
rain.collision_base_size,
|
||||
WorldTimeVisualControllerType.RAIN_COLLISION_BASE_SIZE,
|
||||
))
|
||||
assert(rain_material.emission_box_extents.is_equal_approx(
|
||||
WorldTimeVisualControllerType.RAIN_EMISSION_EXTENTS
|
||||
))
|
||||
|
|
@ -542,6 +553,26 @@ func _validate_weather_presentation() -> void:
|
|||
assert(rain_mesh.size.is_equal_approx(
|
||||
WorldTimeVisualControllerType.RAIN_DROP_SIZE
|
||||
))
|
||||
var rain_collision := visuals.get_node(
|
||||
"LocalRainCanopyCollision"
|
||||
) as GPUParticlesCollisionHeightField3D
|
||||
assert(rain_collision != null)
|
||||
assert(rain_collision.follow_camera_enabled)
|
||||
assert(
|
||||
rain_collision.resolution
|
||||
== GPUParticlesCollisionHeightField3D.RESOLUTION_256
|
||||
)
|
||||
assert(
|
||||
rain_collision.update_mode
|
||||
== GPUParticlesCollisionHeightField3D.UPDATE_MODE_WHEN_MOVED
|
||||
)
|
||||
assert(rain_collision.size.is_equal_approx(
|
||||
WorldTimeVisualControllerType.RAIN_COLLISION_SIZE
|
||||
))
|
||||
assert(
|
||||
rain_collision.heightfield_mask
|
||||
== PrecipitationOcclusionType.RENDER_LAYER_MASK
|
||||
)
|
||||
visuals.call(
|
||||
"_on_weather_changed",
|
||||
WorldWeatherServiceType.Weather.SUNNY,
|
||||
|
|
|
|||
56
world/environment/precipitation_occlusion.gd
Normal file
56
world/environment/precipitation_occlusion.gd
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
class_name PrecipitationOcclusion
|
||||
extends RefCounted
|
||||
|
||||
## A secondary visual layer used only when building the local rain height field.
|
||||
## Canopy meshes remain on layer 1 for ordinary cameras.
|
||||
const RENDER_LAYER_NUMBER: int = 20
|
||||
const RENDER_LAYER_MASK: int = 1 << (RENDER_LAYER_NUMBER - 1)
|
||||
const CANOPY_MATERIAL_NAMES: Array[String] = [
|
||||
"leaf",
|
||||
"leaf_light",
|
||||
"leaf_mid",
|
||||
"leaf_dark",
|
||||
"pine",
|
||||
"tree",
|
||||
]
|
||||
|
||||
|
||||
static func mark_canopy_meshes(root_node: Node) -> int:
|
||||
if root_node == null:
|
||||
return 0
|
||||
var marked_count: int = 0
|
||||
var mesh_instance := root_node as MeshInstance3D
|
||||
if mesh_instance != null and _has_canopy_material(mesh_instance):
|
||||
mesh_instance.layers |= RENDER_LAYER_MASK
|
||||
marked_count += 1
|
||||
for child: Node in root_node.get_children():
|
||||
marked_count += mark_canopy_meshes(child)
|
||||
return marked_count
|
||||
|
||||
|
||||
## Generated tree definitions are already explicit terrain metadata, so every
|
||||
## mesh in their visual scene can safely participate. This also supports older
|
||||
## combined tree meshes whose imported material name does not identify leaves.
|
||||
static func mark_tree_meshes(root_node: Node) -> int:
|
||||
if root_node == null:
|
||||
return 0
|
||||
var marked_count: int = 0
|
||||
var mesh_instance := root_node as MeshInstance3D
|
||||
if mesh_instance != null:
|
||||
mesh_instance.layers |= RENDER_LAYER_MASK
|
||||
marked_count += 1
|
||||
for child: Node in root_node.get_children():
|
||||
marked_count += mark_tree_meshes(child)
|
||||
return marked_count
|
||||
|
||||
|
||||
static func _has_canopy_material(mesh_instance: MeshInstance3D) -> bool:
|
||||
if mesh_instance == null or mesh_instance.mesh == null:
|
||||
return false
|
||||
for surface_index: int in mesh_instance.mesh.get_surface_count():
|
||||
var material := mesh_instance.mesh.surface_get_material(surface_index)
|
||||
if material == null:
|
||||
continue
|
||||
if material.resource_name.to_lower() in CANOPY_MATERIAL_NAMES:
|
||||
return true
|
||||
return false
|
||||
|
|
@ -12,6 +12,9 @@ const PlayerStorageInteractionType = preload(
|
|||
const MeshSurfaceAnchorSamplerType = preload(
|
||||
"res://world/generation/mesh_surface_anchor_sampler.gd"
|
||||
)
|
||||
const PrecipitationOcclusionType = preload(
|
||||
"res://world/environment/precipitation_occlusion.gd"
|
||||
)
|
||||
const WATER_BODY_SCENE: PackedScene = preload("res://world/water_body.tscn")
|
||||
const SALT_WATER_MATERIAL: Material = preload(
|
||||
"res://world/materials/stylized_water.tres"
|
||||
|
|
@ -292,9 +295,21 @@ func _on_generation_completed(summary: Dictionary) -> void:
|
|||
_place_spawn_amenities(spawn_position)
|
||||
_configure_fresh_water(records)
|
||||
_configure_diggable_area()
|
||||
# Imported prop instances may finish applying their scene state while the
|
||||
# generated world is assembled. Mark the final decoration tree once every
|
||||
# prop and material variant is in place so rain canopy occlusion persists.
|
||||
_mark_precipitation_occluders()
|
||||
world_generated.emit(_current_seed, summary)
|
||||
|
||||
|
||||
func _mark_precipitation_occluders() -> void:
|
||||
for prop: Node in _decorations.get_children():
|
||||
var prop_group := prop.get_meta(&"terrain_prop_group", &"") as StringName
|
||||
if prop_group not in [&"grass_tree", &"sand_tree"]:
|
||||
continue
|
||||
PrecipitationOcclusionType.mark_tree_meshes(prop)
|
||||
|
||||
|
||||
func _assign_biomes(
|
||||
records: Array[Dictionary],
|
||||
summary: Dictionary,
|
||||
|
|
|
|||
|
|
@ -17,4 +17,3 @@ minimum_visual_scale = 0.65
|
|||
maximum_visual_scale = 1.2
|
||||
collision_radius = 0.5
|
||||
collision_height = 4.0
|
||||
gatherable_surface_material_names = PackedStringArray("wood")
|
||||
|
|
|
|||
|
|
@ -17,4 +17,3 @@ minimum_visual_scale = 0.75
|
|||
maximum_visual_scale = 1.2
|
||||
collision_radius = 0.65
|
||||
collision_height = 9.5
|
||||
gatherable_surface_material_names = PackedStringArray("wood")
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ const PlayerStorageInteractionType = preload(
|
|||
const FOLIAGE_WIND_SHADER: Shader = preload(
|
||||
"res://world/materials/foliage_wind.gdshader"
|
||||
)
|
||||
const PrecipitationOcclusionType = preload(
|
||||
"res://world/environment/precipitation_occlusion.gd"
|
||||
)
|
||||
const FOLIAGE_MATERIAL_NAMES: Array[StringName] = [
|
||||
&"leaf",
|
||||
&"leaf_light",
|
||||
|
|
@ -60,6 +63,9 @@ var _foliage_wind_enabled: bool = false
|
|||
func _ready() -> void:
|
||||
if rebuild_terrain_collision_on_ready or not has_terrain_collision():
|
||||
_build_terrain_collision()
|
||||
PrecipitationOcclusionType.mark_canopy_meshes(
|
||||
get_node_or_null(terrain_visual_root_path)
|
||||
)
|
||||
_set_foliage_wind_enabled(true)
|
||||
if Engine.is_editor_hint():
|
||||
update_configuration_warnings()
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ const LIGHT_RAIN_FIXED_FPS: int = 8
|
|||
const RAIN_VELOCITY_MIN: float = 16.0
|
||||
const RAIN_VELOCITY_MAX: float = 20.0
|
||||
const RAIN_DROP_SIZE := Vector3(0.014, 0.34, 0.014)
|
||||
const RAIN_COLLISION_BASE_SIZE: float = 0.08
|
||||
const RAIN_COLLISION_SIZE := Vector3(28.0, 24.0, 28.0)
|
||||
const FOG_DAYLIGHT_SCENE_BRIGHTNESS: float = 0.42
|
||||
const FOG_DAYLIGHT_FOG_LIGHT_BRIGHTNESS: float = 0.42
|
||||
const FOG_DAYLIGHT_WATER_BRIGHTNESS: float = 0.42
|
||||
|
|
@ -37,6 +39,9 @@ const FRESH_WATER_MATERIAL: ShaderMaterial = preload(
|
|||
const LocalStormCloudLayerType = preload(
|
||||
"res://world/environment/local_storm_cloud_layer.gd"
|
||||
)
|
||||
const PrecipitationOcclusionType = preload(
|
||||
"res://world/environment/precipitation_occlusion.gd"
|
||||
)
|
||||
|
||||
const DAY_SKY_TOP := Color(0.204, 0.498, 0.643)
|
||||
const DAY_SKY_HORIZON := Color(0.663, 0.843, 0.847)
|
||||
|
|
@ -88,6 +93,7 @@ var _rain_camera_provider: Callable
|
|||
var _environment: Environment
|
||||
var _sky_material: ShaderMaterial
|
||||
var _rain: GPUParticles3D
|
||||
var _rain_collision: GPUParticlesCollisionHeightField3D
|
||||
var _storm_clouds: LocalStormCloudLayer
|
||||
var _elapsed: float = 0.0
|
||||
var _weather_from: WorldWeatherService.Weather = (
|
||||
|
|
@ -211,6 +217,7 @@ func _prepare_rain() -> void:
|
|||
LIGHT_RAIN_FIXED_FPS if _light_performance_profile else 30
|
||||
)
|
||||
_rain.local_coords = false
|
||||
_rain.collision_base_size = RAIN_COLLISION_BASE_SIZE
|
||||
_rain.visibility_aabb = RAIN_VISIBILITY_AABB
|
||||
var process_material := ParticleProcessMaterial.new()
|
||||
process_material.emission_shape = (
|
||||
|
|
@ -222,6 +229,9 @@ func _prepare_rain() -> void:
|
|||
process_material.initial_velocity_min = RAIN_VELOCITY_MIN
|
||||
process_material.initial_velocity_max = RAIN_VELOCITY_MAX
|
||||
process_material.gravity = Vector3(0.0, -2.0, 0.0)
|
||||
process_material.collision_mode = (
|
||||
ParticleProcessMaterial.COLLISION_HIDE_ON_CONTACT
|
||||
)
|
||||
_rain.process_material = process_material
|
||||
var drop_mesh := BoxMesh.new()
|
||||
drop_mesh.size = RAIN_DROP_SIZE
|
||||
|
|
@ -232,9 +242,30 @@ func _prepare_rain() -> void:
|
|||
drop_mesh.material = drop_material
|
||||
_rain.draw_pass_1 = drop_mesh
|
||||
add_child(_rain)
|
||||
_prepare_rain_collision()
|
||||
_update_rain_position()
|
||||
|
||||
|
||||
func _prepare_rain_collision() -> void:
|
||||
_rain_collision = GPUParticlesCollisionHeightField3D.new()
|
||||
_rain_collision.name = "LocalRainCanopyCollision"
|
||||
_rain_collision.size = RAIN_COLLISION_SIZE
|
||||
_rain_collision.resolution = (
|
||||
GPUParticlesCollisionHeightField3D.RESOLUTION_256
|
||||
)
|
||||
_rain_collision.update_mode = (
|
||||
GPUParticlesCollisionHeightField3D.UPDATE_MODE_WHEN_MOVED
|
||||
)
|
||||
_rain_collision.follow_camera_enabled = true
|
||||
_rain_collision.heightfield_mask = (
|
||||
PrecipitationOcclusionType.RENDER_LAYER_MASK
|
||||
)
|
||||
# Local rain remains on its normal layer; unrelated particle systems do not
|
||||
# need to query this weather-specific collision field.
|
||||
_rain_collision.cull_mask = 1
|
||||
add_child(_rain_collision)
|
||||
|
||||
|
||||
func _prepare_storm_clouds() -> void:
|
||||
_storm_clouds = LocalStormCloudLayerType.new()
|
||||
_storm_clouds.name = "LocalStormClouds"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue