From 2656ab02e5c0733695428fe68fd1794ffbbf6094 Mon Sep 17 00:00:00 2001
From: NullBite <me@nullbite.com>
Date: Wed, 21 Jun 2023 16:08:18 -0400
Subject: [PATCH] Initial commit

---
 PartsManager.lua |  88 ++++++++++++++++++++
 logging.lua      |  34 ++++++++
 math.lua         |   9 +++
 util.lua         | 207 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 338 insertions(+)
 create mode 100644 PartsManager.lua
 create mode 100644 logging.lua
 create mode 100644 math.lua
 create mode 100644 util.lua

diff --git a/PartsManager.lua b/PartsManager.lua
new file mode 100644
index 0000000..6a3c647
--- /dev/null
+++ b/PartsManager.lua
@@ -0,0 +1,88 @@
+PartsManager={}
+local pm={}
+
+--- ensure part is initialized
+local function initPart(part)
+	local part_key=part
+	if pm[part_key] == nil then
+		pm[part_key]={}
+	end
+	pm[part_key].part=part
+	if pm[part_key].functions == nil then
+		pm[part_key].functions = {}
+	end
+	if pm[part_key].init==nil then
+		pm[part_key].init="true"
+	end
+end
+--- Add function to part in PartsManager.
+--- @param part table Any object with a setEnabled() method.
+--- @param func function Function to add to model part's function chain.
+--- @param init? boolean Default value for chain. Should only be set once, subsequent uses overwrite the entire chain's initial value.
+function PartsManager.addPartFunction(part, func, init)
+	initPart(part)
+	local part_key=part
+	if init ~= nil then
+		pm[part_key].init=init
+	end
+	table.insert(pm[part_key]["functions"], func)
+end
+
+--- Set initial value for chain.
+--- @param part table Any object with a setEnabled() method.
+--- @param init? boolean Default value for chain. Should only be set once, subsequent uses overwrite the entire chain's initial value.
+function PartsManager.setInitialValue(part, init)
+	assert(init~=nil)
+	initPart(part)
+	local part_key=part
+	pm[part_key].init=init
+end
+
+--- Set initial value for chain on all objects in table.
+--- @param group table A table containing objects with a setEnabled() method.
+--- @param init? boolean Default value for chain. Should only be set once, subsequent uses overwrite the entire chain's initial value.
+function PartsManager.setGroupInitialValue(group, init)
+	assert(init~=nil)
+	for _, v in pairs(group) do
+		PartsManager.setInitialValue(v, init)
+	end
+end
+
+--- Evaluate a part's chain to determine if it should be visible.
+--- @param part table An object managed by PartsManager.
+function PartsManager.evaluatePart(part)
+	local part_key=part
+	assert(pm[part_key] ~= nil)
+
+	local evalFunc=function(x, y) return y(x) end
+	local init=pm[part_key].init
+	return util.ireduce(evalFunc, pm[part_key].functions, true)
+end
+local evaluatePart=PartsManager.evaluatePart
+
+--- Refresh (enable or disable) a part based on the result of it's chain.
+--- @param part table An object managed by PartsManager.
+function PartsManager.refreshPart(part)
+	local part_enabled=evaluatePart(part)
+	part:setVisible(part_enabled)
+	return part_enabled
+end
+
+--- Refresh all parts managed by PartsManager.
+function PartsManager.refreshAll()
+	for _, v in pairs(pm) do
+		PartsManager.refreshPart(v.part)
+	end
+end
+
+--- Add function to list of parts in PartsManager
+--- @param group table A table containing objects with a setEnabled() method.
+--- @param func function Function to add to each model part's function chain.
+--- @param default? boolean Default value for chain. Should only be set once, subsequent uses overwrite the entire chain's initial value.
+function PartsManager.addPartGroupFunction(group, func, default)
+	for _, v in ipairs(group) do
+		PartsManager.addPartFunction(v, func, default)
+	end
+end
+
+return PartsManager
diff --git a/logging.lua b/logging.lua
new file mode 100644
index 0000000..888fd2d
--- /dev/null
+++ b/logging.lua
@@ -0,0 +1,34 @@
+logging = {}
+local loglevels={
+	["SILENT"]=0,
+	["FATAL"]=1,
+	["ERROR"]=2,
+	["WARN"]=3,
+	["INFO"]=4,
+	["DEBUG"]=5,
+	["TRACE"]=6
+}
+
+-- default log level
+local loglevel="INFO"
+
+function logging.setLogLevel(level)
+	loglevel=loglevels[level] and level or loglevel
+end
+
+logging.setLogLevel("INFO")
+
+local function printLog(severity, message)
+	if (loglevels[loglevel]) >= severity then
+		log("[" .. loglevel .. "] " .. message)
+	end
+end
+
+function logging.fatal(message) printLog(1, message) end
+function logging.error(message) printLog(2, message) end
+function logging.warn(message) printLog(3, message) end
+function logging.info(message) printLog(4, message) end
+function logging.debug(message) printLog(5, message) end
+function logging.trace(message) printLog(6, message) end
+
+return logging
diff --git a/math.lua b/math.lua
new file mode 100644
index 0000000..323c3f4
--- /dev/null
+++ b/math.lua
@@ -0,0 +1,9 @@
+math_functions={}
+--- Sine function with period and amplitude
+--- @param x number Input value
+--- @param period number Period of sine wave
+--- @param amp number Peak amplitude of sine wave
+function math_functions.wave(x, period, amp) return math.sin((2/period)*math.pi*(x%period))*amp end
+function math_functions.lerp(a, b, t) return a + ((b - a) * t) end
+
+return math_functions
diff --git a/util.lua b/util.lua
new file mode 100644
index 0000000..4dfd471
--- /dev/null
+++ b/util.lua
@@ -0,0 +1,207 @@
+
+util={}
+--- Create a string representation of a table
+--- @param o table
+function util.dumpTable(o)
+	if type(o) == 'table' then
+		local s = '{ '
+		local first_loop=true
+		for k,v in pairs(o) do
+			if not first_loop then
+				s = s .. ', '
+			end
+			first_loop=false
+			if type(k) ~= 'number' then k = '"'..k..'"' end
+			s = s .. '['..k..'] = ' .. dumpTable(v)
+		end
+		return s .. '} '
+	else
+		return tostring(o)
+	end
+end
+
+do
+	local function format_any_value(obj, buffer)
+		local _type = type(obj)
+		if _type == "table" then
+			buffer[#buffer + 1] = '{"'
+			for key, value in next, obj, nil do
+				buffer[#buffer + 1] = tostring(key) .. '":'
+				format_any_value(value, buffer)
+				buffer[#buffer + 1] = ',"'
+			end
+			buffer[#buffer] = '}' -- note the overwrite
+		elseif _type == "string" then
+			buffer[#buffer + 1] = '"' .. obj .. '"'
+		elseif _type == "boolean" or _type == "number" then
+			buffer[#buffer + 1] = tostring(obj)
+		else
+			buffer[#buffer + 1] = '"???' .. _type .. '???"'
+		end
+	end
+	--- Dumps object as UNSAFE json, i stole this from stackoverflow so i could
+	--  use json.tool to format it so it's easier to read
+	--  TL;DR, do NOT use this as an actual JSON implementation
+	function util.dumpJSON(obj)
+		if obj == nil then return "null" else
+			local buffer = {}
+			format_any_value(obj, buffer)
+			return table.concat(buffer)
+		end
+	end
+end
+
+
+-- TODO: accept model part to determine texture width and height
+---@param uv table
+function util.UV(uv)
+	return vec(
+	uv[1]/TEXTURE_WIDTH,
+	uv[2]/TEXTURE_HEIGHT
+	)
+end
+
+
+---@param inputstr string
+---@param sep string
+function util.splitstring (inputstr, sep)
+	if sep == nil then
+		sep = "%s"
+	end
+	local t={}
+	for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
+		table.insert(t, str)
+	end
+	return t
+end
+
+---@param input string
+function util.parse(input)
+	if input=="nil" then
+		return nil
+	elseif input == "true" or input == "false" then
+		return input=="true"
+	elseif tonumber(input) ~= nil then
+		return tonumber(input)
+	else
+		return input
+	end
+end
+
+---@param func function
+---@param table table
+function util.map(func, table)
+	local t={}
+	for k, v in pairs(table) do
+		t[k]=func(v)
+	end
+	return t
+end
+
+
+---@param func function
+---@param table table
+function util.filter(func, table)
+	local t={}
+	for k, v in pairs(table) do
+		if func(v) then
+			t[k]=v
+		end
+	end
+	return t
+end
+
+---@param tbl table
+---@param val any
+function util.has_value(tbl, val)
+	for _, v in pairs(tbl) do
+		if v==val then return true end
+	end
+	return false
+end
+
+--- Unordered reduction, only use when working with dictionaries and
+--- execution order does not matter
+---@param tbl table Table to reduce
+---@param func function Function used to reduce table
+---@param init any Initial operand for reduce function
+function util.reduce(func, tbl, init)
+	local result = init
+	local first_loop = true
+	for _, v in pairs(tbl) do
+		if first_loop and init == nil then
+			result=v
+		else
+			result = func(result, v)
+		end
+		first_loop=false
+	end
+	return result
+end
+
+--- Ordered reduction, does not work with dictionaries
+---@param tbl table Table to reduce
+---@param func function Function used to reduce table
+---@param init any Initial operand for reduce function
+function util.ireduce(func, tbl, init)
+	local result = init
+	local first_loop = true
+	for _, v in ipairs(tbl) do
+		if first_loop and init == nil then
+			result=v
+		else
+			result = func(result, v)
+		end
+		first_loop=false
+	end
+	return result
+end
+
+--- Merge two tables. First table value takes precedence when conflict occurs.
+---@param tb1 table
+---@param tb2 table
+function util.mergeTable(tb1, tb2)
+	local t={}
+	for k, v in pairs(tb1) do
+		t[k]=v
+	end
+	for k, v in pairs(tb2) do
+		if type(k)=="number" then
+			table.insert(t, v)
+		else
+			if t[k]==nil then
+				t[k]=v
+			end
+		end
+	end
+	return t
+end
+
+function util.debugPrint(var)
+	print(var)
+	return var
+end
+
+function util.debugPrintTable(var)
+	printTable(var)
+	return var
+end
+
+--- Recursively walk a model tree and return a table containing the group and each of its sub-groups
+--- @param group table The group to recurse
+--- @return table Resulting table
+local function recurseModelGroup(group)
+	local t={}
+	table.insert(t, group)
+	if group:getType()=="GROUP" then
+		for k, v in pairs(group:getChildren()) do
+			for _, v2 in pairs(recurseModelGroup(v)) do
+				table.insert(t, v2)
+			end
+		end
+	end
+	return t
+end
+util.recurseModelGroup=recurseModelGroup
+
+return util