feat: author resource-aware riverbank grove
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
[gd_scene load_steps=57 format=3]
|
||||
[gd_scene load_steps=58 format=3]
|
||||
|
||||
[ext_resource type="Terrain3DAssets" path="res://terrain/jajce/assets.tres" id="1_assets"]
|
||||
[ext_resource type="PackedScene" path="res://world/resource_nodes/ResourceNode.tscn" id="2_resource"]
|
||||
@@ -25,6 +25,7 @@
|
||||
[ext_resource type="PackedScene" path="res://assets/foliage/tree-high.glb" id="23_conifer_high"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/CozyButterfly.tscn" id="24_butterfly"]
|
||||
[ext_resource type="Material" path="res://world/jajce/materials/cozy_grass_process_material.tres" id="25_grass_process"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/RiverbankResourceCluster.tscn" id="26_resource_cluster"]
|
||||
|
||||
[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_jajce"]
|
||||
_shader_parameters = {
|
||||
@@ -410,10 +411,6 @@ position = Vector3(-4, 0, -30)
|
||||
position = Vector3(19, 0, 18)
|
||||
scale = Vector3(0.9, 0.9, 0.9)
|
||||
|
||||
[node name="Tree_River_02" parent="FoliageRoot" instance=ExtResource("4_tree")]
|
||||
position = Vector3(31, 0, 14)
|
||||
scale = Vector3(1.25, 1.25, 1.25)
|
||||
|
||||
[node name="Conifer_Citadel_01" parent="FoliageRoot" instance=ExtResource("23_conifer_high")]
|
||||
position = Vector3(-39, 0, 13)
|
||||
rotation_degrees = Vector3(0, 18, 0)
|
||||
@@ -439,11 +436,6 @@ position = Vector3(38, 0, -8)
|
||||
rotation_degrees = Vector3(0, 28, 0)
|
||||
scale = Vector3(2.7, 2.7, 2.7)
|
||||
|
||||
[node name="Conifer_River_01" parent="FoliageRoot" instance=ExtResource("22_conifer")]
|
||||
position = Vector3(38, 0, 20)
|
||||
rotation_degrees = Vector3(0, -51, 0)
|
||||
scale = Vector3(2.55, 2.55, 2.55)
|
||||
|
||||
[node name="Conifer_South_01" parent="FoliageRoot" instance=ExtResource("23_conifer_high")]
|
||||
position = Vector3(-21, 0, -29)
|
||||
rotation_degrees = Vector3(0, 44, 0)
|
||||
@@ -566,17 +558,6 @@ yield_per_action = 2.0
|
||||
safety_risk = 0.24
|
||||
comfort_distance = 26.0
|
||||
|
||||
[node name="Tree_River_Resource_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||
position = Vector3(20, 0, 18)
|
||||
node_id = &"tree_river_resource_01"
|
||||
action_id = &"gather_wood"
|
||||
resource_id = &"wood"
|
||||
initial_amount = 8.0
|
||||
yield_per_action = 2.0
|
||||
safety_risk = 0.22
|
||||
comfort_distance = 30.0
|
||||
discovery_priority = 0.1
|
||||
|
||||
[node name="WoodPile_Village_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||
position = Vector3(5, 0, -13)
|
||||
node_id = &"wood_pile_village_01"
|
||||
@@ -597,15 +578,6 @@ safety_risk = 0.02
|
||||
comfort_distance = 16.0
|
||||
discovery_priority = 0.85
|
||||
|
||||
[node name="BerryBush_River_02" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||
position = Vector3(28, 0, -2)
|
||||
node_id = &"berry_bush_river_02"
|
||||
initial_amount = 5.0
|
||||
yield_per_action = 1.5
|
||||
safety_risk = 0.22
|
||||
comfort_distance = 28.0
|
||||
discovery_priority = 0.2
|
||||
|
||||
[node name="Tree_Forest_Grove_01" parent="WorldObjects/ResourceNodes" instance=ExtResource("2_resource")]
|
||||
position = Vector3(-8, 0, -22)
|
||||
node_id = &"tree_forest_grove_01"
|
||||
@@ -648,6 +620,11 @@ safety_risk = 0.1
|
||||
comfort_distance = 20.0
|
||||
discovery_priority = 0.45
|
||||
|
||||
[node name="ResourceClusters" type="Node3D" parent="WorldObjects"]
|
||||
|
||||
[node name="RiverbankResourceCluster" parent="WorldObjects/ResourceClusters" instance=ExtResource("26_resource_cluster")]
|
||||
position = Vector3(33, 0, 7)
|
||||
|
||||
[node name="StorageSites" type="Node3D" parent="WorldObjects"]
|
||||
|
||||
[node name="VillagePantry" parent="WorldObjects/StorageSites" instance=ExtResource("14_storage")]
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uid://b20nnq0w3x0co
|
||||
@@ -0,0 +1,73 @@
|
||||
[gd_scene load_steps=6 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/jajce/RiverbankResourceCluster.gd" id="1_script"]
|
||||
[ext_resource type="PackedScene" path="res://world/resource_nodes/ResourceNode.tscn" id="2_resource"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/StylizedTree.tscn" id="3_tree"]
|
||||
[ext_resource type="PackedScene" path="res://world/jajce/StylizedBerryPatch.tscn" id="4_berry"]
|
||||
[ext_resource type="PackedScene" path="res://assets/foliage/tree.glb" id="5_conifer"]
|
||||
|
||||
[node name="RiverbankResourceCluster" type="Node3D"]
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="DecorativeInstances" type="Node3D" parent="."]
|
||||
|
||||
[node name="Understory" type="Node3D" parent="DecorativeInstances"]
|
||||
|
||||
[node name="FernClumps" type="MultiMeshInstance3D" parent="DecorativeInstances/Understory"]
|
||||
cast_shadow = 0
|
||||
|
||||
[node name="FlowerClumps" type="MultiMeshInstance3D" parent="DecorativeInstances/Understory"]
|
||||
cast_shadow = 0
|
||||
|
||||
[node name="BankStones" type="MultiMeshInstance3D" parent="DecorativeInstances/Understory"]
|
||||
|
||||
[node name="Trees" type="Node3D" parent="DecorativeInstances"]
|
||||
|
||||
[node name="DeciduousBankTree" parent="DecorativeInstances/Trees" instance=ExtResource("3_tree")]
|
||||
position = Vector3(3, 0, 4)
|
||||
scale = Vector3(0.92, 0.92, 0.92)
|
||||
|
||||
[node name="ConiferBankTree" parent="DecorativeInstances/Trees" instance=ExtResource("5_conifer")]
|
||||
position = Vector3(5, 0, 13)
|
||||
rotation_degrees = Vector3(0, -51, 0)
|
||||
scale = Vector3(2.55, 2.55, 2.55)
|
||||
|
||||
[node name="ResourceAnchors" type="Node3D" parent="."]
|
||||
|
||||
[node name="BerryBush_River_02" parent="ResourceAnchors" instance=ExtResource("2_resource")]
|
||||
position = Vector3(0, 0, -9)
|
||||
node_id = &"berry_bush_river_02"
|
||||
initial_amount = 5.0
|
||||
yield_per_action = 1.5
|
||||
safety_risk = 0.22
|
||||
comfort_distance = 28.0
|
||||
discovery_priority = 0.2
|
||||
|
||||
[node name="MeshInstance3D" parent="ResourceAnchors/BerryBush_River_02/Visual"]
|
||||
visible = false
|
||||
|
||||
[node name="BerryPatchPresentation" parent="ResourceAnchors/BerryBush_River_02/Visual" instance=ExtResource("4_berry")]
|
||||
scale = Vector3(1.15, 1.15, 1.15)
|
||||
|
||||
[node name="InteractionPoint" parent="ResourceAnchors/BerryBush_River_02"]
|
||||
position = Vector3(-2.5, 0, 2)
|
||||
|
||||
[node name="Tree_River_Resource_01" parent="ResourceAnchors" instance=ExtResource("2_resource")]
|
||||
position = Vector3(0, 0, 8)
|
||||
node_id = &"tree_river_resource_01"
|
||||
action_id = &"gather_wood"
|
||||
resource_id = &"wood"
|
||||
initial_amount = 8.0
|
||||
yield_per_action = 2.0
|
||||
safety_risk = 0.22
|
||||
comfort_distance = 30.0
|
||||
discovery_priority = 0.1
|
||||
|
||||
[node name="MeshInstance3D" parent="ResourceAnchors/Tree_River_Resource_01/Visual"]
|
||||
visible = false
|
||||
|
||||
[node name="TreePresentation" parent="ResourceAnchors/Tree_River_Resource_01/Visual" instance=ExtResource("3_tree")]
|
||||
scale = Vector3(1.08, 1.08, 1.08)
|
||||
|
||||
[node name="InteractionPoint" parent="ResourceAnchors/Tree_River_Resource_01"]
|
||||
position = Vector3(-4, 0, 2)
|
||||
@@ -0,0 +1,85 @@
|
||||
class_name StylizedBerryPatch
|
||||
extends Node3D
|
||||
|
||||
const WIND_SHADER := preload("res://world/jajce/materials/wind.gdshader")
|
||||
const LEAF_POSITIONS := [
|
||||
Vector3(-0.52, 0.42, 0.08),
|
||||
Vector3(0.08, 0.48, -0.14),
|
||||
Vector3(0.58, 0.4, 0.18),
|
||||
Vector3(-0.18, 0.7, 0.18),
|
||||
Vector3(0.38, 0.67, -0.22),
|
||||
]
|
||||
const LEAF_SCALES := [
|
||||
Vector3(0.72, 0.38, 0.62),
|
||||
Vector3(0.78, 0.44, 0.68),
|
||||
Vector3(0.66, 0.36, 0.58),
|
||||
Vector3(0.56, 0.32, 0.52),
|
||||
Vector3(0.5, 0.28, 0.46),
|
||||
]
|
||||
const BERRY_POSITIONS := [
|
||||
Vector3(-0.7, 0.64, 0.2),
|
||||
Vector3(-0.43, 0.82, -0.34),
|
||||
Vector3(-0.14, 0.7, 0.46),
|
||||
Vector3(0.08, 0.88, -0.08),
|
||||
Vector3(0.34, 0.7, 0.42),
|
||||
Vector3(0.48, 0.86, -0.3),
|
||||
Vector3(0.74, 0.6, 0.12),
|
||||
Vector3(-0.02, 1.02, 0.22),
|
||||
]
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_build_leaves()
|
||||
_build_berries()
|
||||
|
||||
|
||||
func get_presentation_instance_count() -> int:
|
||||
return LEAF_POSITIONS.size() + BERRY_POSITIONS.size()
|
||||
|
||||
|
||||
func _build_leaves() -> void:
|
||||
var mesh := SphereMesh.new()
|
||||
mesh.radius = 1.0
|
||||
mesh.height = 2.0
|
||||
mesh.radial_segments = 9
|
||||
mesh.rings = 5
|
||||
var material := ShaderMaterial.new()
|
||||
material.shader = WIND_SHADER
|
||||
material.set_shader_parameter("albedo", Color("#426c32"))
|
||||
material.set_shader_parameter("roughness", 0.92)
|
||||
material.set_shader_parameter("wind_strength", 0.045)
|
||||
material.set_shader_parameter("wind_speed", 0.48)
|
||||
material.set_shader_parameter("wind_phase", 1.7)
|
||||
mesh.material = material
|
||||
var transforms: Array[Transform3D] = []
|
||||
for index in LEAF_POSITIONS.size():
|
||||
transforms.append(
|
||||
Transform3D(Basis.IDENTITY.scaled(LEAF_SCALES[index]), LEAF_POSITIONS[index])
|
||||
)
|
||||
$Leaves.multimesh = _create_multimesh(mesh, transforms)
|
||||
|
||||
|
||||
func _build_berries() -> void:
|
||||
var mesh := SphereMesh.new()
|
||||
mesh.radius = 0.12
|
||||
mesh.height = 0.21
|
||||
mesh.radial_segments = 8
|
||||
mesh.rings = 4
|
||||
var material := StandardMaterial3D.new()
|
||||
material.albedo_color = Color("#8f3148")
|
||||
material.roughness = 0.78
|
||||
mesh.material = material
|
||||
var transforms: Array[Transform3D] = []
|
||||
for position in BERRY_POSITIONS:
|
||||
transforms.append(Transform3D(Basis.IDENTITY, position))
|
||||
$Berries.multimesh = _create_multimesh(mesh, transforms)
|
||||
|
||||
|
||||
func _create_multimesh(mesh: Mesh, transforms: Array) -> MultiMesh:
|
||||
var multimesh := MultiMesh.new()
|
||||
multimesh.transform_format = MultiMesh.TRANSFORM_3D
|
||||
multimesh.mesh = mesh
|
||||
multimesh.instance_count = transforms.size()
|
||||
for index in transforms.size():
|
||||
multimesh.set_instance_transform(index, transforms[index])
|
||||
return multimesh
|
||||
@@ -0,0 +1 @@
|
||||
uid://ctb2v3prn7ovc
|
||||
@@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://world/jajce/StylizedBerryPatch.gd" id="1_script"]
|
||||
|
||||
[node name="StylizedBerryPatch" type="Node3D"]
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="Leaves" type="MultiMeshInstance3D" parent="."]
|
||||
cast_shadow = 0
|
||||
|
||||
[node name="Berries" type="MultiMeshInstance3D" parent="."]
|
||||
cast_shadow = 0
|
||||
Reference in New Issue
Block a user