feat(foliage): add recovering grass trails and bush contact
This commit is contained in:
@@ -7,14 +7,40 @@ const CLEARING_LIMIT := 4
|
||||
@export_range(1, SHADER_INTERACTOR_LIMIT, 1) var max_interactors := 8
|
||||
@export_range(0.02, 0.5, 0.01) var update_interval := 0.08
|
||||
@export var path_clearings: Array[NodePath] = []
|
||||
@export var actor_root_path := NodePath("../../..")
|
||||
@export var foliage_root_path := NodePath("../..")
|
||||
@export var simulation_manager_path := NodePath("../../../SimulationManager")
|
||||
@export var primary_interactor_path := NodePath("../../../Player")
|
||||
|
||||
var _elapsed := 0.0
|
||||
var _trail_map := FoliageTrailMap.new()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# JajceWorld first isolates its mutable materials in the parent's _ready().
|
||||
call_deferred("_configure_path_clearings")
|
||||
_refresh_interactors()
|
||||
call_deferred("_configure_interaction")
|
||||
|
||||
|
||||
func _configure_interaction() -> void:
|
||||
_configure_path_clearings()
|
||||
var simulation := get_node_or_null(simulation_manager_path)
|
||||
if simulation != null and simulation.has_signal("state_restored"):
|
||||
simulation.connect("state_restored", reset_interaction)
|
||||
if is_processing():
|
||||
_refresh_interactors(0.0)
|
||||
|
||||
|
||||
func set_interaction_enabled(enabled: bool) -> void:
|
||||
set_process(enabled)
|
||||
reset_interaction()
|
||||
_elapsed = update_interval
|
||||
|
||||
|
||||
func reset_interaction() -> void:
|
||||
_trail_map.clear()
|
||||
if grass_material != null:
|
||||
grass_material.set_shader_parameter("interactor_count", 0)
|
||||
_bind_trail_materials(false)
|
||||
|
||||
|
||||
func _configure_path_clearings() -> void:
|
||||
@@ -44,35 +70,76 @@ func _process(delta: float) -> void:
|
||||
_elapsed += delta
|
||||
if _elapsed < update_interval:
|
||||
return
|
||||
_refresh_interactors(_elapsed)
|
||||
_elapsed = 0.0
|
||||
_refresh_interactors()
|
||||
|
||||
|
||||
func _refresh_interactors() -> void:
|
||||
func _refresh_interactors(delta: float = 0.08) -> void:
|
||||
if grass_material == null:
|
||||
return
|
||||
var camera := get_viewport().get_camera_3d()
|
||||
var origin := camera.global_position if camera != null else Vector3.ZERO
|
||||
var actor_root := get_node_or_null(actor_root_path)
|
||||
var primary := get_node_or_null(primary_interactor_path)
|
||||
var nearest: Array[Node3D] = []
|
||||
var limit := clampi(max_interactors, 1, SHADER_INTERACTOR_LIMIT)
|
||||
for value in get_tree().get_nodes_in_group("grass_interactors"):
|
||||
var candidate := value as Node3D
|
||||
if candidate == null:
|
||||
if (
|
||||
candidate == null
|
||||
or candidate.is_queued_for_deletion()
|
||||
or not candidate.is_visible_in_tree()
|
||||
or candidate.get_viewport() != get_viewport()
|
||||
or (actor_root != null and not actor_root.is_ancestor_of(candidate))
|
||||
):
|
||||
continue
|
||||
var insert_at := nearest.size()
|
||||
var candidate_distance := origin.distance_squared_to(candidate.global_position)
|
||||
for index in nearest.size():
|
||||
if candidate_distance < origin.distance_squared_to(nearest[index].global_position):
|
||||
if (
|
||||
candidate == primary
|
||||
or (
|
||||
nearest[index] != primary
|
||||
and (
|
||||
candidate_distance
|
||||
< origin.distance_squared_to(nearest[index].global_position)
|
||||
)
|
||||
)
|
||||
):
|
||||
insert_at = index
|
||||
break
|
||||
nearest.insert(insert_at, candidate)
|
||||
if nearest.size() > max_interactors:
|
||||
if nearest.size() > limit:
|
||||
nearest.pop_back()
|
||||
|
||||
var positions := PackedVector3Array()
|
||||
positions.resize(SHADER_INTERACTOR_LIMIT)
|
||||
for index in SHADER_INTERACTOR_LIMIT:
|
||||
positions[index] = Vector3(0.0, -1000.0, 0.0)
|
||||
var trail_positions: Dictionary[int, Vector3] = {}
|
||||
for index in nearest.size():
|
||||
positions[index] = nearest[index].global_position
|
||||
trail_positions[nearest[index].get_instance_id()] = positions[index]
|
||||
grass_material.set_shader_parameter("interactor_count", nearest.size())
|
||||
grass_material.set_shader_parameter("interactor_positions", positions)
|
||||
_trail_map.advance(delta, origin, trail_positions)
|
||||
_bind_trail_materials(true)
|
||||
|
||||
|
||||
func _bind_trail_materials(enabled: bool) -> void:
|
||||
if grass_material != null:
|
||||
grass_material.set_shader_parameter("foliage_trail_map", _trail_map.texture)
|
||||
grass_material.set_shader_parameter("foliage_trail_rect", _trail_map.world_rect)
|
||||
grass_material.set_shader_parameter("foliage_trails_enabled", enabled)
|
||||
var foliage_root := get_node_or_null(foliage_root_path)
|
||||
if foliage_root == null:
|
||||
return
|
||||
for value in get_tree().get_nodes_in_group("interactive_foliage"):
|
||||
var patch := value as StylizedBerryPatch
|
||||
if patch != null and foliage_root.is_ancestor_of(patch):
|
||||
patch.set_contact_field(_trail_map.texture, _trail_map.world_rect, enabled)
|
||||
if grass_material != null:
|
||||
patch.set_contact_actors(
|
||||
grass_material.get_shader_parameter("interactor_positions"),
|
||||
int(grass_material.get_shader_parameter("interactor_count")) if enabled else 0
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user