136 lines
4.2 KiB
GDScript
136 lines
4.2 KiB
GDScript
class_name TerrainResourceCluster
|
|
extends Node3D
|
|
|
|
@export var terrain_path := NodePath("../../../TerrainRoot/Terrain3D")
|
|
|
|
var terrain: Terrain3D
|
|
|
|
|
|
func _ready() -> void:
|
|
call_deferred("_initialize_cluster")
|
|
|
|
|
|
func get_resource_anchors() -> Array[ResourceNode]:
|
|
var anchors: Array[ResourceNode] = []
|
|
for child in $ResourceAnchors.get_children():
|
|
if child is ResourceNode:
|
|
anchors.append(child)
|
|
return anchors
|
|
|
|
|
|
func get_presentation_stats() -> Dictionary:
|
|
var understory_instances := 0
|
|
var multimesh_batches := 0
|
|
for child in $DecorativeInstances/Understory.get_children():
|
|
var instance := child as MultiMeshInstance3D
|
|
if instance == null or instance.multimesh == null:
|
|
continue
|
|
understory_instances += instance.multimesh.instance_count
|
|
multimesh_batches += 1
|
|
return {
|
|
"understory_instance_count": understory_instances,
|
|
"multimesh_batch_count": multimesh_batches,
|
|
"decorative_tree_count": $DecorativeInstances/Trees.get_child_count(),
|
|
"resource_anchor_count": get_resource_anchors().size(),
|
|
}
|
|
|
|
|
|
func _initialize_cluster() -> void:
|
|
terrain = get_node_or_null(terrain_path) as Terrain3D
|
|
if terrain == null:
|
|
push_warning("%s: Terrain3D is unavailable" % name)
|
|
return
|
|
_snap_authored_children($DecorativeInstances/Trees)
|
|
for anchor in get_resource_anchors():
|
|
_snap_resource_anchor(anchor)
|
|
_build_understory()
|
|
|
|
|
|
func _build_understory() -> void:
|
|
pass
|
|
|
|
|
|
func _snap_authored_children(parent: Node) -> void:
|
|
for child in parent.get_children():
|
|
if child is Node3D:
|
|
_snap_to_terrain(child)
|
|
|
|
|
|
func _snap_to_terrain(node: Node3D) -> void:
|
|
var position := node.global_position
|
|
var height := terrain.data.get_height(position)
|
|
if not is_nan(height):
|
|
node.global_position.y = height
|
|
|
|
|
|
func _snap_resource_anchor(anchor: ResourceNode) -> void:
|
|
var anchor_position := anchor.global_position
|
|
var anchor_height := terrain.data.get_height(anchor_position)
|
|
if is_nan(anchor_height):
|
|
return
|
|
var interaction_position := anchor.global_transform * anchor.interaction_point.position
|
|
var interaction_height := terrain.data.get_height(interaction_position)
|
|
if not is_nan(interaction_height):
|
|
anchor.interaction_point.position.y = interaction_height - anchor_height + 0.05
|
|
anchor.global_position.y = anchor_height
|
|
|
|
|
|
func _assign_multimesh(
|
|
target: MultiMeshInstance3D,
|
|
mesh: Mesh,
|
|
offsets: Array,
|
|
base_scale: Vector3,
|
|
vertical_offset: float
|
|
) -> void:
|
|
var multimesh := MultiMesh.new()
|
|
multimesh.transform_format = MultiMesh.TRANSFORM_3D
|
|
multimesh.mesh = mesh
|
|
multimesh.instance_count = offsets.size()
|
|
for index in offsets.size():
|
|
var offset: Vector3 = offsets[index]
|
|
var world_sample := to_global(offset)
|
|
var height := terrain.data.get_height(world_sample)
|
|
if is_nan(height):
|
|
height = global_position.y
|
|
var local_position := to_local(Vector3(world_sample.x, height, world_sample.z))
|
|
local_position.y += vertical_offset
|
|
var scale_variation := 0.84 + float((index * 37) % 5) * 0.07
|
|
var rotation := deg_to_rad(float((index * 53) % 360))
|
|
var basis := Basis(Vector3.UP, rotation).scaled(base_scale * scale_variation)
|
|
multimesh.set_instance_transform(index, Transform3D(basis, local_position))
|
|
target.multimesh = multimesh
|
|
|
|
|
|
func _create_fern_mesh(material: Material) -> ArrayMesh:
|
|
var vertices := PackedVector3Array()
|
|
var normals := PackedVector3Array()
|
|
var uvs := PackedVector2Array()
|
|
for frond_index in 5:
|
|
var angle := float(frond_index) / 5.0 * TAU + 0.3
|
|
var direction := Vector3(cos(angle), 0.0, sin(angle))
|
|
var side := Vector3(-direction.z, 0.0, direction.x) * 0.16
|
|
var base := Vector3(0.0, 0.02, 0.0)
|
|
var middle := direction * 0.38 + Vector3.UP * 0.14
|
|
var tip := direction * 0.82 + Vector3.UP * 0.24
|
|
vertices.append_array([base, middle + side, tip, base, tip, middle - side])
|
|
for _vertex in 6:
|
|
normals.append(Vector3.UP)
|
|
for uv in [
|
|
Vector2(0.5, 0.0),
|
|
Vector2(1.0, 0.5),
|
|
Vector2(0.5, 1.0),
|
|
Vector2(0.5, 0.0),
|
|
Vector2(0.5, 1.0),
|
|
Vector2(0.0, 0.5),
|
|
]:
|
|
uvs.append(uv)
|
|
var arrays := []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
arrays[Mesh.ARRAY_VERTEX] = vertices
|
|
arrays[Mesh.ARRAY_NORMAL] = normals
|
|
arrays[Mesh.ARRAY_TEX_UV] = uvs
|
|
var mesh := ArrayMesh.new()
|
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
|
mesh.surface_set_material(0, material)
|
|
return mesh
|