built more assets and started playing with foliage painting
This commit is contained in:
417
addons/dreadpon.spatial_gardener/utility/fun_lib.gd
Normal file
417
addons/dreadpon.spatial_gardener/utility/fun_lib.gd
Normal file
@@ -0,0 +1,417 @@
|
||||
@tool
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# A miscellaneous FUNction LIBrary
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
const Logger = preload("logger.gd")
|
||||
const Globals = preload("globals.gd")
|
||||
enum TimeTrimMode {NONE, EXACT, EXTRA_ONE, KEEP_ONE, KEEP_TWO, KEEP_THREE}
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Nodes
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Remove all children from node and free them
|
||||
static func free_children(node):
|
||||
if !is_instance_valid(node): return
|
||||
for child in node.get_children().duplicate():
|
||||
node.remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
|
||||
# Remove all children from node
|
||||
static func remove_children(node):
|
||||
if !is_instance_valid(node): return
|
||||
for child in node.get_children().duplicate():
|
||||
node.remove_child(child)
|
||||
|
||||
|
||||
# A shorthand for checking/connecting a signal
|
||||
# Kinda wish Godot had a built-in one
|
||||
static func ensure_signal(_signal:Signal, callable: Callable, binds:Array = [], flags:int = 0):
|
||||
if !_signal.is_connected(callable):
|
||||
_signal.connect(callable.bindv(binds), flags)
|
||||
|
||||
|
||||
static func disconnect_all(_signal: Signal):
|
||||
for connection_data in _signal.get_connections():
|
||||
connection_data["signal"].disconnect(connection_data.callable)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Strings
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Capitalize all strings in an array
|
||||
static func capitalize_string_array(array:Array):
|
||||
var narray = array.duplicate()
|
||||
for i in range(0, narray.size()):
|
||||
if narray[i] is String:
|
||||
narray[i] = narray[i].capitalize()
|
||||
return narray
|
||||
|
||||
|
||||
# Build a property hint_string out of strings in an array
|
||||
static func make_hint_string(array:Array):
|
||||
var string = ""
|
||||
for i in range(0, array.size()):
|
||||
if array[i] is String:
|
||||
string += array[i]
|
||||
if i < array.size() - 1:
|
||||
string += ","
|
||||
return string
|
||||
|
||||
|
||||
# Convert to custom string format, context-dependent but independednt to changes to Godot's var_to_str
|
||||
static func vec3_to_str(val: Vector3) -> String:
|
||||
return "%f, %f, %f" % [val.x, val.y, val.z]
|
||||
|
||||
|
||||
# Convert to custom string format, context-dependent but independednt to changes to Godot's var_to_str
|
||||
static func transform3d_to_str(val: Transform3D) -> String:
|
||||
return "%f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f" % [
|
||||
val.basis.x.x, val.basis.x.y, val.basis.x.z,
|
||||
val.basis.y.x, val.basis.y.y, val.basis.y.z,
|
||||
val.basis.z.x, val.basis.z.y, val.basis.z.z,
|
||||
val.origin.x, val.origin.y, val.origin.z
|
||||
]
|
||||
|
||||
|
||||
# Convert from custom string format
|
||||
static func str_to_vec3(string: String, str_version: int) -> Vector3:
|
||||
match str_version:
|
||||
0:
|
||||
var split = string.trim_prefix('(').trim_suffix(')').split_floats(', ')
|
||||
return Vector3(split[0], split[1], split[2])
|
||||
1:
|
||||
var split = string.split_floats(', ')
|
||||
return Vector3(split[0], split[1], split[2])
|
||||
_:
|
||||
push_error("Unsupported str version: %d" % [str_version])
|
||||
return Vector3.ZERO
|
||||
|
||||
|
||||
# Convert from custom string format
|
||||
static func str_to_transform3d(string: String, str_version: int) -> Transform3D:
|
||||
match str_version:
|
||||
0:
|
||||
string = string.replace(' - ', ', ')
|
||||
var split = string.split_floats(', ')
|
||||
return Transform3D(
|
||||
Vector3(split[0], split[3], split[6]),
|
||||
Vector3(split[1], split[4], split[7]),
|
||||
Vector3(split[2], split[5], split[8]),
|
||||
Vector3(split[9], split[10], split[11]))
|
||||
1:
|
||||
var split = string.split_floats(', ')
|
||||
return Transform3D(
|
||||
Vector3(split[0], split[3], split[6]),
|
||||
Vector3(split[1], split[4], split[7]),
|
||||
Vector3(split[2], split[5], split[8]),
|
||||
Vector3(split[9], split[10], split[11]))
|
||||
_:
|
||||
push_error("Unsupported str version: %d" % [str_version])
|
||||
return Transform3D()
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Math
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Clamp a value
|
||||
# Automatically decide which value is min and which is max
|
||||
static func clamp_auto(value, min_value, max_value):
|
||||
var direction = 1.0 if min_value <= max_value else -1.0
|
||||
if direction >= 0:
|
||||
if value < min_value:
|
||||
return min_value
|
||||
elif value > max_value:
|
||||
return max_value
|
||||
else:
|
||||
if value > min_value:
|
||||
return min_value
|
||||
elif value < max_value:
|
||||
return max_value
|
||||
|
||||
return value
|
||||
|
||||
|
||||
# Clamp all Vector3 properties individually
|
||||
static func clamp_vector3(value:Vector3, min_value:Vector3, max_value:Vector3):
|
||||
var result = Vector3()
|
||||
result.x = clamp_auto(value.x, min_value.x, max_value.x)
|
||||
result.y = clamp_auto(value.y, min_value.y, max_value.y)
|
||||
result.z = clamp_auto(value.z, min_value.z, max_value.z)
|
||||
return result
|
||||
|
||||
|
||||
# Lerp all Vector3 properties by 3 independent weights
|
||||
static func vector_tri_lerp(from:Vector3, to:Vector3, weight:Vector3):
|
||||
return Vector3(
|
||||
lerp(from.x, to.x, weight.x),
|
||||
lerp(from.y, to.y, weight.y),
|
||||
lerp(from.z, to.z, weight.z)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Time
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
static func get_msec():
|
||||
return Time.get_ticks_msec()
|
||||
|
||||
|
||||
static func msec_to_time(msec:int = -1, include_msec:bool = true, trim_mode:int = TimeTrimMode.NONE):
|
||||
if msec < 0:
|
||||
msec = get_msec()
|
||||
var time_units := [msec % 1000, msec / 1000 % 60, msec / 1000 / 60 % 60, msec / 1000 / 60 / 60 % 24]
|
||||
var string = ""
|
||||
|
||||
if trim_mode != TimeTrimMode.NONE:
|
||||
for i in range(time_units.size() - 1, -1, -1):
|
||||
match trim_mode:
|
||||
TimeTrimMode.EXACT:
|
||||
if time_units[i] <= 0:
|
||||
time_units.remove_at(i)
|
||||
else:
|
||||
break
|
||||
TimeTrimMode.EXTRA_ONE:
|
||||
if time_units[i] > 0:
|
||||
break
|
||||
if i + 1 < time_units.size() && time_units[i + 1] <= 0:
|
||||
time_units.remove_at(i + 1)
|
||||
TimeTrimMode.KEEP_ONE:
|
||||
if i >= 1:
|
||||
time_units.remove_at(i)
|
||||
TimeTrimMode.KEEP_TWO:
|
||||
if i >= 2:
|
||||
time_units.remove_at(i)
|
||||
TimeTrimMode.KEEP_THREE:
|
||||
if i >= 3:
|
||||
time_units.remove_at(i)
|
||||
|
||||
|
||||
|
||||
for i in range(0, time_units.size()):
|
||||
var time_unit:int = time_units[i]
|
||||
|
||||
if i == 0:
|
||||
if !include_msec: continue
|
||||
string = "%03d" % [time_units[i]]
|
||||
else:
|
||||
string = string.insert(0, "%02d:" % time_units[i])
|
||||
|
||||
string = string.trim_suffix(":")
|
||||
|
||||
return string
|
||||
|
||||
|
||||
static func print_system_time(suffix:String = ""):
|
||||
print("[%s] : %s" % [Time.get_time_string_from_system(), suffix])
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Object class comparison
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
static func get_obj_class_string(obj:Object) -> String:
|
||||
if obj == null: return ""
|
||||
assert(is_instance_of(obj, Object))
|
||||
if obj.has_meta("class"):
|
||||
return obj.get_meta("class")
|
||||
elif obj.get_script():
|
||||
return obj.get_script().get_instance_base_type()
|
||||
else:
|
||||
return obj.get_class()
|
||||
|
||||
|
||||
static func are_same_class(one:Object, two:Object) -> bool:
|
||||
if one == null: return false
|
||||
if two == null: return false
|
||||
assert(is_instance_of(one, Object) && is_instance_of(two, Object))
|
||||
|
||||
# print("1 %s, 2 %s" % [one.get_class(), two.get_class()])
|
||||
|
||||
if one.get_script() && two.get_script() && one.get_script() == two.get_script():
|
||||
return true
|
||||
elif one.has_meta("class") && two.has_meta("class") && one.get_meta("class") == two.get_meta("class"):
|
||||
return true
|
||||
elif one.get_class() == two.get_class():
|
||||
return true
|
||||
# elif !one.is_class(two.get_class()):
|
||||
# return true
|
||||
return false
|
||||
|
||||
|
||||
static func obj_is_script(obj:Object, script:Script) -> bool:
|
||||
if obj == null: return false
|
||||
assert(is_instance_of(obj, Object))
|
||||
return obj.get_script() && obj.get_script() == script
|
||||
|
||||
|
||||
static func obj_is_class_string(obj:Object, class_string:String) -> bool:
|
||||
if obj == null: return false
|
||||
assert(is_instance_of(obj, Object))
|
||||
|
||||
if obj.get_class() == class_string:
|
||||
return true
|
||||
elif obj.has_meta("class") && obj.get_meta("class") == class_string:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Configuration
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# This is here to avoid circular reference lol
|
||||
static func get_setting_safe(setting:String, default_value = null):
|
||||
if ProjectSettings.has_setting(setting):
|
||||
return ProjectSettings.get_setting(setting)
|
||||
return default_value
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Asset management
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
static func save_res(res:Resource, dir:String, res_name:String):
|
||||
assert(res)
|
||||
var logger = Logger.get_for_string("FunLib")
|
||||
var full_path = combine_dir_and_file(dir, res_name)
|
||||
if !is_dir_valid(dir):
|
||||
logger.warn("Unable to save '%s', directory is invalid!" % [full_path])
|
||||
return
|
||||
# Abort explicit saving if our resource and an existing one are the same instance
|
||||
# Since it will be saved on 'Ctrl+S' implicitly by the editor
|
||||
# And allows reverting resource by exiting the editor
|
||||
var loaded_res = load_res(dir, res_name, false, true)
|
||||
if res == loaded_res:
|
||||
return
|
||||
|
||||
# There was a wall of text here regarding problems of saving and re-saving custom resources
|
||||
# But curiously, seems like it went away
|
||||
# These comments and previous state of saving/loading logic is available on commit '7b127ad'
|
||||
|
||||
# Taking over path and subpaths is still required
|
||||
# Still keeping FLAG_CHANGE_PATH in case we want to save to a different location
|
||||
res.take_over_path(full_path)
|
||||
var err = ResourceSaver.save(res, full_path, ResourceSaver.FLAG_CHANGE_PATH | ResourceSaver.FLAG_REPLACE_SUBRESOURCE_PATHS)
|
||||
if err != OK:
|
||||
logger.error("Could not save '%s', error %s!" % [full_path, Globals.get_err_message(err)])
|
||||
|
||||
|
||||
# Passing 'true' as 'no_cache' is important to bypass this cache
|
||||
# We use it by default, but want to allow loading a cache to check if resource exists at path
|
||||
static func load_res(dir:String, res_name:String, no_cache: bool = true, silent: bool = false) -> Resource:
|
||||
var full_path = combine_dir_and_file(dir, res_name)
|
||||
var res = null
|
||||
var logger = Logger.get_for_string("FunLib")
|
||||
|
||||
if ResourceLoader.exists(full_path):
|
||||
res = ResourceLoader.load(full_path, "", ResourceLoader.CacheMode.CACHE_MODE_REPLACE if no_cache else ResourceLoader.CacheMode.CACHE_MODE_REUSE)
|
||||
else:
|
||||
if !silent: logger.warn("Path '%s', doesn't exist!" % [full_path])
|
||||
|
||||
if !res:
|
||||
if !is_dir_valid(dir) || res_name == "":
|
||||
if !silent: logger.warn("Could not load '%s', error %s!" % [full_path, Globals.get_err_message(ERR_FILE_BAD_PATH)])
|
||||
else:
|
||||
if !silent: logger.warn("Could not load '%s'!" % [full_path])
|
||||
return res
|
||||
|
||||
|
||||
static func remove_res(dir:String, res_name:String):
|
||||
var full_path = combine_dir_and_file(dir, res_name)
|
||||
var abs_path = ProjectSettings.globalize_path(full_path)
|
||||
var err = DirAccess.remove_absolute(abs_path)
|
||||
var logger = Logger.get_for_string("FunLib")
|
||||
if err != OK:
|
||||
logger.error("Could not remove '%s', error %s!" % [abs_path, Globals.get_err_message(err)])
|
||||
|
||||
|
||||
static func combine_dir_and_file(dir_path: String, file_name: String):
|
||||
if !dir_path.is_empty() && !dir_path.ends_with("/"):
|
||||
dir_path += "/"
|
||||
return "%s%s" % [dir_path, file_name]
|
||||
|
||||
|
||||
static func is_dir_valid(dir):
|
||||
return !dir.is_empty() && dir != "/" && DirAccess.dir_exists_absolute(dir)
|
||||
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Filesystem
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
static func remove_dir_recursive(path, keep_first:bool = false) -> bool:
|
||||
var dir = DirAccess.open(path)
|
||||
if dir:
|
||||
dir.list_dir_begin()
|
||||
var file_name = dir.get_next()
|
||||
while file_name != "":
|
||||
if dir.current_is_dir():
|
||||
if !remove_dir_recursive(path + "/" + file_name, false):
|
||||
return false
|
||||
else:
|
||||
dir.remove(file_name)
|
||||
file_name = dir.get_next()
|
||||
|
||||
if !keep_first:
|
||||
dir.remove(path)
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
static func iterate_files(dir_path: String, deep: bool, obj: Object, method_name: String, payload):
|
||||
if !is_instance_valid(obj):
|
||||
assert('Object instace invalid!')
|
||||
return
|
||||
if !obj.has_method(method_name):
|
||||
assert('%s does not have a method named "%s"!' % [str(obj), method_name])
|
||||
return
|
||||
|
||||
var dir = DirAccess.open(dir_path)
|
||||
if dir_path.ends_with('/'):
|
||||
dir_path = dir_path.trim_suffix('/')
|
||||
if dir:
|
||||
dir.list_dir_begin()
|
||||
var full_path = ''
|
||||
var file_name = dir.get_next()
|
||||
while file_name != '':
|
||||
full_path = dir_path + "/" + file_name
|
||||
if deep && dir.current_is_dir():
|
||||
iterate_files(full_path, deep, obj, method_name, payload)
|
||||
else:
|
||||
obj.call(method_name, full_path, payload)
|
||||
file_name = dir.get_next()
|
||||
Reference in New Issue
Block a user