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,284 @@
@tool
extends PopupPanel
# Formats and displays the DocumentationData provided by other parts of the addon
# TODO: Adjust title font size based on the editor font size / scaling
const DocumentationInfo = preload("./documentation_info.gd")
const SpecialPages = preload("./pages/special_pages.gd")
var _pages := {}
var _items := {}
var _categories_roots := {}
var _modifiers_root: TreeItem
var _edited_text: String
var _accent_color := Color.CORNFLOWER_BLUE
var _editor_scale := 1.0
var _header_size := 20
var _sub_header_size := 16
var _populated := false
@onready var tree: Tree = $HSplitContainer/Tree
@onready var label: RichTextLabel = $HSplitContainer/RichTextLabel
func _ready() -> void:
tree.create_item() # Create tree root
tree.hide_root = true
tree.item_selected.connect(_on_item_selected)
add_page(SpecialPages.get_scatter_documentation(), tree.create_item())
add_page(SpecialPages.get_item_documentation(), tree.create_item())
add_page(SpecialPages.get_shape_documentation(), tree.create_item())
add_page(SpecialPages.get_cache_documentation(), tree.create_item())
_modifiers_root = tree.create_item()
add_page(SpecialPages.get_modifiers_documentation(), _modifiers_root)
_populate()
# Fed from the StackPanel scene, before the ready function
func set_editor_plugin(editor_plugin: EditorPlugin) -> void:
if not editor_plugin:
return
var editor_interface := editor_plugin.get_editor_interface()
var editor_settings := editor_interface.get_editor_settings()
_accent_color = editor_settings.get("interface/theme/accent_color")
_editor_scale = editor_interface.get_editor_scale()
func show_page(page_name: String) -> void:
if not page_name in _items:
return
var item: TreeItem = _items[page_name]
item.select(0)
popup_centered(Vector2i(900, 600))
# Generate a formatted string from the DocumentationInfo input.
# This string will be stored and later displayed in the RichTextLabel so we
# we don't have to regenerate it everytime we look at another page.
func add_page(info: DocumentationInfo, item: TreeItem = null) -> void:
if not item:
var root: TreeItem = _get_or_create_tree_root(info.get_category())
item = tree.create_item(root)
item.set_text(0, info.get_title())
_begin_formatting()
# Page title
_format_title(info.get_title())
# Paragraphs
for p in info.get_paragraphs():
_format_paragraph(p)
# Parameters
if not info.get_parameters().is_empty():
_format_subtitle("Parameters")
for p in info.get_parameters():
_format_parameter(p)
# Warnings
if not info.get_warnings().is_empty():
_format_subtitle("Warnings")
for w in info.get_warnings():
_format_warning(w)
_pages[item] = _get_formatted_text()
_items[info.get_title()] = item
func _populate():
if _populated: # Already generated the documentation pages
return
var path = _get_root_folder() + "/src/modifiers/"
var result := {}
_discover_modifiers_recursive(path, result)
var names := result.keys()
names.sort()
for n in names:
var info = result[n]
add_page(info)
_populated = true
func _discover_modifiers_recursive(path, result) -> void:
var dir = DirAccess.open(path)
dir.list_dir_begin()
var path_root = dir.get_current_dir() + "/"
while true:
var file = dir.get_next()
if file == "":
break
if file == "base_modifier.gd":
continue
if dir.current_is_dir():
_discover_modifiers_recursive(path_root + file, result)
continue
if not file.ends_with(".gd") and not file.ends_with(".gdc"):
continue
var full_path = path_root + file
var script = load(full_path)
if not script or not script.can_instantiate():
print("Error: Failed to load script ", file)
continue
var modifier = script.new()
var info: DocumentationInfo = modifier.documentation
info.set_title(modifier.display_name)
info.set_category(modifier.category)
if modifier.use_edge_data:
info.add_warning(
"This modifier uses edge data (represented by the blue lines
on the Scatter node). These edges are usually locked to the
local XZ plane, (except for the Path shape when they are
NOT closed). If you can't see these lines, make sure to have at
least one Shape crossing the ProtonScatter local XZ plane.",
1)
if modifier.deprecated:
info.add_warning(
"This modifier has been deprecated. It won't receive any updates
and will be deleted in a future update.",
2)
result[modifier.display_name] = info
dir.list_dir_end()
func _get_root_folder() -> String:
var script: Script = get_script()
var path: String = script.get_path().get_base_dir()
var folders = path.right(-6) # Remove the res://
var tokens = folders.split('/')
return "res://" + tokens[0] + "/" + tokens[1]
func _get_or_create_tree_root(root_name: String) -> TreeItem:
if root_name in _categories_roots:
return _categories_roots[root_name]
var root = tree.create_item(_modifiers_root)
root.set_text(0, root_name)
root.set_selectable(0, false)
_categories_roots[root_name] = root
return root
func _begin_formatting() -> void:
_edited_text = ""
func _get_formatted_text() -> String:
return _edited_text
func _format_title(text: String) -> void:
_edited_text += "[font_size=" + var_to_str(_header_size * _editor_scale) + "]"
_edited_text += "[color=" + _accent_color.to_html() + "]"
_edited_text += "[center][b]"
_edited_text += text
_edited_text += "[/b][/center]"
_edited_text += "[/color]"
_edited_text += "[/font_size]"
_format_line_break(2)
func _format_subtitle(text: String) -> void:
_edited_text += "[font_size=" + var_to_str(_header_size * _editor_scale) + "]"
_edited_text += "[color=" + _accent_color.to_html() + "]"
_edited_text += "[b]" + text + "[/b]"
_edited_text += "[/color]"
_edited_text += "[/font_size]"
_format_line_break(2)
func _format_line_break(count := 1) -> void:
for i in count:
_edited_text += "\n"
func _format_paragraph(text: String) -> void:
_edited_text += "[p]" + text + "[/p]"
_format_line_break(2)
func _format_parameter(p) -> void:
var root_folder = _get_root_folder()
_edited_text += "[indent]"
if not p.type.is_empty():
var file_name = p.type.to_lower() + ".svg"
_edited_text += "[img]" + root_folder + "/icons/types/" + file_name + "[/img] "
_edited_text += "[b]" + p.name + "[/b] "
match p.cost:
1:
_edited_text += "[img]" + root_folder + "/icons/arrow_log.svg[/img]"
2:
_edited_text += "[img]" + root_folder + "/icons/arrow_linear.svg[/img]"
3:
_edited_text += "[img]" + root_folder + "/icons/arrow_exp.svg[/img]"
_format_line_break(2)
_edited_text += "[indent]" + p.description + "[/indent]"
_format_line_break(2)
for warning in p.warnings:
if not warning.text.is_empty():
_format_warning(warning)
_edited_text += "[/indent]"
func _format_warning(w, indent := true) -> void:
if indent:
_edited_text += "[indent]"
var color := "Darkgray"
match w.importance:
1:
color = "yellow"
2:
color = "red"
_edited_text += "[color=" + color + "][i]" + w.text + "[/i][/color]\n"
if indent:
_edited_text += "[/indent]"
_format_line_break(1)
func _on_item_selected() -> void:
var selected: TreeItem = tree.get_selected()
if _pages.has(selected):
var text: String = _pages[selected]
label.set_text(text)
else:
label.set_text("[center] Under construction [/center]")

