built more assets and started playing with foliage painting

This commit is contained in:
derek
2024-12-04 17:02:46 -06:00
parent dd960cc00e
commit 478e2822d2
359 changed files with 34172 additions and 178 deletions

View 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()