108 lines
3.4 KiB
GDScript
108 lines
3.4 KiB
GDScript
## Procedural snow cap geometry for flat roofs and vehicles.
|
|
extends Node3D
|
|
|
|
enum PlacementMode {
|
|
AUTO_TARGET_AABB,
|
|
MANUAL_SURFACE,
|
|
}
|
|
|
|
const SNOW_CAP_SHADER: Shader = preload("res://core/daynight/snow_cap.gdshader")
|
|
|
|
@export var placement_mode: PlacementMode = PlacementMode.AUTO_TARGET_AABB
|
|
@export var target_path: NodePath
|
|
@export var size_inset: Vector2 = Vector2(0.25, 1.0)
|
|
@export var position_offset: Vector3 = Vector3(0.0, -0.08, 0.0)
|
|
@export var max_snow_height: float = 0.2
|
|
@export_range(1, 24) var subdivide_width: int = 6
|
|
@export_range(1, 24) var subdivide_depth: int = 18
|
|
@export var manual_surface_center: Vector3 = Vector3.ZERO
|
|
@export var manual_surface_size: Vector2 = Vector2(2.0, 6.0)
|
|
|
|
var _mesh_instance: MeshInstance3D
|
|
|
|
func _ready() -> void:
|
|
add_to_group("weather_overlay_ignore")
|
|
_ensure_mesh_instance()
|
|
_rebuild()
|
|
|
|
func rebuild() -> void:
|
|
_rebuild()
|
|
|
|
func _ensure_mesh_instance() -> void:
|
|
_mesh_instance = get_node_or_null("SnowCapMesh") as MeshInstance3D
|
|
if _mesh_instance != null:
|
|
return
|
|
|
|
_mesh_instance = MeshInstance3D.new()
|
|
_mesh_instance.name = "SnowCapMesh"
|
|
_mesh_instance.add_to_group("weather_overlay_ignore")
|
|
add_child(_mesh_instance)
|
|
|
|
func _rebuild() -> void:
|
|
var cap_width: float = 0.0
|
|
var cap_depth: float = 0.0
|
|
var cap_surface_center := Vector3.ZERO
|
|
|
|
if placement_mode == PlacementMode.MANUAL_SURFACE:
|
|
cap_width = manual_surface_size.x
|
|
cap_depth = manual_surface_size.y
|
|
cap_surface_center = manual_surface_center
|
|
else:
|
|
var target_mesh: MeshInstance3D = get_node_or_null(target_path) as MeshInstance3D
|
|
if target_mesh == null:
|
|
if _mesh_instance:
|
|
_mesh_instance.visible = false
|
|
return
|
|
|
|
var target_aabb: AABB = _get_target_aabb(target_mesh)
|
|
cap_width = max(target_aabb.size.x - size_inset.x, 0.1)
|
|
cap_depth = max(target_aabb.size.z - size_inset.y, 0.1)
|
|
cap_surface_center = Vector3(
|
|
target_aabb.position.x + target_aabb.size.x * 0.5,
|
|
target_aabb.position.y + target_aabb.size.y,
|
|
target_aabb.position.z + target_aabb.size.z * 0.5
|
|
)
|
|
|
|
var cap_center := cap_surface_center + Vector3(0.0, max_snow_height * 0.5, 0.0)
|
|
|
|
var box_mesh := BoxMesh.new()
|
|
box_mesh.size = Vector3(cap_width, max_snow_height, cap_depth)
|
|
box_mesh.subdivide_width = subdivide_width
|
|
box_mesh.subdivide_depth = subdivide_depth
|
|
_mesh_instance.mesh = box_mesh
|
|
_mesh_instance.position = cap_center + position_offset
|
|
_mesh_instance.material_override = _build_material(cap_width, cap_depth)
|
|
_mesh_instance.visible = true
|
|
|
|
func _build_material(cap_width: float, cap_depth: float) -> ShaderMaterial:
|
|
var material := ShaderMaterial.new()
|
|
material.shader = SNOW_CAP_SHADER
|
|
material.set_shader_parameter("max_height", max_snow_height)
|
|
material.set_shader_parameter("half_width", cap_width * 0.5)
|
|
material.set_shader_parameter("half_depth", cap_depth * 0.5)
|
|
return material
|
|
|
|
func _get_target_aabb(target_mesh: MeshInstance3D) -> AABB:
|
|
var points: Array[Vector3] = []
|
|
for point in _get_aabb_points(target_mesh.get_aabb()):
|
|
points.append(target_mesh.transform * point)
|
|
|
|
var merged := AABB(points[0], Vector3.ZERO)
|
|
for point in points:
|
|
merged = merged.expand(point)
|
|
return merged
|
|
|
|
func _get_aabb_points(aabb: AABB) -> Array[Vector3]:
|
|
var p: Vector3 = aabb.position
|
|
var s: Vector3 = aabb.size
|
|
return [
|
|
p,
|
|
p + Vector3(s.x, 0.0, 0.0),
|
|
p + Vector3(0.0, s.y, 0.0),
|
|
p + Vector3(0.0, 0.0, s.z),
|
|
p + Vector3(s.x, s.y, 0.0),
|
|
p + Vector3(s.x, 0.0, s.z),
|
|
p + Vector3(0.0, s.y, s.z),
|
|
p + s,
|
|
]
|