View File

@@ -0,0 +1,29 @@
[gd_scene load_steps=3 format=3 uid="uid://cfg8iqtuion8b"]
[ext_resource type="Script" path="res://addons/proton_scatter/src/documentation/documentation.gd" id="1_5c4lw"]
[ext_resource type="PackedScene" uid="uid://cojoo2c73fpsb" path="res://addons/proton_scatter/src/documentation/panel.tscn" id="2_vpfxu"]
[node name="Documentation" type="PopupPanel"]
title = "ProtonScatter documentation"
exclusive = true
unresizable = false
borderless = false
script = ExtResource("1_5c4lw")
[node name="HSplitContainer" parent="." instance=ExtResource("2_vpfxu")]
offset_left = 4.0
offset_top = 4.0
offset_right = -1824.0
offset_bottom = -984.0
[node name="Tree" parent="HSplitContainer" index="0"]
offset_right = 80.0
offset_bottom = 92.0
hide_root = true
[node name="RichTextLabel" parent="HSplitContainer" index="1"]
offset_left = 92.0
offset_right = 92.0
offset_bottom = 92.0
[editable path="HSplitContainer"]

View File

@@ -0,0 +1,113 @@
@tool
extends RefCounted
# Stores raw documentation data.
# The data is provided by any class that needs an entry in the documentation
# panel. This was initially designed for all the modifiers, but might be expanded
# to other parts of the addon as well.
# Formatting is handled by the main Documentation class.
const Util := preload("../common/util.gd")
class Warning:
var text: String
var importance: int
class Parameter:
var name: String
var cost: int
var type: String
var description: String
var warnings: Array[Warning] = []
func set_name(text: String) -> Parameter:
name = Util.remove_line_breaks(text)
return self
func set_description(text: String) -> Parameter:
description = Util.remove_line_breaks(text)
return self
func set_cost(val: int) -> Parameter:
cost = val
return self
func set_type(val: String) -> Parameter:
type = Util.remove_line_breaks(val)
return self
func add_warning(warning: String, warning_importance := -1) -> Parameter:
var w = Warning.new()
w.text = Util.remove_line_breaks(warning)
w.importance = warning_importance
warnings.push_back(w)
return self
var _category: String
var _page_title: String
var _paragraphs: Array[String] = []
var _warnings: Array[Warning] = []
var _parameters: Array[Parameter] = []
func set_category(text: String) -> void:
_category = text
func set_title(text: String) -> void:
_page_title = text
func add_paragraph(text: String) -> void:
_paragraphs.push_back(Util.remove_line_breaks(text))
# Warning importance:
# 0: Default (Grey)
# 1: Mid (Yellow)
# 2: Critical (Red)
func add_warning(text: String, importance: int = 0) -> void:
var w = Warning.new()
w.text = Util.remove_line_breaks(text)
w.importance = importance
_warnings.push_back(w)
# Add documentation for a user exposed parameter.
# Cost:
# 0: None
# 1: Log
# 2: Linear
# 3: Exponential
func add_parameter(name := "") -> Parameter:
var p = Parameter.new()
p.name = name
p.cost = 0
_parameters.push_back(p)
return p
func get_title() -> String:
return _page_title
func get_category() -> String:
return _category
func get_paragraphs() -> Array[String]:
return _paragraphs
func get_warnings() -> Array[Warning]:
return _warnings
func get_parameters() -> Array[Parameter]:
return _parameters

