Added footsteps, new tree, various other tweaks

This commit is contained in:
derek
2024-12-05 11:47:34 -06:00
parent 816ae85938
commit 023879ea9f
389 changed files with 20484 additions and 234 deletions

View File

@@ -0,0 +1,45 @@
@tool
extends PanelContainer
const ScatterCache := preload("res://addons/proton_scatter/src/cache/scatter_cache.gd")
@onready var _rebuild_button: Button = %RebuildButton
@onready var _restore_button: Button = %RestoreButton
@onready var _clear_button: Button = %ClearButton
@onready var _enable_for_all_button: Button = %EnableForAllButton
var _cache: ScatterCache
func _ready() -> void:
_rebuild_button.pressed.connect(_on_rebuild_pressed)
_restore_button.pressed.connect(_on_restore_pressed)
_clear_button.pressed.connect(_on_clear_pressed)
_enable_for_all_button.pressed.connect(_on_enable_for_all_pressed)
custom_minimum_size.y = size.y * 1.25
func set_object(cache: ScatterCache) -> void:
_cache = cache
func _on_rebuild_pressed() -> void:
if is_instance_valid(_cache):
_cache.update_cache()
func _on_restore_pressed() -> void:
if is_instance_valid(_cache):
_cache.restore_cache()
func _on_clear_pressed() -> void:
if is_instance_valid(_cache):
_cache.clear_cache()
func _on_enable_for_all_pressed() -> void:
if is_instance_valid(_cache):
_cache.enable_for_all_nodes()

View File

