Implements tree visibility occlusion system.

Adds tree fading based on camera line of sight.
Trees now fade out when they obstruct the player's view,
improving visibility.

Also includes several fixes:
- Fixes tree distribution.
- Adds a see-through shader.
- Adds camera and occlusion scripts.
This commit is contained in:
Dan Baker 2025-07-01 10:32:21 +01:00
parent a1efaf6294
commit f98773237e
10 changed files with 314 additions and 137 deletions

View file

@ -1,20 +1,22 @@
class_name TreeNode
extends Node3D
@onready var area: Area3D = $InteractRange
var tree_data: TreeDataResource
var model_instance: Node3D
var mesh_instances: Array[MeshInstance3D] = []
var original_materials: Array[Material] = []
var outline_material: Material
var base_circle: MeshInstance3D
var circle_material: Material
var is_invisible: bool = false
var fade_tween: Tween
@onready var area: Area3D = $InteractRange
@onready var last_blocked: Timer = $LastBlocked
func _ready():
setup_outline_material()
area.body_entered.connect(_on_player_entered)
area.body_exited.connect(_on_player_exited)
last_blocked.timeout.connect(_on_fade_timer_timeout)
func set_tree_data(data: TreeDataResource):
tree_data = data
@ -35,16 +37,9 @@ func spawn_model():
model_instance = tree_data.model.instantiate()
tree_data.apply_seasonal_colors(model_instance, Season.current)
add_child(model_instance)
# Create base circle after model is loaded
if not base_circle:
create_base_circle()
# Re-scan for mesh instances in the new model
find_all_mesh_instances(model_instance)
update_outline_materials()
# Apply any additional properties from tree_data
apply_tree_properties()
@ -59,7 +54,7 @@ func apply_tree_properties():
# Apply random scale based on tree properties
if tree_data.max_height > 0:
var scale_variation = randf_range(0.8, 1.2)
var scale_variation = randf_range(1.5, 2)
model_instance.scale *= scale_variation
func setup_outline_material() -> void:
@ -68,67 +63,113 @@ func setup_outline_material() -> void:
outline_material.shader = preload("res://outline.gdshader")
outline_material.set_shader_parameter("color", Vector3(0.702, 0.557, 0.259))
func update_outline_materials():
if mesh_instances.is_empty():
print("Warning: No MeshInstance3D found in model!")
return
# Store original materials for the new model
original_materials.clear()
for mesh in mesh_instances:
if mesh.get_surface_override_material(0):
original_materials.append(mesh.get_surface_override_material(0))
else:
original_materials.append(mesh.get_surface_override_material(0))
func find_all_mesh_instances(node: Node):
if node is MeshInstance3D:
mesh_instances.append(node)
for child in node.get_children():
find_all_mesh_instances(child)
func create_base_circle():
base_circle = MeshInstance3D.new()
add_child(base_circle)
func fade_out_tree_simple(duration: float = 0.25):
# Reset the timer
last_blocked.stop()
last_blocked.start()
# Create a flat cylinder for the circle
var cylinder = CylinderMesh.new()
cylinder.top_radius = 0.4 # Adjust size as needed
cylinder.bottom_radius = 0.4
cylinder.height = 1 # Very thin to make it look like a flat circle
cylinder.rings = 1
cylinder.radial_segments = 9 # More segments = smoother circle
base_circle.mesh = cylinder
# Create transparent material with outline
circle_material = StandardMaterial3D.new()
circle_material.flags_transparent = true
circle_material.albedo_color = Color(1.0, 0.843, 0.0, 0.0) # Fully transparent gold
# Add rim lighting effect for outline appearance
circle_material.rim_enabled = true
circle_material.rim = 1.0
circle_material.rim_tint = 1.0
circle_material.rim_color = Color(1.0, 0.843, 0.0) # Gold rim
base_circle.material_override = circle_material
if is_invisible:
return
base_circle.position.y = 1 # Slightly above ground to avoid z-fighting
is_invisible = true
# Start hidden
base_circle.visible = true
# Kill any existing fade tween
if fade_tween:
fade_tween.kill()
for mesh_instance in mesh_instances:
fade_mesh_materials(mesh_instance, 1.0, 0.1, duration)
# This gets called when the timer times out
func _on_fade_timer_timeout():
fade_in_tree_immediate()
func fade_in_tree_immediate(duration: float = 0.25):
if not is_invisible:
return
# Kill any existing fade tween
if fade_tween:
fade_tween.kill()
for mesh_instance in mesh_instances:
fade_mesh_materials(mesh_instance, 0.1, 1.0, duration)
is_invisible = false
func fade_mesh_materials(mesh_instance: MeshInstance3D, from_alpha: float, to_alpha: float, duration: float):
if not mesh_instance.mesh:
return
var surface_count = mesh_instance.mesh.get_surface_count()
for surface_id in range(surface_count):
var material = mesh_instance.get_surface_override_material(surface_id)
# Always ensure we have a unique material instance
if not material:
# No override material - duplicate from base material
var base_material = mesh_instance.mesh.surface_get_material(surface_id)
if base_material:
material = base_material.duplicate()
mesh_instance.set_surface_override_material(surface_id, material)
else:
# Override material exists - duplicate it to ensure uniqueness
material = material.duplicate()
mesh_instance.set_surface_override_material(surface_id, material)
# Only work with StandardMaterial3D
if material and material is StandardMaterial3D:
var std_mat = material as StandardMaterial3D
# Set transparency mode based on target alpha
if to_alpha < 1.0:
std_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
# Force shadow casting
mesh_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_ON
# Set initial alpha
std_mat.albedo_color.a = from_alpha
# Create and store the tween
fade_tween = create_tween()
fade_tween.tween_method(
func(alpha: float):
std_mat.albedo_color.a = alpha,
from_alpha, to_alpha, duration
)
# When fading to full opacity, reset transparency mode
if to_alpha >= 1.0:
fade_tween.tween_callback(
func(): std_mat.transparency = BaseMaterial3D.TRANSPARENCY_DISABLED
)
func toggle_tree_visibility(duration: float = 1.0):
if is_invisible:
fade_in_tree_immediate(duration)
else:
fade_out_tree_simple(duration)
func _on_player_entered(body: Node3D):
if body.is_in_group("player"):
base_circle.visible = true
# Apply outline to all mesh instances
for mesh in mesh_instances:
mesh.material_overlay = outline_material
if not is_invisible:
for mesh in mesh_instances:
mesh.material_overlay = outline_material
func _on_player_exited(body: Node3D):
if body.is_in_group("player"):
Log.pr('Out of range...')
base_circle.visible = false
# Remove outline from all mesh instances
for mesh in mesh_instances:
mesh.material_overlay = null
if not is_invisible:
# Reset the material overlay to null
for mesh in mesh_instances:
mesh.material_overlay = null