built more assets and started playing with foliage painting
This commit is contained in:
170
addons/dreadpon.spatial_gardener/gardener/data_import_export.gd
Normal file
170
addons/dreadpon.spatial_gardener/gardener/data_import_export.gd
Normal file
@@ -0,0 +1,170 @@
|
||||
extends RefCounted
|
||||
|
||||
const Logger = preload("../utility/logger.gd")
|
||||
const Globals = preload("../utility/globals.gd")
|
||||
const FunLib = preload("../utility/fun_lib.gd")
|
||||
const Defaults = preload("../utility/defaults.gd")
|
||||
const Greenhouse = preload("../greenhouse/greenhouse.gd")
|
||||
const Toolshed = preload("../toolshed/toolshed.gd")
|
||||
const Painter = preload("painter.gd")
|
||||
const Arborist = preload("../arborist/arborist.gd")
|
||||
const Placeform = preload("../arborist/placeform.gd")
|
||||
const InputFieldResource = preload("../utility/input_field_resource/input_field_resource.gd")
|
||||
|
||||
var logger = null
|
||||
var arborist: Arborist = null
|
||||
var greenhouse: Greenhouse = null
|
||||
var toolshed: Toolshed = null
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Lifecycle
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func _init(_arborist: Arborist, _greenhouse: Greenhouse, _toolshed: Toolshed = null):
|
||||
logger = Logger.get_for(self)
|
||||
arborist = _arborist
|
||||
greenhouse = _greenhouse
|
||||
toolshed = _toolshed
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Importing/exporting data
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Import data of a single plant (Greenhouse_Plant + placeforms)
|
||||
func import_plant_data(file_path: String, plant_idx: int):
|
||||
var file := FileAccess.open(file_path, FileAccess.READ)
|
||||
if !file:
|
||||
logger.error("Could not import '%s', error %s!" % [file_path, Globals.get_err_message(FileAccess.get_open_error())])
|
||||
|
||||
var test_json_conv = JSON.new()
|
||||
var err = test_json_conv.parse(file.get_as_text())
|
||||
if err != OK:
|
||||
logger.error("Could not parse json at '%s', error %s!" % [file_path, Globals.get_err_message(err)])
|
||||
return
|
||||
|
||||
var import_data = test_json_conv.data
|
||||
file.close()
|
||||
|
||||
_import_process_data(plant_idx, import_data)
|
||||
|
||||
if import_data is Dictionary && !import_data.get("plant_data", {}).is_empty():
|
||||
logger.info("Successfully imported plant settings and placeform(s) from '%s'" % [file_path])
|
||||
else:
|
||||
logger.info("Successfully imported placeform(s) from '%s'" % [file_path])
|
||||
|
||||
|
||||
# Export data of a single plant (Greenhouse_Plant + placeforms)
|
||||
func export_plant_data(file_path: String, plant_idx: int):
|
||||
DirAccess.make_dir_recursive_absolute(file_path.get_base_dir())
|
||||
var file := FileAccess.open(file_path, FileAccess.WRITE)
|
||||
if !file:
|
||||
logger.error("Could not export '%s', error %s!" % [file_path, Globals.get_err_message(FileAccess.get_open_error())])
|
||||
|
||||
var data = _export_gather_data(plant_idx)
|
||||
|
||||
var json_string = JSON.stringify(data)
|
||||
file.store_string(json_string)
|
||||
file.close()
|
||||
logger.info("Successfully exported plant settings and placeform(s) to '%s'" % [file_path])
|
||||
|
||||
|
||||
|
||||
|
||||
# Import data of an entire Greenhouse + placeforms
|
||||
func import_greenhouse_data(file_path: String):
|
||||
var file := FileAccess.open(file_path, FileAccess.READ)
|
||||
if !file:
|
||||
logger.error("Could not import '%s', error %s!" % [file_path, Globals.get_err_message(FileAccess.get_open_error())])
|
||||
|
||||
var test_json_conv = JSON.new()
|
||||
var err = test_json_conv.parse(file.get_as_text())
|
||||
if err != OK:
|
||||
logger.error("Could not parse json at '%s', error %s!" % [file_path, Globals.get_err_message(err)])
|
||||
return
|
||||
|
||||
var import_data = test_json_conv.data
|
||||
file.close()
|
||||
|
||||
for i in import_data.size():
|
||||
_import_process_data(i, import_data[i])
|
||||
|
||||
logger.info("Successfully imported entire greenhouse of %d plants from '%s" % [import_data.size(), file_path])
|
||||
|
||||
|
||||
# Export data of an entire Greenhouse + placeforms
|
||||
func export_greenhouse_data(file_path: String):
|
||||
DirAccess.make_dir_recursive_absolute(file_path.get_base_dir())
|
||||
var file := FileAccess.open(file_path, FileAccess.WRITE)
|
||||
if !file:
|
||||
logger.error("Could not export '%s', error %s!" % [file_path, Globals.get_err_message(FileAccess.get_open_error())])
|
||||
|
||||
var data = []
|
||||
for plant_idx in range(greenhouse.greenhouse_plant_states.size()):
|
||||
data.append(_export_gather_data(plant_idx))
|
||||
|
||||
var json_string = JSON.stringify(data)
|
||||
file.store_string(json_string)
|
||||
file.close()
|
||||
logger.info("Successfully exported entire greenhouse of %d plants to '%s'" % [data.size(), file_path])
|
||||
|
||||
|
||||
|
||||
|
||||
func _export_gather_data(plant_idx: int) -> Dictionary:
|
||||
var plant_data = greenhouse.greenhouse_plant_states[plant_idx].ifr_to_dict(true)
|
||||
var placeforms: Array = []
|
||||
arborist.octree_managers[plant_idx].get_all_placeforms(placeforms)
|
||||
var placeform_data := []
|
||||
for placeform in placeforms:
|
||||
placeform_data.append({
|
||||
'placement': FunLib.vec3_to_str(placeform[0]),
|
||||
'surface_normal': FunLib.vec3_to_str(placeform[1]),
|
||||
'transform': FunLib.transform3d_to_str(placeform[2]),
|
||||
})
|
||||
|
||||
logger.info("Successfully gathered plant settings and %d placeform(s) at index %d" % [placeform_data.size(), plant_idx])
|
||||
|
||||
return {
|
||||
plant_data = plant_data,
|
||||
placeform_data = placeform_data
|
||||
}
|
||||
|
||||
|
||||
func _import_process_data(plant_idx: int, data):
|
||||
var placeform_data := []
|
||||
var plant_data := {}
|
||||
# New version, plant settings + transforms
|
||||
if data is Dictionary:
|
||||
placeform_data = data.placeform_data
|
||||
plant_data = data.plant_data
|
||||
# Old version, supports transforms-only, for Spatial Gardener 1.2.0 compatability
|
||||
else:
|
||||
placeform_data = data
|
||||
|
||||
var str_version = 1
|
||||
if !placeform_data.is_empty():
|
||||
var placeforms := []
|
||||
if placeform_data[0].transform.contains(" - "):
|
||||
str_version = 0
|
||||
|
||||
if !plant_data.is_empty():
|
||||
plant_idx = greenhouse.add_plant_from_dict(plant_data, str_version)
|
||||
|
||||
if !placeform_data.is_empty():
|
||||
var placeforms := []
|
||||
for placeform_dict in placeform_data:
|
||||
placeforms.append(Placeform.mk(
|
||||
FunLib.str_to_vec3(placeform_dict.placement, str_version),
|
||||
FunLib.str_to_vec3(placeform_dict.surface_normal, str_version),
|
||||
FunLib.str_to_transform3d(placeform_dict.transform, str_version)))
|
||||
|
||||
arborist.batch_add_instances(placeforms, plant_idx)
|
||||
arborist.call_deferred("emit_member_count", plant_idx)
|
||||
261
addons/dreadpon.spatial_gardener/gardener/debug_viewer.gd
Normal file
261
addons/dreadpon.spatial_gardener/gardener/debug_viewer.gd
Normal file
@@ -0,0 +1,261 @@
|
||||
@tool
|
||||
extends Node3D
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# A previewer for octree structure
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
const FunLib = preload("../utility/fun_lib.gd")
|
||||
const DponDebugDraw = preload("../utility/debug_draw.gd")
|
||||
const MMIOctreeManager = preload("../arborist/mmi_octree/mmi_octree_manager.gd")
|
||||
const MMIOctreeNode = preload("../arborist/mmi_octree/mmi_octree_node.gd")
|
||||
|
||||
|
||||
# How many/which plants we want to preview
|
||||
enum PlantViewModeFlags {
|
||||
VIEW_NONE = 0,
|
||||
VIEW_SELECTED_PLANT = 1,
|
||||
VIEW_ALL_ACTIVE_PLANTS = 2,
|
||||
VIEW_MAX = 3,
|
||||
}
|
||||
|
||||
# What parts of an octree we want to preview
|
||||
enum RenderModeFlags {
|
||||
DRAW_OCTREE_NODES = 101,
|
||||
DRAW_OCTREE_MEMBERS = 102,
|
||||
}
|
||||
|
||||
|
||||
var octree_MMIs:Array = []
|
||||
var active_plant_view_mode:int = PlantViewModeFlags.VIEW_NONE
|
||||
var active_render_modes:Array = [RenderModeFlags.DRAW_OCTREE_NODES]
|
||||
|
||||
var brush_active_plants:Array = []
|
||||
var prop_edit_selected_plant: int = -1
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Debug view menu
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Create and initialize a debug view menu
|
||||
static func make_debug_view_menu():
|
||||
var debug_view_menu := MenuButton.new()
|
||||
debug_view_menu.text = "Gardener Debug Viewer"
|
||||
debug_view_menu.get_popup().hide_on_checkable_item_selection = false
|
||||
debug_view_menu.get_popup().hide_on_item_selection = false
|
||||
|
||||
for i in range(0, PlantViewModeFlags.size() - 1):
|
||||
debug_view_menu.get_popup().add_radio_check_item(PlantViewModeFlags.keys()[i].capitalize(), PlantViewModeFlags.values()[i])
|
||||
|
||||
debug_view_menu.get_popup().add_separator()
|
||||
|
||||
for i in range(0, RenderModeFlags.size()):
|
||||
debug_view_menu.get_popup().add_check_item(RenderModeFlags.keys()[i].capitalize(), RenderModeFlags.values()[i])
|
||||
|
||||
return debug_view_menu
|
||||
|
||||
|
||||
# Callback when flag is checked on a menu
|
||||
func flag_checked(debug_view_menu:MenuButton, flag:int):
|
||||
var flag_group = flag <= PlantViewModeFlags.VIEW_MAX
|
||||
if flag_group:
|
||||
active_plant_view_mode = flag
|
||||
else:
|
||||
if active_render_modes.has(flag):
|
||||
active_render_modes.erase(flag)
|
||||
else:
|
||||
active_render_modes.append(flag)
|
||||
|
||||
up_to_date_debug_view_menu(debug_view_menu)
|
||||
|
||||
|
||||
# Reset a menu to the current state of this DebugViewer
|
||||
func up_to_date_debug_view_menu(debug_view_menu:MenuButton):
|
||||
for i in range(0, debug_view_menu.get_popup().get_item_count()):
|
||||
debug_view_menu.get_popup().set_item_checked(i, false)
|
||||
|
||||
update_debug_view_menu_to_flag(debug_view_menu, active_plant_view_mode)
|
||||
for render_mode in active_render_modes:
|
||||
update_debug_view_menu_to_flag(debug_view_menu, render_mode)
|
||||
|
||||
|
||||
# Tick a flag in a menu
|
||||
# TODO Decide if this should be simplified and moved to up_to_date_debug_view_menu
|
||||
# Since flag checks happen in flag_checked anyways
|
||||
func update_debug_view_menu_to_flag(debug_view_menu:MenuButton, flag:int):
|
||||
var flag_group = flag <= PlantViewModeFlags.VIEW_MAX
|
||||
for i in range(0, debug_view_menu.get_popup().get_item_count()):
|
||||
var item_id = debug_view_menu.get_popup().get_item_id(i)
|
||||
var id_group = item_id <= PlantViewModeFlags.VIEW_MAX
|
||||
var opposite_state = !debug_view_menu.get_popup().is_item_checked(i)
|
||||
|
||||
if item_id == flag:
|
||||
if flag_group:
|
||||
debug_view_menu.get_popup().set_item_checked(i, true)
|
||||
else:
|
||||
debug_view_menu.get_popup().set_item_checked(i, opposite_state)
|
||||
elif flag_group == id_group && flag_group:
|
||||
debug_view_menu.get_popup().set_item_checked(i, false)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Brush active plants
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Keep a local copy of selected for brush plant indexes
|
||||
func set_brush_active_plant(is_brush_active, plant_index:int):
|
||||
if is_brush_active:
|
||||
if !brush_active_plants.has(plant_index):
|
||||
brush_active_plants.append(plant_index)
|
||||
else:
|
||||
if brush_active_plants.has(plant_index):
|
||||
brush_active_plants.erase(plant_index)
|
||||
brush_active_plants.sort()
|
||||
|
||||
|
||||
func reset_brush_active_plants():
|
||||
brush_active_plants = []
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Selected for prop edit plants
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func set_prop_edit_selected_plant(plant_index:int):
|
||||
prop_edit_selected_plant = plant_index
|
||||
|
||||
|
||||
func reset_prop_edit_selected_plant():
|
||||
prop_edit_selected_plant = -1
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Debug redraw requests
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func request_debug_redraw(octree_managers:Array):
|
||||
debug_redraw(octree_managers)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Drawing the structure
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Redraw every fitting octree
|
||||
func debug_redraw(octree_managers:Array):
|
||||
var used_octree_managers = []
|
||||
|
||||
match active_plant_view_mode:
|
||||
# Don't draw anything
|
||||
PlantViewModeFlags.VIEW_NONE:
|
||||
ensure_MMIs(0)
|
||||
# Draw only the plant selected for prop edit
|
||||
PlantViewModeFlags.VIEW_SELECTED_PLANT:
|
||||
if prop_edit_selected_plant >= 0:
|
||||
ensure_MMIs(1)
|
||||
used_octree_managers.append(octree_managers[prop_edit_selected_plant])
|
||||
else:
|
||||
ensure_MMIs(0)
|
||||
# Draw all brush active plants
|
||||
PlantViewModeFlags.VIEW_ALL_ACTIVE_PLANTS:
|
||||
ensure_MMIs(brush_active_plants.size())
|
||||
for plant_index in brush_active_plants:
|
||||
used_octree_managers.append(octree_managers[plant_index])
|
||||
|
||||
for i in range(0, used_octree_managers.size()):
|
||||
var MMI:MultiMeshInstance3D = octree_MMIs[i]
|
||||
var octree_mamager:MMIOctreeManager = used_octree_managers[i]
|
||||
debug_draw_node(octree_mamager.root_octree_node, MMI)
|
||||
|
||||
|
||||
func erase_all():
|
||||
ensure_MMIs(0)
|
||||
|
||||
|
||||
# Make sure there is an MMI for every octree we're about to draw
|
||||
# Passing 0 effectively erases any debug renders
|
||||
func ensure_MMIs(amount:int):
|
||||
if octree_MMIs.size() < amount:
|
||||
for i in range(octree_MMIs.size(), amount):
|
||||
var MMI = MultiMeshInstance3D.new()
|
||||
add_child(MMI)
|
||||
MMI.cast_shadow = false
|
||||
MMI.multimesh = MultiMesh.new()
|
||||
MMI.multimesh.transform_format = 1
|
||||
MMI.multimesh.use_colors = true
|
||||
MMI.multimesh.mesh = DponDebugDraw.generate_cube(Vector3.ONE, Color.WHITE)
|
||||
octree_MMIs.append(MMI)
|
||||
elif octree_MMIs.size() > amount:
|
||||
var MMI = null
|
||||
while octree_MMIs.size() > amount:
|
||||
MMI = octree_MMIs.pop_back()
|
||||
remove_child(MMI)
|
||||
MMI.queue_free()
|
||||
|
||||
|
||||
# Recursively draw an octree node
|
||||
func debug_draw_node(octree_node:MMIOctreeNode, MMI:MultiMeshInstance3D):
|
||||
var draw_node := active_render_modes.has(RenderModeFlags.DRAW_OCTREE_NODES)
|
||||
var draw_members := active_render_modes.has(RenderModeFlags.DRAW_OCTREE_MEMBERS)
|
||||
|
||||
# Reset the instance counts if this node is a root
|
||||
if !octree_node.parent:
|
||||
MMI.multimesh.instance_count = 0
|
||||
MMI.multimesh.visible_instance_count = 0
|
||||
set_debug_redraw_instance_count(octree_node, MMI, draw_node, draw_members)
|
||||
|
||||
var extents:Vector3
|
||||
var render_transform:Transform3D
|
||||
var index:int
|
||||
|
||||
if draw_node:
|
||||
extents = Vector3(octree_node.extent, octree_node.extent, octree_node.extent) * 0.999 * 2.0
|
||||
render_transform = Transform3D(Basis.IDENTITY.scaled(extents), octree_node.center_pos)
|
||||
index = MMI.multimesh.visible_instance_count
|
||||
MMI.multimesh.visible_instance_count += 1
|
||||
MMI.multimesh.set_instance_transform(index, render_transform)
|
||||
MMI.multimesh.set_instance_color(index, octree_node.debug_get_color())
|
||||
|
||||
if draw_members && octree_node.is_leaf:
|
||||
var member_extent = FunLib.get_setting_safe("dreadpons_spatial_gardener/debug/debug_viewer_octree_member_size", 0.0) * 0.5
|
||||
extents = Vector3(member_extent, member_extent, member_extent)
|
||||
var basis = Basis.IDENTITY.scaled(extents)
|
||||
for placeform in octree_node.get_placeforms():
|
||||
render_transform = Transform3D(basis, placeform[0])
|
||||
index = MMI.multimesh.visible_instance_count
|
||||
MMI.multimesh.visible_instance_count += 1
|
||||
MMI.multimesh.set_instance_transform(index, render_transform)
|
||||
MMI.multimesh.set_instance_color(index, Color.WHITE)
|
||||
|
||||
for child in octree_node.child_nodes:
|
||||
debug_draw_node(child, MMI)
|
||||
|
||||
|
||||
# Recursively set the appropriate instance count for an MMI
|
||||
func set_debug_redraw_instance_count(octree_node:MMIOctreeNode, MMI:MultiMeshInstance3D, draw_node:bool, draw_members:bool):
|
||||
if draw_node:
|
||||
MMI.multimesh.instance_count += 1
|
||||
|
||||
if octree_node.is_leaf && draw_members:
|
||||
MMI.multimesh.instance_count += octree_node.member_count()
|
||||
|
||||
for child in octree_node.child_nodes:
|
||||
set_debug_redraw_instance_count(child, MMI, draw_node, draw_members)
|
||||
778
addons/dreadpon.spatial_gardener/gardener/gardener.gd
Normal file
778
addons/dreadpon.spatial_gardener/gardener/gardener.gd
Normal file
@@ -0,0 +1,778 @@
|
||||
@tool
|
||||
extends Node3D
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Manages the lifecycles and connection of all components:
|
||||
# Greenhouse plants, Toolshed brushes, Painter controller
|
||||
# And the Arborist plant placement manager
|
||||
#
|
||||
# A lot of these connections go through the Gardener
|
||||
# Because some signal receivers need additional data the signal senders don't know about
|
||||
# E.g. painter doesn't know about plant states, but arborist needs them to apply painting changes
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
const FunLib = preload("../utility/fun_lib.gd")
|
||||
const Logger = preload("../utility/logger.gd")
|
||||
const Defaults = preload("../utility/defaults.gd")
|
||||
const Greenhouse = preload("../greenhouse/greenhouse.gd")
|
||||
const Toolshed = preload("../toolshed/toolshed.gd")
|
||||
const Painter = preload("painter.gd")
|
||||
const Arborist = preload("../arborist/arborist.gd")
|
||||
const DebugViewer = preload("debug_viewer.gd")
|
||||
const UI_SidePanel_SCN = preload("../controls/side_panel/ui_side_panel.tscn")
|
||||
const UI_SidePanel = preload("../controls/side_panel/ui_side_panel.gd")
|
||||
const Globals = preload("../utility/globals.gd")
|
||||
const DataImportExport = preload("data_import_export.gd")
|
||||
|
||||
const PropAction = preload("../utility/input_field_resource/prop_action.gd")
|
||||
const PA_PropSet = preload("../utility/input_field_resource/pa_prop_set.gd")
|
||||
const PA_PropEdit = preload("../utility/input_field_resource/pa_prop_edit.gd")
|
||||
const PA_ArrayInsert = preload("../utility/input_field_resource/pa_array_insert.gd")
|
||||
const PA_ArrayRemove = preload("../utility/input_field_resource/pa_array_remove.gd")
|
||||
const PA_ArraySet = preload("../utility/input_field_resource/pa_array_set.gd")
|
||||
|
||||
|
||||
|
||||
var plugin_version: String = ""
|
||||
var storage_version: int = 0
|
||||
#export
|
||||
var refresh_octree_shared_LOD_variants:bool = false : set = set_refresh_octree_shared_LOD_variants
|
||||
|
||||
# file_management
|
||||
var garden_work_directory:String : set = set_garden_work_directory
|
||||
# gardening
|
||||
var gardening_collision_mask := pow(2, 0) : set = set_gardening_collision_mask
|
||||
|
||||
var initialized_for_edit:bool = false : set = set_initialized_for_edit
|
||||
var is_edited: bool = false
|
||||
|
||||
var toolshed:Toolshed = null
|
||||
var greenhouse:Greenhouse = null
|
||||
var painter:Painter = null
|
||||
var arborist:Arborist = null
|
||||
var debug_viewer:DebugViewer = null
|
||||
|
||||
var _resource_previewer = null
|
||||
var _base_control:Control = null
|
||||
var _undo_redo = null
|
||||
|
||||
var _side_panel:UI_SidePanel = null
|
||||
var ui_category_brushes:Control = null
|
||||
var ui_category_plants:Control = null
|
||||
|
||||
var painting_node:Node3D = null
|
||||
|
||||
var logger = null
|
||||
var forward_input_events:bool = true
|
||||
|
||||
|
||||
signal changed_initialized_for_edit(state)
|
||||
signal greenhouse_prop_action_executed(prop_action, final_val)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Lifecycle
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func _init():
|
||||
set_meta("class", "Gardener")
|
||||
|
||||
|
||||
# Update plugin/storage versions that might have been stored inside a .tscn file for this Gardener
|
||||
# In case it was created in an older version of this plugin
|
||||
func update_plugin_ver():
|
||||
plugin_version = get_plugin_ver()
|
||||
storage_version = get_storage_ver()
|
||||
|
||||
|
||||
static func get_plugin_ver():
|
||||
return '1.3.3'
|
||||
|
||||
|
||||
static func get_storage_ver():
|
||||
return 3
|
||||
|
||||
|
||||
func _ready():
|
||||
update_plugin_ver()
|
||||
|
||||
logger = Logger.get_for(self, name)
|
||||
|
||||
# Without editor we only care about an Arborist
|
||||
# But it is already self-sufficient, so no need to initialize it
|
||||
if !Engine.is_editor_hint(): return
|
||||
|
||||
if has_node('painting'):
|
||||
painting_node = get_node('painting')
|
||||
else:
|
||||
painting_node = Node3D.new()
|
||||
painting_node.name = "painting"
|
||||
add_child(painting_node)
|
||||
|
||||
if has_node('debug_viewer'):
|
||||
debug_viewer = get_node('debug_viewer')
|
||||
else:
|
||||
debug_viewer = DebugViewer.new()
|
||||
debug_viewer.name = "debug_viewer"
|
||||
add_child(debug_viewer)
|
||||
|
||||
init_painter()
|
||||
painter.set_brush_collision_mask(gardening_collision_mask)
|
||||
|
||||
reload_resources()
|
||||
init_arborist()
|
||||
|
||||
set_gardening_collision_mask(gardening_collision_mask)
|
||||
|
||||
|
||||
func _enter_tree():
|
||||
pass
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
if !Engine.is_editor_hint(): return
|
||||
|
||||
_apply_changes()
|
||||
stop_editing()
|
||||
|
||||
|
||||
func _process(delta):
|
||||
if painter:
|
||||
painter.update(delta)
|
||||
|
||||
|
||||
func _apply_changes():
|
||||
if !Engine.is_editor_hint(): return
|
||||
if !FunLib.is_dir_valid(garden_work_directory): return
|
||||
|
||||
save_toolshed()
|
||||
save_greenhouse()
|
||||
toolshed.set_undo_redo(_undo_redo)
|
||||
greenhouse.set_undo_redo(_undo_redo)
|
||||
|
||||
|
||||
func add_child(node:Node, legible_unique_name:bool = false, internal:InternalMode = 0):
|
||||
super.add_child(node, legible_unique_name)
|
||||
update_configuration_warnings()
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Input
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func forwarded_input(camera, event):
|
||||
if !forward_input_events: return false
|
||||
|
||||
var handled = painter.forwarded_input(camera, event)
|
||||
if !handled:
|
||||
handled = toolshed.forwarded_input(camera, event)
|
||||
if !handled:
|
||||
handled = arborist._unhandled_input(event)
|
||||
|
||||
return handled
|
||||
|
||||
|
||||
# A hack to propagate editor camera
|
||||
# Should be called by plugin.gd
|
||||
func propagate_camera(camera:Camera3D):
|
||||
if arborist:
|
||||
arborist.active_camera_override = camera
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Initialization
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# This is supposed to address a problem decribed in "start_gardener_edit()" of "plugin.gd"
|
||||
# Instead of recalculating everything, we hope it's enough to just restore the member references
|
||||
func restore_references():
|
||||
logger = Logger.get_for(self, name)
|
||||
if !Engine.is_editor_hint(): return
|
||||
|
||||
if has_node('painting'):
|
||||
painting_node = get_node('painting')
|
||||
if has_node('debug_viewer'):
|
||||
debug_viewer = get_node('debug_viewer')
|
||||
|
||||
init_painter()
|
||||
painter.set_brush_collision_mask(gardening_collision_mask)
|
||||
|
||||
reload_resources()
|
||||
|
||||
if has_node("Arborist") && is_instance_of(get_node("Arborist"), Arborist):
|
||||
arborist = get_node("Arborist")
|
||||
|
||||
set_gardening_collision_mask(gardening_collision_mask)
|
||||
|
||||
|
||||
# Initialize a Painter
|
||||
# Assumed to be the first manager to initialize
|
||||
func init_painter():
|
||||
FunLib.free_children(painting_node)
|
||||
painter = Painter.new(painting_node)
|
||||
painter.stroke_updated.connect(on_painter_stroke_updated)
|
||||
painter.changed_active_brush_prop.connect(on_changed_active_brush_prop)
|
||||
painter.stroke_started.connect(on_painter_stroke_started)
|
||||
painter.stroke_finished.connect(on_painter_stroke_finished)
|
||||
|
||||
|
||||
# Initialize the Arborist and connect it to other objects
|
||||
# Won't be called without editor, as Arborist is already self-sufficient
|
||||
func init_arborist():
|
||||
# A fancy way of saying
|
||||
# "Make sure there is a correct node with a correct name"
|
||||
if has_node("Arborist") && is_instance_of(get_node("Arborist"), Arborist):
|
||||
arborist = get_node("Arborist")
|
||||
logger.info("Found existing Arborist")
|
||||
else:
|
||||
if has_node("Arborist"):
|
||||
var old_arborist = get_node("Arborist")
|
||||
old_arborist.owner = null
|
||||
remove_child(old_arborist)
|
||||
old_arborist.queue_free()
|
||||
logger.info("Removed invalid Arborist")
|
||||
arborist = Arborist.new()
|
||||
arborist.name = "Arborist"
|
||||
add_child(arborist)
|
||||
logger.info("Added new Arborist")
|
||||
|
||||
if greenhouse:
|
||||
pair_arborist_greenhouse()
|
||||
pair_debug_viewer_arborist()
|
||||
pair_debug_viewer_greenhouse()
|
||||
|
||||
|
||||
# Initialize a Greenhouse and a Toolshed
|
||||
# Rebuild UI if needed
|
||||
func reload_resources():
|
||||
var last_toolshed = toolshed
|
||||
var last_greenhouse = greenhouse
|
||||
|
||||
var created_new_toolshed := false
|
||||
var created_new_greenhouse := false
|
||||
|
||||
if !FunLib.is_dir_valid(garden_work_directory):
|
||||
logger.warn("Skipped loading Toolshed and Greenhouse, please specify a working directory for this Gardener (%s)" % [str(self)])
|
||||
else:
|
||||
toolshed = FunLib.load_res(garden_work_directory, "toolshed.tres", false)
|
||||
greenhouse = FunLib.load_res(garden_work_directory, "greenhouse.tres", false)
|
||||
if !toolshed:
|
||||
logger.warn("Unable to load Toolshed, created a new one")
|
||||
toolshed = Defaults.DEFAULT_TOOLSHED()
|
||||
created_new_toolshed = true
|
||||
if !greenhouse:
|
||||
logger.warn("Unable to load Greenhouse, created a new one")
|
||||
greenhouse = Greenhouse.new()
|
||||
created_new_greenhouse = true
|
||||
|
||||
toolshed.set_undo_redo(_undo_redo)
|
||||
greenhouse.set_undo_redo(_undo_redo)
|
||||
|
||||
if last_toolshed:
|
||||
last_toolshed.prop_action_executed.disconnect(on_toolshed_prop_action_executed)
|
||||
last_toolshed.prop_action_executed_on_brush.disconnect(on_toolshed_prop_action_executed_on_brush)
|
||||
FunLib.ensure_signal(toolshed.prop_action_executed, on_toolshed_prop_action_executed)
|
||||
FunLib.ensure_signal(toolshed.prop_action_executed_on_brush, on_toolshed_prop_action_executed_on_brush)
|
||||
|
||||
if last_greenhouse:
|
||||
last_greenhouse.prop_action_executed.disconnect(on_greenhouse_prop_action_executed)
|
||||
last_greenhouse.prop_action_executed_on_plant_state.disconnect(on_greenhouse_prop_action_executed_on_plant_state)
|
||||
last_greenhouse.prop_action_executed_on_plant_state_plant.disconnect(on_greenhouse_prop_action_executed_on_plant_state_plant)
|
||||
last_greenhouse.prop_action_executed_on_LOD_variant.disconnect(on_greenhouse_prop_action_executed_on_LOD_variant)
|
||||
last_greenhouse.req_octree_reconfigure.disconnect(on_greenhouse_req_octree_reconfigure)
|
||||
last_greenhouse.req_octree_recenter.disconnect(on_greenhouse_req_octree_recenter)
|
||||
last_greenhouse.req_import_plant_data.disconnect(on_greenhouse_req_import_plant_data)
|
||||
last_greenhouse.req_export_plant_data.disconnect(on_greenhouse_req_export_plant_data)
|
||||
last_greenhouse.req_import_greenhouse_data.disconnect(on_greenhouse_req_import_greenhouse_data)
|
||||
last_greenhouse.req_export_greenhouse_data.disconnect(on_greenhouse_req_export_greenhouse_data)
|
||||
FunLib.ensure_signal(greenhouse.prop_action_executed, on_greenhouse_prop_action_executed)
|
||||
FunLib.ensure_signal(greenhouse.prop_action_executed_on_plant_state, on_greenhouse_prop_action_executed_on_plant_state)
|
||||
FunLib.ensure_signal(greenhouse.prop_action_executed_on_plant_state_plant, on_greenhouse_prop_action_executed_on_plant_state_plant)
|
||||
FunLib.ensure_signal(greenhouse.prop_action_executed_on_LOD_variant, on_greenhouse_prop_action_executed_on_LOD_variant)
|
||||
FunLib.ensure_signal(greenhouse.req_octree_reconfigure, on_greenhouse_req_octree_reconfigure)
|
||||
FunLib.ensure_signal(greenhouse.req_octree_recenter, on_greenhouse_req_octree_recenter)
|
||||
FunLib.ensure_signal(greenhouse.req_import_plant_data, on_greenhouse_req_import_plant_data)
|
||||
FunLib.ensure_signal(greenhouse.req_export_plant_data, on_greenhouse_req_export_plant_data)
|
||||
FunLib.ensure_signal(greenhouse.req_import_greenhouse_data, on_greenhouse_req_import_greenhouse_data)
|
||||
FunLib.ensure_signal(greenhouse.req_export_greenhouse_data, on_greenhouse_req_export_greenhouse_data)
|
||||
|
||||
if arborist:
|
||||
pair_arborist_greenhouse()
|
||||
|
||||
if toolshed && toolshed != last_toolshed && _side_panel:
|
||||
ui_category_brushes = toolshed.create_ui(_base_control, _resource_previewer)
|
||||
_side_panel.set_tool_ui(ui_category_brushes, 0)
|
||||
if greenhouse && greenhouse != last_greenhouse && _side_panel:
|
||||
ui_category_plants = greenhouse.create_ui(_base_control, _resource_previewer)
|
||||
_side_panel.set_tool_ui(ui_category_plants, 1)
|
||||
|
||||
if arborist:
|
||||
for i in range(0, arborist.octree_managers.size()):
|
||||
arborist.emit_member_count(i)
|
||||
|
||||
if created_new_toolshed:
|
||||
save_toolshed()
|
||||
if created_new_greenhouse:
|
||||
save_greenhouse()
|
||||
|
||||
|
||||
# It's possible we load a different Greenhouse while an Arborist is already initialized
|
||||
# So collapse that into a function
|
||||
func pair_arborist_greenhouse():
|
||||
if !arborist || !greenhouse:
|
||||
if !arborist: logger.warn("Arborist->Greenhouse: Arborist is not initialized!")
|
||||
if !greenhouse: logger.warn("Arborist->Greenhouse: Greenhouse is not initialized!")
|
||||
return
|
||||
# We could duplicate an array, but that's additional overhead so we assume Arborist won't change it
|
||||
arborist.setup(greenhouse.greenhouse_plant_states)
|
||||
|
||||
if !arborist.member_count_updated.is_connected(greenhouse.plant_count_updated):
|
||||
arborist.member_count_updated.connect(greenhouse.plant_count_updated)
|
||||
|
||||
|
||||
func pair_debug_viewer_greenhouse():
|
||||
if !debug_viewer || !greenhouse:
|
||||
if !debug_viewer: logger.warn("DebugViewer->Greenhouse: DebugViewer is not initialized!")
|
||||
if !greenhouse: logger.warn("DebugViewer->Greenhouse: Greenhouse is not initialized!")
|
||||
return
|
||||
|
||||
debug_viewer.set_prop_edit_selected_plant(greenhouse.greenhouse_plant_states.find(greenhouse.selected_for_edit_resource))
|
||||
reinit_debug_draw_brush_active()
|
||||
|
||||
|
||||
func pair_debug_viewer_arborist():
|
||||
if !debug_viewer || !arborist:
|
||||
if !debug_viewer: logger.warn("DebugViewer->Arborist: DebugViewer is not initialized!")
|
||||
if !arborist: logger.warn("DebugViewer->Arborist: Arborist is not initialized!")
|
||||
return
|
||||
|
||||
if !arborist.req_debug_redraw.is_connected(debug_viewer.request_debug_redraw):
|
||||
arborist.req_debug_redraw.connect(debug_viewer.request_debug_redraw)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Start/stop editing lifecycle
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Start editing (painting) a scene
|
||||
func start_editing(__base_control:Control, __resource_previewer, __undoRedo, __side_panel:UI_SidePanel):
|
||||
_base_control = __base_control
|
||||
_resource_previewer = __resource_previewer
|
||||
_undo_redo = __undoRedo
|
||||
|
||||
_side_panel = __side_panel
|
||||
changed_initialized_for_edit.connect(_side_panel.set_main_control_state)
|
||||
|
||||
ui_category_brushes = toolshed.create_ui(_base_control, _resource_previewer)
|
||||
ui_category_plants = greenhouse.create_ui(_base_control, _resource_previewer)
|
||||
_side_panel.set_tool_ui(ui_category_brushes, 0)
|
||||
_side_panel.set_tool_ui(ui_category_plants, 1)
|
||||
toolshed.set_undo_redo(_undo_redo)
|
||||
greenhouse.set_undo_redo(_undo_redo)
|
||||
|
||||
arborist._undo_redo = _undo_redo
|
||||
|
||||
# # Making sure we and UI are on the same page (setting property values and checkboxes/tabs)
|
||||
painter_update_to_active_brush(toolshed.active_brush)
|
||||
_side_panel.set_main_control_state(initialized_for_edit)
|
||||
|
||||
painter.start_editing()
|
||||
|
||||
for i in range(0, arborist.octree_managers.size()):
|
||||
arborist.emit_member_count(i)
|
||||
# Make sure LOD_Variants in a shared Octree array are up-to-date
|
||||
set_refresh_octree_shared_LOD_variants(true)
|
||||
is_edited = true
|
||||
|
||||
|
||||
# Stop editing (painting) a scene
|
||||
func stop_editing():
|
||||
if is_instance_valid(_side_panel):
|
||||
changed_initialized_for_edit.disconnect(_side_panel.set_main_control_state)
|
||||
_side_panel = null
|
||||
|
||||
if is_instance_valid(painter):
|
||||
painter.stop_editing()
|
||||
is_edited = false
|
||||
|
||||
|
||||
# We can properly start editing only when a workDirectory is set
|
||||
func validate_initialized_for_edit():
|
||||
var work_directory_valid = FunLib.is_dir_valid(garden_work_directory)
|
||||
|
||||
# Originally there were two conditions to fulfill, not just the workDirectory
|
||||
# Keeping this in case it will be needed in the future
|
||||
var _initialized_for_edit = work_directory_valid
|
||||
if initialized_for_edit != _initialized_for_edit:
|
||||
set_initialized_for_edit(_initialized_for_edit)
|
||||
|
||||
|
||||
# Pass a request for updating a debug view menu
|
||||
func up_to_date_debug_view_menu(debug_view_menu:MenuButton):
|
||||
assert(debug_viewer)
|
||||
debug_viewer.up_to_date_debug_view_menu(debug_view_menu)
|
||||
debug_viewer.request_debug_redraw(arborist.octree_managers)
|
||||
|
||||
|
||||
# Pass a request for checking a debug view menu flag
|
||||
func debug_view_flag_checked(debug_view_menu:MenuButton, flag:int):
|
||||
assert(debug_viewer)
|
||||
debug_viewer.flag_checked(debug_view_menu, flag)
|
||||
debug_viewer.request_debug_redraw(arborist.octree_managers)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Handle changes in owned properties
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func set_gardening_collision_mask(val):
|
||||
gardening_collision_mask = val
|
||||
if painter:
|
||||
painter.set_brush_collision_mask(gardening_collision_mask)
|
||||
if arborist:
|
||||
arborist.set_gardening_collision_mask(gardening_collision_mask)
|
||||
|
||||
|
||||
func set_garden_work_directory(val):
|
||||
if !val.is_empty() && !val.ends_with("/"):
|
||||
val += "/"
|
||||
|
||||
var changed = garden_work_directory != val
|
||||
garden_work_directory = val
|
||||
|
||||
if !Engine.is_editor_hint(): return
|
||||
# If we changed a directory, reload everything that resides there
|
||||
if changed:
|
||||
if is_inside_tree():
|
||||
reload_resources()
|
||||
validate_initialized_for_edit()
|
||||
|
||||
|
||||
func set_initialized_for_edit(val):
|
||||
initialized_for_edit = val
|
||||
changed_initialized_for_edit.emit(initialized_for_edit)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Handle communication with the Greenhouse
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# When Greenhouse properties are changed
|
||||
func on_greenhouse_prop_action_executed(prop_action:PropAction, final_val):
|
||||
if is_instance_of(prop_action, PA_ArrayInsert):
|
||||
arborist.on_plant_added(final_val[prop_action.index], prop_action.index)
|
||||
reinit_debug_draw_brush_active()
|
||||
elif is_instance_of(prop_action, PA_ArrayRemove):
|
||||
arborist.on_plant_removed(prop_action.val, prop_action.index)
|
||||
reinit_debug_draw_brush_active()
|
||||
elif is_instance_of(prop_action, PA_PropSet) && prop_action.prop == "plant_types/selected_for_edit_resource":
|
||||
debug_viewer.set_prop_edit_selected_plant(greenhouse.greenhouse_plant_states.find(final_val))
|
||||
debug_viewer.request_debug_redraw(arborist.octree_managers)
|
||||
|
||||
greenhouse_prop_action_executed.emit(prop_action, final_val)
|
||||
|
||||
|
||||
# When Greenhouse_PlantState properties are changed
|
||||
func on_greenhouse_prop_action_executed_on_plant_state(prop_action:PropAction, final_val, plant_state):
|
||||
var plant_index = greenhouse.greenhouse_plant_states.find(plant_state)
|
||||
|
||||
match prop_action.prop:
|
||||
"plant/plant_brush_active":
|
||||
if is_instance_of(prop_action, PA_PropSet) || is_instance_of(prop_action, PA_PropEdit):
|
||||
debug_viewer.set_brush_active_plant(plant_state.plant_brush_active, plant_index)
|
||||
debug_viewer.request_debug_redraw(arborist.octree_managers)
|
||||
|
||||
|
||||
# When Greenhouse_Plant properties are changed
|
||||
func on_greenhouse_prop_action_executed_on_plant_state_plant(prop_action:PropAction, final_val, plant, plant_state):
|
||||
var plant_index = greenhouse.greenhouse_plant_states.find(plant_state)
|
||||
|
||||
match prop_action.prop:
|
||||
"mesh/mesh_LOD_variants":
|
||||
if is_instance_of(prop_action, PA_ArrayInsert):
|
||||
var mesh_index = prop_action.index
|
||||
arborist.on_LOD_variant_added(plant_index, mesh_index, final_val[mesh_index])
|
||||
elif is_instance_of(prop_action, PA_ArrayRemove):
|
||||
var mesh_index = prop_action.index
|
||||
arborist.on_LOD_variant_removed(plant_index, mesh_index)
|
||||
elif is_instance_of(prop_action, PA_ArraySet):
|
||||
var mesh_index = prop_action.index
|
||||
arborist.on_LOD_variant_set(plant_index, mesh_index, final_val[mesh_index])
|
||||
|
||||
"mesh/mesh_LOD_max_distance":
|
||||
if is_instance_of(prop_action, PA_PropSet) || is_instance_of(prop_action, PA_PropEdit):
|
||||
arborist.update_plant_LOD_max_distance(plant_index, final_val)
|
||||
|
||||
"mesh/mesh_LOD_kill_distance":
|
||||
if is_instance_of(prop_action, PA_PropSet) || is_instance_of(prop_action, PA_PropEdit):
|
||||
arborist.update_plant_LOD_kill_distance(plant_index, final_val)
|
||||
|
||||
|
||||
# When Greenhouse_LODVariant properties are changed
|
||||
func on_greenhouse_prop_action_executed_on_LOD_variant(prop_action:PropAction, final_val, LOD_variant, plant, plant_state):
|
||||
var plant_index = greenhouse.greenhouse_plant_states.find(plant_state)
|
||||
var mesh_index = plant.mesh_LOD_variants.find(LOD_variant)
|
||||
|
||||
match prop_action.prop:
|
||||
"spawned_spatial":
|
||||
if is_instance_of(prop_action, PA_PropSet) || is_instance_of(prop_action, PA_PropEdit):
|
||||
arborist.on_LOD_variant_prop_changed_spawned_spatial(plant_index, mesh_index, final_val)
|
||||
"cast_shadow":
|
||||
if is_instance_of(prop_action, PA_PropSet) || is_instance_of(prop_action, PA_PropEdit):
|
||||
arborist.set_LODs_to_active_index(plant_index)
|
||||
|
||||
|
||||
# A request to reconfigure an octree
|
||||
func on_greenhouse_req_octree_reconfigure(plant, plant_state):
|
||||
if !is_edited: return
|
||||
var plant_index = greenhouse.greenhouse_plant_states.find(plant_state)
|
||||
arborist.reconfigure_octree(plant_state, plant_index)
|
||||
|
||||
|
||||
# A request to recenter an octree
|
||||
func on_greenhouse_req_octree_recenter(plant, plant_state):
|
||||
if !is_edited: return
|
||||
var plant_index = greenhouse.greenhouse_plant_states.find(plant_state)
|
||||
arborist.recenter_octree(plant_state, plant_index)
|
||||
|
||||
|
||||
# Update brush active indexes for DebugViewer
|
||||
func reinit_debug_draw_brush_active():
|
||||
debug_viewer.reset_brush_active_plants()
|
||||
for plant_index in range(0, greenhouse.greenhouse_plant_states.size()):
|
||||
var plant_state = greenhouse.greenhouse_plant_states[plant_index]
|
||||
debug_viewer.set_brush_active_plant(plant_state.plant_brush_active, plant_index)
|
||||
debug_viewer.request_debug_redraw(arborist.octree_managers)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Importing/exporting data
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# A request to import plant data
|
||||
func on_greenhouse_req_import_plant_data(file_path: String, plant_idx: int):
|
||||
if !is_edited: return
|
||||
var import_export = DataImportExport.new(arborist, greenhouse)
|
||||
import_export.import_plant_data(file_path, plant_idx)
|
||||
|
||||
|
||||
# A request to export plant data
|
||||
func on_greenhouse_req_export_plant_data(file_path: String, plant_idx: int):
|
||||
if !is_edited: return
|
||||
var import_export = DataImportExport.new(arborist, greenhouse)
|
||||
import_export.export_plant_data(file_path, plant_idx)
|
||||
|
||||
|
||||
# A request to import entire greenhouse data
|
||||
func on_greenhouse_req_import_greenhouse_data(file_path: String):
|
||||
if !is_edited: return
|
||||
var import_export = DataImportExport.new(arborist, greenhouse)
|
||||
import_export.import_greenhouse_data(file_path)
|
||||
|
||||
|
||||
# A request to export entire greenhouse data
|
||||
func on_greenhouse_req_export_greenhouse_data(file_path: String):
|
||||
if !is_edited: return
|
||||
var import_export = DataImportExport.new(arborist, greenhouse)
|
||||
import_export.export_greenhouse_data(file_path)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Painter stroke lifecycle
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func on_painter_stroke_started(brush_data:Dictionary):
|
||||
var active_brush = toolshed.active_brush
|
||||
arborist.on_stroke_started(active_brush, greenhouse.greenhouse_plant_states)
|
||||
|
||||
|
||||
func on_painter_stroke_finished(brush_data:Dictionary):
|
||||
arborist.on_stroke_finished()
|
||||
|
||||
|
||||
func on_painter_stroke_updated(brush_data:Dictionary):
|
||||
arborist.on_stroke_updated(brush_data)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Painter - Toolshed relations
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Changed active brush from Toolshed. Update the painter
|
||||
func on_toolshed_prop_action_executed(prop_action:PropAction, final_val):
|
||||
assert(painter)
|
||||
if prop_action.prop != "brush/active_brush": return
|
||||
if !(is_instance_of(prop_action, PA_PropSet)) && !(is_instance_of(prop_action, PA_PropEdit)): return
|
||||
if final_val != toolshed.active_brush:
|
||||
logger.error("Passed final_val is not equal to toolshed.active_brush!")
|
||||
return
|
||||
|
||||
painter_update_to_active_brush(final_val)
|
||||
|
||||
|
||||
func painter_update_to_active_brush(active_brush):
|
||||
assert(active_brush)
|
||||
painter.queue_call_when_camera('update_all_props_to_active_brush', [active_brush])
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Quick edit for brush properties
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Property change instigated by Painter
|
||||
func on_changed_active_brush_prop(prop: String, val, final:bool):
|
||||
var prop_action: PropAction = null
|
||||
if final:
|
||||
prop_action = PA_PropSet.new(prop, val)
|
||||
else:
|
||||
prop_action = PA_PropEdit.new(prop, val)
|
||||
|
||||
if prop_action:
|
||||
toolshed.active_brush.request_prop_action(prop_action)
|
||||
|
||||
|
||||
# Propagate active_brush property changes to Painter
|
||||
func on_toolshed_prop_action_executed_on_brush(prop_action:PropAction, final_val, brush):
|
||||
assert(painter)
|
||||
if !(is_instance_of(prop_action, PA_PropSet)) && !(is_instance_of(prop_action, PA_PropEdit)): return
|
||||
if brush != toolshed.active_brush: return
|
||||
|
||||
match prop_action.prop:
|
||||
"shape/shape_volume_size":
|
||||
painter.set_active_brush_size(final_val)
|
||||
"shape/shape_projection_size":
|
||||
painter.set_active_brush_size(final_val)
|
||||
"behavior/behavior_strength":
|
||||
painter.set_active_brush_strength(final_val)
|
||||
"behavior/behavior_overlap_mode":
|
||||
painter_update_to_active_brush(brush)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Saving, loading and file management
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func save_toolshed():
|
||||
if FunLib.is_dir_valid(garden_work_directory):
|
||||
FunLib.save_res(toolshed, garden_work_directory, "toolshed.tres")
|
||||
|
||||
|
||||
func save_greenhouse():
|
||||
if FunLib.is_dir_valid(garden_work_directory):
|
||||
FunLib.save_res(greenhouse, garden_work_directory, "greenhouse.tres")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Property export
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Writing this by hand THRICE for each property is honestly tiring
|
||||
# Built-in Godot reflection would go a long way
|
||||
func _get(property):
|
||||
match property:
|
||||
"file_management/garden_work_directory":
|
||||
return garden_work_directory
|
||||
"gardening/gardening_collision_mask":
|
||||
return gardening_collision_mask
|
||||
"plugin_version":
|
||||
return
|
||||
"storage_version":
|
||||
return storage_version
|
||||
|
||||
|
||||
func _set(property, val):
|
||||
var return_val = true
|
||||
|
||||
match property:
|
||||
"file_management/garden_work_directory":
|
||||
set_garden_work_directory(val)
|
||||
"gardening/gardening_collision_mask":
|
||||
set_gardening_collision_mask(val)
|
||||
_:
|
||||
return_val = false
|
||||
|
||||
return return_val
|
||||
|
||||
|
||||
func _get_property_list():
|
||||
return [
|
||||
{
|
||||
"name": "file_management/garden_work_directory",
|
||||
"type": TYPE_STRING,
|
||||
"usage": PROPERTY_USAGE_DEFAULT,
|
||||
"hint": PROPERTY_HINT_DIR
|
||||
},
|
||||
{
|
||||
"name": "gardening/gardening_collision_mask",
|
||||
"type": TYPE_INT,
|
||||
"usage": PROPERTY_USAGE_DEFAULT,
|
||||
"hint": PROPERTY_HINT_LAYERS_3D_PHYSICS
|
||||
},
|
||||
{
|
||||
"name": "plugin_version",
|
||||
"type": TYPE_STRING,
|
||||
"usage": PROPERTY_USAGE_NO_EDITOR,
|
||||
},
|
||||
{
|
||||
"name": "storage_version",
|
||||
"type": TYPE_STRING,
|
||||
"usage": PROPERTY_USAGE_NO_EDITOR,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Warning to be displayed in editor SceneTree
|
||||
func _get_configuration_warnings():
|
||||
var arborist_check = get_node("Arborist")
|
||||
if arborist_check && is_instance_of(arborist_check, Arborist):
|
||||
return ""
|
||||
else:
|
||||
return "Gardener is missing a valid Arborist child\nSince it should be created automatically, try reloading a scene or recreating a Gardener"
|
||||
|
||||
|
||||
func set_refresh_octree_shared_LOD_variants(val):
|
||||
refresh_octree_shared_LOD_variants = false
|
||||
if val && arborist && greenhouse:
|
||||
for i in range(0, greenhouse.greenhouse_plant_states.size()):
|
||||
arborist.refresh_octree_shared_LOD_variants(i, greenhouse.greenhouse_plant_states[i].plant.mesh_LOD_variants)
|
||||
534
addons/dreadpon.spatial_gardener/gardener/painter.gd
Normal file
534
addons/dreadpon.spatial_gardener/gardener/painter.gd
Normal file
@@ -0,0 +1,534 @@
|
||||
@tool
|
||||
extends RefCounted
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Handles keeping track of brush strokes, brush position and some of the brush settings
|
||||
# Also notifies others of painting lifecycle updates
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
const FunLib = preload("../utility/fun_lib.gd")
|
||||
const DponDebugDraw = preload("../utility/debug_draw.gd")
|
||||
const Toolshed_Brush = preload("../toolshed/toolshed_brush.gd")
|
||||
const Globals = preload("../utility/globals.gd")
|
||||
|
||||
|
||||
enum ModifierKeyboardKey {KEY_SHIFT, KEY_CTRL, KEY_ALT, KEY_TAB}
|
||||
enum BrushPrimaryKeyboardKey {MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_XBUTTON1, MOUSE_BUTTON_XBUTTON2}
|
||||
enum BrushPropEditFlag {MODIFIER, NONE, SIZE, STRENGTH}
|
||||
|
||||
|
||||
var owned_spatial:Node3D = null
|
||||
# Used for immediate updates when changes happen to the brush
|
||||
# This should NOT be used in update() or each frame in general
|
||||
var _cached_camera: Camera3D = null
|
||||
|
||||
const sphere_brush_material = preload("../shaders/shm_sphere_brush.tres")
|
||||
const circle_brush_material = preload("../shaders/shm_circle_brush.tres")
|
||||
var paint_brush_node:MeshInstance3D = null
|
||||
var detached_paint_brush_container:Node = null
|
||||
|
||||
# Temporary variables to store current quick prop edit state
|
||||
var brush_prop_edit_flag = BrushPropEditFlag.NONE
|
||||
const brush_prop_edit_max_dist:float = 500.0
|
||||
var brush_prop_edit_max_val:float = 0.0
|
||||
var brush_prop_edit_cur_val:float = 0.0
|
||||
var brush_prop_edit_start_pos:Vector2 = Vector2.ZERO
|
||||
var brush_prop_edit_offset:float = 0.0
|
||||
|
||||
var can_draw:bool = false
|
||||
var is_drawing:bool = false
|
||||
var pending_movement_update:bool = false
|
||||
var brush_collision_mask:int : set = set_brush_collision_mask
|
||||
|
||||
# Used to pass during stroke-state signals sent to Gardener/Arborist
|
||||
# Meant to avoid retrieving transform from an actual 3D node
|
||||
# And more importantly to cache a raycast normal at every given point in time
|
||||
var active_brush_data:Dictionary = {'brush_pos': Vector3.ZERO, 'brush_normal': Vector3.UP, 'brush_basis': Basis()}
|
||||
|
||||
# Variables to sync quick brush property edit with UI and vice-versa
|
||||
# And also for keeping brush state up-to-date without needing a reference to actual active brush
|
||||
var active_brush_overlap_mode: int = Toolshed_Brush.OverlapMode.VOLUME
|
||||
var active_brush_size:float : set = set_active_brush_size
|
||||
var active_brush_strength:float : set = set_active_brush_strength
|
||||
var active_brush_max_size:float : set = set_active_brush_max_size
|
||||
var active_brush_max_strength:float : set = set_active_brush_max_strength
|
||||
|
||||
# A queue of methods to be called once _cached_camera becomes available
|
||||
var when_camera_queue: Array = []
|
||||
|
||||
# Ooooh boy
|
||||
# Go to finish_brush_prop_edit() for explanation
|
||||
var mouse_move_call_delay: int = 0
|
||||
|
||||
|
||||
signal changed_active_brush_prop(prop, val, final)
|
||||
|
||||
signal stroke_started
|
||||
signal stroke_finished
|
||||
signal stroke_updated
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Lifecycle
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# When working with this object, we assume it does not exist outside the editor
|
||||
func _init(_owned_spatial):
|
||||
set_meta("class", "Painter")
|
||||
|
||||
owned_spatial = _owned_spatial
|
||||
#
|
||||
paint_brush_node = MeshInstance3D.new()
|
||||
paint_brush_node.name = "active_brush"
|
||||
set_brush_mesh()
|
||||
|
||||
#owned_spatial.add_child(paint_brush_node)
|
||||
detached_paint_brush_container = Node.new()
|
||||
owned_spatial.add_child(detached_paint_brush_container)
|
||||
detached_paint_brush_container.add_child(paint_brush_node)
|
||||
set_can_draw(false)
|
||||
|
||||
|
||||
func update(delta):
|
||||
if _cached_camera:
|
||||
# Handle queue of methods that need a _cached_camera
|
||||
for queue_item in when_camera_queue.duplicate():
|
||||
callv(queue_item.method_name, queue_item.args)
|
||||
when_camera_queue.erase(queue_item)
|
||||
consume_brush_drawing_update(delta)
|
||||
|
||||
|
||||
func set_brush_mesh(is_sphere: bool = false):
|
||||
if is_sphere:
|
||||
paint_brush_node.mesh = SphereMesh.new()
|
||||
paint_brush_node.mesh.radial_segments = 32
|
||||
paint_brush_node.mesh.rings = 16
|
||||
paint_brush_node.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
paint_brush_node.material_override = sphere_brush_material.duplicate()
|
||||
else:
|
||||
paint_brush_node.mesh = QuadMesh.new()
|
||||
paint_brush_node.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
paint_brush_node.material_override = circle_brush_material.duplicate()
|
||||
|
||||
|
||||
# Queue a call to method that needs a _cached_camera to be set
|
||||
func queue_call_when_camera(method_name: String, args: Array = []):
|
||||
when_camera_queue.append({'method_name': method_name, 'args': args})
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Editing lifecycle
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func start_editing():
|
||||
set_can_draw(true)
|
||||
|
||||
|
||||
func stop_editing():
|
||||
stop_brush_stroke()
|
||||
set_can_draw(false)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Input
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func forwarded_input(camera:Camera3D, event):
|
||||
if !can_draw: return
|
||||
|
||||
_cached_camera = camera
|
||||
var handled = false
|
||||
|
||||
# If inactive property edit
|
||||
# And event == mouseMotion
|
||||
# -> move the brush
|
||||
if brush_prop_edit_flag <= BrushPropEditFlag.NONE:
|
||||
if (is_instance_of(event, InputEventMouseMotion)
|
||||
|| (is_instance_of(event, InputEventMouseButton) && event.button_index == MOUSE_BUTTON_WHEEL_UP)
|
||||
|| (is_instance_of(event, InputEventMouseButton) && event.button_index == MOUSE_BUTTON_WHEEL_DOWN)):
|
||||
|
||||
if mouse_move_call_delay > 0:
|
||||
mouse_move_call_delay -= 1
|
||||
else:
|
||||
move_brush()
|
||||
pending_movement_update = true
|
||||
# Don't handle input - moving a brush is not destructive
|
||||
|
||||
# If inactive property edit
|
||||
# And event == overlap mode key
|
||||
# -> cycle overlap modes
|
||||
if brush_prop_edit_flag <= BrushPropEditFlag.NONE && is_instance_of(event, InputEventKey) && event.keycode == get_overlap_mode_key():
|
||||
if event.pressed && !event.is_echo():
|
||||
cycle_overlap_modes()
|
||||
handled = true
|
||||
|
||||
# If inactive property edit/modifier key pressed
|
||||
# And event == modifier key pressed
|
||||
# -> remember/forget the modifier
|
||||
if brush_prop_edit_flag <= BrushPropEditFlag.NONE && is_instance_of(event, InputEventKey) && event.keycode == get_property_edit_modifier():
|
||||
if event.pressed:
|
||||
brush_prop_edit_flag = BrushPropEditFlag.MODIFIER
|
||||
if !event.pressed:
|
||||
brush_prop_edit_flag = BrushPropEditFlag.NONE
|
||||
handled = true
|
||||
|
||||
# If inactive property edit or modifier key pressed
|
||||
# And event == property edit trigger pressed
|
||||
# -> start property edit
|
||||
if brush_prop_edit_flag <= BrushPropEditFlag.NONE && is_instance_of(event, InputEventMouseButton) && event.button_index == get_property_edit_button():
|
||||
if event.pressed:
|
||||
brush_prop_edit_flag = BrushPropEditFlag.SIZE if brush_prop_edit_flag != BrushPropEditFlag.MODIFIER else BrushPropEditFlag.STRENGTH
|
||||
start_brush_prop_edit(event.global_position)
|
||||
handled = true
|
||||
|
||||
# If editing property
|
||||
# And event == property edit trigger released
|
||||
# -> stop property edit
|
||||
if brush_prop_edit_flag > BrushPropEditFlag.NONE && is_instance_of(event, InputEventMouseButton) && event.button_index == get_property_edit_button():
|
||||
if !event.pressed:
|
||||
finish_brush_prop_edit(camera)
|
||||
brush_prop_edit_flag = BrushPropEditFlag.NONE
|
||||
handled = true
|
||||
|
||||
# If editing property
|
||||
# And event == mouseMotion
|
||||
# -> update property value
|
||||
if brush_prop_edit_flag > BrushPropEditFlag.NONE && is_instance_of(event, InputEventMouseMotion):
|
||||
brush_prop_edit_calc_val(event.global_position)
|
||||
handled = true
|
||||
|
||||
# If editing property
|
||||
# And event == paint trigger pressed/releasedq
|
||||
# -> start/stop the brush stroke
|
||||
if brush_prop_edit_flag == BrushPropEditFlag.NONE && is_instance_of(event, InputEventMouseButton) && event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if event.pressed:
|
||||
move_brush()
|
||||
start_brush_stroke()
|
||||
else:
|
||||
stop_brush_stroke()
|
||||
handled = true
|
||||
|
||||
return handled
|
||||
|
||||
|
||||
func get_property_edit_modifier():
|
||||
# This convolution exists because a project setting with default value is not saved for some reason and load as "null"
|
||||
# See https://github.com/godotengine/godot/issues/56598
|
||||
var key = FunLib.get_setting_safe("dreadpons_spatial_gardener/input_and_ui/brush_prop_edit_modifier", Globals.KeyboardKey.KEY_SHIFT)
|
||||
return Globals.index_to_enum(key, Globals.KeyboardKey)
|
||||
|
||||
|
||||
func get_property_edit_button():
|
||||
var key = FunLib.get_setting_safe("dreadpons_spatial_gardener/input_and_ui/brush_prop_edit_button", Globals.MouseButton.MOUSE_BUTTON_XBUTTON1)
|
||||
return Globals.index_to_enum(key, Globals.MouseButton)
|
||||
|
||||
|
||||
func get_overlap_mode_key():
|
||||
var key = FunLib.get_setting_safe("dreadpons_spatial_gardener/input_and_ui/brush_overlap_mode_button", Globals.KeyboardKey.KEY_QUOTELEFT)
|
||||
return Globals.index_to_enum(key, Globals.KeyboardKey)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Painting lifecycle
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func set_can_draw(state):
|
||||
can_draw = state
|
||||
if state:
|
||||
paint_brush_node.visible = true
|
||||
else:
|
||||
paint_brush_node.visible = false
|
||||
|
||||
|
||||
func start_brush_stroke():
|
||||
if is_drawing: return
|
||||
is_drawing = true
|
||||
stroke_started.emit(active_brush_data)
|
||||
|
||||
|
||||
func stop_brush_stroke():
|
||||
if !is_drawing: return
|
||||
is_drawing = false
|
||||
active_brush_data = {'brush_pos': Vector3.ZERO, 'brush_normal': Vector3.UP, 'brush_basis': Basis()}
|
||||
stroke_finished.emit(active_brush_data)
|
||||
|
||||
|
||||
# Actually update the stroke only if it was preceeded by the input event
|
||||
func consume_brush_drawing_update(delta):
|
||||
if !can_draw: return
|
||||
if !is_drawing: return
|
||||
if !pending_movement_update: return
|
||||
|
||||
pending_movement_update = false
|
||||
stroke_updated.emit(active_brush_data)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Brush movement
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func move_brush():
|
||||
if !_cached_camera: return
|
||||
update_active_brush_data()
|
||||
refresh_brush_transform()
|
||||
|
||||
|
||||
# Update brush data that is passed through signals to Gardener/Arborist
|
||||
# Raycast overrides exist for compatability with gardener tests
|
||||
func update_active_brush_data(raycast_overrides: Dictionary = {}):
|
||||
var space_state = paint_brush_node.get_world_3d().direct_space_state
|
||||
var start = project_mouse_near() if !raycast_overrides.has('start') else raycast_overrides.start
|
||||
var end = project_mouse_far() if !raycast_overrides.has('end') else raycast_overrides.end
|
||||
|
||||
var params = PhysicsRayQueryParameters3D.create(start, end, brush_collision_mask, [])
|
||||
var ray_result:Dictionary = space_state.intersect_ray(params)
|
||||
|
||||
if !ray_result.is_empty():
|
||||
active_brush_data.brush_pos = ray_result.position
|
||||
active_brush_data.brush_normal = ray_result.normal
|
||||
else:
|
||||
# If raycast failed - align to camera plane, retaining current distance to camera
|
||||
var camera_normal = -_cached_camera.global_transform.basis.z
|
||||
var planar_dist_to_camera = (active_brush_data.brush_pos - _cached_camera.global_transform.origin).dot(camera_normal)
|
||||
var brush_pos:Vector3 = project_mouse(planar_dist_to_camera)
|
||||
active_brush_data.brush_pos = brush_pos
|
||||
|
||||
# It's possible we don't have _cached_camera defined here since
|
||||
# Gardener tests might call update_active_brush_data() without setting it
|
||||
if _cached_camera:
|
||||
# Cache to use with Projection brush
|
||||
active_brush_data.brush_basis = _cached_camera.global_transform.basis
|
||||
|
||||
|
||||
# Update transform of a paint brush 3D node
|
||||
func refresh_brush_transform():
|
||||
if active_brush_data.is_empty(): return
|
||||
|
||||
match active_brush_overlap_mode:
|
||||
Toolshed_Brush.OverlapMode.VOLUME:
|
||||
paint_brush_node.global_transform.origin = active_brush_data.brush_pos
|
||||
paint_brush_node.global_transform.basis = Basis()
|
||||
Toolshed_Brush.OverlapMode.PROJECTION:
|
||||
paint_brush_node.global_transform.origin = active_brush_data.brush_pos
|
||||
paint_brush_node.global_transform.basis = active_brush_data.brush_basis
|
||||
# Projection brush size is in viewport-space, but it will move forward and backward
|
||||
# Thus appearing smaller or bigger
|
||||
# So we need to update it's size to keep it consistent
|
||||
set_brush_diameter(active_brush_size)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Brush quick property edit lifecycle
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Quickly edit a brush property without using the UI (aka like in Blender)
|
||||
# The flow here is as follows:
|
||||
# 1. Respond to mouse events, calculate property value, emit a signal
|
||||
# 2. Signal is received in the Gardener, passed to an active Toolshed_Brush
|
||||
# 3. Active brush updates it's values
|
||||
# 4. Toolshed notifies Painter of a value change
|
||||
# 5. Painter updates it's helper variables and visual representation
|
||||
|
||||
# Switching between Volume/Projection brush is here too, but it's not connected to the whole Blender-like process
|
||||
# It's just a hotkey handling
|
||||
|
||||
# Set the initial value of edited property and mouse offset
|
||||
func start_brush_prop_edit(mouse_pos):
|
||||
match brush_prop_edit_flag:
|
||||
BrushPropEditFlag.SIZE:
|
||||
brush_prop_edit_cur_val = active_brush_size
|
||||
brush_prop_edit_max_val = active_brush_max_size
|
||||
BrushPropEditFlag.STRENGTH:
|
||||
brush_prop_edit_cur_val = active_brush_strength
|
||||
brush_prop_edit_max_val = active_brush_max_strength
|
||||
|
||||
brush_prop_edit_start_pos = mouse_pos
|
||||
brush_prop_edit_offset = brush_prop_edit_cur_val / brush_prop_edit_max_val * brush_prop_edit_max_dist
|
||||
|
||||
|
||||
# Calculate edited property value based on mouse offset
|
||||
func brush_prop_edit_calc_val(mouse_pos):
|
||||
brush_prop_edit_cur_val = clamp((mouse_pos.x - brush_prop_edit_start_pos.x + brush_prop_edit_offset) / brush_prop_edit_max_dist, 0.0, 1.0) * brush_prop_edit_max_val
|
||||
|
||||
match active_brush_overlap_mode:
|
||||
Toolshed_Brush.OverlapMode.VOLUME:
|
||||
match brush_prop_edit_flag:
|
||||
BrushPropEditFlag.SIZE:
|
||||
changed_active_brush_prop.emit("shape/shape_volume_size", brush_prop_edit_cur_val, false)
|
||||
BrushPropEditFlag.STRENGTH:
|
||||
changed_active_brush_prop.emit("behavior/behavior_strength", brush_prop_edit_cur_val, false)
|
||||
Toolshed_Brush.OverlapMode.PROJECTION:
|
||||
match brush_prop_edit_flag:
|
||||
BrushPropEditFlag.SIZE:
|
||||
changed_active_brush_prop.emit("shape/shape_projection_size", brush_prop_edit_cur_val, false)
|
||||
|
||||
|
||||
# Stop editing brush property and reset helper variables and mouse position
|
||||
func finish_brush_prop_edit(camera:Camera3D):
|
||||
match active_brush_overlap_mode:
|
||||
Toolshed_Brush.OverlapMode.VOLUME:
|
||||
match brush_prop_edit_flag:
|
||||
BrushPropEditFlag.SIZE:
|
||||
changed_active_brush_prop.emit("shape/shape_volume_size", brush_prop_edit_cur_val, true)
|
||||
BrushPropEditFlag.STRENGTH:
|
||||
changed_active_brush_prop.emit("behavior/behavior_strength", brush_prop_edit_cur_val, true)
|
||||
Toolshed_Brush.OverlapMode.PROJECTION:
|
||||
match brush_prop_edit_flag:
|
||||
BrushPropEditFlag.SIZE:
|
||||
changed_active_brush_prop.emit("shape/shape_projection_size", brush_prop_edit_cur_val, true)
|
||||
|
||||
Input.warp_mouse(brush_prop_edit_start_pos)
|
||||
|
||||
brush_prop_edit_flag = BrushPropEditFlag.NONE
|
||||
brush_prop_edit_start_pos = Vector2.ZERO
|
||||
brush_prop_edit_max_val = 0.0
|
||||
brush_prop_edit_cur_val = 0.0
|
||||
|
||||
# Apparently warp_mouse() sometimes takes a few mouse motion events to actually take place
|
||||
# Sometimes it's instant, sometimes it takes 1, and sometimes 2 events (at least on my machine)
|
||||
# This leads to brush jumping to position used in prop edit and then back. Like it's on a string
|
||||
# As an workaround, we delay processing motion input for 2 events (which should be enough for 99% of cases?)
|
||||
mouse_move_call_delay = 2
|
||||
|
||||
|
||||
# Cycle between brush overlap modes on a button press
|
||||
func cycle_overlap_modes():
|
||||
active_brush_overlap_mode += 1
|
||||
if active_brush_overlap_mode > Toolshed_Brush.OverlapMode.PROJECTION:
|
||||
active_brush_overlap_mode = Toolshed_Brush.OverlapMode.VOLUME
|
||||
changed_active_brush_prop.emit("behavior/behavior_overlap_mode", active_brush_overlap_mode, true)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Setters for brush parameters meant to be accessed from outside
|
||||
# In response to UI inputs
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func update_all_props_to_active_brush(brush: Toolshed_Brush):
|
||||
var max_size = 1.0
|
||||
var max_strength = 1.0
|
||||
var curr_size = 1.0
|
||||
var curr_strength = brush.behavior_strength
|
||||
|
||||
match brush.behavior_overlap_mode:
|
||||
Toolshed_Brush.OverlapMode.VOLUME:
|
||||
max_size = FunLib.get_setting_safe("dreadpons_spatial_gardener/input_and_ui/brush_volume_size_slider_max_value", 100.0)
|
||||
curr_size = brush.shape_volume_size
|
||||
Toolshed_Brush.OverlapMode.PROJECTION:
|
||||
max_size = FunLib.get_setting_safe("dreadpons_spatial_gardener/input_and_ui/brush_projection_size_slider_max_value", 1000.0)
|
||||
curr_size = brush.shape_projection_size
|
||||
|
||||
set_active_brush_overlap_mode(brush.behavior_overlap_mode)
|
||||
set_active_brush_max_size(max_size)
|
||||
set_active_brush_max_strength(max_strength)
|
||||
set_active_brush_size(curr_size)
|
||||
set_active_brush_strength(curr_strength)
|
||||
|
||||
|
||||
# Update helper variables and visuals
|
||||
func set_active_brush_size(val):
|
||||
active_brush_size = val
|
||||
paint_brush_node.material_override.set_shader_parameter("proximity_multiplier", active_brush_size * 0.5)
|
||||
queue_call_when_camera('set_brush_diameter', [active_brush_size])
|
||||
|
||||
|
||||
# Update helper variables and visuals
|
||||
func set_active_brush_max_size(val):
|
||||
active_brush_max_size = val
|
||||
queue_call_when_camera('set_brush_diameter', [active_brush_size])
|
||||
|
||||
|
||||
# Update helper variables
|
||||
func set_active_brush_strength(val):
|
||||
active_brush_strength = val
|
||||
|
||||
|
||||
# Update helper variables
|
||||
func set_active_brush_max_strength(val):
|
||||
active_brush_max_strength = val
|
||||
|
||||
|
||||
# Update visuals
|
||||
func set_brush_diameter(diameter: float):
|
||||
match active_brush_overlap_mode:
|
||||
|
||||
Toolshed_Brush.OverlapMode.VOLUME:
|
||||
paint_brush_node.mesh.radius = diameter * 0.5
|
||||
paint_brush_node.mesh.height = diameter
|
||||
|
||||
Toolshed_Brush.OverlapMode.PROJECTION:
|
||||
var camera_normal = -_cached_camera.global_transform.basis.z
|
||||
var planar_dist_to_camera = (active_brush_data.brush_pos - _cached_camera.global_transform.origin).dot(camera_normal)
|
||||
var circle_center:Vector3 = active_brush_data.brush_pos
|
||||
var circle_edge:Vector3
|
||||
# If we're editing props (or just finished it as indicated by 'mouse_move_call_delay')
|
||||
# Then to prevent size doubling/overflow use out brush position as mouse position
|
||||
# (Since out mouse WILL be offset due to us dragging it to the side)
|
||||
if brush_prop_edit_flag > BrushPropEditFlag.NONE || mouse_move_call_delay > 0:
|
||||
var screen_space_brush_pos = _cached_camera.unproject_position(active_brush_data.brush_pos)
|
||||
circle_edge = _cached_camera.project_position(screen_space_brush_pos + Vector2(diameter * 0.5, 0), planar_dist_to_camera)
|
||||
else:
|
||||
circle_edge = project_mouse(planar_dist_to_camera, Vector2(diameter * 0.5, 0))
|
||||
var size = (circle_edge - circle_center).length()
|
||||
paint_brush_node.mesh.size = Vector2(size, size) * 2.0
|
||||
|
||||
|
||||
func set_brush_collision_mask(val):
|
||||
brush_collision_mask = val
|
||||
|
||||
|
||||
# Update helper variables and visuals
|
||||
func set_active_brush_overlap_mode(val):
|
||||
active_brush_overlap_mode = val
|
||||
|
||||
match active_brush_overlap_mode:
|
||||
Toolshed_Brush.OverlapMode.VOLUME:
|
||||
set_brush_mesh(true)
|
||||
Toolshed_Brush.OverlapMode.PROJECTION:
|
||||
set_brush_mesh(false)
|
||||
|
||||
# Since we are rebuilding the mesh here
|
||||
# It means that we need to move it in a proper position as well
|
||||
move_brush()
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Camera3D/raycasting methods
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
func project_mouse_near() -> Vector3:
|
||||
return project_mouse(_cached_camera.near)
|
||||
|
||||
|
||||
func project_mouse_far() -> Vector3:
|
||||
return project_mouse(_cached_camera.far - 0.1)
|
||||
|
||||
|
||||
func project_mouse(distance: float, offset: Vector2 = Vector2.ZERO) -> Vector3:
|
||||
return _cached_camera.project_position(_cached_camera.get_viewport().get_mouse_position() + offset, distance)
|
||||
Reference in New Issue
Block a user