@@ -0,0 +1,49 @@
[gd_scene load_steps=5 format=3 uid="uid://dilbceex72g24"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/cache/inspector_plugin/cache_panel.gd" id="1_h1g4a"]
[ext_resource type="Texture2D" uid="uid://yqlpvcmb7mfi" path="res://addons/proton_scatter/icons/rebuild.svg" id="2_0ml76"]
[ext_resource type="Texture2D" uid="uid://ddjrq1h4mkn6a" path="res://addons/proton_scatter/icons/load.svg" id="3_i6mdl"]
[ext_resource type="Texture2D" uid="uid://btb6rqhhi27mx" path="res://addons/proton_scatter/icons/remove.svg" id="4_bfbdy"]
[node name="CachePanel" type="PanelContainer"]
custom_minimum_size = Vector2(0, 82.5)
offset_right = 161.0
offset_bottom = 66.0
size_flags_horizontal = 3
script = ExtResource("1_h1g4a")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
alignment = 1
[node name="RebuildButton" type="Button" parent="MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
text = "Update Cache"
icon = ExtResource("2_0ml76")
[node name="RestoreButton" type="Button" parent="MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
text = "Restore Transforms"
icon = ExtResource("3_i6mdl")
[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="ClearButton" type="Button" parent="MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
text = "Clear Cache"
icon = ExtResource("4_bfbdy")
[node name="EnableForAllButton" type="Button" parent="MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Enable Cache For All"

View File

@@ -0,0 +1,17 @@
@tool
extends EditorInspectorPlugin
const CachePanel = preload("./cache_panel.tscn")
const ScatterCache = preload("../../cache/scatter_cache.gd")
func _can_handle(object):
return is_instance_of(object, ScatterCache)
func _parse_category(object, category: String):
if category == "ScatterCache" or category == "scatter_cache.gd":
var ui = CachePanel.instantiate()
ui.set_object(object)
add_custom_control(ui)

View File

@@ -0,0 +1,216 @@
@tool
extends Node
# ProtonScatterCacheNode
#
# Saves the transforms created by ProtonScatter nodes in an external resource
# and restore them when loading the scene.
#
# Use this node when you don't want to wait for scatter nodes to fully rebuild
# at start.
# You can also enable "Show output in tree" to get the same effect, but the
# cache makes it much more VCS friendly, and doesn't clutter your scene tree.
const DEFAULT_CACHE_FOLDER := "res://addons/proton_scatter/cache/"
const ProtonScatter := preload("res://addons/proton_scatter/src/scatter.gd")
const ProtonScatterTransformList := preload("../common/transform_list.gd")
signal cache_restored
@export_file("*.res", "*.tres") var cache_file := "":
set(val):
cache_file = val
update_configuration_warnings()
@export var auto_rebuild_cache_when_saving := true
@export_group("Debug", "dbg_")
@export var dbg_disable_thread := false
# The resource where transforms are actually stored
var _local_cache: ProtonScatterCacheResource
var _scene_root: Node
var _scatter_nodes: Dictionary #Key: ProtonScatter, Value: cached version
var _local_cache_changed := false
func _ready() -> void:
if not is_inside_tree():
return
_scene_root = _get_local_scene_root(self)
# Check if cache_file is empty, indicating the default case
if cache_file.is_empty():
if Engine.is_editor_hint():
# Ensure the cache folder exists
_ensure_cache_folder_exists()
else:
printerr("ProtonScatter error: You load a ScatterCache node with an empty cache file attribute. Outside of the editor, the addon can't set a default value. Please open the scene in the editor and set a default value.")
return
# Retrieve the scene name to create a unique recognizable name
var scene_path: String = _scene_root.get_scene_file_path()
var scene_name: String
# If the scene path is not available, set a random name
if scene_path.is_empty():
scene_name = str(randi())
else:
# Use the base name of the scene file and append a hash to avoid collisions
scene_name = scene_path.get_file().get_basename()
scene_name += "_" + str(scene_path.hash())
# Set the cache path to the cache folder, incorporating the scene name
cache_file = DEFAULT_CACHE_FOLDER.get_basename().path_join(scene_name + "_scatter_cache.res")
return
restore_cache.call_deferred()
func _get_configuration_warnings() -> PackedStringArray:
var warnings = PackedStringArray()
if cache_file.is_empty():
warnings.push_back("No path set for the cache file. Select where to store the cache in the inspector.")
return warnings
func _notification(what):
if what == NOTIFICATION_EDITOR_PRE_SAVE and auto_rebuild_cache_when_saving:
update_cache()
func clear_cache() -> void:
_scatter_nodes.clear()
_local_cache = null
func update_cache() -> void:
if cache_file.is_empty():
printerr("Cache file path is empty.")
return
_purge_outdated_nodes()
_discover_scatter_nodes(_scene_root)
if not _local_cache:
_local_cache = ProtonScatterCacheResource.new()
for s in _scatter_nodes:
# Ignore this node if its cache is already up to date
var cached_version: int = _scatter_nodes[s]
if s.build_version == cached_version:
continue
# If transforms are not available, try to rebuild once.
if not s.transforms:
s.rebuild.call_deferred()
await s.build_completed
if not s.transforms:
continue # Move on to the next if still no results.
# Store the transforms in the cache.
_local_cache.store(_scene_root.get_path_to(s), s.transforms.list)
_scatter_nodes[s] = s.build_version
_local_cache_changed = true
# Only save the cache on disk if there's something new to save
if not _local_cache_changed:
return
# TODO: Save large files on a thread
var err = ResourceSaver.save(_local_cache, cache_file)
_local_cache_changed = false
if err != OK:
printerr("ProtonScatter error: Failed to save the cache file. Code: ", err)
func restore_cache() -> void:
# Load the cache file if it exists
if not ResourceLoader.exists(cache_file):
printerr("Could not find cache file ", cache_file)
return
# Cache files are large, load on a separate thread
ResourceLoader.load_threaded_request(cache_file)
while true:
match ResourceLoader.load_threaded_get_status(cache_file):
ResourceLoader.ThreadLoadStatus.THREAD_LOAD_INVALID_RESOURCE:
return
ResourceLoader.ThreadLoadStatus.THREAD_LOAD_IN_PROGRESS:
await get_tree().process_frame
ResourceLoader.ThreadLoadStatus.THREAD_LOAD_FAILED:
return
ResourceLoader.ThreadLoadStatus.THREAD_LOAD_LOADED:
break
_local_cache = ResourceLoader.load_threaded_get(cache_file)
if not _local_cache:
printerr("Could not load cache: ", cache_file)
return
_scatter_nodes.clear()
_discover_scatter_nodes(_scene_root)
for s in _scatter_nodes:
if s.force_rebuild_on_load:
continue # Ignore the cache if the scatter node is about to rebuild anyway.
# Send the cached transforms to the scatter node.
var transforms = ProtonScatterTransformList.new()
transforms.list = _local_cache.get_transforms(_scene_root.get_path_to(s))
s._perform_sanity_check()
s._on_transforms_ready(transforms)
s.build_version = 0
_scatter_nodes[s] = 0
cache_restored.emit()
func enable_for_all_nodes() -> void:
_purge_outdated_nodes()
_discover_scatter_nodes(_scene_root)
for s in _scatter_nodes:
s.force_rebuild_on_load = false
# If the node comes from an instantiated scene, returns the root of that
# instance. Returns the tree root node otherwise.
func _get_local_scene_root(node: Node) -> Node:
if not node.scene_file_path.is_empty():
return node
var parent: Node = node.get_parent()
if not parent:
return node
return _get_local_scene_root(parent)
func _discover_scatter_nodes(node: Node) -> void:
if node is ProtonScatter and not _scatter_nodes.has(node):
_scatter_nodes[node] = node.build_version
for c in node.get_children():
_discover_scatter_nodes(c)
func _purge_outdated_nodes() -> void:
var nodes_to_remove: Array[ProtonScatter] = []
for node in _scatter_nodes:
if not is_instance_valid(node):
nodes_to_remove.push_back(node)
_local_cache.erase(_scene_root.get_path_to(node))
_local_cache_changed = true
for node in nodes_to_remove:
_scatter_nodes.erase(node)
func _ensure_cache_folder_exists() -> void:
if not DirAccess.dir_exists_absolute(DEFAULT_CACHE_FOLDER):
DirAccess.make_dir_recursive_absolute(DEFAULT_CACHE_FOLDER)