This commit is contained in:
Maxim Khomutov 2022-09-25 22:37:01 +03:00
parent 7f7275b7dc
commit b7e0a2df69
7 changed files with 530 additions and 2213 deletions

1
.gitignore vendored
View File

@ -47,4 +47,3 @@ luac.out
BeamMP-Server.exe
ServerConfig.toml
*.log
trafficSync/

11
LICENSE Normal file
View File

@ -0,0 +1,11 @@
Copyright (c) 2022 Maxim Khomutov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without limitation in the rights to use, copy, modify, merge, publish, and/ or distribute copies of the Software in an educational or personal context, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Permission is granted to sell and/ or distribute copies of the Software in a commercial context, subject to the following conditions:
- Substantial changes: adding, removing, or modifying large parts, shall be developed in the Software. Reorganizing logic in the software does not warrant a substantial change.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,2 +1,16 @@
# TrafficSync-BeamMP for
Mod to sync traffic signals like those found on East Coast USA on BeamMP servers
# CommandEngine
## About
* Status: **WIP**
CommandEngine is plugin for BeamMP.\
Plugin for handle commands from client chat or server console.
## Wiki
-- TODO
* 1
* 2
* 3

View File

@ -0,0 +1,119 @@
---
--- Created by SantaSpeen.
--- DateTime: 25.09.2022 13:38
---
json = require("json")
-- -- -- -- -- Init functions -- -- -- -- --
function log(...)
print("[".. pluginName .."] " .. tostring(...))
end
function SplitString (str, char)
local res = {};
for i in string.gmatch(str, "[^" .. char .. "]+") do
table.insert(res, i)
end
return res
end
-- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- -- -- -- -- Init Variables -- -- -- -- --
pluginName = "CommandEngine"
prefix = "/"
-- Table settings: cmd_name = { "event", "mode", { "command syntax", "Help note." } }
-- Mods settings: a - all mode; c - only console; u - only from user;
commandsStorage = {
help = { "CE_help", "c", "displays this help."},
stop = { "shutdownServer", "c", "Also as exit."}
}
pluginPath = FS.GetParentFolder(string.gsub(debug.getinfo(1).source,"\\", "/"))
-- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- -- -- -- -- Register Events -- -- -- -- -
log("Register global events.")
MP.RegisterEvent("CE_addCommand", "addCommandToStorage")
-- -- -- -- -- -- -- -- -- -- -- -- -- -- --
function getCommand(commandName, handlerMode)
for k, v in pairs(commandsStorage) do
if commandName == k then
local mode = v[2]
if mode == "s" then
return nil
elseif mode == "a" then
return v
elseif mode == handlerMode then
return v
end
end
end
end
function chatHandler(sender_id, sender_name, message)
if message:sub(1,1) ~= prefix then return 0 end
local cmd = SplitString(message:sub(2), prefix)[1]
cmdSettings = getCommand(cmd, "u")
if cmdSettings then
local Future = MP.TriggerGlobalEvent(cmdSettings[1], sender_id, sender_name)
while not Future:IsDone() do MP.Sleep(100) end
local Results = Future:GetResults()
MP.SendChatMessage(sender_id, Results[1])
end
end
function consoleHandler(cmd)
cmdSettings = getCommand(cmd, "c")
if cmdSettings then
local Future = MP.TriggerGlobalEvent(cmdSettings[1], -1, "console")
while not Future:IsDone() do MP.Sleep(100) end
local Results = Future:GetResults()
return Results[1]
end
end
function addCommandToStorage(commands)
log(commands)
for k,v in pairs(commands) do commandsStorage[k] = v end
end
function printHelpCommandEngine(...)
local wrap = function (text)
local ltext = string.len(text)
if ltext > 24 then
return string.sub(text, 1, 20) .. "... "
end
return text .. string.sub(" ", 1, 24 - ltext)
end
local helpMessage = "CommandEngine Console:\nM Syntax What it does\n Commands:\n" --> I hate this
for k, v in pairs(commandsStorage) do
helpNote = v[3]
if type(helpNote) == "table" then
helpMessage = helpMessage .. v[2] .. " " .. wrap(v[3][1]) .. v[3][2] .. "\n"
else
helpMessage = helpMessage .. v[2] .. " " .. wrap(k) .. v[3] .. "\n"
end
end
return helpMessage
end
function isCommandEngineReady() return true end
function shutdown() exit() return "Goodbye!" end
function onInit()
log("Register default events.")
MP.RegisterEvent("onConsoleInput", "consoleHandler")
MP.RegisterEvent("onChatMessage", "chatHandler")
MP.RegisterEvent("CE_help", "printHelpCommandEngine")
MP.RegisterEvent("shutdownServer", "shutdown")
log("Ready.")
MP.RegisterEvent("CE_isReady", "isCommandEngineReady")
end

File diff suppressed because it is too large Load Diff

View File

