Initial commit
This commit is contained in:
commit
5be8beb69f
7
plugin.cfg
Normal file
7
plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="GDScript Basics"
|
||||||
|
description="Basic (and advanced) utility for gdscript."
|
||||||
|
author="Patrick Wuttke<mewin@mewin.de>"
|
||||||
|
version="0.2"
|
||||||
|
script="plugin.gd"
|
8
plugin.gd
Normal file
8
plugin.gd
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
func _enter_tree():
|
||||||
|
pass
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
pass
|
118
scripts/libs/algorithm.gd
Normal file
118
scripts/libs/algorithm.gd
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
###
|
||||||
|
### algorithmic utility
|
||||||
|
###
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
class_name GDBAlgorithm
|
||||||
|
|
||||||
|
class __SortBy:
|
||||||
|
var prop_name := ""
|
||||||
|
var descending := false
|
||||||
|
|
||||||
|
func __sort(obj0, obj1):
|
||||||
|
var res = obj0.get(prop_name) < obj1.get(prop_name)
|
||||||
|
if descending:
|
||||||
|
return !res
|
||||||
|
return res
|
||||||
|
|
||||||
|
################
|
||||||
|
# public stuff #
|
||||||
|
################
|
||||||
|
func _init() -> void:
|
||||||
|
assert(0, "This class should not be instantiated.")
|
||||||
|
|
||||||
|
# Packs source code into a small script object, like a lambda in other languages.
|
||||||
|
# This function can then be called by calling the "eval" (or whatever you pass as
|
||||||
|
# fn_name) function of the object. This is especially useful for GDScript functions
|
||||||
|
# like "sort_custom" which take an object and a function as parameters.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# var names := ["Bob", "Alice", "Elfriede"]
|
||||||
|
# names.sort_custom(GDBAlgorithm.lambda("return name0.substr(1) < name1.substr(1)", ["name0", "name1"]), "eval")
|
||||||
|
# print(names) # prints "[Elfriede, Alice, Bob]"
|
||||||
|
#
|
||||||
|
# @param source The source code of your lambda function.
|
||||||
|
# @param params A list of parameter names your function takes.
|
||||||
|
# @param fn_name The name of the resulting function, defaults to "eval".
|
||||||
|
# @returns An object containing the lambda function as fn_name.
|
||||||
|
static func lambda(source : String, params := [], fn_name := "eval") -> Reference:
|
||||||
|
var script := GDScript.new();
|
||||||
|
script.source_code = "tool\nextends Reference\nfunc {fn_name}({param_list}):\n\t{source}".format({
|
||||||
|
"source": source,
|
||||||
|
"param_list": PoolStringArray(params).join(","),
|
||||||
|
"fn_name": fn_name
|
||||||
|
})
|
||||||
|
var err : int = script.reload()
|
||||||
|
if err == OK:
|
||||||
|
return script.new()
|
||||||
|
else:
|
||||||
|
printerr("lambda: script compilation failed.")
|
||||||
|
return null
|
||||||
|
|
||||||
|
# Removes elements from a collection only if the specified condition is met.
|
||||||
|
# Takes either an object and a function name or a lambda and a parameter list
|
||||||
|
# as parameters.
|
||||||
|
#
|
||||||
|
# Example 1:
|
||||||
|
# func is_negative(num):
|
||||||
|
# return num < 0
|
||||||
|
#
|
||||||
|
# func remove_negatives(col):
|
||||||
|
# GDBAlgorithm.remove_if(col, self, "is_negative")
|
||||||
|
#
|
||||||
|
# Example 2:
|
||||||
|
# func remove_negatives2(col):
|
||||||
|
# GDBAlgorithm.remove_if(col, "num < 0", ["num"])
|
||||||
|
static func remove_if(collection, p0, p1 = null) -> int:
|
||||||
|
if p0 is String:
|
||||||
|
if p1 && !p1 is Array:
|
||||||
|
printerr("remove_if failed: invalid parameters")
|
||||||
|
return 0
|
||||||
|
var lmb := lambda(p0 as String, p1 as Array if p1 else [])
|
||||||
|
if !lmb:
|
||||||
|
return 0 # errors have already been printed
|
||||||
|
return __remove_if(collection, lmb, "eval")
|
||||||
|
elif !p0 is Object || !p1 is String:
|
||||||
|
printerr("remove_if failed: invalid parameters")
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return __remove_if(collection, p0, p1)
|
||||||
|
|
||||||
|
static func sort_by(arr : Array, prop_name : String, descending := false):
|
||||||
|
var comparator := __SortBy.new()
|
||||||
|
comparator.prop_name = prop_name
|
||||||
|
comparator.descending = descending
|
||||||
|
arr.sort_custom(comparator, "__sort")
|
||||||
|
|
||||||
|
#################
|
||||||
|
# private stuff #
|
||||||
|
#################
|
||||||
|
static func __remove_if(collection, object : Object, predicate : String) -> int:
|
||||||
|
if collection is Array:
|
||||||
|
return __remove_if_array(collection, object, predicate)
|
||||||
|
elif collection is Dictionary:
|
||||||
|
return __remove_if_dict(collection, object, predicate)
|
||||||
|
else:
|
||||||
|
var values_to_remove := []
|
||||||
|
for ele in collection:
|
||||||
|
if object.call(predicate, ele):
|
||||||
|
values_to_remove.append(ele)
|
||||||
|
for ele in values_to_remove:
|
||||||
|
collection.erase(ele)
|
||||||
|
return values_to_remove.size()
|
||||||
|
|
||||||
|
static func __remove_if_array(array : Array, object : Object, predicate : String) -> int:
|
||||||
|
var removed := 0
|
||||||
|
for i in range(array.size() - 1, -1, -1):
|
||||||
|
if object.call(predicate, array[i]):
|
||||||
|
array.remove(i)
|
||||||
|
removed += 1
|
||||||
|
return removed
|
||||||
|
|
||||||
|
static func __remove_if_dict(dict : Dictionary, object : Object, predicate : String) -> int:
|
||||||
|
var removed := 0
|
||||||
|
for key in dict.keys():
|
||||||
|
if object.call(predicate, dict[key]):
|
||||||
|
dict.erase(key)
|
||||||
|
removed += 1
|
||||||
|
return removed
|
15
scripts/libs/constants.gd
Normal file
15
scripts/libs/constants.gd
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
###
|
||||||
|
### global constants
|
||||||
|
###
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
class_name GDBConstants
|
||||||
|
|
||||||
|
const SETTING_RECENT_PLACES = "recent_places"
|
||||||
|
const SETTING_FAVOURITE_PLACES = "favourite_places"
|
||||||
|
|
||||||
|
################
|
||||||
|
# public stuff #
|
||||||
|
################
|
||||||
|
func _init() -> void:
|
||||||
|
assert(0, "This class should not be instantiated.")
|
148
scripts/libs/coroutine.gd
Normal file
148
scripts/libs/coroutine.gd
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
###
|
||||||
|
### utility for writing coroutines
|
||||||
|
###
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
class_name GDBCoroutine
|
||||||
|
|
||||||
|
class __WaitAll:
|
||||||
|
var __SIG_SEPERATOR = Reference.new()
|
||||||
|
var remaining_signals : Array
|
||||||
|
var results := []
|
||||||
|
|
||||||
|
func __connect_signals():
|
||||||
|
for i in range(remaining_signals.size()):
|
||||||
|
var ele = remaining_signals[i]
|
||||||
|
assert(ele is Dictionary && ele.has("object") && ele.has("signal"))
|
||||||
|
ele["object"].connect(ele["signal"], self, "_on_signal", [__SIG_SEPERATOR, ele["object"], ele["signal"], i])
|
||||||
|
results.resize(remaining_signals.size())
|
||||||
|
|
||||||
|
func _on_signal(p0 = null, p1 = null, p2 = null, p3 = null, p4 = null, p5 = null, p6 = null, p7 = null):
|
||||||
|
var params := [p0, p1, p2, p3, p4, p5, p6, p7]
|
||||||
|
assert(__SIG_SEPERATOR in params)
|
||||||
|
|
||||||
|
# store the parameters
|
||||||
|
var param = params.pop_front()
|
||||||
|
var sig_params = []
|
||||||
|
while param != __SIG_SEPERATOR:
|
||||||
|
sig_params.append(param)
|
||||||
|
param = params.pop_front()
|
||||||
|
|
||||||
|
# object, signal and index should be remaining (and some nulls)
|
||||||
|
assert(params.size() >= 3)
|
||||||
|
for i in range(remaining_signals.size()):
|
||||||
|
if remaining_signals[i]["object"] == params[0] && remaining_signals[i]["signal"] == params[1]:
|
||||||
|
remaining_signals.remove(i)
|
||||||
|
results[params[2]] = sig_params
|
||||||
|
break
|
||||||
|
|
||||||
|
if remaining_signals.empty():
|
||||||
|
emit_signal("finished", results)
|
||||||
|
|
||||||
|
signal finished(results)
|
||||||
|
|
||||||
|
class __WaitAny:
|
||||||
|
var __SIG_SEPERATOR = Reference.new()
|
||||||
|
|
||||||
|
func __connect_signals(signals : Array):
|
||||||
|
for ele in signals:
|
||||||
|
assert(ele is Dictionary && ele.has("object") && ele.has("signal"))
|
||||||
|
ele["object"].connect(ele["signal"], self, "_on_signal", [__SIG_SEPERATOR, ele["object"], ele["signal"]])
|
||||||
|
|
||||||
|
func _on_signal(p0 = null, p1 = null, p2 = null, p3 = null, p4 = null, p5 = null, p6 = null):
|
||||||
|
var params := [p0, p1, p2, p3, p4, p5, p6]
|
||||||
|
assert(__SIG_SEPERATOR in params)
|
||||||
|
|
||||||
|
# store the parameters
|
||||||
|
var param = params.pop_front()
|
||||||
|
var sig_params := []
|
||||||
|
while param != __SIG_SEPERATOR:
|
||||||
|
sig_params.append(param)
|
||||||
|
param = params.pop_front()
|
||||||
|
|
||||||
|
# object and signal should be remaining (and some nulls)
|
||||||
|
assert(params.size() >= 2)
|
||||||
|
emit_signal("finished", params[0], params[1], sig_params)
|
||||||
|
|
||||||
|
for con in get_signal_connection_list("_on_signal"):
|
||||||
|
con["source"].disconnect(con["signal"], self, "_on_signal")
|
||||||
|
|
||||||
|
signal finished(obj, sig, result)
|
||||||
|
|
||||||
|
################
|
||||||
|
# public stuff #
|
||||||
|
################
|
||||||
|
func _init() -> void:
|
||||||
|
assert(0, "This class should not be instantiated.")
|
||||||
|
|
||||||
|
#! Wait for multiple signals.
|
||||||
|
#!
|
||||||
|
#! Utility function to wait for multiple signals to occur, in any order.
|
||||||
|
#! The returned object will emit a "finished" signal after all signals
|
||||||
|
#! fired.
|
||||||
|
#!
|
||||||
|
#! The parameters provided to the signal handlers are stored inside a
|
||||||
|
#! "results" field inside the returned object and also provided to the
|
||||||
|
#! finish signal. Their order corresponds to the order of the objects
|
||||||
|
#! and signals provided in the parameters.
|
||||||
|
#!
|
||||||
|
#! If no signals have been provided, it will fire on the next frame. (This could
|
||||||
|
#! easily be increased by editing the source of this function, if required.)
|
||||||
|
#!
|
||||||
|
#! For technical reasons the signals must provide at most four parameters.
|
||||||
|
#!
|
||||||
|
#! Example (waits until all three buttons have been pressed):
|
||||||
|
#! yield(coroutine.wait_for_all([
|
||||||
|
#! {"object": $button0, "signal": "pressed"},
|
||||||
|
#! {"object": $button1, "signal": "pressed"},
|
||||||
|
#! {"object": $button2, "signal": "pressed"}
|
||||||
|
#! ]), "finished")
|
||||||
|
#!
|
||||||
|
#! \param objects_and_signals An array of dictionaries, each containing
|
||||||
|
#! "object" and "signal" elements.
|
||||||
|
#! \returns An object with a "finished" signal.
|
||||||
|
static func wait_for_all(objects_and_signals : Array) -> Object:
|
||||||
|
var obj := __WaitAll.new()
|
||||||
|
if objects_and_signals:
|
||||||
|
obj.remaining_signals = objects_and_signals
|
||||||
|
else:
|
||||||
|
obj.remaining_signals = [{"object": GDBUtility.get_scene_tree(), "signal": "idle_frame"}]
|
||||||
|
obj.__connect_signals()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
#! Wait for multiple signals.
|
||||||
|
#!
|
||||||
|
#! Utility function to wait for one of multiple signals to occur.
|
||||||
|
#! The returned object will emit a "finished" signal after any of the
|
||||||
|
#! provided signals occured. It will only be emitted a single time.
|
||||||
|
#!
|
||||||
|
#! If no signals have been provided, the signal will never fire.
|
||||||
|
#!
|
||||||
|
#! The provided signal takes three parameters: 1. the object that
|
||||||
|
#! emitted the initial signal, 2. the signal that had been emitted
|
||||||
|
#! and 3. the parameters provided to the signal handler, as an array.
|
||||||
|
#!
|
||||||
|
#! For technical reasons the signals must provide at most four parameters.
|
||||||
|
#!
|
||||||
|
#! Example (waits until any of the buttons has been pressed):
|
||||||
|
#! var res = yield(coroutine.wait_for_any([
|
||||||
|
#! {"object": $button0, "signal": "pressed"},
|
||||||
|
#! {"object": $button1, "signal": "pressed"},
|
||||||
|
#! {"object": $button2, "signal": "pressed"}
|
||||||
|
#! ]), "finished")
|
||||||
|
#! var button = res[0]
|
||||||
|
#! button.disabled = true
|
||||||
|
#!
|
||||||
|
#! \param objects_and_signals An array of dictionaries, each containing
|
||||||
|
#! "object" and "signal" elements.
|
||||||
|
#! \returns An object with a "finished" signal.
|
||||||
|
static func wait_for_any(objects_and_signals : Array):
|
||||||
|
var obj := __WaitAny.new()
|
||||||
|
obj.__connect_signals(objects_and_signals)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
static func await(res):
|
||||||
|
if res is GDScriptFunctionState:
|
||||||
|
return yield(res, "completed")
|
||||||
|
yield(GDBUtility.get_scene_tree(), "idle_frame")
|
||||||
|
return res
|
29
scripts/libs/debug.gd
Normal file
29
scripts/libs/debug.gd
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
###
|
||||||
|
### debugging tools
|
||||||
|
### mostly validity checks
|
||||||
|
###
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
class_name GDBDebug
|
||||||
|
|
||||||
|
################
|
||||||
|
# public stuff #
|
||||||
|
################
|
||||||
|
func _init() -> void:
|
||||||
|
assert(0, "This class should not be instantiated.")
|
||||||
|
|
||||||
|
static func assert_valid_num(v):
|
||||||
|
assert(!is_nan(v) && !is_inf(v))
|
||||||
|
|
||||||
|
static func assert_valid_vec2(v : Vector2):
|
||||||
|
assert(!is_nan(v.x) && !is_inf(v.x))
|
||||||
|
assert(!is_nan(v.y) && !is_inf(v.y))
|
||||||
|
|
||||||
|
static func assert_valid_vec3(v : Vector3):
|
||||||
|
assert(!is_nan(v.x) && !is_inf(v.x))
|
||||||
|
assert(!is_nan(v.y) && !is_inf(v.y))
|
||||||
|
assert(!is_nan(v.z) && !is_inf(v.z))
|
||||||
|
|
||||||
|
static func deprecated(note : String) -> void:
|
||||||
|
printerr("Usage of deprecated function: " + note)
|
||||||
|
print_stack()
|
80
scripts/libs/format.gd
Normal file
80
scripts/libs/format.gd
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
###
|
||||||
|
### various formatting functions
|
||||||
|
###
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
class_name GDBFormat
|
||||||
|
|
||||||
|
################
|
||||||
|
# public stuff #
|
||||||
|
################
|
||||||
|
func _init() -> void:
|
||||||
|
assert(0, "This class should not be instantiated.")
|
||||||
|
|
||||||
|
static func format_bytes(bytes : int) -> String:
|
||||||
|
var UNITS = ["B", "KiB", "MiB", "GiB", "TiB"]
|
||||||
|
var idx = 0
|
||||||
|
while bytes > 1024 && idx < UNITS.size() - 1:
|
||||||
|
bytes = bytes / 1024.0
|
||||||
|
idx += 1
|
||||||
|
return "%.4f %s" % [bytes, UNITS[idx]]
|
||||||
|
|
||||||
|
static func format_time(time : float) -> String:
|
||||||
|
# warning-ignore:integer_division
|
||||||
|
var minutes = int(time) / 60
|
||||||
|
var seconds = int(time) % 60
|
||||||
|
return "%02d:%02d" % [minutes, seconds]
|
||||||
|
|
||||||
|
static func format_unixtime(unix_time : int, format := tr("{0year}-{0month}-{0day} {0hour}:{0minute}")) -> String:
|
||||||
|
var datetime = OS.get_datetime_from_unix_time(unix_time)
|
||||||
|
datetime["year2"] = datetime["year"] % 100
|
||||||
|
datetime["0year"] = "%02d" % datetime["year2"]
|
||||||
|
datetime["0month"] = "%02d" % datetime["month"]
|
||||||
|
datetime["0day"] = "%02d" % datetime["day"]
|
||||||
|
datetime["0hour"] = "%02d" % datetime["hour"]
|
||||||
|
datetime["0minute"] = "%02d" % datetime["minute"]
|
||||||
|
datetime["0second"] = "%02d" % datetime["second"]
|
||||||
|
|
||||||
|
# return "%02d-%02d-%02d %02d:%02d" % [datetime["year"] % 100, datetime["month"], datetime["day"], datetime["hour"], datetime["minute"]]
|
||||||
|
return format.format(datetime)
|
||||||
|
|
||||||
|
static func smart_format_unixtime(unix_time : int, format_date := tr("{0year}-{0month}-{0day}"), format_time := tr("{0hour}:{0minute}")) -> String:
|
||||||
|
var now = OS.get_unix_time()
|
||||||
|
var datetime = OS.get_datetime_from_unix_time(unix_time)
|
||||||
|
|
||||||
|
__datetime_add_fields(datetime)
|
||||||
|
|
||||||
|
if now == unix_time:
|
||||||
|
return GDBUtility.translate("just now")
|
||||||
|
elif now > unix_time:
|
||||||
|
var tdiff = now - unix_time
|
||||||
|
if tdiff < 60: # < 60 seconds
|
||||||
|
return GDBUtility.translate("%d seconds ago") % tdiff
|
||||||
|
elif tdiff < 3600: # < 60 minutes
|
||||||
|
return GDBUtility.translate("%d minutes ago") % (tdiff / 60)
|
||||||
|
var now_datetime = OS.get_datetime_from_unix_time(now)
|
||||||
|
if now_datetime["year"] == datetime["year"] && now_datetime["month"] == datetime["month"] && now_datetime["day"] == datetime["day"]:
|
||||||
|
return GDBUtility.translate("today at %s") % format_time.format(datetime)
|
||||||
|
else:
|
||||||
|
var tdiff = unix_time - now
|
||||||
|
if tdiff < 60:
|
||||||
|
return GDBUtility.translate("in %d seconds") % tdiff
|
||||||
|
elif tdiff < 3600:
|
||||||
|
return GDBUtility.translate("in %d minutes") % (tdiff / 60)
|
||||||
|
var now_datetime = OS.get_datetime_from_unix_time(now)
|
||||||
|
if now_datetime["year"] == datetime["year"] && now_datetime["month"] == datetime["month"] && now_datetime["day"] == datetime["day"]:
|
||||||
|
return GDBUtility.translate("today at %s") % format_time.format(datetime)
|
||||||
|
|
||||||
|
return "%s %s" % [format_date.format(datetime), format_time.format(datetime)]
|
||||||
|
|
||||||
|
#################
|
||||||
|
# private stuff #
|
||||||
|
#################
|
||||||
|
static func __datetime_add_fields(datetime : Dictionary) -> void:
|
||||||
|
datetime["year2"] = datetime["year"] % 100
|
||||||
|
datetime["0year"] = "%02d" % datetime["year2"]
|
||||||
|
datetime["0month"] = "%02d" % datetime["month"]
|
||||||
|
datetime["0day"] = "%02d" % datetime["day"]
|
||||||
|
datetime["0hour"] = "%02d" % datetime["hour"]
|
||||||
|
datetime["0minute"] = "%02d" % datetime["minute"]
|
||||||
|
datetime["0second"] = "%02d" % datetime["second"]
|
100
scripts/libs/fsutil.gd
Normal file
100
scripts/libs/fsutil.gd
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
###
|
||||||
|
### file system utility
|
||||||
|
###
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
class_name GDBFsUtil
|
||||||
|
|
||||||
|
const __META_FILENAME_REGEX = "__gdb_fsutil_filename_regex__"
|
||||||
|
|
||||||
|
################
|
||||||
|
# public stuff #
|
||||||
|
################
|
||||||
|
func _init() -> void:
|
||||||
|
assert(0, "This class should not be instantiated.")
|
||||||
|
|
||||||
|
static func find_all_by_name(path : String, name : String, files_only = true) -> Dictionary:
|
||||||
|
var result = []
|
||||||
|
__find_all_by_name(path, name, result, files_only)
|
||||||
|
return result
|
||||||
|
|
||||||
|
static func escape_filename(filename : String) -> String:
|
||||||
|
return __get_filename_regex().sub(filename, "_", true)
|
||||||
|
|
||||||
|
static func is_filename_valid(filename : String) -> bool:
|
||||||
|
return __get_filename_regex().search(filename) == null
|
||||||
|
|
||||||
|
static func load_image_resource(filename : String):
|
||||||
|
var res = load(filename)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
var img = Image.new()
|
||||||
|
if img.load(filename) == OK:
|
||||||
|
var tex = ImageTexture.new()
|
||||||
|
tex.create_from_image(img)
|
||||||
|
return tex
|
||||||
|
return null
|
||||||
|
|
||||||
|
#! Retrieve information about the current git branch.
|
||||||
|
#!
|
||||||
|
#! Used to display this information in development builds.
|
||||||
|
#! Returns a dictionary with the following keys:
|
||||||
|
#! - "branch" - the name of the current branch
|
||||||
|
#! - "commit" - the full hash of the latest commit
|
||||||
|
#! - "commit_short" - the shortened hash of the last commit
|
||||||
|
static func get_git_info() -> Dictionary:
|
||||||
|
var file = File.new()
|
||||||
|
if file.open("res://.git/HEAD", File.READ) != OK:
|
||||||
|
return {"branch": "<unknown>", "commit": "???", "commit_short": "???"}
|
||||||
|
var text = file.get_line()
|
||||||
|
if !text.begins_with("ref:"):
|
||||||
|
return {"branch": "<detached>", "commit": text, "commit_short": text.left(7)}
|
||||||
|
var ref = text.right(5).get_file()
|
||||||
|
file.close()
|
||||||
|
if file.open("res://.git/refs/heads/%s" % ref, File.READ) != OK:
|
||||||
|
return {"branch": ref, "commit": "<unknown>", "commit_short": "<unknown>"}
|
||||||
|
var commitid = file.get_line()
|
||||||
|
return {"branch": ref, "commit": commitid, "commit_short": commitid.left(7)}
|
||||||
|
|
||||||
|
|
||||||
|
static func get_home_folder() -> String:
|
||||||
|
if OS.has_feature("X11"):
|
||||||
|
if OS.has_environment("HOME"):
|
||||||
|
return OS.get_environment("HOME")
|
||||||
|
elif OS.has_feature("Windows"):
|
||||||
|
if OS.has_environment("USERPROFILE"):
|
||||||
|
return OS.get_environment("USERPROFILE")
|
||||||
|
|
||||||
|
return "/"
|
||||||
|
|
||||||
|
#################
|
||||||
|
# private stuff #
|
||||||
|
#################
|
||||||
|
static func __get_filename_regex() -> RegEx:
|
||||||
|
var tree := GDBUtility.get_scene_tree()
|
||||||
|
if tree.has_meta(__META_FILENAME_REGEX):
|
||||||
|
return tree.get_meta(__META_FILENAME_REGEX) as RegEx
|
||||||
|
var filename_regex := RegEx.new()
|
||||||
|
filename_regex.compile("[^a-zA-Z0-9_\\-\\. ]")
|
||||||
|
tree.set_meta(__META_FILENAME_REGEX, filename_regex)
|
||||||
|
return filename_regex
|
||||||
|
|
||||||
|
static func __find_all_by_name(path, name, result, files_only):
|
||||||
|
var dir = Directory.new()
|
||||||
|
if dir.open(path) != OK:
|
||||||
|
# print("cannot open dir %s" % path)
|
||||||
|
return
|
||||||
|
dir.list_dir_begin(true)
|
||||||
|
while true:
|
||||||
|
var fname = dir.get_next()
|
||||||
|
if fname == "":
|
||||||
|
break
|
||||||
|
var full_name = path + "/" + fname
|
||||||
|
if fname == name && (!files_only || dir.file_exists(full_name)):
|
||||||
|
result.append(full_name)
|
||||||
|
|
||||||
|
# dir_exists doesnt work for res:// paths, just attempt to add, will silently fail for files
|
||||||
|
if true: # dir.dir_exists(full_name):
|
||||||
|
__find_all_by_name(full_name, name, result, files_only)
|
||||||
|
|
||||||
|
dir.list_dir_end()
|
173
scripts/libs/geoutil.gd
Normal file
173
scripts/libs/geoutil.gd
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
###
|
||||||
|
### geometry utility code
|
||||||
|
### some of this might not work yet, please dont use it
|
||||||
|
###
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
class_name GDBGeoUtility
|
||||||
|
|
||||||
|
class Grid3D:
|
||||||
|
var size : Vector3
|
||||||
|
var data : Array
|
||||||
|
|
||||||
|
func _init(size_ : Vector3, init_val = null):
|
||||||
|
size = size_
|
||||||
|
data = []
|
||||||
|
data.resize(size.x * size.y * size.z)
|
||||||
|
for i in range(data.size()):
|
||||||
|
data[i] = init_val
|
||||||
|
|
||||||
|
func _idx(x, y, z):
|
||||||
|
return x + y * size.x + z * size.x * size.y
|
||||||
|
|
||||||
|
func get_at(x, y, z):
|
||||||
|
return data[_idx(x, y, z)]
|
||||||
|
|
||||||
|
func set_at(x, y, z, val):
|
||||||
|
data[_idx(x, y, z)] = val
|
||||||
|
|
||||||
|
################
|
||||||
|
# public stuff #
|
||||||
|
################
|
||||||
|
func _init() -> void:
|
||||||
|
assert(0, "This class should not be instantiated.")
|
||||||
|
|
||||||
|
static func full_aabb(root : Spatial):
|
||||||
|
var aabb = AABB()
|
||||||
|
for vi in GDBUtility.find_nodes_by_type(root, VisualInstance):
|
||||||
|
var local_aabb = vi.get_aabb()
|
||||||
|
local_aabb = vi.global_transform.xform(local_aabb)
|
||||||
|
aabb = aabb.merge(local_aabb)
|
||||||
|
return aabb
|
||||||
|
|
||||||
|
static func gen_aabb(points : Array, point_transform = Transform()) -> AABB:
|
||||||
|
var aabb = AABB()
|
||||||
|
for point in points:
|
||||||
|
aabb = aabb.expand(point_transform.xform(point))
|
||||||
|
return aabb
|
||||||
|
|
||||||
|
static func collsion_aabb(collision_object : CollisionObject) -> AABB:
|
||||||
|
var aabb := AABB()
|
||||||
|
for owner_id in collision_object.get_shape_owners():
|
||||||
|
var trans = collision_object.shape_owner_get_transform(owner_id)
|
||||||
|
for shape_id in range(collision_object.shape_owner_get_shape_count(owner_id)):
|
||||||
|
var shape = collision_object.shape_owner_get_shape(owner_id, shape_id)
|
||||||
|
var new_aabb = shape_aabb(shape)
|
||||||
|
if new_aabb.size:
|
||||||
|
aabb = aabb.merge(trans.xform(new_aabb))
|
||||||
|
return aabb
|
||||||
|
|
||||||
|
static func shape_aabb(shape : Shape) -> AABB:
|
||||||
|
if shape is BoxShape:
|
||||||
|
return AABB(-shape.extents, 2.0 * shape.extents)
|
||||||
|
elif shape is CapsuleShape:
|
||||||
|
return AABB(Vector3(-shape.radius, -shape.radius - 0.5 * shape.height, -shape.radius), \
|
||||||
|
Vector3(2.0 * shape.radius, 2.0 * shape.radius + shape.height, 2.0 * shape.radius))
|
||||||
|
elif shape is CylinderShape:
|
||||||
|
return AABB(Vector3(-shape.radius, -0.5 * shape.height, -shape.radius), \
|
||||||
|
Vector3(2.0 * shape.radius, shape.height, 2.0 * shape.radius))
|
||||||
|
elif shape is PlaneShape:
|
||||||
|
return AABB()
|
||||||
|
elif shape is SphereShape:
|
||||||
|
return AABB(-Vector3(shape.radius, shape.radius, shape.radius), 2.0 * Vector3(shape.radius, shape.radius, shape.radius))
|
||||||
|
else:
|
||||||
|
# TODO: polygon shapes
|
||||||
|
return AABB()
|
||||||
|
|
||||||
|
static func orphan_global_transform(node : Node):
|
||||||
|
var transform : Transform
|
||||||
|
if node is Spatial:
|
||||||
|
transform = node.transform
|
||||||
|
var parent = node.get_parent()
|
||||||
|
if parent != null:
|
||||||
|
transform = orphan_global_transform(parent) * transform
|
||||||
|
return transform
|
||||||
|
|
||||||
|
static func voxelize_surf(mesh : ArrayMesh, surf : int, particle_size = -1.0) -> PoolVector3Array:
|
||||||
|
var arrays = mesh.surface_get_arrays(surf)
|
||||||
|
var points = arrays[Mesh.ARRAY_VERTEX]
|
||||||
|
return PoolVector3Array(points)
|
||||||
|
|
||||||
|
static func voxelize(mesh : ArrayMesh, particle_size = -1.0) -> PoolVector3Array:
|
||||||
|
var points = PoolVector3Array()
|
||||||
|
for surf in range(mesh.get_surface_count()):
|
||||||
|
points.append_array(voxelize_surf(mesh, surf, particle_size))
|
||||||
|
return points
|
||||||
|
|
||||||
|
static func transform_to(node : Node, root : Node):
|
||||||
|
var transform = Transform()
|
||||||
|
var node_ = node
|
||||||
|
while node_ != root && node_ != null:
|
||||||
|
if node_ is Spatial:
|
||||||
|
transform = transform * node_.transform
|
||||||
|
node_ = node_.get_parent()
|
||||||
|
return transform
|
||||||
|
|
||||||
|
static func mesh_to_grid(mesh : ArrayMesh, grid_size : float, point_transform = Transform()):
|
||||||
|
var aabb = AABB()
|
||||||
|
for i in range(mesh.get_surface_count()):
|
||||||
|
var points = mesh.surface_get_arrays(i)[Mesh.ARRAY_VERTEX]
|
||||||
|
aabb = aabb.merge(gen_aabb(points, point_transform))
|
||||||
|
var grid_x = ceil(aabb.size.x / grid_size)
|
||||||
|
var grid_y = ceil(aabb.size.y / grid_size)
|
||||||
|
var grid_z = ceil(aabb.size.z / grid_size)
|
||||||
|
var grid = Grid3D.new(Vector3(grid_x, grid_y, grid_z), 0)
|
||||||
|
var grid_origin = aabb.position
|
||||||
|
|
||||||
|
for i in range(mesh.get_surface_count()):
|
||||||
|
__insert_surf(mesh, i, grid_origin, grid_size, grid, point_transform)
|
||||||
|
|
||||||
|
var result = []
|
||||||
|
for x in range(grid.size.x):
|
||||||
|
for y in range(grid.size.y):
|
||||||
|
var inside = false
|
||||||
|
for z in range(grid.size.z):
|
||||||
|
var cell_value = grid.get_at(x, y, z) % 2
|
||||||
|
if cell_value == 1:
|
||||||
|
inside = !inside
|
||||||
|
if inside || cell_value > 0:
|
||||||
|
result.append(Vector3(x, y, z))
|
||||||
|
return result
|
||||||
|
|
||||||
|
# http://www.boris-belousov.net/2016/12/01/quat-dist/
|
||||||
|
static func basis_angle(basis0 : Basis, basis1 : Basis):
|
||||||
|
var basis_diff = basis0 * basis1.transposed()
|
||||||
|
var tr : float = basis_diff.x.x + basis_diff.y.y + basis_diff.z.z
|
||||||
|
return acos((tr - 1) / 2)
|
||||||
|
|
||||||
|
static func angle_normalize(angle : float) -> float:
|
||||||
|
while angle < -PI:
|
||||||
|
angle += 2.0 * PI
|
||||||
|
while angle > PI:
|
||||||
|
angle -= 2.0 * PI
|
||||||
|
return angle
|
||||||
|
|
||||||
|
static func angle_diff(angle0 : float, angle1 : float) -> float:
|
||||||
|
return angle_normalize(angle0 - angle1)
|
||||||
|
|
||||||
|
#################
|
||||||
|
# private stuff #
|
||||||
|
#################
|
||||||
|
static func __insert_tri(a : Vector3, b : Vector3, c : Vector3, grid_origin : Vector3, grid_size : float, grid : Grid3D, point_transform : Transform):
|
||||||
|
var ray_dir = Vector3(0, 0, 1)
|
||||||
|
for x in range(grid.size.x):
|
||||||
|
for y in range(grid.size.y):
|
||||||
|
var ray_origin = grid_origin + grid_size * Vector3(x, y, 0)
|
||||||
|
var inters = Geometry.ray_intersects_triangle(ray_origin, ray_dir, point_transform.xform(a), point_transform.xform(b), point_transform.xform(c))
|
||||||
|
if inters != null:
|
||||||
|
var z = floor(0.99 * (inters.z - grid_origin.z) / grid_size)
|
||||||
|
grid.set_at(x, y, z, grid.get_at(x, y, z) + 1)
|
||||||
|
|
||||||
|
static func __insert_surf(mesh : ArrayMesh, surf : int, grid_origin : Vector3, grid_size : float, grid : Grid3D, point_transform : Transform):
|
||||||
|
var arrays = mesh.surface_get_arrays(surf)
|
||||||
|
if arrays.size() >= Mesh.ARRAY_INDEX - 1 && arrays[Mesh.ARRAY_INDEX].size() > 0:
|
||||||
|
# index mode
|
||||||
|
var indices = arrays[Mesh.ARRAY_INDEX]
|
||||||
|
var vertices = arrays[Mesh.ARRAY_VERTEX]
|
||||||
|
for i in range(0, indices.size() - 2, 3):
|
||||||
|
__insert_tri(vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]], grid_origin, grid_size, grid, point_transform)
|
||||||
|
else:
|
||||||
|
# normal mode
|
||||||
|
var vertices = arrays[Mesh.ARRAY_VERTEX]
|
||||||
|
for i in range(0, vertices.size() - 2, 3):
|
||||||
|
__insert_tri(vertices[i], vertices[i + 1], vertices[i + 2], grid_origin, grid_size, grid, point_transform)
|
306
scripts/libs/graphing.gd
Normal file
306
scripts/libs/graphing.gd
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
###
|
||||||
|
### graph class + algorithms
|
||||||
|
###
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
class Edge:
|
||||||
|
var idx0 : int
|
||||||
|
var idx1 : int
|
||||||
|
var weight : float
|
||||||
|
|
||||||
|
func _init(idx0_ : int, idx1_ : int, weight_ : float):
|
||||||
|
idx0 = idx0_
|
||||||
|
idx1 = idx1_
|
||||||
|
weight = weight_
|
||||||
|
|
||||||
|
class Graph:
|
||||||
|
var vertices = []
|
||||||
|
var edges = []
|
||||||
|
|
||||||
|
func append_vertex(vertex):
|
||||||
|
var idx = vertices.size()
|
||||||
|
vertices.append(vertex)
|
||||||
|
return idx
|
||||||
|
|
||||||
|
func append_vertices(vertices_ : Array):
|
||||||
|
for vertex in vertices_:
|
||||||
|
vertices.append(vertex)
|
||||||
|
|
||||||
|
func append_edge(idx0, idx1, weight := 1.0):
|
||||||
|
for edge in edges:
|
||||||
|
if (edge.idx0 == idx0 && edge.idx1 == idx1) || \
|
||||||
|
(edge.idx0 == idx1 && edge.idx1 == idx0):
|
||||||
|
return
|
||||||
|
edges.append(Edge.new(idx0, idx1, weight))
|
||||||
|
|
||||||
|
func remove_vertex(vertex):
|
||||||
|
var idx = vertices.find(vertex)
|
||||||
|
if idx > -1:
|
||||||
|
remove_vertex_at(idx)
|
||||||
|
|
||||||
|
func remove_vertex_at(idx):
|
||||||
|
vertices.remove(idx)
|
||||||
|
# fix edges
|
||||||
|
var to_remove = []
|
||||||
|
for edge in edges:
|
||||||
|
if edge.idx0 == idx || edge.idx1 == idx:
|
||||||
|
to_remove.append(edge)
|
||||||
|
else:
|
||||||
|
if edge.idx0 > idx:
|
||||||
|
edge.idx0 -= 1
|
||||||
|
if edge.idx1 > idx:
|
||||||
|
edge.idx1 -= 1
|
||||||
|
for edge in to_remove:
|
||||||
|
edges.erase(edge)
|
||||||
|
|
||||||
|
func remove_edge(idx0, idx1):
|
||||||
|
var to_remove = []
|
||||||
|
for edge in edges:
|
||||||
|
if (edge.idx0 == idx0 && edge.idx1 == idx1) || \
|
||||||
|
(edge.idx0 == idx1 && edge.idx1 == idx0):
|
||||||
|
to_remove.append(edge)
|
||||||
|
for edge in to_remove:
|
||||||
|
edges.erase(edge)
|
||||||
|
|
||||||
|
func duplicate(deep = false):
|
||||||
|
var dupl = Graph.new()
|
||||||
|
dupl.vertices = vertices.duplicate(deep)
|
||||||
|
for edge in edges:
|
||||||
|
dupl.append_edge(edge.idx0, edge.idx1, edge.weight)
|
||||||
|
return dupl
|
||||||
|
|
||||||
|
################
|
||||||
|
# public stuff #
|
||||||
|
################
|
||||||
|
func _init() -> void:
|
||||||
|
assert(0, "This class should not be instantiated.")
|
||||||
|
|
||||||
|
# generates a delaunay triangulation for the given point list
|
||||||
|
func gen_delaunay(point_list : Array) -> Graph:
|
||||||
|
assert(point_list.size() > 2)
|
||||||
|
var graph = Graph.new()
|
||||||
|
var tris = []
|
||||||
|
|
||||||
|
graph.append_vertices(point_list)
|
||||||
|
__gen_super_triangle(graph, point_list)
|
||||||
|
tris.append(Vector3(graph.vertices.size() - 3, graph.vertices.size() - 2, graph.vertices.size() - 1))
|
||||||
|
|
||||||
|
for idx in range(point_list.size()):
|
||||||
|
var point = point_list[idx]
|
||||||
|
var bad_triangles = []
|
||||||
|
for tri in tris:
|
||||||
|
var p0 = graph.vertices[tri.x]
|
||||||
|
var p1 = graph.vertices[tri.y]
|
||||||
|
var p2 = graph.vertices[tri.z]
|
||||||
|
|
||||||
|
if __point_in_circumcircle(point, p0, p1, p2):
|
||||||
|
bad_triangles.append(tri)
|
||||||
|
var polygon := []
|
||||||
|
for bad_tri in bad_triangles:
|
||||||
|
if !__edge_shared(bad_tri.x, bad_tri.y, bad_triangles):
|
||||||
|
polygon.append(Vector2(bad_tri.x, bad_tri.y))
|
||||||
|
if !__edge_shared(bad_tri.y, bad_tri.z, bad_triangles):
|
||||||
|
polygon.append(Vector2(bad_tri.y, bad_tri.z))
|
||||||
|
if !__edge_shared(bad_tri.z, bad_tri.x, bad_triangles):
|
||||||
|
polygon.append(Vector2(bad_tri.z, bad_tri.x))
|
||||||
|
for bad_tri in bad_triangles:
|
||||||
|
tris.erase(bad_tri)
|
||||||
|
graph.remove_edge(bad_tri.x, bad_tri.y)
|
||||||
|
graph.remove_edge(bad_tri.y, bad_tri.z)
|
||||||
|
graph.remove_edge(bad_tri.z, bad_tri.x)
|
||||||
|
|
||||||
|
for edge in polygon:
|
||||||
|
var tri = Vector3(edge.x, edge.y, idx)
|
||||||
|
var p0 = graph.vertices[tri.x]
|
||||||
|
var p1 = graph.vertices[tri.y]
|
||||||
|
var p2 = graph.vertices[tri.z]
|
||||||
|
|
||||||
|
tris.append(tri)
|
||||||
|
graph.append_edge(tri.x, tri.y, p0.distance_to(p1))
|
||||||
|
graph.append_edge(tri.y, tri.z, p1.distance_to(p2))
|
||||||
|
graph.append_edge(tri.z, tri.x, p2.distance_to(p0))
|
||||||
|
|
||||||
|
# remove super triangle
|
||||||
|
for idx in range(graph.vertices.size() - 1, graph.vertices.size() - 4, -1):
|
||||||
|
graph.remove_vertex_at(idx)
|
||||||
|
|
||||||
|
return graph
|
||||||
|
|
||||||
|
# generate the minimum spanning tree
|
||||||
|
func gen_mst(graph : Graph) -> Graph:
|
||||||
|
var mst = graph.duplicate()
|
||||||
|
var possible_edges = mst.edges
|
||||||
|
var forests = []
|
||||||
|
mst.edges = []
|
||||||
|
|
||||||
|
possible_edges.sort_custom(self, "__edge_weight_comp")
|
||||||
|
for idx in range(mst.vertices.size()):
|
||||||
|
forests.append([idx])
|
||||||
|
|
||||||
|
while !possible_edges.empty():
|
||||||
|
var edge = possible_edges.pop_front()
|
||||||
|
# already connected?
|
||||||
|
if forests[edge.idx0] == forests[edge.idx1]:
|
||||||
|
continue
|
||||||
|
# append edge
|
||||||
|
mst.append_edge(edge.idx0, edge.idx1, edge.weight)
|
||||||
|
# merge forests
|
||||||
|
for idx in forests[edge.idx1]:
|
||||||
|
forests[edge.idx0].append(idx)
|
||||||
|
forests[idx] = forests[edge.idx0]
|
||||||
|
|
||||||
|
return mst
|
||||||
|
|
||||||
|
# finds the shortest path through the given graph
|
||||||
|
# returns a dictionary with a "distance" and a "path"
|
||||||
|
func find_path(graph : Graph, start_idx : int, end_idx : int) -> Dictionary:
|
||||||
|
var dist := []
|
||||||
|
var prev := []
|
||||||
|
var queue := []
|
||||||
|
for idx in range(graph.vertices.size()):
|
||||||
|
dist.append(INF)
|
||||||
|
prev.append(-1)
|
||||||
|
queue.append(idx)
|
||||||
|
dist[start_idx] = 0.0
|
||||||
|
|
||||||
|
while !queue.empty():
|
||||||
|
var shortest_dist = INF
|
||||||
|
var shortest_idx : int
|
||||||
|
for idx in queue:
|
||||||
|
if dist[idx] < shortest_dist:
|
||||||
|
shortest_dist = dist[idx]
|
||||||
|
shortest_idx = idx
|
||||||
|
if shortest_idx == end_idx: # found it
|
||||||
|
if shortest_dist == INF:
|
||||||
|
return {
|
||||||
|
"distance": INF,
|
||||||
|
"path": []
|
||||||
|
}
|
||||||
|
var path = [end_idx]
|
||||||
|
var idx = end_idx
|
||||||
|
while prev[idx] != -1:
|
||||||
|
path.append(prev[idx])
|
||||||
|
idx = prev[idx]
|
||||||
|
assert(idx == start_idx)
|
||||||
|
return {
|
||||||
|
"distance": dist[end_idx],
|
||||||
|
"path": path
|
||||||
|
}
|
||||||
|
queue.erase(shortest_idx)
|
||||||
|
for edge in graph.edges:
|
||||||
|
var target_idx
|
||||||
|
if edge.idx0 == shortest_idx:
|
||||||
|
target_idx = edge.idx1
|
||||||
|
elif edge.idx1 == shortest_idx:
|
||||||
|
target_idx = edge.idx0
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
var new_dist = shortest_dist + edge.weight
|
||||||
|
if new_dist < dist[target_idx]:
|
||||||
|
dist[target_idx] = new_dist
|
||||||
|
prev[target_idx] = shortest_idx
|
||||||
|
return {
|
||||||
|
"distance": INF,
|
||||||
|
"path": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# draws a graph onto a control
|
||||||
|
func dump_graph_2d(graph : Graph, control : Control) -> void:
|
||||||
|
var normalized_graph = graph.duplicate()
|
||||||
|
var min_xy = Vector2.INF
|
||||||
|
var max_xy = -Vector2.INF
|
||||||
|
|
||||||
|
# normalize graph
|
||||||
|
for vertex in normalized_graph.vertices:
|
||||||
|
if vertex.x < min_xy.x:
|
||||||
|
min_xy.x = vertex.x
|
||||||
|
if vertex.x > max_xy.x:
|
||||||
|
max_xy.x = vertex.x
|
||||||
|
if vertex.y < min_xy.y:
|
||||||
|
min_xy.y = vertex.y
|
||||||
|
if vertex.y > max_xy.y:
|
||||||
|
max_xy.y = vertex.y
|
||||||
|
|
||||||
|
for idx in range(normalized_graph.vertices.size()):
|
||||||
|
var normalized_vertex = normalized_graph.vertices[idx]
|
||||||
|
normalized_vertex.x = inverse_lerp(min_xy.x, max_xy.x, normalized_vertex.x)
|
||||||
|
normalized_vertex.y = inverse_lerp(min_xy.y, max_xy.y, normalized_vertex.y)
|
||||||
|
normalized_graph.vertices[idx] = normalized_vertex
|
||||||
|
|
||||||
|
var img_size = control.rect_size
|
||||||
|
|
||||||
|
for edge in normalized_graph.edges:
|
||||||
|
var v0 = normalized_graph.vertices[edge.idx0]
|
||||||
|
var v1 = normalized_graph.vertices[edge.idx1]
|
||||||
|
var coord0 = __img_vertex_coord(v0, img_size)
|
||||||
|
var coord1 = __img_vertex_coord(v1, img_size)
|
||||||
|
|
||||||
|
control.draw_line(coord0, coord1, Color.yellow, 2.0, true)
|
||||||
|
|
||||||
|
for vertex in normalized_graph.vertices:
|
||||||
|
var coord = __img_vertex_coord(vertex, img_size)
|
||||||
|
control.draw_circle(coord, 5.0, Color.red)
|
||||||
|
|
||||||
|
#################
|
||||||
|
# private stuff #
|
||||||
|
#################
|
||||||
|
func __gen_super_triangle(graph : Graph, point_list : Array) -> void:
|
||||||
|
var min_xy = Vector2.INF
|
||||||
|
var max_xy = -Vector2.INF
|
||||||
|
|
||||||
|
for point in point_list:
|
||||||
|
if point.x < min_xy.x:
|
||||||
|
min_xy.x = point.x
|
||||||
|
if point.y < min_xy.y:
|
||||||
|
min_xy.y = point.y
|
||||||
|
if point.x > max_xy.x:
|
||||||
|
max_xy.x = point.x
|
||||||
|
if point.y > max_xy.y:
|
||||||
|
max_xy.y = point.y
|
||||||
|
|
||||||
|
min_xy -= Vector2(1.0, 1.0)
|
||||||
|
|
||||||
|
var p1 = Vector2(2.0 * max_xy.x, min_xy.y)
|
||||||
|
var p2 = Vector2(min_xy.x, 2.0 * max_xy.y)
|
||||||
|
|
||||||
|
var idx0 = graph.append_vertex(min_xy)
|
||||||
|
var idx1 = graph.append_vertex(p1)
|
||||||
|
var idx2 = graph.append_vertex(p2)
|
||||||
|
graph.append_edge(idx0, idx1, min_xy.distance_to(p1))
|
||||||
|
graph.append_edge(idx1, idx2, p1.distance_to(p2))
|
||||||
|
graph.append_edge(idx2, idx0, p2.distance_to(min_xy))
|
||||||
|
|
||||||
|
func __edge_shared(p0 : int, p1 : int, tris : Array) -> bool:
|
||||||
|
var edge_count = 0
|
||||||
|
for tri in tris:
|
||||||
|
if (p0 == tri.x || p0 == tri.y || p0 == tri.z) && \
|
||||||
|
(p1 == tri.x || p1 == tri.y || p1 == tri.z):
|
||||||
|
edge_count += 1
|
||||||
|
return edge_count > 1
|
||||||
|
|
||||||
|
func __point_in_circumcircle(point : Vector2, p0 : Vector2, p1 : Vector2, p2 : Vector2) -> bool:
|
||||||
|
var p0x_ = p0.x - point.x
|
||||||
|
var p0y_ = p0.y - point.y
|
||||||
|
var p1x_ = p1.x - point.x
|
||||||
|
var p1y_ = p1.y - point.y
|
||||||
|
var p2x_ = p2.x - point.x
|
||||||
|
var p2y_ = p2.y - point.y
|
||||||
|
return (
|
||||||
|
(p0x_*p0x_ + p0y_*p0y_) * (p1x_*p2y_-p2x_*p1y_) - \
|
||||||
|
(p1x_*p1x_ + p1y_*p1y_) * (p0x_*p2y_-p2x_*p0y_) + \
|
||||||
|
(p2x_*p2x_ + p2y_*p2y_) * (p0x_*p1y_-p1x_*p0y_) \
|
||||||
|
) > 0;
|
||||||
|
|
||||||
|
func __check_poly(polygon) -> void:
|
||||||
|
for edge0 in polygon:
|
||||||
|
for edge1 in polygon:
|
||||||
|
if edge0 == edge1:
|
||||||
|
continue
|
||||||
|
assert(edge0.x != edge1.x || edge0.y != edge1.y)
|
||||||
|
assert(edge0.x != edge1.y || edge0.y != edge1.x)
|
||||||
|
|
||||||
|
func __edge_weight_comp(e0 : Edge, e1 : Edge):
|
||||||
|
return e0.weight < e1.weight
|
||||||
|
|
||||||
|
func __img_vertex_coord(vertex : Vector2, img_size : Vector2):
|
||||||
|
return Vector2(5.0, 5.0) + vertex * (img_size - Vector2(10.0, 10.0))
|
74
scripts/libs/math.gd
Normal file
74
scripts/libs/math.gd
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
###
|
||||||
|
### math utility
|
||||||
|
###
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
class_name GDBMath
|
||||||
|
|
||||||
|
################
|
||||||
|
# public stuff #
|
||||||
|
################
|
||||||
|
func _init() -> void:
|
||||||
|
assert(0, "This class should not be instantiated.")
|
||||||
|
|
||||||
|
static func angle(vec0 : Vector3, vec1 : Vector3, axis : Vector3) -> float:
|
||||||
|
var res = vec0.angle_to(vec1)
|
||||||
|
var crs = axis.cross(vec0)
|
||||||
|
var dir = vec1 - vec0
|
||||||
|
|
||||||
|
if dir.dot(crs) < 0:
|
||||||
|
return -res
|
||||||
|
else:
|
||||||
|
return res
|
||||||
|
|
||||||
|
static func find_perp(a : Vector3, b : Vector3, d : Vector3) -> Vector3:
|
||||||
|
var t0 = d.dot(a)
|
||||||
|
var t1 = d.dot(b)
|
||||||
|
|
||||||
|
if t0 - t1 < 0.01:
|
||||||
|
return a
|
||||||
|
|
||||||
|
var t = -t1 / (t0 - t1)
|
||||||
|
# assert(t >= 0 && t <= 1)
|
||||||
|
var n = b.linear_interpolate(a, t).normalized()
|
||||||
|
var an = n.angle_to(d)
|
||||||
|
assert(abs(n.dot(d)) < 0.01)
|
||||||
|
assert(abs(an - 0.5 * PI) < 0.01)
|
||||||
|
return n
|
||||||
|
|
||||||
|
static func rects_intersect(pos0 : Vector2, size0 : Vector2, pos1 : Vector2, size1 : Vector2) -> bool:
|
||||||
|
if pos0.x + size0.x < pos1.x || pos1.x + size1.x < pos0.x:
|
||||||
|
return false
|
||||||
|
if pos0.y + size0.y < pos1.y || pos1.y + size1.y < pos1.y:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
static func min_element(array : Array) -> int:
|
||||||
|
var min_idx := -1
|
||||||
|
for idx in range(array.size()):
|
||||||
|
if min_idx == -1 || array[min_idx] > array[idx]:
|
||||||
|
min_idx = idx
|
||||||
|
return min_idx
|
||||||
|
|
||||||
|
static func max_element(array : Array) -> int:
|
||||||
|
var min_idx := -1
|
||||||
|
for idx in range(array.size()):
|
||||||
|
if min_idx == -1 || array[min_idx] < array[idx]:
|
||||||
|
min_idx = idx
|
||||||
|
return min_idx
|
||||||
|
|
||||||
|
static func to_int(any, default := 0) -> int:
|
||||||
|
if any is int:
|
||||||
|
return any
|
||||||
|
elif any is bool || any is float || any is String:
|
||||||
|
return int(any)
|
||||||
|
else:
|
||||||
|
return default
|
||||||
|
|
||||||
|
static func to_float(any, default := 0.0) -> float:
|
||||||
|
if any is float:
|
||||||
|
return any
|
||||||
|
elif any is bool || any is int || any is String:
|
||||||
|
return float(any)
|
||||||
|
else:
|
||||||
|
return default
|
144
scripts/libs/settings.gd
Normal file
144
scripts/libs/settings.gd
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
###
|
||||||
|
### settings management API
|
||||||
|
###
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
class_name GDBSettings
|
||||||
|
|
||||||
|
const __META_STATE = "__gdb_settings_state__"
|
||||||
|
|
||||||
|
class __State:
|
||||||
|
# modification of the libraries behaviour
|
||||||
|
var settings_file_name := "user://settings.dat"
|
||||||
|
var disable_default_settings := false
|
||||||
|
var settings_changed := false
|
||||||
|
var values := {}
|
||||||
|
|
||||||
|
signal setting_changed(name, value)
|
||||||
|
|
||||||
|
################
|
||||||
|
# public stuff #
|
||||||
|
################
|
||||||
|
func _init() -> void:
|
||||||
|
assert(0, "This class should not be instantiated.")
|
||||||
|
|
||||||
|
static func has_value(name : String) -> bool:
|
||||||
|
return __get_state().values.has(name)
|
||||||
|
|
||||||
|
static func get_value(name : String, def = null, type = TYPE_NIL):
|
||||||
|
if type == TYPE_NIL && def != null:
|
||||||
|
type = typeof(def)
|
||||||
|
|
||||||
|
var val = __get_state().values.get(name, def)
|
||||||
|
if type is Object:
|
||||||
|
if !val is type:
|
||||||
|
return def
|
||||||
|
else:
|
||||||
|
match type:
|
||||||
|
TYPE_NIL:
|
||||||
|
return val
|
||||||
|
TYPE_STRING:
|
||||||
|
val = str(val)
|
||||||
|
TYPE_INT:
|
||||||
|
val = int(val)
|
||||||
|
TYPE_REAL:
|
||||||
|
val = float(val)
|
||||||
|
TYPE_BOOL:
|
||||||
|
val = bool(val)
|
||||||
|
_:
|
||||||
|
if typeof(val) != type:
|
||||||
|
return def
|
||||||
|
if val is Array || val is Dictionary:
|
||||||
|
val = val.duplicate()
|
||||||
|
return val
|
||||||
|
|
||||||
|
static func set_value(name : String, value) -> void:
|
||||||
|
if __get_state().values.get(name) != value:
|
||||||
|
__set_value(name, value)
|
||||||
|
|
||||||
|
# save next frame (in case multiple values are changed)
|
||||||
|
__get_state().settings_changed = true
|
||||||
|
yield(GDBUtility.get_scene_tree(), "idle_frame")
|
||||||
|
if __get_state().settings_changed:
|
||||||
|
store_to_file()
|
||||||
|
__get_state().settings_changed = false
|
||||||
|
|
||||||
|
static func toggle_value(name : String) -> void:
|
||||||
|
set_value(name, !__get_state().values.get(name, false))
|
||||||
|
|
||||||
|
static func array_add_value(name : String, value) -> void:
|
||||||
|
var arr = get_value(name, [])
|
||||||
|
arr.append(value)
|
||||||
|
set_value(name, arr)
|
||||||
|
|
||||||
|
static func array_remove_value(name : String, value) -> void:
|
||||||
|
var arr = get_value(name, [])
|
||||||
|
arr.erase(value)
|
||||||
|
set_value(name, arr)
|
||||||
|
|
||||||
|
static func array_has_value(name : String, value) -> bool:
|
||||||
|
var arr = get_value(name, [])
|
||||||
|
return arr.has(value)
|
||||||
|
|
||||||
|
static func array_toggle_value(name : String, value) -> void:
|
||||||
|
var arr = get_value(name, [])
|
||||||
|
if arr.has(value):
|
||||||
|
arr.erase(value)
|
||||||
|
else:
|
||||||
|
arr.append(value)
|
||||||
|
set_value(name, arr)
|
||||||
|
|
||||||
|
static func load_from_file() -> void:
|
||||||
|
var in_file = File.new()
|
||||||
|
|
||||||
|
if not in_file.file_exists(__get_state().settings_file_name):
|
||||||
|
return # nothing to load
|
||||||
|
|
||||||
|
in_file.open(__get_state().settings_file_name, File.READ)
|
||||||
|
__load_data(parse_json(in_file.get_as_text()))
|
||||||
|
|
||||||
|
static func store_to_file() -> void:
|
||||||
|
var out_file = File.new()
|
||||||
|
out_file.open(__get_state().settings_file_name, File.WRITE)
|
||||||
|
out_file.store_line(__save_data())
|
||||||
|
out_file.close()
|
||||||
|
|
||||||
|
static func set_disable_default_settings(disable : bool) -> void:
|
||||||
|
__get_state().disable_default_settings = disable
|
||||||
|
|
||||||
|
static func is_disable_default_settings() -> bool:
|
||||||
|
return __get_state().disable_default_settings
|
||||||
|
|
||||||
|
static func connect_static(sig_name : String, receiver : Object, method : String, binds := []) -> int:
|
||||||
|
return __get_state().connect(sig_name, receiver, method, binds)
|
||||||
|
|
||||||
|
static func disconnect_static(sig_name : String, receiver : Object, method : String) -> void:
|
||||||
|
__get_state().disconnect(sig_name, receiver, method)
|
||||||
|
|
||||||
|
#################
|
||||||
|
# private stuff #
|
||||||
|
#################
|
||||||
|
static func __save_data():
|
||||||
|
return to_json(__get_state().values)
|
||||||
|
|
||||||
|
static func __load_data(data : Dictionary):
|
||||||
|
for name in data:
|
||||||
|
__set_value(name, data[name])
|
||||||
|
|
||||||
|
static func __set_value(name : String, value):
|
||||||
|
__get_state().values[name] = value
|
||||||
|
__on_setting_changed(name, value)
|
||||||
|
__get_state().emit_signal("setting_changed", name, value)
|
||||||
|
|
||||||
|
static func __on_setting_changed(name : String, value):
|
||||||
|
if __get_state().disable_default_settings:
|
||||||
|
return
|
||||||
|
|
||||||
|
match name:
|
||||||
|
"fullscreen":
|
||||||
|
OS.window_fullscreen = value
|
||||||
|
"vsync":
|
||||||
|
OS.vsync_enabled = value
|
||||||
|
|
||||||
|
static func __get_state() -> __State:
|
||||||
|
return GDBUtility.get_state_object(__META_STATE, __State)
|
385
scripts/libs/util.gd
Normal file
385
scripts/libs/util.gd
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
###
|
||||||
|
### a set of random utility functions
|
||||||
|
###
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
class_name GDBUtility
|
||||||
|
|
||||||
|
################
|
||||||
|
# public stuff #
|
||||||
|
################
|
||||||
|
func _init() -> void:
|
||||||
|
assert(0, "This class should not be instantiated.")
|
||||||
|
|
||||||
|
static func find_node_filter(root : Node, object : Object, method : String, bfs := false) -> Node:
|
||||||
|
if object.call(method, root):
|
||||||
|
return root
|
||||||
|
|
||||||
|
if bfs:
|
||||||
|
for child in root.get_children():
|
||||||
|
if object.call(method, child):
|
||||||
|
return child
|
||||||
|
|
||||||
|
for child in root.get_children():
|
||||||
|
var res = find_node_filter(child, object, method)
|
||||||
|
if res != null:
|
||||||
|
return res
|
||||||
|
return null
|
||||||
|
|
||||||
|
static func find_node_by_type(root : Node, tp, bfs := false) -> Node:
|
||||||
|
if root is tp:
|
||||||
|
return root
|
||||||
|
|
||||||
|
if bfs:
|
||||||
|
for child in root.get_children():
|
||||||
|
if child is tp:
|
||||||
|
return child
|
||||||
|
|
||||||
|
for child in root.get_children():
|
||||||
|
var res = find_node_by_type(child, tp, bfs)
|
||||||
|
if res != null:
|
||||||
|
return res
|
||||||
|
return null
|
||||||
|
|
||||||
|
static func find_node_by_name(root : Node, name : String, bfs := false, use_cache := true) -> Node:
|
||||||
|
if root.name == name:
|
||||||
|
return root
|
||||||
|
|
||||||
|
# create the cache regardless of use_cache
|
||||||
|
if !root.has_meta("__find_by_name_node_cache"):
|
||||||
|
root.set_meta("__find_by_name_node_cache", {})
|
||||||
|
|
||||||
|
var cache = root.get_meta("__find_by_name_node_cache")
|
||||||
|
if use_cache && name in cache:
|
||||||
|
var node = root.get_node(cache[name])
|
||||||
|
if node:
|
||||||
|
return node
|
||||||
|
else:
|
||||||
|
cache.erase(name)
|
||||||
|
|
||||||
|
if bfs:
|
||||||
|
for child in root.get_children():
|
||||||
|
if child.name == name:
|
||||||
|
cache[name] = child
|
||||||
|
return child
|
||||||
|
|
||||||
|
for child in root.get_children():
|
||||||
|
var res = find_node_by_name(child, name)
|
||||||
|
if res != null:
|
||||||
|
cache[name] = root.get_path_to(res)
|
||||||
|
return res
|
||||||
|
return null
|
||||||
|
|
||||||
|
static func find_nodes_by_type(root : Node, tp, as_path = false) -> Array:
|
||||||
|
var result = []
|
||||||
|
__find_nodes_by_type(root, tp, as_path, result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
static func find_nodes_with_member(root : Node, member : String) -> Array:
|
||||||
|
var result = []
|
||||||
|
__find_nodes_with_member(root, member, result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
static func find_parent_with_method(root : Node, method : String) -> Node:
|
||||||
|
if root.has_method(method):
|
||||||
|
return root
|
||||||
|
else:
|
||||||
|
var parent = root.get_parent()
|
||||||
|
if parent == null:
|
||||||
|
return null
|
||||||
|
return find_parent_with_method(parent, method)
|
||||||
|
|
||||||
|
static func find_parent_with_member(root : Node, member : String) -> Node:
|
||||||
|
if root.get(member) != null:
|
||||||
|
return root
|
||||||
|
else:
|
||||||
|
var parent = root.get_parent()
|
||||||
|
if parent == null:
|
||||||
|
return null
|
||||||
|
return find_parent_with_member(parent, member)
|
||||||
|
|
||||||
|
static func find_parent_with_name(root : Node, name : String) -> Node:
|
||||||
|
if root.name == name:
|
||||||
|
return root
|
||||||
|
else:
|
||||||
|
var parent = root.get_parent()
|
||||||
|
if parent == null:
|
||||||
|
return null
|
||||||
|
return find_parent_with_name(parent, name)
|
||||||
|
|
||||||
|
static func find_parent_with_type(root : Node, tp) -> Node:
|
||||||
|
if (tp is Script && tp.instance_has(root)) \
|
||||||
|
|| root is tp:
|
||||||
|
return root
|
||||||
|
else:
|
||||||
|
var parent = root.get_parent()
|
||||||
|
if parent == null:
|
||||||
|
return null
|
||||||
|
return find_parent_with_type(parent, tp)
|
||||||
|
|
||||||
|
static func find_treeitem_with_metadata(root : TreeItem, metadata, column := 0) -> TreeItem:
|
||||||
|
while root:
|
||||||
|
if root.get_metadata(column) == metadata:
|
||||||
|
return root
|
||||||
|
var in_children := find_treeitem_with_metadata(root.get_children(), metadata, column)
|
||||||
|
if in_children:
|
||||||
|
return in_children
|
||||||
|
root = root.get_next()
|
||||||
|
return null
|
||||||
|
|
||||||
|
static func hide_properties(properties : Array, names_to_hide : Array) -> void:
|
||||||
|
for i in range(properties.size() - 1, -1, -1):
|
||||||
|
if properties[i]["name"] in names_to_hide:
|
||||||
|
properties.remove(i)
|
||||||
|
|
||||||
|
static func type_name(type : int) -> String:
|
||||||
|
match type:
|
||||||
|
TYPE_NIL:
|
||||||
|
return "null"
|
||||||
|
TYPE_BOOL:
|
||||||
|
return "bool"
|
||||||
|
TYPE_INT:
|
||||||
|
return "int"
|
||||||
|
TYPE_REAL:
|
||||||
|
return "float"
|
||||||
|
TYPE_STRING:
|
||||||
|
return "String"
|
||||||
|
TYPE_VECTOR2:
|
||||||
|
return "Vector2"
|
||||||
|
TYPE_RECT2:
|
||||||
|
return "Rect2"
|
||||||
|
TYPE_VECTOR3:
|
||||||
|
return "Vector3"
|
||||||
|
TYPE_TRANSFORM2D:
|
||||||
|
return "Transform2D"
|
||||||
|
TYPE_PLANE:
|
||||||
|
return "Plane"
|
||||||
|
TYPE_QUAT:
|
||||||
|
return "Quat"
|
||||||
|
TYPE_AABB:
|
||||||
|
return "AABB"
|
||||||
|
TYPE_BASIS:
|
||||||
|
return "Basis"
|
||||||
|
TYPE_TRANSFORM:
|
||||||
|
return "Transform"
|
||||||
|
TYPE_COLOR:
|
||||||
|
return "Color"
|
||||||
|
TYPE_NODE_PATH:
|
||||||
|
return "NodePath"
|
||||||
|
TYPE_RID:
|
||||||
|
return "RID"
|
||||||
|
TYPE_OBJECT:
|
||||||
|
return "Object"
|
||||||
|
TYPE_DICTIONARY:
|
||||||
|
return "Dictionary"
|
||||||
|
TYPE_ARRAY:
|
||||||
|
return "Array"
|
||||||
|
TYPE_RAW_ARRAY:
|
||||||
|
return "PoolByteArray"
|
||||||
|
TYPE_INT_ARRAY:
|
||||||
|
return "PoolIntArray"
|
||||||
|
TYPE_REAL_ARRAY:
|
||||||
|
return "PoolRealArray"
|
||||||
|
TYPE_STRING_ARRAY:
|
||||||
|
return "PoolStringArray"
|
||||||
|
TYPE_VECTOR2_ARRAY:
|
||||||
|
return "Vector2Array"
|
||||||
|
TYPE_VECTOR3_ARRAY:
|
||||||
|
return "Vector3Array"
|
||||||
|
TYPE_COLOR_ARRAY:
|
||||||
|
return "ColorArray"
|
||||||
|
_:
|
||||||
|
return "Unknown"
|
||||||
|
|
||||||
|
static func format_type(type : Dictionary, none_name := "void") -> String:
|
||||||
|
match type["type"]:
|
||||||
|
TYPE_NIL:
|
||||||
|
return none_name
|
||||||
|
TYPE_OBJECT:
|
||||||
|
return type.get("class_name", "Object")
|
||||||
|
var tp:
|
||||||
|
return type_name(tp)
|
||||||
|
|
||||||
|
static func format_method_signature(method : Dictionary, format := "{return} {name}({args})") -> String:
|
||||||
|
var args := PoolStringArray()
|
||||||
|
var rettype := "void"
|
||||||
|
if method.has("return"):
|
||||||
|
rettype = format_type(method["return"])
|
||||||
|
for i in range(method["args"].size()):
|
||||||
|
var arg = method["args"][i]
|
||||||
|
var def = ""
|
||||||
|
if i < method["default_args"].size():
|
||||||
|
def = " = %s" % str(method["default_args"][i])
|
||||||
|
args.append("{name}: {type}{def}".format({
|
||||||
|
"name": arg["name"],
|
||||||
|
"type": format_type(arg, "Variant"),
|
||||||
|
"def": def
|
||||||
|
}))
|
||||||
|
return format.format({
|
||||||
|
"return": rettype,
|
||||||
|
"name": method["name"],
|
||||||
|
"args": args.join(", ")
|
||||||
|
})
|
||||||
|
|
||||||
|
static func format_signal_signature(sig : Dictionary) -> String:
|
||||||
|
return format_method_signature(sig, "{name}({args})")
|
||||||
|
|
||||||
|
static func get_type_property_list(type : Dictionary) -> Array:
|
||||||
|
match type["type"]:
|
||||||
|
TYPE_VECTOR2:
|
||||||
|
return [
|
||||||
|
{"name": "x", "type": TYPE_REAL},
|
||||||
|
{"name": "y", "type": TYPE_REAL}
|
||||||
|
]
|
||||||
|
TYPE_RECT2:
|
||||||
|
return [
|
||||||
|
{"name": "position", "type": TYPE_VECTOR2},
|
||||||
|
{"name": "size", "type": TYPE_VECTOR2},
|
||||||
|
{"name": "end", "type": TYPE_VECTOR2}
|
||||||
|
]
|
||||||
|
TYPE_VECTOR3:
|
||||||
|
return [
|
||||||
|
{"name": "x", "type": TYPE_REAL},
|
||||||
|
{"name": "y", "type": TYPE_REAL},
|
||||||
|
{"name": "z", "type": TYPE_REAL}
|
||||||
|
]
|
||||||
|
TYPE_TRANSFORM2D:
|
||||||
|
return [
|
||||||
|
{"name": "x", "type": TYPE_VECTOR2},
|
||||||
|
{"name": "y", "type": TYPE_VECTOR2},
|
||||||
|
{"name": "origin", "type": TYPE_VECTOR2}
|
||||||
|
]
|
||||||
|
TYPE_PLANE:
|
||||||
|
return [
|
||||||
|
{"name": "normal", "type": TYPE_VECTOR3},
|
||||||
|
{"name": "x", "type": TYPE_REAL},
|
||||||
|
{"name": "y", "type": TYPE_REAL},
|
||||||
|
{"name": "z", "type": TYPE_REAL},
|
||||||
|
{"name": "d", "type": TYPE_REAL}
|
||||||
|
]
|
||||||
|
TYPE_QUAT:
|
||||||
|
return [
|
||||||
|
{"name": "x", "type": TYPE_REAL},
|
||||||
|
{"name": "y", "type": TYPE_REAL},
|
||||||
|
{"name": "z", "type": TYPE_REAL},
|
||||||
|
{"name": "w", "type": TYPE_REAL}
|
||||||
|
]
|
||||||
|
TYPE_AABB:
|
||||||
|
return [
|
||||||
|
{"name": "position", "type": TYPE_VECTOR3},
|
||||||
|
{"name": "size", "type": TYPE_VECTOR3},
|
||||||
|
{"name": "end", "type": TYPE_VECTOR3}
|
||||||
|
]
|
||||||
|
TYPE_BASIS:
|
||||||
|
return [
|
||||||
|
{"name": "x", "type": TYPE_VECTOR3},
|
||||||
|
{"name": "y", "type": TYPE_VECTOR3},
|
||||||
|
{"name": "z", "type": TYPE_VECTOR3}
|
||||||
|
]
|
||||||
|
TYPE_TRANSFORM:
|
||||||
|
return [
|
||||||
|
{"name": "basis", "type": TYPE_BASIS},
|
||||||
|
{"name": "origin", "type": TYPE_VECTOR3}
|
||||||
|
]
|
||||||
|
TYPE_COLOR:
|
||||||
|
return [
|
||||||
|
{"name": "r", "type": TYPE_REAL},
|
||||||
|
{"name": "g", "type": TYPE_REAL},
|
||||||
|
{"name": "b", "type": TYPE_REAL},
|
||||||
|
{"name": "a", "type": TYPE_REAL},
|
||||||
|
{"name": "h", "type": TYPE_REAL},
|
||||||
|
{"name": "s", "type": TYPE_REAL},
|
||||||
|
{"name": "v", "type": TYPE_REAL},
|
||||||
|
{"name": "r8", "type": TYPE_INT},
|
||||||
|
{"name": "g8", "type": TYPE_INT},
|
||||||
|
{"name": "b8", "type": TYPE_INT},
|
||||||
|
{"name": "a8", "type": TYPE_INT}
|
||||||
|
]
|
||||||
|
TYPE_OBJECT:
|
||||||
|
if type.has("class_name"):
|
||||||
|
return ClassDB.class_get_property_list(type["class_name"])
|
||||||
|
return []
|
||||||
|
|
||||||
|
static func get_property_type(obj : Object, property : String) -> int:
|
||||||
|
return typeof(obj.get(property)) # TODO
|
||||||
|
|
||||||
|
static func get_method_arg_types(obj : Object, method : String) -> Array:
|
||||||
|
var methods := obj.get_method_list()
|
||||||
|
var types := []
|
||||||
|
for ele in methods:
|
||||||
|
if ele["name"] == method:
|
||||||
|
for arg in ele["args"]:
|
||||||
|
types.append(arg["type"])
|
||||||
|
break
|
||||||
|
return types
|
||||||
|
|
||||||
|
static func get_scene_tree() -> SceneTree:
|
||||||
|
return Engine.get_main_loop() as SceneTree
|
||||||
|
|
||||||
|
static func wait_frames(nframes : int) -> void:
|
||||||
|
var tree := get_scene_tree()
|
||||||
|
for i in range(max(1, nframes)): # yield at least once so this always returns a GDScriptFunctionState
|
||||||
|
yield(tree, "idle_frame")
|
||||||
|
|
||||||
|
static func disconnect_all(sender : Object, receiver : Object, signal_name := "") -> void:
|
||||||
|
if signal_name == "":
|
||||||
|
for sig in sender.get_signal_list():
|
||||||
|
disconnect_all(sender, receiver, sig["name"])
|
||||||
|
return
|
||||||
|
|
||||||
|
for connection in sender.get_signal_connection_list(signal_name):
|
||||||
|
if connection["target"] != receiver:
|
||||||
|
continue
|
||||||
|
sender.disconnect(signal_name, receiver, connection["method"])
|
||||||
|
|
||||||
|
static func copy_signal_handlers(target : Object, source : Object, sig_name : String) -> void:
|
||||||
|
for connection in source.get_signal_connection_list(sig_name):
|
||||||
|
target.connect(sig_name, connection["target"], connection["method"], connection["binds"])
|
||||||
|
|
||||||
|
static func copy_all_signal_handlers(target : Object, source : Object, sig_names : Array) -> void:
|
||||||
|
for sig_name in sig_names:
|
||||||
|
copy_signal_handlers(target, source, sig_name)
|
||||||
|
|
||||||
|
static func control_is_in_front_of(front : Control, back : Control) -> bool:
|
||||||
|
var front_popup : bool = find_parent_with_type(front, Popup) != null
|
||||||
|
var back_popup : bool = find_parent_with_type(back, Popup) != null
|
||||||
|
|
||||||
|
if front_popup && !back_popup:
|
||||||
|
return true
|
||||||
|
elif !front_popup && back_popup:
|
||||||
|
return false
|
||||||
|
elif front_popup && back_popup:
|
||||||
|
return popup_is_in_front_of(front, back)
|
||||||
|
else:
|
||||||
|
return back.is_greater_than(front)
|
||||||
|
|
||||||
|
static func popup_is_in_front_of(front : Popup, back : Popup) -> bool:
|
||||||
|
return back.is_greater_than(front) # no idea?!
|
||||||
|
|
||||||
|
static func get_state_object(meta_key : String, type):
|
||||||
|
var tree := get_scene_tree()
|
||||||
|
if !tree.has_meta(meta_key):
|
||||||
|
tree.set_meta(meta_key, type.new())
|
||||||
|
return tree.get_meta(meta_key)
|
||||||
|
|
||||||
|
static func translate(text : String) -> String:
|
||||||
|
return get_scene_tree().root.tr(text)
|
||||||
|
|
||||||
|
#################
|
||||||
|
# private stuff #
|
||||||
|
#################
|
||||||
|
static func __find_nodes_by_type(root : Node, tp, as_path, result):
|
||||||
|
if root is tp:
|
||||||
|
if as_path:
|
||||||
|
result.append(root.get_path())
|
||||||
|
else:
|
||||||
|
result.append(root)
|
||||||
|
for child in root.get_children():
|
||||||
|
__find_nodes_by_type(child, tp, as_path, result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
static func __find_nodes_with_member(root : Node, member : String, result):
|
||||||
|
if root.get(member) != null:
|
||||||
|
result.append(root)
|
||||||
|
for child in root.get_children():
|
||||||
|
__find_nodes_with_member(child, member, result)
|
Loading…
x
Reference in New Issue
Block a user