@tool
extends MultiMeshInstance3D
@export var clump_center: NodePath:
set(v):
clump_center = v
_request_scatter()
@export var clump_radius: int = 10:
set(v):
clump_radius = v
_request_scatter()
@export_range(1, 50) var clump_angulate: int = 1:
set(v):
clump_angulate = v
_request_scatter()
@export_range(0, 200) var thickness: int = 10:
set(v):
thickness = v
_request_scatter()
@export var meadowing: bool = false:
set(v):
meadowing = v
_request_scatter()
@export var meadow_radius: int = 0:
set(v):
meadow_radius = v
_request_scatter()
@export_range(0, 10) var blade_width: float = 1.0:
set(v):
blade_width = v
_request_scatter()
@export_range(0, 10) var blade_height: float = 1.0:
set(v):
blade_height = v
_request_scatter()
@export_range(0, 100) var amount_randomize: int = 3:
set(v):
amount_randomize = v
_request_scatter()
@export var y_offset: float = 0.0:
set(v):
y_offset = v
_request_scatter()
@export var randomize_hori: bool = true:
set(v):
randomize_hori = v
_request_scatter()
@export var randomize_vert: bool = true:
set(v):
randomize_vert = v
_request_scatter()
@export var skip_instances: Array[int] = []
@export var ground_collision_mask: int = 1
@export var max_instances: int = 20000
@export var player_node: Node3D
var _last_center_position: Vector3
var _needs_update := false
var _material_cache: ShaderMaterial
func _ready() -> void:
if not multimesh:
multimesh = MultiMesh.new()
multimesh.transform_format = MultiMesh.TRANSFORM_3D
_cache_material()
if clump_center != NodePath():
var c = get_node_or_null(clump_center)
if c:
_last_center_position = c.global_transform.origin
if Engine.is_editor_hint():
set_process(true)
_request_scatter()
func _process(_delta: float) -> void:
if Engine.is_editor_hint():
if not is_visible_in_tree():
return
if _needs_update:
scatter()
_needs_update = false
_update_shader_player()
# Checa mudança de posição do centro de forma otimizada
if clump_center != NodePath():
var c = get_node_or_null(clump_center)
if c and c.global_transform.origin != _last_center_position:
_last_center_position = c.global_transform.origin
_request_scatter()
func _request_scatter() -> void:
if Engine.is_editor_hint():
_needs_update = true
func scatter() -> void:
if not multimesh:
return
var center_node = get_node_or_null(clump_center)
if not center_node:
return
var center = center_node.global_transform.origin
var count = min(thickness * thickness, max_instances)
multimesh.instance_count = count
var space_state = get_world_3d().direct_space_state
var current_index := 0
for i in range(count):
if i in skip_instances:
continue
var angle = randf() * TAU
var dist = randf() * clump_radius
if meadowing:
dist += meadow_radius
var x = center.x + cos(angle) * dist
var z = center.z + sin(angle) * dist
# Raycast direto otimizado
var query = PhysicsRayQueryParameters3D.create(
Vector3(x, 500, z),
Vector3(x, -500, z),
ground_collision_mask
)
query.collide_with_areas = false
var hit = space_state.intersect_ray(query)
if hit.is_empty():
continue
var t = Transform3D()
if randomize_hori:
t = t.rotated(Vector3.UP, randf_range(-sqrt(amount_randomize), sqrt(amount_randomize)))
if randomize_vert:
t = t.rotated(Vector3.LEFT, randf_range(-0.2, 0.2))
t = t.rotated(Vector3.FORWARD, randf_range(-0.2, 0.2))
var scale_rand = randf_range(0.8, 1.2)
t = t.scaled(Vector3(blade_width * scale_rand, blade_height * scale_rand, blade_width * scale_rand))
t.origin = Vector3(x, hit.position.y + y_offset, z)
multimesh.set_instance_transform(current_index, t)
current_index += 1
multimesh.instance_count = current_index
func _cache_material() -> void:
if material_override is ShaderMaterial:
_material_cache = material_override
elif multimesh and multimesh.mesh:
var mat = multimesh.mesh.surface_get_material(0)
if mat is ShaderMaterial:
_material_cache = mat
func _update_shader_player() -> void:
if not player_node:
return
if not _material_cache:
_cache_material()
if _material_cache:
_material_cache.set_shader_parameter("player_pos", player_node.global_transform.origin)