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

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