extends Node const SHADER_INTERACTOR_LIMIT := 8 const CLEARING_LIMIT := 4 @export var grass_material: ShaderMaterial @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] = [] var _elapsed := 0.0 func _ready() -> void: # JajceWorld first isolates its mutable materials in the parent's _ready(). call_deferred("_configure_path_clearings") _refresh_interactors() func _configure_path_clearings() -> void: if grass_material == null: return var segments := PackedVector4Array() segments.resize(CLEARING_LIMIT) var widths := Vector4.ZERO var count := 0 for path in path_clearings.slice(0, CLEARING_LIMIT): var strip := get_node_or_null(path) as MeshInstance3D if strip == null or not strip.mesh is BoxMesh: continue var box := strip.mesh as BoxMesh var half_length := strip.global_basis.z * box.size.z * 0.5 var start := strip.global_position - half_length var end := strip.global_position + half_length segments[count] = Vector4(start.x, start.z, end.x, end.z) widths[count] = strip.global_basis.x.length() * box.size.x * 0.5 + 0.12 count += 1 grass_material.set_shader_parameter("clearing_count", count) grass_material.set_shader_parameter("clearing_segments", segments) grass_material.set_shader_parameter("clearing_half_widths", widths) func _process(delta: float) -> void: _elapsed += delta if _elapsed < update_interval: return _elapsed = 0.0 _refresh_interactors() func _refresh_interactors() -> 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 nearest: Array[Node3D] = [] for value in get_tree().get_nodes_in_group("grass_interactors"): var candidate := value as Node3D if candidate == null: 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): insert_at = index break nearest.insert(insert_at, candidate) if nearest.size() > max_interactors: 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) for index in nearest.size(): positions[index] = nearest[index].global_position grass_material.set_shader_parameter("interactor_count", nearest.size()) grass_material.set_shader_parameter("interactor_positions", positions)