summaryrefslogtreecommitdiff
path: root/builtin/common
diff options
context:
space:
mode:
authorShadowNinja <shadowninja@minetest.net>2014-04-27 17:55:49 -0400
committerShadowNinja <shadowninja@minetest.net>2014-05-07 17:14:23 -0400
commit1cd512913e4d4ad1fb43d4b6e3d7971bb6c67528 (patch)
tree8c1e2c708f567656684e89faee4f7c8e6c5ec673 /builtin/common
parentfef2729fd0945601e6772780514ee55fec35b068 (diff)
downloadminetest-1cd512913e4d4ad1fb43d4b6e3d7971bb6c67528.tar.gz
minetest-1cd512913e4d4ad1fb43d4b6e3d7971bb6c67528.tar.bz2
minetest-1cd512913e4d4ad1fb43d4b6e3d7971bb6c67528.zip
Organize builtin into subdirectories
Diffstat (limited to 'builtin/common')
-rw-r--r--builtin/common/async_event.lua42
-rw-r--r--builtin/common/misc_helpers.lua428
-rw-r--r--builtin/common/serialize.lua223
-rw-r--r--builtin/common/vector.lua146
4 files changed, 839 insertions, 0 deletions
diff --git a/builtin/common/async_event.lua b/builtin/common/async_event.lua
new file mode 100644
index 000000000..ef4bf4354
--- /dev/null
+++ b/builtin/common/async_event.lua
@@ -0,0 +1,42 @@
+
+local core = engine or minetest
+
+core.async_jobs = {}
+
+local function handle_job(jobid, serialized_retval)
+ local retval = core.deserialize(serialized_retval)
+ assert(type(core.async_jobs[jobid]) == "function")
+ core.async_jobs[jobid](retval)
+ core.async_jobs[jobid] = nil
+end
+
+if engine ~= nil then
+ core.async_event_handler = handle_job
+else
+ minetest.register_globalstep(function(dtime)
+ for i, job in ipairs(core.get_finished_jobs()) do
+ handle_job(job.jobid, job.retval)
+ end
+ end)
+end
+
+function core.handle_async(func, parameter, callback)
+ -- Serialize function
+ local serialized_func = string.dump(func)
+
+ assert(serialized_func ~= nil)
+
+ -- Serialize parameters
+ local serialized_param = core.serialize(parameter)
+
+ if serialized_param == nil then
+ return false
+ end
+
+ local jobid = core.do_async_callback(serialized_func, serialized_param)
+
+ core.async_jobs[jobid] = callback
+
+ return true
+end
+
diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua
new file mode 100644
index 000000000..9c7349726
--- /dev/null
+++ b/builtin/common/misc_helpers.lua
@@ -0,0 +1,428 @@
+-- Minetest: builtin/misc_helpers.lua
+
+--------------------------------------------------------------------------------
+function basic_dump2(o)
+ if type(o) == "number" then
+ return tostring(o)
+ elseif type(o) == "string" then
+ return string.format("%q", o)
+ elseif type(o) == "boolean" then
+ return tostring(o)
+ elseif type(o) == "function" then
+ return "<function>"
+ elseif type(o) == "userdata" then
+ return "<userdata>"
+ elseif type(o) == "nil" then
+ return "nil"
+ else
+ error("cannot dump a " .. type(o))
+ return nil
+ end
+end
+
+--------------------------------------------------------------------------------
+function dump2(o, name, dumped)
+ name = name or "_"
+ dumped = dumped or {}
+ io.write(name, " = ")
+ if type(o) == "number" or type(o) == "string" or type(o) == "boolean"
+ or type(o) == "function" or type(o) == "nil"
+ or type(o) == "userdata" then
+ io.write(basic_dump2(o), "\n")
+ elseif type(o) == "table" then
+ if dumped[o] then
+ io.write(dumped[o], "\n")
+ else
+ dumped[o] = name
+ io.write("{}\n") -- new table
+ for k,v in pairs(o) do
+ local fieldname = string.format("%s[%s]", name, basic_dump2(k))
+ dump2(v, fieldname, dumped)
+ end
+ end
+ else
+ error("cannot dump a " .. type(o))
+ return nil
+ end
+end
+
+--------------------------------------------------------------------------------
+function dump(o, dumped)
+ dumped = dumped or {}
+ if type(o) == "number" then
+ return tostring(o)
+ elseif type(o) == "string" then
+ return string.format("%q", o)
+ elseif type(o) == "table" then
+ if dumped[o] then
+ return "<circular reference>"
+ end
+ dumped[o] = true
+ local t = {}
+ for k,v in pairs(o) do
+ t[#t+1] = "[" .. dump(k, dumped) .. "] = " .. dump(v, dumped)
+ end
+ return "{" .. table.concat(t, ", ") .. "}"
+ elseif type(o) == "boolean" then
+ return tostring(o)
+ elseif type(o) == "function" then
+ return "<function>"
+ elseif type(o) == "userdata" then
+ return "<userdata>"
+ elseif type(o) == "nil" then
+ return "nil"
+ else
+ error("cannot dump a " .. type(o))
+ return nil
+ end
+end
+
+--------------------------------------------------------------------------------
+function string:split(sep)
+ local sep, fields = sep or ",", {}
+ local pattern = string.format("([^%s]+)", sep)
+ self:gsub(pattern, function(c) fields[#fields+1] = c end)
+ return fields
+end
+
+--------------------------------------------------------------------------------
+function file_exists(filename)
+ local f = io.open(filename, "r")
+ if f==nil then
+ return false
+ else
+ f:close()
+ return true
+ end
+end
+
+--------------------------------------------------------------------------------
+function string:trim()
+ return (self:gsub("^%s*(.-)%s*$", "%1"))
+end
+
+assert(string.trim("\n \t\tfoo bar\t ") == "foo bar")
+
+--------------------------------------------------------------------------------
+function math.hypot(x, y)
+ local t
+ x = math.abs(x)
+ y = math.abs(y)
+ t = math.min(x, y)
+ x = math.max(x, y)
+ if x == 0 then return 0 end
+ t = t / x
+ return x * math.sqrt(1 + t * t)
+end
+
+--------------------------------------------------------------------------------
+function get_last_folder(text,count)
+ local parts = text:split(DIR_DELIM)
+
+ if count == nil then
+ return parts[#parts]
+ end
+
+ local retval = ""
+ for i=1,count,1 do
+ retval = retval .. parts[#parts - (count-i)] .. DIR_DELIM
+ end
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+function cleanup_path(temppath)
+
+ local parts = temppath:split("-")
+ temppath = ""
+ for i=1,#parts,1 do
+ if temppath ~= "" then
+ temppath = temppath .. "_"
+ end
+ temppath = temppath .. parts[i]
+ end
+
+ parts = temppath:split(".")
+ temppath = ""
+ for i=1,#parts,1 do
+ if temppath ~= "" then
+ temppath = temppath .. "_"
+ end
+ temppath = temppath .. parts[i]
+ end
+
+ parts = temppath:split("'")
+ temppath = ""
+ for i=1,#parts,1 do
+ if temppath ~= "" then
+ temppath = temppath .. ""
+ end
+ temppath = temppath .. parts[i]
+ end
+
+ parts = temppath:split(" ")
+ temppath = ""
+ for i=1,#parts,1 do
+ if temppath ~= "" then
+ temppath = temppath
+ end
+ temppath = temppath .. parts[i]
+ end
+
+ return temppath
+end
+
+local tbl = engine or minetest
+function tbl.formspec_escape(text)
+ if text ~= nil then
+ text = string.gsub(text,"\\","\\\\")
+ text = string.gsub(text,"%]","\\]")
+ text = string.gsub(text,"%[","\\[")
+ text = string.gsub(text,";","\\;")
+ text = string.gsub(text,",","\\,")
+ end
+ return text
+end
+
+
+function tbl.splittext(text,charlimit)
+ local retval = {}
+
+ local current_idx = 1
+
+ local start,stop = string.find(text," ",current_idx)
+ local nl_start,nl_stop = string.find(text,"\n",current_idx)
+ local gotnewline = false
+ if nl_start ~= nil and (start == nil or nl_start < start) then
+ start = nl_start
+ stop = nl_stop
+ gotnewline = true
+ end
+ local last_line = ""
+ while start ~= nil do
+ if string.len(last_line) + (stop-start) > charlimit then
+ table.insert(retval,last_line)
+ last_line = ""
+ end
+
+ if last_line ~= "" then
+ last_line = last_line .. " "
+ end
+
+ last_line = last_line .. string.sub(text,current_idx,stop -1)
+
+ if gotnewline then
+ table.insert(retval,last_line)
+ last_line = ""
+ gotnewline = false
+ end
+ current_idx = stop+1
+
+ start,stop = string.find(text," ",current_idx)
+ nl_start,nl_stop = string.find(text,"\n",current_idx)
+
+ if nl_start ~= nil and (start == nil or nl_start < start) then
+ start = nl_start
+ stop = nl_stop
+ gotnewline = true
+ end
+ end
+
+ --add last part of text
+ if string.len(last_line) + (string.len(text) - current_idx) > charlimit then
+ table.insert(retval,last_line)
+ table.insert(retval,string.sub(text,current_idx))
+ else
+ last_line = last_line .. " " .. string.sub(text,current_idx)
+ table.insert(retval,last_line)
+ end
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+
+if minetest then
+ local dirs1 = {9, 18, 7, 12}
+ local dirs2 = {20, 23, 22, 21}
+
+ function minetest.rotate_and_place(itemstack, placer, pointed_thing,
+ infinitestacks, orient_flags)
+ orient_flags = orient_flags or {}
+
+ local unode = minetest.get_node_or_nil(pointed_thing.under)
+ if not unode then
+ return
+ end
+ local undef = minetest.registered_nodes[unode.name]
+ if undef and undef.on_rightclick then
+ undef.on_rightclick(pointed_thing.under, unode, placer,
+ itemstack, pointed_thing)
+ return
+ end
+ local pitch = placer:get_look_pitch()
+ local fdir = minetest.dir_to_facedir(placer:get_look_dir())
+ local wield_name = itemstack:get_name()
+
+ local above = pointed_thing.above
+ local under = pointed_thing.under
+ local iswall = (above.y == under.y)
+ local isceiling = not iswall and (above.y < under.y)
+ local anode = minetest.get_node_or_nil(above)
+ if not anode then
+ return
+ end
+ local pos = pointed_thing.above
+ local node = anode
+
+ if undef and undef.buildable_to then
+ pos = pointed_thing.under
+ node = unode
+ iswall = false
+ end
+
+ if minetest.is_protected(pos, placer:get_player_name()) then
+ minetest.record_protection_violation(pos,
+ placer:get_player_name())
+ return
+ end
+
+ local ndef = minetest.registered_nodes[node.name]
+ if not ndef or not ndef.buildable_to then
+ return
+ end
+
+ if orient_flags.force_floor then
+ iswall = false
+ isceiling = false
+ elseif orient_flags.force_ceiling then
+ iswall = false
+ isceiling = true
+ elseif orient_flags.force_wall then
+ iswall = true
+ isceiling = false
+ elseif orient_flags.invert_wall then
+ iswall = not iswall
+ end
+
+ if iswall then
+ minetest.set_node(pos, {name = wield_name,
+ param2 = dirs1[fdir+1]})
+ elseif isceiling then
+ if orient_flags.force_facedir then
+ minetest.set_node(pos, {name = wield_name,
+ param2 = 20})
+ else
+ minetest.set_node(pos, {name = wield_name,
+ param2 = dirs2[fdir+1]})
+ end
+ else -- place right side up
+ if orient_flags.force_facedir then
+ minetest.set_node(pos, {name = wield_name,
+ param2 = 0})
+ else
+ minetest.set_node(pos, {name = wield_name,
+ param2 = fdir})
+ end
+ end
+
+ if not infinitestacks then
+ itemstack:take_item()
+ return itemstack
+ end
+ end
+
+
+--------------------------------------------------------------------------------
+--Wrapper for rotate_and_place() to check for sneak and assume Creative mode
+--implies infinite stacks when performing a 6d rotation.
+--------------------------------------------------------------------------------
+
+
+ minetest.rotate_node = function(itemstack, placer, pointed_thing)
+ minetest.rotate_and_place(itemstack, placer, pointed_thing,
+ minetest.setting_getbool("creative_mode"),
+ {invert_wall = placer:get_player_control().sneak})
+ return itemstack
+ end
+end
+
+--------------------------------------------------------------------------------
+function tbl.explode_table_event(evt)
+ if evt ~= nil then
+ local parts = evt:split(":")
+ if #parts == 3 then
+ local t = parts[1]:trim()
+ local r = tonumber(parts[2]:trim())
+ local c = tonumber(parts[3]:trim())
+ if type(r) == "number" and type(c) == "number" and t ~= "INV" then
+ return {type=t, row=r, column=c}
+ end
+ end
+ end
+ return {type="INV", row=0, column=0}
+end
+
+--------------------------------------------------------------------------------
+function tbl.explode_textlist_event(evt)
+ if evt ~= nil then
+ local parts = evt:split(":")
+ if #parts == 2 then
+ local t = parts[1]:trim()
+ local r = tonumber(parts[2]:trim())
+ if type(r) == "number" and t ~= "INV" then
+ return {type=t, index=r}
+ end
+ end
+ end
+ return {type="INV", index=0}
+end
+
+--------------------------------------------------------------------------------
+-- mainmenu only functions
+--------------------------------------------------------------------------------
+if engine ~= nil then
+ engine.get_game = function(index)
+ local games = game.get_games()
+
+ if index > 0 and index <= #games then
+ return games[index]
+ end
+
+ return nil
+ end
+
+ function fgettext(text, ...)
+ text = engine.gettext(text)
+ local arg = {n=select('#', ...), ...}
+ if arg.n >= 1 then
+ -- Insert positional parameters ($1, $2, ...)
+ result = ''
+ pos = 1
+ while pos <= text:len() do
+ newpos = text:find('[$]', pos)
+ if newpos == nil then
+ result = result .. text:sub(pos)
+ pos = text:len() + 1
+ else
+ paramindex = tonumber(text:sub(newpos+1, newpos+1))
+ result = result .. text:sub(pos, newpos-1) .. tostring(arg[paramindex])
+ pos = newpos + 2
+ end
+ end
+ text = result
+ end
+ return engine.formspec_escape(text)
+ end
+end
+--------------------------------------------------------------------------------
+-- core only fct
+--------------------------------------------------------------------------------
+if minetest ~= nil then
+ --------------------------------------------------------------------------------
+ function minetest.pos_to_string(pos)
+ return "(" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")"
+ end
+end
+
diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua
new file mode 100644
index 000000000..93fffe80d
--- /dev/null
+++ b/builtin/common/serialize.lua
@@ -0,0 +1,223 @@
+-- Minetest: builtin/serialize.lua
+
+-- https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua
+-- Copyright (c) 2006-2997 Fabien Fleutot <metalua@gmail.com>
+-- License: MIT
+--------------------------------------------------------------------------------
+-- Serialize an object into a source code string. This string, when passed as
+-- an argument to deserialize(), returns an object structurally identical
+-- to the original one. The following are currently supported:
+-- * strings, numbers, booleans, nil
+-- * tables thereof. Tables can have shared part, but can't be recursive yet.
+-- Caveat: metatables and environments aren't saved.
+--------------------------------------------------------------------------------
+
+local no_identity = { number=1, boolean=1, string=1, ['nil']=1 }
+
+function minetest.serialize(x)
+
+ local gensym_max = 0 -- index of the gensym() symbol generator
+ local seen_once = { } -- element->true set of elements seen exactly once in the table
+ local multiple = { } -- element->varname set of elements seen more than once
+ local nested = { } -- transient, set of elements currently being traversed
+ local nest_points = { }
+ local nest_patches = { }
+
+ local function gensym()
+ gensym_max = gensym_max + 1 ; return gensym_max
+ end
+
+ -----------------------------------------------------------------------------
+ -- nest_points are places where a table appears within itself, directly or not.
+ -- for instance, all of these chunks create nest points in table x:
+ -- "x = { }; x[x] = 1", "x = { }; x[1] = x", "x = { }; x[1] = { y = { x } }".
+ -- To handle those, two tables are created by mark_nest_point:
+ -- * nest_points [parent] associates all keys and values in table parent which
+ -- create a nest_point with boolean `true'
+ -- * nest_patches contain a list of { parent, key, value } tuples creating
+ -- a nest point. They're all dumped after all the other table operations
+ -- have been performed.
+ --
+ -- mark_nest_point (p, k, v) fills tables nest_points and nest_patches with
+ -- informations required to remember that key/value (k,v) create a nest point
+ -- in table parent. It also marks `parent' as occuring multiple times, since
+ -- several references to it will be required in order to patch the nest
+ -- points.
+ -----------------------------------------------------------------------------
+ local function mark_nest_point (parent, k, v)
+ local nk, nv = nested[k], nested[v]
+ assert (not nk or seen_once[k] or multiple[k])
+ assert (not nv or seen_once[v] or multiple[v])
+ local mode = (nk and nv and "kv") or (nk and "k") or ("v")
+ local parent_np = nest_points [parent]
+ local pair = { k, v }
+ if not parent_np then parent_np = { }; nest_points [parent] = parent_np end
+ parent_np [k], parent_np [v] = nk, nv
+ table.insert (nest_patches, { parent, k, v })
+ seen_once [parent], multiple [parent] = nil, true
+ end
+
+ -----------------------------------------------------------------------------
+ -- First pass, list the tables and functions which appear more than once in x
+ -----------------------------------------------------------------------------
+ local function mark_multiple_occurences (x)
+ if no_identity [type(x)] then return end
+ if seen_once [x] then seen_once [x], multiple [x] = nil, true
+ elseif multiple [x] then -- pass
+ else seen_once [x] = true end
+
+ if type (x) == 'table' then
+ nested [x] = true
+ for k, v in pairs (x) do
+ if nested[k] or nested[v] then mark_nest_point (x, k, v) else
+ mark_multiple_occurences (k)
+ mark_multiple_occurences (v)
+ end
+ end
+ nested [x] = nil
+ end
+ end
+
+ local dumped = { } -- multiply occuring values already dumped in localdefs
+ local localdefs = { } -- already dumped local definitions as source code lines
+
+ -- mutually recursive functions:
+ local dump_val, dump_or_ref_val
+
+ --------------------------------------------------------------------
+ -- if x occurs multiple times, dump the local var rather than the
+ -- value. If it's the first time it's dumped, also dump the content
+ -- in localdefs.
+ --------------------------------------------------------------------
+ function dump_or_ref_val (x)
+ if nested[x] then return 'false' end -- placeholder for recursive reference
+ if not multiple[x] then return dump_val (x) end
+ local var = dumped [x]
+ if var then return "_[" .. var .. "]" end -- already referenced
+ local val = dump_val(x) -- first occurence, create and register reference
+ var = gensym()
+ table.insert(localdefs, "_["..var.."]="..val)
+ dumped [x] = var
+ return "_[" .. var .. "]"
+ end
+
+ -----------------------------------------------------------------------------
+ -- Second pass, dump the object; subparts occuring multiple times are dumped
+ -- in local variables which can be referenced multiple times;
+ -- care is taken to dump locla vars in asensible order.
+ -----------------------------------------------------------------------------
+ function dump_val(x)
+ local t = type(x)
+ if x==nil then return 'nil'
+ elseif t=="number" then return tostring(x)
+ elseif t=="string" then return string.format("%q", x)
+ elseif t=="boolean" then return x and "true" or "false"
+ elseif t=="function" then
+ return "loadstring("..string.format("%q", string.dump(x))..")"
+ elseif t=="table" then
+ local acc = { }
+ local idx_dumped = { }
+ local np = nest_points [x]
+ for i, v in ipairs(x) do
+ if np and np[v] then
+ table.insert (acc, 'false') -- placeholder
+ else
+ table.insert (acc, dump_or_ref_val(v))
+ end
+ idx_dumped[i] = true
+ end
+ for k, v in pairs(x) do
+ if np and (np[k] or np[v]) then
+ --check_multiple(k); check_multiple(v) -- force dumps in localdefs
+ elseif not idx_dumped[k] then
+ table.insert (acc, "[" .. dump_or_ref_val(k) .. "] = " .. dump_or_ref_val(v))
+ end
+ end
+ return "{ "..table.concat(acc,", ").." }"
+ else
+ error ("Can't serialize data of type "..t)
+ end
+ end
+
+ local function dump_nest_patches()
+ for _, entry in ipairs(nest_patches) do
+ local p, k, v = unpack (entry)
+ assert (multiple[p])
+ local set = dump_or_ref_val (p) .. "[" .. dump_or_ref_val (k) .. "] = " ..
+ dump_or_ref_val (v) .. " -- rec "
+ table.insert (localdefs, set)
+ end
+ end
+
+ mark_multiple_occurences (x)
+ local toplevel = dump_or_ref_val (x)
+ dump_nest_patches()
+
+ if next (localdefs) then
+ return "local _={ }\n" ..
+ table.concat (localdefs, "\n") ..
+ "\nreturn " .. toplevel
+ else
+ return "return " .. toplevel
+ end
+end
+
+-- Deserialization.
+-- http://stackoverflow.com/questions/5958818/loading-serialized-data-into-a-table
+--
+
+local env = {
+ loadstring = loadstring,
+}
+
+local function noop() end
+
+local safe_env = {
+ loadstring = noop,
+}
+
+local function stringtotable(sdata, safe)
+ if sdata:byte(1) == 27 then return nil, "binary bytecode prohibited" end
+ local f, message = assert(loadstring(sdata))
+ if not f then return nil, message end
+ if safe then
+ setfenv(f, safe_env)
+ else
+ setfenv(f, env)
+ end
+ return f()
+end
+
+function minetest.deserialize(sdata, safe)
+ local table = {}
+ local okay, results = pcall(stringtotable, sdata, safe)
+ if okay then
+ return results
+ end
+ minetest.log('error', 'minetest.deserialize(): '.. results)
+ return nil
+end
+
+-- Run some unit tests
+local function unit_test()
+ function unitTest(name, success)
+ if not success then
+ error(name .. ': failed')
+ end
+ end
+
+ unittest_input = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
+ unittest_output = minetest.deserialize(minetest.serialize(unittest_input))
+
+ unitTest("test 1a", unittest_input.cat.sound == unittest_output.cat.sound)
+ unitTest("test 1b", unittest_input.cat.speed == unittest_output.cat.speed)
+ unitTest("test 1c", unittest_input.dog.sound == unittest_output.dog.sound)
+
+ unittest_input = {escapechars="\n\r\t\v\\\"\'", noneuropean="θשׁ٩∂"}
+ unittest_output = minetest.deserialize(minetest.serialize(unittest_input))
+ unitTest("test 3a", unittest_input.escapechars == unittest_output.escapechars)
+ unitTest("test 3b", unittest_input.noneuropean == unittest_output.noneuropean)
+end
+unit_test() -- Run it
+unit_test = nil -- Hide it
+
diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua
new file mode 100644
index 000000000..77944b612
--- /dev/null
+++ b/builtin/common/vector.lua
@@ -0,0 +1,146 @@
+
+vector = {}
+
+local function assert_vector(v)
+ assert(type(v) == "table" and v.x and v.y and v.z, "Invalid vector")
+end
+
+function vector.new(a, b, c)
+ if type(a) == "table" then
+ assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()")
+ return {x=a.x, y=a.y, z=a.z}
+ elseif a then
+ assert(b and c, "Invalid arguments for vector.new()")
+ return {x=a, y=b, z=c}
+ end
+ return {x=0, y=0, z=0}
+end
+
+function vector.equals(a, b)
+ assert_vector(a)
+ assert_vector(b)
+ return a.x == b.x and
+ a.y == b.y and
+ a.z == b.z
+end
+
+function vector.length(v)
+ assert_vector(v)
+ return math.hypot(v.x, math.hypot(v.y, v.z))
+end
+
+function vector.normalize(v)
+ assert_vector(v)
+ local len = vector.length(v)
+ if len == 0 then
+ return {x=0, y=0, z=0}
+ else
+ return vector.divide(v, len)
+ end
+end
+
+function vector.round(v)
+ assert_vector(v)
+ return {
+ x = math.floor(v.x + 0.5),
+ y = math.floor(v.y + 0.5),
+ z = math.floor(v.z + 0.5)
+ }
+end
+
+function vector.distance(a, b)
+ assert_vector(a)
+ assert_vector(b)
+ local x = a.x - b.x
+ local y = a.y - b.y
+ local z = a.z - b.z
+ return math.hypot(x, math.hypot(y, z))
+end
+
+function vector.direction(pos1, pos2)
+ assert_vector(pos1)
+ assert_vector(pos2)
+ local x_raw = pos2.x - pos1.x
+ local y_raw = pos2.y - pos1.y
+ local z_raw = pos2.z - pos1.z
+ local x_abs = math.abs(x_raw)
+ local y_abs = math.abs(y_raw)
+ local z_abs = math.abs(z_raw)
+ if x_abs >= y_abs and
+ x_abs >= z_abs then
+ y_raw = y_raw * (1 / x_abs)
+ z_raw = z_raw * (1 / x_abs)
+ x_raw = x_raw / x_abs
+ end
+ if y_abs >= x_abs and
+ y_abs >= z_abs then
+ x_raw = x_raw * (1 / y_abs)
+ z_raw = z_raw * (1 / y_abs)
+ y_raw = y_raw / y_abs
+ end
+ if z_abs >= y_abs and
+ z_abs >= x_abs then
+ x_raw = x_raw * (1 / z_abs)
+ y_raw = y_raw * (1 / z_abs)
+ z_raw = z_raw / z_abs
+ end
+ return {x=x_raw, y=y_raw, z=z_raw}
+end
+
+
+function vector.add(a, b)
+ assert_vector(a)
+ if type(b) == "table" then
+ assert_vector(b)
+ return {x = a.x + b.x,
+ y = a.y + b.y,
+ z = a.z + b.z}
+ else
+ return {x = a.x + b,
+ y = a.y + b,
+ z = a.z + b}
+ end
+end
+
+function vector.subtract(a, b)
+ assert_vector(a)
+ if type(b) == "table" then
+ assert_vector(b)
+ return {x = a.x - b.x,
+ y = a.y - b.y,
+ z = a.z - b.z}
+ else
+ return {x = a.x - b,
+ y = a.y - b,
+ z = a.z - b}
+ end
+end
+
+function vector.multiply(a, b)
+ assert_vector(a)
+ if type(b) == "table" then
+ assert_vector(b)
+ return {x = a.x * b.x,
+ y = a.y * b.y,
+ z = a.z * b.z}
+ else
+ return {x = a.x * b,
+ y = a.y * b,
+ z = a.z * b}
+ end
+end
+
+function vector.divide(a, b)
+ assert_vector(a)
+ if type(b) == "table" then
+ assert_vector(b)
+ return {x = a.x / b.x,
+ y = a.y / b.y,
+ z = a.z / b.z}
+ else
+ return {x = a.x / b,
+ y = a.y / b,
+ z = a.z / b}
+ end
+end
+