@ -1,219 +1,400 @@
--[[ json.lua
A compact pure-Lua JSON library.
The main functions are: json.stringify, json.parse.
## json.stringify:
This expects the following to be true of any tables being encoded:
* They only have string or number keys. Number keys must be represented as
strings in json; this is part of the json spec.
* They are not recursive. Such a structure cannot be specified in json.
A Lua table is considered to be an array if and only if its set of keys is a
consecutive sequence of positive integers starting at 1. Arrays are encoded like
so: `[2, 3, false, "hi"]`. Any other type of Lua table is encoded as a json
object, encoded like so: `{"key1": 2, "key2": false}`.
Because the Lua nil value cannot be a key, and as a table value is considerd
equivalent to a missing key, there is no way to express the json "null" value in
a Lua table. The only way this will output "null" is if your entire input obj is
nil itself.
An empty Lua table, {}, could be considered either a json object or array -
it's an ambiguous edge case. We choose to treat this as an object as it is the
more general type.
To be clear, none of the above considerations is a limitation of this code.
Rather, it is what we get when we completely observe the json specification for
as arbitrary a Lua object as json is capable of expressing.
## json.parse:
This function parses json, with the exception that it does not pay attention to
\u-escaped unicode code points in strings.
It is difficult for Lua to return null as a value. In order to prevent the loss
of keys with a null value in a json string, this function uses the one-off
table value json.null (which is just an empty table) to indicate null values.
This way you can check if a value is null with the conditional
`val == json.null`.
If you have control over the data and are using Lua, I would recommend just
avoiding null values in your data to begin with.
--]]
--
-- json.lua
--
-- Copyright (c) 2019 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--
local json = { _version = "0.1.2" }
local json = {}
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
-- Internal functions.
local escape_char_map = {
[ "\\" ] = "\\\\",
[ "\"" ] = "\\\"",
[ "\b" ] = "\\b",
[ "\f" ] = "\\f",
[ "\n" ] = "\\n",
[ "\r" ] = "\\r",
[ "\t" ] = "\\t",
}
local function kind_of(obj)
if type(obj) ~= 'table' then return type(obj) end
local i = 1
for _ in pairs(obj) do
if obj[i] ~= nil then i = i + 1 else return 'table' end
end
if i == 1 then return 'table' else return 'array' end
end
local function escape_str(s)
local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}
local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'}
for i, c in ipairs(in_char) do
s = s:gsub(c, '\\' .. out_char[i])
end
return s
end
-- Returns pos, did_find; there are two cases:
-- 1. Delimiter found: pos = pos after leading space + delim; did_find = true.
-- 2. Delimiter not found: pos = pos after leading space; did_find = false.
-- This throws an error if err_if_missing is true and the delim is not found.
local function skip_delim(str, pos, delim, err_if_missing)
pos = pos + #str:match('^%s*', pos)
if str:sub(pos, pos) ~= delim then
if err_if_missing then
error('Expected ' .. delim .. ' near position ' .. pos)
end
return pos, false
end
return pos + 1, true
end
-- Expects the given pos to be the first character after the opening quote.
-- Returns val, pos; the returned pos is after the closing quote character.
local function parse_str_val(str, pos, val)
val = val or ''
local early_end_error = 'End of input found while parsing string.'
if pos > #str then error(early_end_error) end
local c = str:sub(pos, pos)
if c == '"' then return val, pos + 1 end
if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end
-- We must have a \ character.
local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
local nextc = str:sub(pos + 1, pos + 1)
if not nextc then error(early_end_error) end
return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))
end
-- Returns val, pos; the returned pos is after the number's final character.
local function parse_num_val(str, pos)
local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
local val = tonumber(num_str)
if not val then error('Error parsing number at position ' .. pos .. '.') end
return val, pos + #num_str
local escape_char_map_inv = { [ "\\/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
-- Public values and functions.
function json.rawStringify(obj, as_key)
local s = {} -- We'll build the string as an array of strings to be concatenated.
local kind = kind_of(obj) -- This is 'array' if it's an array or type(obj) otherwise.
if kind == 'array' then
if as_key then error('Can\'t encode array as key.') end
s[#s + 1] = '['
for i, val in ipairs(obj) do
if i > 1 then s[#s + 1] = ', ' end
s[#s + 1] = json.rawStringify(val)
end
s[#s + 1] = ']'
elseif kind == 'table' then
if as_key then error('Can\'t encode table as key.') end
s[#s + 1] = '{'
for k, v in pairs(obj) do
if #s > 1 then s[#s + 1] = ', ' end
s[#s + 1] = json.rawStringify(k, true)
s[#s + 1] = ':'
s[#s + 1] = json.rawStringify(v)
end
s[#s + 1] = '}'
elseif kind == 'string' then
return '"' .. escape_str(obj) .. '"'
elseif kind == 'number' then
if as_key then return '"' .. tostring(obj) .. '"' end
return tostring(obj)
elseif kind == 'boolean' then
return tostring(obj)
elseif kind == 'nil' then
return 'null'
else
error('Unjsonifiable type: ' .. kind .. '.')
end
return table.concat(s)
local function escape_char(c)
return escape_char_map[c] or string.format("\\u%04x", c:byte())
end
function json.stringify(obj, as_key)
local s = {} -- We'll build the string as an array of strings to be concatenated.
local kind = kind_of(obj) -- This is 'array' if it's an array or type(obj) otherwise.
if kind == 'array' then
if as_key then error('Can\'t encode array as key.') end
s[#s + 1] = '['
for i, val in ipairs(obj) do
if i > 1 then s[#s + 1] = ', ' end
s[#s + 1] = json.stringify(val)
end
s[#s + 1] = ']'
elseif kind == 'table' then
if as_key then error('Can\'t encode table as key.') end
s[#s + 1] = '{\t'
for k, v in pairs(obj) do
if #s > 1 then s[#s + 1] = ',' end
if kind_of(v) == 'table' then s[#s + 1] = "\n" else s[#s + 1] = "\n\t" end
s[#s + 1] = json.stringify(k, true)
s[#s + 1] = ':'
s[#s + 1] = json.stringify(v)
end
s[#s + 1] = '\n}'
elseif kind == 'string' then
return '"' .. escape_str(obj) .. '"'
elseif kind == 'number' then
if as_key then return '"' .. tostring(obj) .. '"' end
return tostring(obj)
elseif kind == 'boolean' then
return tostring(obj)
elseif kind == 'nil' then
return 'null'
else
error('Unjsonifiable type: ' .. kind .. '.')
end
return table.concat(s)
local function encode_nil(val)
return "null"
end
json.null = {} -- This is a one-off table to represent the null value.
function json.parse(str, pos, end_delim)
pos = pos or 1
if pos > #str then error('Reached unexpected end of input.') end
local pos = pos + #str:match('^%s*', pos) -- Skip whitespace.
local first = str:sub(pos, pos)
if first == '{' then -- Parse an object.
local obj, key, delim_found = {}, true, true
pos = pos + 1
while true do
key, pos = json.parse(str, pos, '}')
if key == nil then return obj, pos end
if not delim_found then error('Comma missing between object items.') end
pos = skip_delim(str, pos, ':', true) -- true -> error if missing.
obj[key], pos = json.parse(str, pos)
pos, delim_found = skip_delim(str, pos, ',')
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if rawget(val, 1) ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
elseif first == '[' then -- Parse an array.
local arr, val, delim_found = {}, true, true
pos = pos + 1
while true do
val, pos = json.parse(str, pos, ']')
if val == nil then return arr, pos end
if not delim_found then error('Comma missing between array items.') end
arr[#arr + 1] = val
pos, delim_found = skip_delim(str, pos, ',')
end
elseif first == '"' then -- Parse a string.
return parse_str_val(str, pos + 1)
elseif first == '-' or first:match('%d') then -- Parse a number.
return parse_num_val(str, pos)
elseif first == end_delim then -- End of an object or array.
return nil, pos + 1
else -- Parse true, false, or null.
local literals = {['true'] = true, ['false'] = false, ['null'] = json.null}
for lit_str, lit_val in pairs(literals) do
local lit_end = pos + #lit_str - 1
if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
end
local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
error('Invalid json syntax starting at ' .. pos_info_str)
end
end
return json
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(3, 6), 16 )
local n2 = tonumber( s:sub(9, 12), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local has_unicode_escape = false
local has_surrogate_escape = false
local has_escape = false
local last
for j = i + 1, #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
end
if last == 92 then -- "\\" (escape char)
if x == 117 then -- "u" (unicode escape sequence)
local hex = str:sub(j + 1, j + 5)
if not hex:find("%x%x%x%x") then
decode_error(str, j, "invalid unicode escape in string")
end
if hex:find("^[dD][89aAbB]") then
has_surrogate_escape = true
else
has_unicode_escape = true
end
else
local c = string.char(x)
if not escape_chars[c] then
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
end
has_escape = true
end
last = nil
elseif x == 34 then -- '"' (end of string)
local s = str:sub(i + 1, j - 1)
if has_surrogate_escape then
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
end
if has_unicode_escape then
s = s:gsub("\\u....", parse_unicode_escape)
end
if has_escape then
s = s:gsub("\\.", escape_char_map_inv)
end
return s, j + 1
else
last = x
end
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json

View File

@ -1,995 +0,0 @@
-- This Source Code Form is subject to the terms of the bCDDL, v. 1.1.
-- If a copy of the bCDDL was not distributed with this
-- file, You can obtain one at http://beamng.com/bCDDL-1.1.txt
--[[
Usage:
a = vec3(1,2,3)
b = vec3({1,2,3})
c = vec3({x = 1, y = 2, z = 3})
print(a == b)
print( (a-b) == vec3(0, 0, 0) )
print( (c*1) )
print( vec3(10,0,0):dot(vec3(10,0,0)) )
]]
local min, max, sqrt, abs, random = math.min, math.max, math.sqrt, math.abs, math.random
local newLuaVec3xyz
local LuaVec3 = {}
LuaVec3.__index = LuaVec3
local ffifound, ffi = pcall(require, 'ffi')
if ffifound then
-- FFI available, so use it
ffi.cdef [[struct __luaVec3_t {double x,y,z;};
struct __luaQuat_t {double x,y,z,w;};]]
newLuaVec3xyz = ffi.typeof("struct __luaVec3_t")
ffi.metatype("struct __luaVec3_t", LuaVec3)
else
ffi = nil
-- no FFI available, compatibility mode
newLuaVec3xyz = function (x, y, z)
return (setmetatable({x = x, y = y, z = z}, LuaVec3)) -- parenthesis needed to workaround extreme slowdown from: NYI return to lower frame
end
end
function vec3toString(v)
return string.format('vec3(%.9g,%.9g,%.9g)', v.x, v.y, v.z)
end
function stringToVec3(s)
local args = split(s, ' ')
if #args ~= 3 then return nil end
return newLuaVec3xyz(tonumber(args[1]), tonumber(args[2]), tonumber(args[3]))
end
function vec3(x, y, z)
if y == nil then
if type(x) == 'table' and x[3] ~= nil then
return newLuaVec3xyz(x[1], x[2], x[3])
else
if x ~= nil then
return newLuaVec3xyz(x.x, x.y, x.z)
else
return newLuaVec3xyz(0, 0, 0)
end
end
else
return newLuaVec3xyz(x, y, z or 0)
end
end
function LuaVec3:set(x, y, z)
if y == nil then
self.x, self.y, self.z = x.x, x.y, x.z
else
self.x, self.y, self.z = x, y, z
end
end
function LuaVec3:xyz()
return self.x, self.y, self.z
end
function LuaVec3:__tostring()
return string.format('vec3(%.9g,%.9g,%.9g)', self.x, self.y, self.z)
end
function LuaVec3:toTable()
return {self.x, self.y, self.z}
end
function LuaVec3:toDict()
return {x = self.x, y = self.y, z = self.z}
end
function LuaVec3:length()
return sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
end
function LuaVec3:lengthGuarded()
return sqrt(self.x * self.x + self.y * self.y + self.z * self.z) + 1e-30
end
function LuaVec3:squaredLength()
return self.x * self.x + self.y * self.y + self.z * self.z
end
function LuaVec3.__add(a, b)
return newLuaVec3xyz(a.x + b.x, a.y + b.y, a.z + b.z)
end
function LuaVec3.__sub(a, b)
return newLuaVec3xyz(a.x - b.x, a.y - b.y, a.z - b.z)
end
function LuaVec3.__unm(a)
return newLuaVec3xyz(-a.x, -a.y, -a.z)
end
function LuaVec3.__mul(a, b)
if type(b) == 'number' then
return newLuaVec3xyz(b * a.x, b * a.y, b * a.z)
else
return newLuaVec3xyz(a * b.x, a * b.y, a * b.z)
end
end
function LuaVec3.__div(a,b)
if type(b) == 'number' then
b = 1 / b
return newLuaVec3xyz(b * a.x, b * a.y, b * a.z)
else
a = 1 / a
return newLuaVec3xyz(a * b.x, a * b.y, a * b.z)
end
end
function LuaVec3.__eq(a, b)
return b ~= nil and a.x == b.x and a.y == b.y and a.z == b.z
end
function LuaVec3:dot(a)
return self.x * a.x + self.y * a.y + self.z * a.z
end
function LuaVec3:cross(a)
return newLuaVec3xyz(self.y * a.z - self.z * a.y, self.z * a.x - self.x * a.z, self.x * a.y - self.y * a.x)
end
function LuaVec3:z0()
return newLuaVec3xyz(self.x, self.y, 0)
end
function LuaVec3:perpendicular()
local k = abs(self.x) + 0.5
k = k - math.floor(k)
return newLuaVec3xyz(-self.y, self.x - k * self.z, k * self.y)
end
function LuaVec3:perpendicularN()
local p = self:perpendicular()
local r = 1 / (p:length() + 1e-30)
p.x, p.y, p.z = p.x * r, p.y * r, p.z * r
return p
end
function LuaVec3:cosAngle(a)
return max(min(self:dot(a) / (sqrt(self:squaredLength() * a:squaredLength()) + 1e-30), 1), -1)
end
function LuaVec3:normalize()
local r = 1 / (self:length() + 1e-30)
self.x, self.y, self.z = self.x * r, self.y * r, self.z * r
end
function LuaVec3:resize(a)
local r = a / (self:length() + 1e-30)
self.x, self.y, self.z = self.x * r, self.y * r, self.z * r
end
function LuaVec3:normalized()
local r = 1 / (self:length() + 1e-30)
return newLuaVec3xyz(self.x * r, self.y * r, self.z * r)
end
function LuaVec3:resized(a)
local r = a / (self:length() + 1e-30)
return newLuaVec3xyz(self.x * r, self.y * r, self.z * r)
end
function LuaVec3:distance(a)
local tmp = self.x - a.x
local d = tmp * tmp
tmp = self.y - a.y
d = d + tmp * tmp
tmp = self.z - a.z
return sqrt(d + tmp * tmp)
end
function LuaVec3:squaredDistance(a)
local tmp = self.x - a.x
local d = tmp * tmp
tmp = self.y - a.y
d = d + tmp * tmp
tmp = self.z - a.z
return d + tmp * tmp
end
-- a, b are two line points, self is the point
function LuaVec3:distanceToLine(a, b)
local ab = a - b
local an = a - self
return an:distance(ab * (ab:dot(an) / (ab:squaredLength() + 1e-30)))
end
function LuaVec3:squaredDistanceToLine(a, b)
local ab = a - b
local an = a - self
return an:squaredDistance(ab * (ab:dot(an) / (ab:squaredLength() + 1e-30)))
end
function LuaVec3:distanceToLineSegment(a, b)
local ab, an = a - b, a - self
return an:distance(ab * min(max(ab:dot(an) / (ab:squaredLength() + 1e-30), 0), 1))
end
function LuaVec3:xnormDistanceToLineSegment(a, b)
local ab, an = a - b, a - self
local xnorm = ab:dot(an) / (ab:squaredLength() + 1e-30)
return xnorm, an:distance(ab * min(max(xnorm, 0), 1))
end
function LuaVec3:squaredDistanceToLineSegment(a, b)
local ab, an = a - b, a - self
return an:squaredDistance(ab * min(max(ab:dot(an) / (ab:squaredLength() + 1e-30), 0), 1))
end
function LuaVec3:xnormSquaredDistanceToLineSegment(a, b)
local ab, an = a - b, a - self
local xnorm = ab:dot(an) / (ab:squaredLength() + 1e-30)
return xnorm, an:squaredDistance(ab * min(max(xnorm, 0), 1))
end
function LuaVec3:xnormOnLine(a, b)
local ba = b - a
return ba:dot(self - a) / (ba:squaredLength() + 1e-30)
end
function LuaVec3:triangleBarycentricNorm(a, b, c)
local ca, bc = c - a, b - c
local norm = ca:cross(bc)
local normsqlen = norm:squaredLength() + 1e-30
local pacnorm = (self - c):cross(norm)
return bc:dot(pacnorm) / normsqlen, ca:dot(pacnorm) / normsqlen, norm / sqrt(normsqlen)
end
function LuaVec3:toPoint3F()
return Point3F(self.x, self.y, self.z)
end
function LuaVec3:toFloat3()
return float3(self.x, self.y, self.z)
end
function LuaVec3:projectToOriginPlane(pnorm)
return self - (pnorm * (self:dot(pnorm)))
end
-- self is a point in plane
function LuaVec3:xnormPlaneWithLine(pnorm, a, b)
return (self - a):dot(pnorm) / ((b - a):dot(pnorm) + 1e-30)
end
-- self is center of sphere, returns two xnorms (low, high). It returns pair 1,0 if no hit found
function LuaVec3:xnormsSphereWithLine(radius, a, b)
local lDif = b - a
local invDif2len = 1 / max(lDif:squaredLength(), 1e-30)
local ac = a - self
local dotab = -ac:dot(lDif) * invDif2len
local D = dotab * dotab + (radius * radius - ac:squaredLength()) * invDif2len
if D >= 0 then
D = sqrt(D)
return dotab - D, dotab + D
else
return 1, 0
end
end
function LuaVec3:basisCoordinates(c1, c2, c3)
local c2xc3 = c2:cross(c3)
local invDet = 1 / c1:dot(c2xc3)
return newLuaVec3xyz(c2xc3:dot(self)*invDet, c3:cross(c1):dot(self)*invDet, c1:cross(c2):dot(self)*invDet)
end
function LuaVec3:componentMul(b)
return newLuaVec3xyz(self.x * b.x, self.y * b.y, self.z * b.z)
end
function LuaVec3:setMin(b)
self.x, self.y, self.z = min(self.x, b.x), min(self.y, b.y), min(self.z, b.z)
end
function LuaVec3:setMax(b)
self.x, self.y, self.z = max(self.x, b.x), max(self.y, b.y), max(self.z, b.z)
end
function LuaVec3:setAdd(b)
self.x, self.y, self.z = self.x + b.x, self.y + b.y, self.z + b.z
end
function LuaVec3:setSub(b)
self.x, self.y, self.z = self.x - b.x, self.y - b.y, self.z - b.z
end
function LuaVec3:setScaled(b)
self.x, self.y, self.z = self.x * b, self.y * b, self.z * b
end
local function fractPos(x)
return x - math.floor(x)
end
function LuaVec3:getBlueNoise3d()
self.x, self.y, self.z = fractPos(self.x + 0.81917251339616443), fractPos(self.y + 0.6710436067037892084), fractPos(self.z + 0.54970047790197026)
return self
end
function LuaVec3:getBlueNoise2d()
self.x, self.y, self.z = fractPos(self.x + 0.75487766624669276), fractPos(self.y + 0.56984029099805327), 0
return self
end
function LuaVec3:getRandomPointInSphere(radius)
radius = radius or 1
local sx, sy, sz = 0, 0, 0;
for i = 1, 4 do
local x, y, z = random(), random(), random()
sx, sy, sz = sx + x, sy + y, sz + z
local xc, yc, zc = x - 0.5, y - 0.5, z - 0.5
if xc * xc + yc * yc + zc * zc <= 0.25 then
local r2 = radius * 2
self.x, self.y, self.z = xc * r2, yc * r2, zc * r2
return self
end
end
sx, sy, sz = sx - 2, sy - 2, sz - 2
local u = random()
local norm = sqrt(0.5 * (sqrt(u) + u)/(sx*sx + sy*sy + sz*sz + 1e-25)) * radius
self.x, self.y, self.z = sx*norm, sy*norm, sz*norm
return self
end
function LuaVec3:getRandomPointInCircle(radius)
radius = radius or 1
local sx, sy = 0, 0
for i = 1, 4 do
local x, y = random(), random()
sx, sy = sx + x, sy + y
local xc, yc = x - 0.5, y - 0.5
if xc * xc + yc * yc <= 0.25 then
local r2 = radius * 2
self.x, self.y, self.z = xc * r2, yc * r2, 0
return self
end
end
sx, sy = sx - 2, sy - 2
local norm = sqrt(random()/(sx*sx+sy*sy + 1e-25)) * radius
self.x, self.y, self.z = sx*norm, sy*norm, 0
return self
end
function LuaVec3:getBluePointInSphere(radius)
radius = radius or 1
local bx, by, bz = self.x, self.y, self.z
for i = 1, 8 do -- seen up to 6
local x, y, z = fractPos(bx + 0.81917251339616443 * i), fractPos(by + 0.6710436067037892084 * i), fractPos(bz + 0.54970047790197026 * i)
local xc, yc, zc = x - 0.5, y - 0.5, z - 0.5
if xc * xc + yc * yc + zc * zc <= 0.25 then
self.x, self.y, self.z = x, y, z
local r2 = radius * 2
return newLuaVec3xyz(xc * r2, yc * r2, zc * r2)
end
end
return newLuaVec3xyz(0, 0, 0)
end
function LuaVec3:getBluePointInCircle(radius)
radius = radius or 1
local bx, by = self.x, self.y
for i = 1, 5 do -- seen up to 3
local x, y = fractPos(bx + 0.75487766624669276 * i), fractPos(by + 0.56984029099805327 * i)
local xc, yc = x - 0.5, y - 0.5
if xc * xc + yc * yc <= 0.25 then
self.x, self.y = x, y
local r2 = radius * 2
return newLuaVec3xyz(xc * r2, yc * r2, 0)
end
end
return newLuaVec3xyz(0, 0, 0)
end
-- returns random gauss number in [0..3]
function randomGauss3()
return random() + random() + random()
end
-- returns xnormals for the two lines: http://geomalgorithms.com/a07-_distance.html
function closestLinePoints(l1p0, l1p1, l2p0, l2p1)
local a, b, c, d, e
do
-- limit the number of live vars to help out luajit
local u = l1p1 - l1p0
local v = l2p1 - l2p0
local w = l1p0 - l2p0
a = u:squaredLength()
b = u:dot(v)
c = v:squaredLength()
d = u:dot(w)
e = v:dot(w)
end
local D = a * c - b * b
if D < 1e-8 then
local tc
if b > c then
tc = d / b
else
tc = e / (c + 1e-30)
end
return 0, tc
else
return (b * e - c * d) / D, (a * e - b * d) / D
end
end
function linePointFromXnorm(p0, p1, xnorm)
return newLuaVec3xyz(p0.x + (p1.x-p0.x) * xnorm, p0.y + (p1.y-p0.y) * xnorm, p0.z + (p1.z-p0.z) * xnorm)
end
--------------------------------------------------------------------------------
-- Quaternion
local LuaQuat = {}
LuaQuat.__index = LuaQuat
local newLuaQuatxyzw
if ffi then
newLuaQuatxyzw = ffi.typeof("struct __luaQuat_t")
ffi.metatype("struct __luaQuat_t", LuaQuat)
else
newLuaQuatxyzw = function (_x, _y, _z, _w)
return (setmetatable({ x = _x, y = _y, z = _z, w = _w }, LuaQuat)) -- parenthesis needed to workaround extreme slowdown from: NYI return to lower frame
end
end
-- Returns quat. Both inputs should be normalized
function LuaVec3:getRotationTo(v)
local w = 1 + self:dot(v)
local qv
if (w < 1e-6) then
w = 0
qv = v:perpendicular()
else
qv = self:cross(v)
end
local q = newLuaQuatxyzw(qv.x, qv.y, qv.z, -w)
q:normalize()
return q
end
-- Rotates by quaternion q
function LuaVec3:rotated(q)
local qv = newLuaVec3xyz(q.x, q.y, q.z)
local t = 2 * qv:cross(self)
return self - q.w * t + qv:cross(t)
end
-- we follow t3d's quat convention which has uses a negative w :/
function quat(x, y, z, w)
if y == nil then
if type(x) == 'table' and x[4] ~= nil then
return newLuaQuatxyzw(x[1], x[2], x[3], x[4])
elseif x == nil then
return newLuaQuatxyzw(1, 0, 0, 0)
else
return newLuaQuatxyzw(x.x, x.y, x.z, x.w)
end
else
return newLuaQuatxyzw(x, y, z, w)
end
end
function LuaQuat:__tostring()
return string.format('quat(%.9g,%.9g,%.9g,%.9g)', self.x, self.y, self.z, self.w)
end
function LuaQuat:toTable()
return {self.x, self.y, self.z, self.w}
end
function LuaQuat:toDict()
return {x = self.x, y = self.y, z = self.z, w = self.w}
end
function LuaQuat:set(a)
self.x, self.y, self.z, self.w = a.x, a.y, a.z, a.w
end
function LuaQuat:norm()
return sqrt(self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w)
end
function LuaQuat:squaredNorm()
return self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w
end
function LuaQuat:normalize()
local r = 1/(self:norm() + 1e-30)
self.x, self.y, self.z, self.w = self.x * r, self.y * r, self.z * r, self.w * r
end
function LuaQuat:normalized()
local r = 1/(self:norm() + 1e-30)
return newLuaQuatxyzw(self.x * r, self.y * r, self.z * r, self.w * r)
end
function LuaQuat:inversed()
local InvSqNorm = -1 / (self:squaredNorm() + 1e-30)
return newLuaQuatxyzw(self.x * InvSqNorm, self.y * InvSqNorm, self.z * InvSqNorm, -self.w * InvSqNorm)
end
function LuaQuat.__unm(a)
return newLuaQuatxyzw(-a.x, -a.y, -a.z, -a.w)
end
function LuaQuat.__mul(a, b)
if type(a) == 'number' then
return newLuaQuatxyzw(b.x * a, b.y * a, b.z * a, b.w * a)
elseif type(b) == 'number' then
return newLuaQuatxyzw(a.x * b, a.y * b, a.z * b, a.w * b)
elseif (ffi and ffi.istype('struct __luaVec3_t', b)) or b.w == nil then
local qv = newLuaVec3xyz(a.x, a.y, a.z)
local t = 2 * qv:cross(b)
return b - a.w * t + qv:cross(t)
else
return newLuaQuatxyzw(a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
a.w * b.y + a.y * b.w + a.z * b.x - a.x * b.z,
a.w * b.z + a.z * b.w + a.x * b.y - a.y * b.x,
a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z)
end
end
function LuaQuat.__sub(a, b)
return newLuaQuatxyzw(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w)
end
function LuaQuat.__div(a, b)
if type(a) == 'number' then
return newLuaQuatxyzw(b.x / a, b.y / a, b.z / a, b.w / a)
elseif type(b) == 'number' then
return newLuaQuatxyzw(a.x / b, a.y / b, a.z / b, a.w / b)
end
return a * b:inversed()
end
function LuaQuat.__add(a, b)
return newLuaQuatxyzw(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w)
end
function LuaQuat:dot(a)
return self.x * a.x + self.y * a.y + self.z * a.z + self.w * a.w
end
function LuaQuat:distance(a)
return 0.5 * (self - a):squaredNorm()
end
function LuaQuat:nlerp(a, t)
local tmp = (1 - t) * self + (self:dot(a) < 0 and -t or t) * a
tmp:normalize()
return tmp
end
function LuaQuat:slerp(a, t)
local dot = clamp(self:dot(a), -1, 1)
if dot > 0.9995 then
return self:nlerp(a, t)
end
local theta = math.acos(dot)*t
return (self*math.cos(theta) + (a - self*dot):normalized()*math.sin(theta)):normalized();
end
-- returns reverse rotation
function LuaQuat:conjugated()
return newLuaQuatxyzw(-self.x, -self.y, -self.z, self.w)
end
function LuaQuat:scale(a)
self.x, self.y, self.z, self.w = self.x * a, self.y * a, self.z * a, self.w * a
return self
end
--http://bediyap.com/programming/convert-quaternion-to-euler-rotations/
function LuaQuat.toEulerYXZ(q)
local wxsq = q.w*q.w-q.x*q.x
local yzsq = q.z*q.z-q.y*q.y
return newLuaVec3xyz(
math.atan2(2*(q.x*q.y + q.w*q.z), wxsq-yzsq),
math.asin(max(min(-2*(q.y*q.z - q.w*q.x), 1), -1)),
math.atan2(2*(q.x*q.z + q.w*q.y), wxsq+yzsq))
end
function LuaQuat:toTorqueQuat()
local sinhalf = math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
local tw = math.acos(self.w) * 360 / math.pi
if sinhalf ~= 0 then
return {x = self.x / sinhalf, y = self.y / sinhalf, z = self.z / sinhalf, w = tw}
else
return {x = 1, y = 0, z = 0, w = tw}
end
end
-- function LuaQuat:pow(a)
-- self:scale(a)
-- local vlen = sqrt( self.x*self.x + self.y*self.y + self.z*self.z )
-- local ret = math.exp(self.w)
-- local coef = ret * math.sin(vlen) / (vlen + 1e-60)
-- return newLuaQuatxyzw( coef*self.x, coef*self.y, coef*self.z, -ret* math.cos(vlen) )
-- end
local function quatFromAxesMatrix(m)
local q = {[0] = 0, 0, 0, 0}
local trace = m[0][0] + m[1][1] + m[2][2];
if trace > 0 then
local s = sqrt(trace + 1)
q[3] = s * 0.5
s = 0.5 / s
q[0] = (m[1][2] - m[2][1]) * s
q[1] = (m[2][0] - m[0][2]) * s
q[2] = (m[0][1] - m[1][0]) * s
else
local i = 0
if m[1][1] > m[0][0] then i = 1 end
if m[2][2] > m[i][i] then i = 2 end
local j = (i + 1) % 3
local k = (j + 1) % 3
local s = sqrt((m[i][i] - (m[j][j] + m[k][k])) + 1)
q[i] = s * 0.5
s = 0.5 / s
q[j] = (m[i][j] + m[j][i]) * s
q[k] = (m[i][k] + m[k][i]) * s
q[3] = (m[j][k] - m[k][j]) * s
end
local tmp = newLuaQuatxyzw(q[0], q[1], q[2], q[3])
tmp:normalize()
return tmp
end
function quatFromDir(dir, up)
local k = up or vec3(0, 0, 1)
local dirNorm = dir:normalized()
local i = dirNorm:cross(k)
i:normalize()
k = i:cross(dirNorm)
k:normalize()
return quatFromAxesMatrix({[0]={[0]=i.x, dirNorm.x, k.x}, {[0]=i.y, dirNorm.y, k.y}, {[0]=i.z, dirNorm.z, k.z}})
end
function lookAt(lookAt, up)
up = up or vec3(0, 0, 1)
local forward = lookAt:normalized()
local right = forward:cross(up)
right:normalize()
up = right:cross(forward)
up:normalize()
local w = sqrt(1 + right.x + up.y + forward.z) * 0.5
local w4_recip = 1 / (4 * w)
local x = (forward.y - up.z) * w4_recip
local y = (right.z - forward.x) * w4_recip
local z = (up.x - right.y) * w4_recip
return newLuaQuatxyzw(x,y,z,w)
end
function quatFromAxisAngle(axle, angleRad)
angleRad = angleRad * 0.5
local fsin = math.sin(angleRad)
return newLuaQuatxyzw(fsin * axle.x, fsin * axle.y, fsin * axle.z, math.cos(angleRad))
end
function quatFromEuler(x, y, z)
x, y, z = x * 0.5, y * 0.5, z * 0.5
local sx = math.sin(x)
local cx = math.cos(x)
local sy = math.sin(y)
local cy = math.cos(y)
local sz = math.sin(z)
local cz = math.cos(z)
local cycz, sysz, sycz, cysz = cy*cz, sy*sz, sy*cz, cy*sz
return newLuaQuatxyzw(cycz*sx + sysz*cx, sycz*cx + cysz*sx, cysz*cx - sycz*sx, cycz*cx - sysz*sx)
end
-- returns -1, 1
function sign2(x)
return max(min(x * math.huge, 1), -1)
end
-- returns -1, 0, 1
function sign(x)
return max(min((x * 1e200) * 1e200, 1), -1)
end
fsign = sign
-- returns sign(s) * abs(v)
function signApply(s, v)
local absv = abs(v)
return max(min((s * 1e200) * 1e200, absv), -absv)
end
function guardZero(x) --branchless
return 1 / max(min(1/x, 1e300), -1e300)
end
function clamp(x, minValue, maxValue )
return min(max(x, minValue), maxValue)
end
function square(a)
return a * a
end
function round(a)
return math.floor(a+.5)
end
function isnan(a)
return not(a == a)
end
function isinf(a)
return abs(a) == math.huge
end
function isnaninf(a)
return a * 0 ~= 0
end
function linearScale(v, minValue, maxValue, minOutput, maxOutput)
return minOutput + min(max((v - minValue) / (maxValue - minValue), 0), 1) * (maxOutput - minOutput)
end
function lerp(from, to, t)
return from + (to - from) * t -- monotonic
end
function smoothstep(x)
x = min(max(x, 0), 1) -- monotonic guard
return x*x*(3 - 2*x)
end
function smootherstep(x)
return min(max(x*x*x*(x*(x*6 - 15) + 10), 0), 1)
end
function smootheststep(x)
x = min(max(x, 0), 1)
return square(x*x)*(35-x*(x*(x*20-70)+84))
end
function smoothmin(a, b, k)
k = k or 0.1
local h = min(max(0.5 + 0.5*(b-a)/k, 0), 1)
return a*h - (b - k*h)*(1-h)
end
function biasFun(x, k)
local xk = x * k
return (x + xk)/(1 + xk)
end
function nanError(x)
if x ~= x then
error('NaN found')
end
return x
end
function axisSystemCreate(nx, ny, nz)
local rx, ry, rz = vec3(), vec3(), vec3()
local row = ny:cross(nz)
local invdet = 1 / nx:dot(row)
row = row * invdet
rx.x, ry.x, rz.x = row.x, row.y, row.z
row = nz:cross(nx) * invdet
rx.y, ry.y, rz.y = row.x, row.y, row.z
row = nx:cross(ny) * invdet
rx.z, ry.z, rz.z = row.x, row.y, row.z
return rx, ry, rz
end
function axisSystemApply(nx, ny, nz, v)
return nx * v.x + ny * v.y + nz * v.z
end
function cardinalSpline(p0, p1, p2, p3, t, s, d1, d2, d3)
d1, d2, d3 = max(d1 or 1, 1e-30), d2 or 1, max(d3 or 1, 1e-30)
s = (s or 0.5) * 2
local sd2 = s * d2
local tt, t_1 = t * t, t-1
local t_1sq = t_1 * t_1
local m1 = (p1 - p0) / d1 + (p0 - p2) / (d1 + d2)
local m2 = (p1 - p3) / (d2 + d3) + (p3 - p2) / d3
return t*t_1sq*sd2*m1 + tt*t_1*sd2*m2 + t_1sq * (2 * t + 1) * p1 - tt * (2*t-3) * p2 + s*t_1*(t*t_1 + tt) * (p2 - p1)
end
function catmullRom(p0, p1, p2, p3, t, s)
return cardinalSpline(p0, p1, p2, p3, t, s or 0.5, 1, 1, 1)
end
function catmullRomChordal(p0, p1, p2, p3, t, s)
return cardinalSpline(p0, p1, p2, p3, t, s or 0.5, p0:distance(p1), p1:distance(p2), p2:distance(p3))
end
function catmullRomCentripetal(p0, p1, p2, p3, t, s)
return cardinalSpline(p0, p1, p2, p3, t, s or 0.5, sqrt(p0:distance(p1)), sqrt(p1:distance(p2)), sqrt(p2:distance(p3)))
end
function monotonicSteffen(y0, y1, y2, y3, x0, x1, x2, x3, x)
local x1x0, x2x1, x3x2 = x1-x0, x2-x1, x3-x2
local delta0, delta1, delta2 = (y1-y0) / (x1x0 + 1e-30), (y2-y1) / (x2x1 + 1e-30), (y3-y2) / (x3x2 + 1e-30)
local m1 = (sign(delta0)+sign(delta1)) * min(abs(delta0),abs(delta1), 0.5*abs((x2x1*delta0 + x1x0*delta1) / (x2-x0 + 1e-30)))
local m2 = (sign(delta1)+sign(delta2)) * min(abs(delta1),abs(delta2), 0.5*abs((x3x2*delta1 + x2x1*delta2) / (x3-x1 + 1e-30)))
local xx1 = x - x1
local xrel = xx1 / max(x2x1, 1e-30)
return y1 + xx1*(m1 + xrel*(delta1 - m1 + (xrel - 1)*(m1 + m2 - 2*delta1)))
end
function biQuadratic(p0, p1, p2, p3, t)
local p12 = p1 + (p2 - p1) * (t * 0.5 + 0.25)
if t <= 0.5 then
local p01 = p0 + (p1 - p0) * (t * 0.5 + 0.75)
return p01 + (p12 - p01) * (t + 0.5)
else
return p12 + (p2 + (p3 - p2) * (t * 0.5 - 0.25) - p12) * (t - 0.5)
end
end
function overlapsOBB_OBB(c1, x1, y1, z1, c2, x2, y2, z2)
local cc = c1 - c2
local d11, d12, d13 = abs(x1:dot(x2)), abs(x1:dot(y2)), abs(x1:dot(z2))
local d21, d22, d23 = abs(y1:dot(x2)), abs(y1:dot(y2)), abs(y1:dot(z2))
local d31, d32, d33 = abs(z1:dot(x2)), abs(z1:dot(y2)), abs(z1:dot(z2))
return abs(cc:dot(x1))-d11-d12-d13<=x1:squaredLength() and abs(cc:dot(y1))-d21-d22-d23<=y1:squaredLength()
and abs(cc:dot(z1))-d31-d32-d33<=z1:squaredLength() and abs(cc:dot(x2))-d11-d21-d31<=x2:squaredLength()
and abs(cc:dot(y2))-d12-d22-d32<=y2:squaredLength() and abs(cc:dot(z2))-d13-d23-d33<=z2:squaredLength()
end
-- untested
function containsOBB_OBB(c1, x1, y1, z1, c2, x2, y2, z2)
local cc = c1 - c2
return abs(cc:dot(x1))+abs(x1:dot(x2))+abs(x1:dot(y2))+abs(x1:dot(z2))<=x1:squaredLength()
and abs(cc:dot(y1))+abs(y1:dot(x2))+abs(y1:dot(y2))+abs(y1:dot(z2))<=y1:squaredLength()
and abs(cc:dot(z1))+abs(z1:dot(x2))+abs(z1:dot(y2))+abs(z1:dot(z2))<=z1:squaredLength()
end
function overlapsOBB_Sphere(c1, x1, y1, z1, c2, r2)
local cc = c1 - c2
local x1len, y1len, z1len = x1:length(), y1:length(), z1:length()
local ccx, ccy, ccz = abs(cc:dot(x1)), abs(cc:dot(y1)), abs(cc:dot(z1))
return ccx<=x1len*(x1len+r2) and ccy<=y1len*(y1len+r2) and ccz<=z1len*(z1len+r2)
and (ccx<=x1len*x1len or ccy<=y1len*y1len or ccz<=z1len*z1len or
square(ccx/(x1len+1e-30)-x1len)+square(ccy/(y1len+1e-30)-y1len)+square(ccz/(z1len+1e-30)-z1len)<=r2*r2)
end
function overlapsOBB_Plane(c1, x1, y1, z1, plpos, pln)
return abs((c1 - plpos):dot(pln))<=abs(x1:dot(pln))+abs(y1:dot(pln))+abs(z1:dot(pln))
end
function containsOBB_Sphere(c1, x1, y1, z1, c2, r2)
local cc = c1 - c2
local x1len, y1len, z1len = x1:length(), y1:length(), z1:length()
return abs(cc:dot(x1))<=x1len*(x1len-r2) and abs(cc:dot(y1))<=y1len*(y1len-r2) and abs(cc:dot(z1))<=z1len*(z1len-r2)
end
function containsSphere_OBB(c1, r1, c2, x2, y2, z2)
local cc = c1 - c2
local ccx1, cc_x2, y2z2, y2_z2 = cc+x2, cc-x2, y2+z2, y2-z2
return max((ccx1+y2z2):squaredLength(), (ccx1+y2_z2):squaredLength(), (ccx1-y2_z2):squaredLength(), (ccx1-y2z2):squaredLength(),
(cc_x2+y2z2):squaredLength(), (cc_x2+y2_z2):squaredLength(), (cc_x2-y2_z2):squaredLength(), (cc_x2-y2z2):squaredLength())<=r1*r1
end
function containsOBB_point(c1, x1, y1, z1, p)
local cc = c1 - p
return abs(cc:dot(x1))<=x1:squaredLength() and abs(cc:dot(y1))<=y1:squaredLength() and abs(cc:dot(z1))<=z1:squaredLength()
end
function containsEllipsoid_Point(c1, x1, y1, z1, p)
local cc = p - c1
local x, y, z = cc:dot(x1), cc:dot(y1), cc:dot(z1)
local a2, b2, c2 = x1:squaredLength(), y1:squaredLength(), z1:squaredLength()
a2, b2, c2 = a2*a2, b2*b2, c2*c2
local b2c2 = b2*c2
return x*x*b2c2 + a2*(y*y*c2 + z*z*b2) <= a2*b2c2
end
function constainsCylinder_Point(cposa, cposb, cR, p)
local xnorm, r2 = p:xnormSquaredDistanceToLineSegment(cposa, cposb)
return xnorm >=0 and xnorm <= 1 and r2 <= cR*cR
end
function altitudeOBB_Plane(c1, x1, y1, z1, plpos, pln)
return (c1 - plpos):dot(pln)+abs(x1:dot(pln))+abs(y1:dot(pln))+abs(z1:dot(pln))
end
-- returns signed distance of plane on the ray
function intersectsRay_Plane(rpos, rdir, plpos, pln)
return min((plpos - rpos):dot(pln) / rdir:dot(pln), math.huge)
end
-- hit: minhit < maxhit, inside: minhit < 0
function intersectsRay_OBB(rpos, rdir, c1, x1, y1, z1)
local rposc1 = c1 - rpos
local rposc1x1, x1sq, invrdirx1 = rposc1:dot(x1), x1:squaredLength(), 1 / rdir:dot(x1)
local dx1, dx2 = (rposc1x1 - x1sq) * invrdirx1, min((rposc1x1 + x1sq) * invrdirx1, math.huge)
local rposc1y1, y1sq, invrdiry1 = rposc1:dot(y1), y1:squaredLength(), 1 / rdir:dot(y1)
local dy1, dy2 = (rposc1y1 - y1sq) * invrdiry1, min((rposc1y1 + y1sq) * invrdiry1, math.huge)
local rposc1z1, z1sq, invrdirz1 = rposc1:dot(z1), z1:squaredLength(), 1 / rdir:dot(z1)
local dz1, dz2 = (rposc1z1 - z1sq) * invrdirz1, min((rposc1z1 + z1sq) * invrdirz1, math.huge)
local minhit, maxhit = max(min(dx1, dx2), min(dy1, dy2), min(dz1, dz2)), min(max(dx1, dx2), max(dy1, dy2), max(dz1, dz2))
return (minhit <= maxhit and minhit or math.huge), maxhit
end
function intersectsRay_Sphere(rpos, rdir, cpos, cr)
local rcpos = cpos - rpos
local dcr = rdir:dot(rcpos)
local s = dcr*dcr - rcpos:squaredLength() + cr*cr
if s < 0 then return math.huge, math.huge end
s = sqrt(s)
return dcr - s, dcr + s
end
function intersectsRay_Ellipsoid(rpos, rdir, c1, x1, y1, z1)
local invx1, invy1, invz1 = 1 / (x1:squaredLength() + 1e-30), 1 / (y1:squaredLength() + 1e-30), 1 / (z1:squaredLength() + 1e-30)
local cc = rpos - c1
local pM = vec3(cc:dot(x1)*invx1, cc:dot(y1)*invy1, cc:dot(z1)*invz1)
local dirM = vec3(rdir:dot(x1)*invx1, rdir:dot(y1)*invy1, rdir:dot(z1)*invz1)
local a, b, c = dirM:squaredLength(), 2*pM:dot(dirM), pM:squaredLength() - 1
local d = b*b - 4*a*c
if d < 0 then return math.huge, math.huge end
d = -b -sign(b)*sqrt(d)
local r1, r2 = 0.5*d / a, 2*c / d
return min(r1, r2), max(r1,r2)
end
function intersectsRay_Cylinder(rpos, rdir, cposa, cposb, cR)
local rca, cba = cposa - rpos, cposb - cposa
local cpnorm = cba:normalized()
local cp = rca:projectToOriginPlane(cpnorm)
local rdp = rdir:projectToOriginPlane(cpnorm)
local minhit, maxhit = intersectsRay_Sphere(vec3(0,0,0), rdp:normalized(), cp, cR)
local invrdplen = 1 / (rdp:length() + 1e-30)
minhit, maxhit = minhit * invrdplen, maxhit * invrdplen
local plhita, plhitb = intersectsRay_Plane(rpos, rdir, cposa, cpnorm), intersectsRay_Plane(rpos, rdir, cposb, cpnorm)
minhit, maxhit = max(minhit, min(plhita, plhitb)), min(maxhit, max(plhita, plhitb))
return (minhit <= maxhit and minhit or math.huge), maxhit
end
-- returns hit distance, barycentric x, y
function intersectsRay_Triangle(rpos, rdir, a, b, c)
local ca, bc = c - a, b - c
local norm = ca:cross(bc)
local rposc = rpos - c
local pOnTri = rposc:dot(norm) / rdir:dot(norm)
if pOnTri <= 0 then
local pacnorm = (rposc - rdir * pOnTri):cross(norm)
local bx, by = bc:dot(pacnorm), ca:dot(pacnorm)
local normSq = norm:squaredLength() + 1e-30
if min(bx, by) >= 0 and bx + by <= normSq then
return -pOnTri, bx / normSq, by / normSq
end
end
return math.huge, -1, -1
end