211 lines
6.2 KiB
GDScript
211 lines
6.2 KiB
GDScript
class_name RiverbankResourceCluster
|
|
extends Node3D
|
|
|
|
const FERN_OFFSETS := [
|
|
Vector3(-0.8, 0.0, -10.8),
|
|
Vector3(1.1, 0.0, -8.7),
|
|
Vector3(2.9, 0.0, -6.8),
|
|
Vector3(-1.4, 0.0, -4.6),
|
|
Vector3(1.8, 0.0, -2.7),
|
|
Vector3(3.6, 0.0, -0.5),
|
|
Vector3(-0.5, 0.0, 1.2),
|
|
Vector3(2.4, 0.0, 3.4),
|
|
Vector3(-1.8, 0.0, 5.7),
|
|
Vector3(1.4, 0.0, 7.6),
|
|
Vector3(3.8, 0.0, 9.4),
|
|
Vector3(0.2, 0.0, 11.8),
|
|
]
|
|
const FLOWER_OFFSETS := [
|
|
Vector3(0.2, 0.0, -10.2),
|
|
Vector3(2.2, 0.0, -7.5),
|
|
Vector3(-0.4, 0.0, -5.5),
|
|
Vector3(3.4, 0.0, -3.2),
|
|
Vector3(0.8, 0.0, 0.1),
|
|
Vector3(2.8, 0.0, 2.1),
|
|
Vector3(-0.7, 0.0, 4.4),
|
|
Vector3(2.0, 0.0, 6.4),
|
|
Vector3(0.4, 0.0, 9.8),
|
|
]
|
|
const STONE_OFFSETS := [
|
|
Vector3(3.9, 0.0, -9.6),
|
|
Vector3(-1.2, 0.0, -7.2),
|
|
Vector3(3.2, 0.0, -4.3),
|
|
Vector3(-0.7, 0.0, -1.2),
|
|
Vector3(3.7, 0.0, 4.6),
|
|
Vector3(-1.1, 0.0, 8.4),
|
|
Vector3(2.7, 0.0, 11.2),
|
|
]
|
|
|
|
@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("RiverbankResourceCluster: Terrain3D is unavailable")
|
|
return
|
|
_snap_authored_children($DecorativeInstances/Trees)
|
|
for anchor in get_resource_anchors():
|
|
_snap_resource_anchor(anchor)
|
|
_build_understory()
|
|
|
|
|
|
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 _build_understory() -> void:
|
|
var fern_material := StandardMaterial3D.new()
|
|
fern_material.albedo_color = Color("#315f35")
|
|
fern_material.roughness = 0.94
|
|
fern_material.cull_mode = BaseMaterial3D.CULL_DISABLED
|
|
_assign_multimesh(
|
|
$DecorativeInstances/Understory/FernClumps,
|
|
_create_fern_mesh(fern_material),
|
|
FERN_OFFSETS,
|
|
Vector3(0.8, 0.8, 0.8),
|
|
0.03
|
|
)
|
|
|
|
var flower_mesh := SphereMesh.new()
|
|
flower_mesh.radius = 0.13
|
|
flower_mesh.height = 0.22
|
|
flower_mesh.radial_segments = 7
|
|
flower_mesh.rings = 4
|
|
var flower_material := StandardMaterial3D.new()
|
|
flower_material.albedo_color = Color("#efc55a")
|
|
flower_material.roughness = 0.82
|
|
flower_mesh.material = flower_material
|
|
_assign_multimesh(
|
|
$DecorativeInstances/Understory/FlowerClumps, flower_mesh, FLOWER_OFFSETS, Vector3.ONE, 0.2
|
|
)
|
|
|
|
var stone_mesh := SphereMesh.new()
|
|
stone_mesh.radius = 1.0
|
|
stone_mesh.height = 2.0
|
|
stone_mesh.radial_segments = 8
|
|
stone_mesh.rings = 4
|
|
var stone_material := StandardMaterial3D.new()
|
|
stone_material.albedo_color = Color("#77786b")
|
|
stone_material.roughness = 0.96
|
|
stone_mesh.material = stone_material
|
|
_assign_multimesh(
|
|
$DecorativeInstances/Understory/BankStones,
|
|
stone_mesh,
|
|
STONE_OFFSETS,
|
|
Vector3(0.62, 0.24, 0.44),
|
|
0.22
|
|
)
|
|
|
|
|
|
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
|