modlib/vector.lua

274 lines
5.7 KiB
Lua
Raw Normal View History

-- Localize globals
2021-10-10 19:59:53 +02:00
local assert, math, pairs, rawget, rawset, setmetatable, unpack, vector = assert, math, pairs, rawget, rawset, setmetatable, unpack, vector
2021-06-17 19:45:08 +02:00
-- Set environment
local _ENV = {}
setfenv(1, _ENV)
local mt_vector = vector
index_aliases = {
2021-03-27 20:10:49 +01:00
x = 1,
y = 2,
z = 3,
2021-08-29 10:52:41 +02:00
w = 4;
"x", "y", "z", "w";
}
metatable = {
2021-03-27 20:10:49 +01:00
__index = function(table, key)
local index = index_aliases[key]
if index ~= nil then
2021-10-10 19:59:53 +02:00
return rawget(table, index)
2021-03-27 20:10:49 +01:00
end
2021-06-17 19:45:08 +02:00
return _ENV[key]
2021-03-27 20:10:49 +01:00
end,
__newindex = function(table, key, value)
2021-06-17 19:45:08 +02:00
-- TODO
2021-06-18 20:57:15 +02:00
local index = index_aliases[key]
2021-03-27 20:10:49 +01:00
if index ~= nil then
return rawset(table, index, value)
end
end
}
function new(v)
2021-03-27 20:10:49 +01:00
return setmetatable(v, metatable)
end
2022-01-12 12:16:02 +01:00
function zeros(n)
local v = {}
for i = 1, n do
v[i] = 0
end
return new(v)
end
function from_xyzw(v)
2021-03-27 20:10:49 +01:00
return new{v.x, v.y, v.z, v.w}
end
2020-12-19 15:28:53 +01:00
function from_minetest(v)
2021-03-27 20:10:49 +01:00
return new{v.x, v.y, v.z}
2020-12-19 15:28:53 +01:00
end
function to_xyzw(v)
2021-03-27 20:10:49 +01:00
return {x = v[1], y = v[2], z = v[3], w = v[4]}
end
--+ not necessarily required, as Minetest respects the metatable
function to_minetest(v)
2021-03-27 20:10:49 +01:00
return mt_vector.new(unpack(v))
end
2021-03-24 10:38:10 +01:00
function equals(v, w)
2021-03-27 20:10:49 +01:00
for k, v in pairs(v) do
if v ~= w[k] then return false end
end
return true
end
metatable.__eq = equals
2021-03-24 10:38:10 +01:00
function less_than(v, w)
2021-03-27 20:10:49 +01:00
for k, v in pairs(v) do
if v >= w[k] then return false end
end
return true
end
metatable.__lt = less_than
2021-03-24 10:38:10 +01:00
function less_or_equal(v, w)
2021-03-27 20:10:49 +01:00
for k, v in pairs(v) do
if v > w[k] then return false end
end
return true
end
metatable.__le = less_or_equal
2021-03-24 10:38:10 +01:00
function combine(v, w, f)
2021-03-27 20:10:49 +01:00
local new_vector = {}
for key, value in pairs(v) do
new_vector[key] = f(value, w[key])
end
return new(new_vector)
end
2020-12-19 15:28:53 +01:00
function apply(v, f, ...)
2021-03-27 20:10:49 +01:00
local new_vector = {}
for key, value in pairs(v) do
new_vector[key] = f(value, ...)
end
return new(new_vector)
end
function combinator(f)
2021-03-27 20:10:49 +01:00
return function(v, w)
return combine(v, w, f)
end, function(v, ...)
return apply(v, f, ...)
end
end
2020-12-27 23:22:28 +01:00
function invert(v)
2022-01-12 12:15:17 +01:00
local res = {}
2021-03-27 20:10:49 +01:00
for key, value in pairs(v) do
2022-01-12 12:15:17 +01:00
res[key] = -value
2021-03-27 20:10:49 +01:00
end
2022-01-12 12:15:17 +01:00
return new(res)
2020-12-27 23:22:28 +01:00
end
2021-03-24 10:38:10 +01:00
add, add_scalar = combinator(function(v, w) return v + w end)
subtract, subtract_scalar = combinator(function(v, w) return v - w end)
multiply, multiply_scalar = combinator(function(v, w) return v * w end)
divide, divide_scalar = combinator(function(v, w) return v / w end)
pow, pow_scalar = combinator(function(v, w) return v ^ w end)
metatable.__add = add
2020-12-27 23:22:28 +01:00
metatable.__unm = invert
metatable.__sub = subtract
metatable.__mul = multiply
metatable.__div = divide
2021-02-02 15:43:55 +01:00
--+ linear interpolation
--: ratio number from 0 (all the first vector) to 1 (all the second vector)
2021-03-24 10:38:10 +01:00
function interpolate(v, w, ratio)
2021-04-01 00:04:56 +02:00
return add(multiply_scalar(v, 1 - ratio), multiply_scalar(w, ratio))
2021-02-02 15:43:55 +01:00
end
2020-09-22 19:14:21 +02:00
function norm(v)
2021-03-27 20:10:49 +01:00
local sum = 0
for _, value in pairs(v) do
sum = sum + value ^ 2
end
return sum
2020-09-22 19:14:21 +02:00
end
function length(v)
2021-03-27 20:10:49 +01:00
return math.sqrt(norm(v))
2020-09-22 19:14:21 +02:00
end
-- Minor code duplication for the sake of performance
2021-03-24 10:38:10 +01:00
function distance(v, w)
2021-03-27 20:10:49 +01:00
local sum = 0
for key, value in pairs(v) do
sum = sum + (value - w[key]) ^ 2
end
return math.sqrt(sum)
end
2020-09-22 19:14:21 +02:00
function normalize(v)
2021-03-27 20:10:49 +01:00
return divide_scalar(v, length(v))
2020-12-19 15:28:53 +01:00
end
2022-01-12 12:16:55 +01:00
function normalize_zero(v)
local len = length(v)
if len == 0 then
-- Return a zeroed vector with the same keys
local zeroed = {}
for k in pairs(v) do
zeroed[k] = 0
end
return new(zeroed)
end
return divide_scalar(v, len)
end
2020-12-19 15:28:53 +01:00
function floor(v)
2021-03-27 20:10:49 +01:00
return apply(v, math.floor)
2020-12-19 15:28:53 +01:00
end
function ceil(v)
2021-03-27 20:10:49 +01:00
return apply(v, math.ceil)
2020-12-19 15:28:53 +01:00
end
function clamp(v, min, max)
2021-03-27 20:10:49 +01:00
return apply(apply(v, math.max, min), math.min, max)
end
2021-03-24 10:38:10 +01:00
function cross3(v, w)
2021-03-27 20:10:49 +01:00
assert(#v == 3 and #w == 3)
return new{
v[2] * w[3] - v[3] * w[2],
v[3] * w[1] - v[1] * w[3],
v[1] * w[2] - v[2] * w[1]
}
end
2021-03-24 10:38:10 +01:00
function dot(v, w)
2021-03-27 20:10:49 +01:00
local sum = 0
for i, c in pairs(v) do
sum = sum + c * w[i]
end
return sum
end
2021-03-24 10:36:28 +01:00
--+ Angle between two vectors
--> Signed angle in radians
2021-03-24 10:38:10 +01:00
function angle(v, w)
2021-03-27 20:10:49 +01:00
-- Based on dot(v, w) = |v| * |w| * cos(x)
return math.acos(dot(v, w) / length(v) / length(w))
2021-03-24 10:36:28 +01:00
end
2021-07-03 17:29:35 +02:00
-- Uses Rodrigues' rotation formula
2022-01-11 18:02:39 +01:00
-- axis must be normalized
2021-07-03 17:29:35 +02:00
function rotate3(v, axis, angle)
2022-01-11 18:02:39 +01:00
assert(#v == 3 and #axis == 3)
2021-07-03 17:29:35 +02:00
local cos = math.cos(angle)
return multiply_scalar(v, cos)
2022-01-11 18:02:39 +01:00
-- Minetest's coordinate system is *left-handed*, so `v` and `axis` must be swapped here
+ multiply_scalar(cross3(v, axis), math.sin(angle))
2021-07-03 17:29:35 +02:00
+ multiply_scalar(axis, dot(axis, v) * (1 - cos))
end
function box_box_collision(diff, box, other_box)
2021-03-27 20:10:49 +01:00
for index, diff in pairs(diff) do
if box[index] + diff > other_box[index + 3] or other_box[index] > box[index + 3] + diff then
return false
end
end
return true
end
local function moeller_trumbore(origin, direction, triangle, is_tri)
2021-03-27 20:10:49 +01:00
local point_1, point_2, point_3 = unpack(triangle)
local edge_1, edge_2 = subtract(point_2, point_1), subtract(point_3, point_1)
local h = cross3(direction, edge_2)
local a = dot(edge_1, h)
if math.abs(a) < 1e-9 then
return
end
local f = 1 / a
local diff = subtract(origin, point_1)
local u = f * dot(diff, h)
if u < 0 or u > 1 then
return
end
local q = cross3(diff, edge_1)
local v = f * dot(direction, q)
if v < 0 or (is_tri and u or 0) + v > 1 then
2021-03-27 20:10:49 +01:00
return
end
local pos_on_line = f * dot(edge_2, q)
if pos_on_line >= 0 then
return pos_on_line, u, v
2021-03-27 20:10:49 +01:00
end
end
function ray_triangle_intersection(origin, direction, triangle)
return moeller_trumbore(origin, direction, triangle, true)
end
function ray_parallelogram_intersection(origin, direction, parallelogram)
return moeller_trumbore(origin, direction, parallelogram)
end
function triangle_normal(triangle)
2021-03-27 20:10:49 +01:00
local point_1, point_2, point_3 = unpack(triangle)
local edge_1, edge_2 = subtract(point_2, point_1), subtract(point_3, point_1)
return normalize(cross3(edge_1, edge_2))
2021-06-17 19:45:08 +02:00
end
-- Export environment
return _ENV