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