View File

@@ -0,0 +1,150 @@
@tool
extends RefCounted
const DocumentationInfo = preload("../documentation_info.gd")
static func get_scatter_documentation() -> DocumentationInfo:
var info := DocumentationInfo.new()
info.set_title("ProtonScatter")
info.add_paragraph(
"ProtonScatter is a content positioning add-on. It is suited to place
a large amount of objects in a procedural way.")
info.add_paragraph(
"This add-on is [color=red][b]IN BETA[/b][/color] which means breaking
changes may happen. It is not recommended to use in production yet."
)
info.add_paragraph(
"First, define [i]what[/i] you want to place using [b]ScatterItems[/b]
nodes.")
info.add_paragraph(
"Then, define [i]where[/i] to place them using [b]ScatterShapes[/b]
nodes.")
info.add_paragraph(
"Finaly, define [i]how[/i] the content should be placed using the
[b]Modifier stack[/b] that's on the [b]ProtonScatter[/b] node.")
info.add_paragraph(
"Each of these components have their dedicated documenation page, but
first, you should check out the example scenes in the demo folder.")
var p := info.add_parameter("General / Global seed")
p.set_type("int")
p.set_description(
"The random seed to use on this node. Modifiers using random components
can access this value and use it accordingly. You can also specify
a custom seed for specific modifiers as well.")
p = info.add_parameter("General / Show output in tree")
p.set_type("bool")
p.set_description(
"Show the generated items in the editor scene tree. By default this
option is disabled as it creates quite a bit of clutter when instancing
is disabled. It also increases the scene file size significantly.")
p = info.add_parameter("Performance / Use instancing")
p.set_type("bool")
p.set_description(
"When enabled, ProtonScatter will use MultiMeshInstance3D nodes
instead of duplicating the source nodes. This allows the GPU to render
thousands of meshes in a single draw call.")
p.add_warning("Collisions and attached scripts are ignored when this
option is enabled.", 1)
return info
static func get_item_documentation() -> DocumentationInfo:
var info := DocumentationInfo.new()
info.set_title("ScatterItems")
info.add_paragraph("TODO: Write this page")
return info
static func get_shape_documentation() -> DocumentationInfo:
var info := DocumentationInfo.new()
info.set_title("ScatterShapes")
info.add_paragraph("TODO: Write this page")
return info
static func get_cache_documentation() -> DocumentationInfo:
var info := DocumentationInfo.new()
info.set_title("ScatterCache")
info.add_paragraph(
"By default, Scatter nodes will recalculate their output on load,
which can be slow in really complex scenes. The cache allows you to
store these results in a file on your disk, and load these instead.")
info.add_paragraph(
"This can significantly speed up loading times, while also being VCS
friendly since the transforms are stored in their own files, rather
than your scenes files.")
info.add_paragraph("[b]How to use:[/b]")
info.add_paragraph(
"[p]+ Disable the [code]Force rebuild on load[code] on every Scatter item you want to cache.[/p]
[p]+ Add a ScatterCache node anywhere in your scene.[/p]
[p]+ Press the 'Rebuild' button to scan for other ProtonScatter nodes
and store their results in the cache.[/p]")
info.add_paragraph("[i]A single cache per scene is enough.[/i]")
var p := info.add_parameter("Cache File")
p.set_cost(0)
p.set_description("Path to the cache file. By default they are store in the
add-on folder. Their name has a random component to avoid naming collisions
with scenes sharing the same file name. You are free to place this file
anywhere, using any name you would like.")
return info
static func get_modifiers_documentation() -> DocumentationInfo:
var info := DocumentationInfo.new()
info.set_title("Modifiers")
info.add_paragraph(
"A modifier takes in a Transform3D list, create, modify or delete
transforms, then pass it down to the next modifier. Remember that
[b] modifiers are processed from top to bottom [/b]. A modifier
down the stack will recieve a list processed by the modifiers above.")
info.add_paragraph(
"The initial transform list is empty, so it's necessary to start the
stack with a [b] Create [/b] modifier.")
info.add_paragraph(
"When clicking the [b] Expand button [/b] (the little arrow on the left)
you get access to this modifier's parameters. This is where you can
adjust its behavior according to your needs.")
info.add_paragraph(
"Three common options might be found on these modifiers. (They may
not appear if they are irrelevant). They are defined as follow:")
var p := info.add_parameter("Use local seed")
p.set_type("bool")
p.set_description(
"The dice icon on the left allows you to force a specific seed for the
modifier. If this option is not used then the Global seed from the
ProtonScatter node will be used instead.")
p = info.add_parameter("Restrict height")
p.set_type("bool")
p.set_description(
"When applicable, the modifier will remain within the local XZ plane
instead of using the full volume described by the ScatterShape nodes.")
p = info.add_parameter("Reference frame")
p.set_type("int")
p.set_description(
"[p]+ [b]Global[/b]: Modifier operates in Global space. [/p]
[p]+ [b]Local[/b]: Modifier operates in local space, relative to the ProtonScatter node.[/p]
[p]+ [b]Individual[/b]: Modifier operates on local space, relative to each
individual transforms.[/p]"
)
return info

View File

@@ -0,0 +1,22 @@
[gd_scene format=3 uid="uid://cojoo2c73fpsb"]
[node name="HSplitContainer" type="HSplitContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
split_offset = 250
[node name="Tree" type="Tree" parent="."]
layout_mode = 2
offset_right = 250.0
offset_bottom = 648.0
[node name="RichTextLabel" type="RichTextLabel" parent="."]
layout_mode = 2
offset_left = 262.0
offset_right = 1152.0
offset_bottom = 648.0
bbcode_enabled = true
text = "[center] [b] [i] Documentation page [/i] [/b] [/center]"