mirror of
https://github.com/luanti-org/luanti.git
synced 2025-06-27 16:36:03 +00:00
WIP matrix & rotation lua APIs
This commit is contained in:
parent
a87ce1bad7
commit
513532a93c
15 changed files with 1509 additions and 5 deletions
158
doc/lua_api.md
158
doc/lua_api.md
|
@ -4151,6 +4151,164 @@ For example:
|
||||||
* `core.hash_node_position` (Only works on node positions.)
|
* `core.hash_node_position` (Only works on node positions.)
|
||||||
* `core.dir_to_wallmounted` (Involves wallmounted param2 values.)
|
* `core.dir_to_wallmounted` (Involves wallmounted param2 values.)
|
||||||
|
|
||||||
|
Rotations
|
||||||
|
=========
|
||||||
|
|
||||||
|
As abusing vectors of euler angles is discouraged as error-prone,
|
||||||
|
Luanti provides a proper helper class for working with 3d rotations.
|
||||||
|
|
||||||
|
You must not rely on the specific type or imprecision of the current implementation.
|
||||||
|
|
||||||
|
Constructors
|
||||||
|
------------
|
||||||
|
|
||||||
|
* `Rotation.identity()`: Constructs a no-op rotation.
|
||||||
|
* `Rotation.quaternion(x, y, z, w)`:
|
||||||
|
Constructs a rotation from a quaternion (which need not be normalized)
|
||||||
|
* `Rotation.axis_angle(axis, angle)`:
|
||||||
|
Constructs a rotation around the given axis by the given angle
|
||||||
|
* `axis` is a vector, which need not be normalized
|
||||||
|
* `angle` is in radians
|
||||||
|
* Shorthands for rotations around the respective axes:
|
||||||
|
* `Rotation.x(pitch)`
|
||||||
|
* `Rotation.y(yaw)`
|
||||||
|
* `Rotation.z(roll)`
|
||||||
|
* `Rotation.euler_angles(pitch, yaw, roll)`
|
||||||
|
* All angles in radians.
|
||||||
|
* Rotation order is ZYX: First pitch is applied, then yaw, then roll. Equivalent to
|
||||||
|
`Rotation.compose(Rotation.z(roll), Rotation.y(yaw), Rotation.x(pitch))`.
|
||||||
|
* Consistent with the euler angles that can be used for bones.
|
||||||
|
|
||||||
|
Conversions
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Corresponding to the constructors, quaternions can be converted
|
||||||
|
to different representations; note that you need not get the same values out -
|
||||||
|
you merely get values that produce the same rotation when passed to the corresponding constructor:
|
||||||
|
|
||||||
|
* `x, y, z, w = Rotation:to_quaternion()`
|
||||||
|
* Returns the normalized quaternion representation.
|
||||||
|
* `axis, angle = Rotation:to_axis_angle()`
|
||||||
|
* `axis` is a normalized vector.
|
||||||
|
* `angle` is in radians.
|
||||||
|
* `pitch, yaw, roll = Rotation:to_euler_angles()`
|
||||||
|
* Angles are all in radians.
|
||||||
|
* `pitch`, `yaw`, `roll`: Rotation around the X-, Y-, and Z-axis respectively.
|
||||||
|
* Rotation order is ZYX: First pitch is applied, then yaw, then roll.
|
||||||
|
* Coordinate system is right-handed <!-- TODO -->
|
||||||
|
|
||||||
|
Methods
|
||||||
|
-------
|
||||||
|
|
||||||
|
* `Rotation:apply(vec)`: Returns the result of applying the rotation to the given vector.
|
||||||
|
* `Rotation.compose(...)`: Returns the composition of the given rotations,
|
||||||
|
in right-to-left order: `second:compose(first):apply(v)`
|
||||||
|
is equivalent to `second:apply(first:apply(v))`.
|
||||||
|
`Rotation.compose()` is an alias for `Rotation.identity()`,
|
||||||
|
`Rotation:compose()` copies the rotation.
|
||||||
|
* `Rotation:invert()`: Returns the inverse rotation.
|
||||||
|
* `Rotation:slerp(from, to, time)`: Interpolate from one rotation to another.
|
||||||
|
* `time = 0` is all `from`, `time = 1` is all `to`.
|
||||||
|
* `Rotation:angle_to(other)`: Returns the absolute angle between two quaternions.
|
||||||
|
* Useful to measure similarity.
|
||||||
|
|
||||||
|
|
||||||
|
Matrices
|
||||||
|
========
|
||||||
|
|
||||||
|
Luanti uses 4x4 matrices to represent transformations of 3d vectors.
|
||||||
|
The matrices use row-major conventions:
|
||||||
|
The first row is the image of the vector (1, 0, 0, 0),
|
||||||
|
the second row is the image of (0, 1, 0, 0), and so on.
|
||||||
|
Thus the translation is in the last row.
|
||||||
|
|
||||||
|
You must account for reasonable imprecisions in matrix calculations,
|
||||||
|
as they currently use 32-bit floats; they may use 64-bit floats in the future.
|
||||||
|
You must not rely on the internal representation or type of matrices;
|
||||||
|
e.g. they may be implemented in pure Lua as a table in the future.
|
||||||
|
|
||||||
|
Matrices are very suitable for constructing, composing and applying
|
||||||
|
linear transformations; they are not so useful for exact storage of transformations,
|
||||||
|
decomposition into rotation and scale will not be exact.
|
||||||
|
|
||||||
|
Constructors
|
||||||
|
------------
|
||||||
|
|
||||||
|
* `Matrix4.new(r1c1, r1c2, ..., r4c4)`:
|
||||||
|
Constructs a matrix from the given 16 numbers in row-major order.
|
||||||
|
* `Matrix4.identity()`: Constructs an identity matrix.
|
||||||
|
* `Matrix4.all(number)`: Constructs a matrix where all entries are the given number.
|
||||||
|
* `Matrix4.translation(vec)`: Constructs a matrix that translates vectors by the given `vector`.
|
||||||
|
* `Matrix4.rotation(rot)`: Constructs a matrix that applies the given `Rotation` to vectors.
|
||||||
|
* `Matrix4.scale(vec)`: Constructs a matrix that applies the given
|
||||||
|
component-wise scaling factors to vectors.
|
||||||
|
* `Matrix4.reflection(normal)`: Constructs a matrix that reflects vectors
|
||||||
|
at the plane with the given plane normal vector (which need not be normalized).
|
||||||
|
|
||||||
|
Methods
|
||||||
|
-------
|
||||||
|
|
||||||
|
Storage:
|
||||||
|
|
||||||
|
* `Matrix4:get(row, col)`: Get an entry.
|
||||||
|
* `row` and `col` range from 1 to 4
|
||||||
|
* `Matrix4:set(row, col, element)`: Set an entry.
|
||||||
|
* `row` and `col` range from 1 to 4
|
||||||
|
* `x, y, z, w = Matrix4:get_row(row)`
|
||||||
|
* `Matrix4:set_row(row, x, y, z, w)`
|
||||||
|
* `x, y, z, w = Matrix4:get_column(col)`
|
||||||
|
* `Matrix4:set_column(col, x, y, z, w)`
|
||||||
|
* `Matrix4:copy()`: Copy the matrix.
|
||||||
|
* `... = Matrix4:unpack()`: Get the entries of the matrix in row-major order.
|
||||||
|
|
||||||
|
Linear algebra:
|
||||||
|
|
||||||
|
* `Matrix4.compose(...)`: Returns the composition of the given matrices.
|
||||||
|
* `Matrix4:transpose()`: Returns the transpose of the matrix.
|
||||||
|
* `Matrix4:invert()`: Returns the inverse, or `nil` if the matrix is (close to being) singular.
|
||||||
|
* `x, y, z, w = Matrix4:transform_4d(x, y, z, w)`: Apply the matrix to a 4d vector.
|
||||||
|
* `Matrix4:transform_position(pos)`:
|
||||||
|
* Apply the matrix to a vector representing a position.
|
||||||
|
* Applies the transformation as if w = 1 and discards the resulting w component.
|
||||||
|
* `Matrix4:transform_direction(dir)`:
|
||||||
|
* Apply the matrix to a vector representing a direction.
|
||||||
|
* Ignores the fourth row and column; does not apply the translation (w = 0).
|
||||||
|
* `Matrix4:equals(other, [tolerance = 0])`:
|
||||||
|
Returns whether all components differ in absolute value at most by the given tolerance.
|
||||||
|
* `Matrix4:is_affine_transform([tolerance = 0])`:
|
||||||
|
Whether the matrix is an affine transformation in 3d space,
|
||||||
|
meaning it is a 3d linear transformation plus a translation.
|
||||||
|
(This is the case if the last column is approximately 0, 0, 0, 1.)
|
||||||
|
|
||||||
|
For working with affine transforms, the following methods are available:
|
||||||
|
|
||||||
|
* `Matrix4:get_translation()`:
|
||||||
|
Returns the translation as a vector.
|
||||||
|
* `Matrix4:set_translation(vec)`
|
||||||
|
|
||||||
|
For TRS transforms specifically,
|
||||||
|
let `self = Matrix4.compose(Matrix4.translation(t), Matrix4.rotation(r), Matrix4.scale(s))`.
|
||||||
|
Then we can decompose `self` further. Note that `self` must not shear or reflect.
|
||||||
|
|
||||||
|
* `rotation, scale = Matrix4:get_rs()`:
|
||||||
|
Extracts a `Rotation` equivalent to `r`.
|
||||||
|
along with the corresponding component-wise scaling factors as a vector.
|
||||||
|
|
||||||
|
|
||||||
|
Operators
|
||||||
|
---------
|
||||||
|
|
||||||
|
Similar to vectors, matrices define some arithmetic operators:
|
||||||
|
|
||||||
|
* `m1 == m2`: Returns whether `m1` and `m2` are identical.
|
||||||
|
* `-m`: Returns the additive inverse.
|
||||||
|
* `m1 + m2`: Returns the sum of both matrices.
|
||||||
|
* `m1 - m2`: Shorthand for `m1 + (-m2)`.
|
||||||
|
* `m * s` or `s * m`: Returns the matrix `m` scaled by the scalar `s`.
|
||||||
|
* Note: *All* entries are scaled, including the last row.
|
||||||
|
|
||||||
|
Matrices also define a `__tostring` metamethod.
|
||||||
|
This is only intended for human readability and not for serialization.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -201,6 +201,7 @@ dofile(modpath .. "/inventory.lua")
|
||||||
dofile(modpath .. "/load_time.lua")
|
dofile(modpath .. "/load_time.lua")
|
||||||
dofile(modpath .. "/on_shutdown.lua")
|
dofile(modpath .. "/on_shutdown.lua")
|
||||||
dofile(modpath .. "/color.lua")
|
dofile(modpath .. "/color.lua")
|
||||||
|
dofile(modpath .. "/matrix4.lua")
|
||||||
|
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
|
287
games/devtest/mods/unittests/matrix4.lua
Normal file
287
games/devtest/mods/unittests/matrix4.lua
Normal file
|
@ -0,0 +1,287 @@
|
||||||
|
local function describe(_, func)
|
||||||
|
func()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function it(section_name, func)
|
||||||
|
print("Running test: " .. section_name)
|
||||||
|
func()
|
||||||
|
end
|
||||||
|
|
||||||
|
local assert = require("luassert")
|
||||||
|
|
||||||
|
local function assert_close(a, b)
|
||||||
|
assert(a:equals(b, 1e-4))
|
||||||
|
end
|
||||||
|
|
||||||
|
local mat1 = Matrix4.new(
|
||||||
|
1, 2, 3, 4,
|
||||||
|
5, 6, 7, 8,
|
||||||
|
9, 10, 11, 12,
|
||||||
|
13, 14, 15, 16
|
||||||
|
)
|
||||||
|
|
||||||
|
it("identity", function()
|
||||||
|
assert.equals(Matrix4.new(
|
||||||
|
1, 0, 0, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
), Matrix4.identity())
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("getters & setters", function()
|
||||||
|
it("get & set", function()
|
||||||
|
local mat = Matrix4.all(0)
|
||||||
|
local i = 0
|
||||||
|
for row = 1, 4 do
|
||||||
|
for col = 1, 4 do
|
||||||
|
i = i + 1
|
||||||
|
assert.equals(0, mat:get(row, col))
|
||||||
|
mat:set(row, col, i)
|
||||||
|
assert.equals(i, mat:get(row, col))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert.equals(mat1, mat)
|
||||||
|
end)
|
||||||
|
it("get_row & set_row", function()
|
||||||
|
local mat = mat1:copy()
|
||||||
|
assert.same({5, 6, 7, 8}, {mat:get_row(2)})
|
||||||
|
mat:set_row(2, 1, 2, 3, 4)
|
||||||
|
assert.same({1, 2, 3, 4}, {mat:get_row(2)})
|
||||||
|
end)
|
||||||
|
it("get_column & set_column", function()
|
||||||
|
local mat = mat1:copy()
|
||||||
|
assert.same({3, 7, 11, 15}, {mat:get_column(3)})
|
||||||
|
mat:set_column(3, 1, 2, 3, 4)
|
||||||
|
assert.same({1, 2, 3, 4}, {mat:get_column(3)})
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("copy", function()
|
||||||
|
assert.equals(mat1, mat1:copy())
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("unpack", function()
|
||||||
|
assert.equals(mat1, Matrix4.new(mat1:unpack()))
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("transform", function()
|
||||||
|
it("4d", function()
|
||||||
|
assert.same({
|
||||||
|
1 * 1 + 2 * 5 + 3 * 9 + 4 * 13,
|
||||||
|
1 * 2 + 2 * 6 + 3 * 10 + 4 * 14,
|
||||||
|
1 * 3 + 2 * 7 + 3 * 11 + 4 * 15,
|
||||||
|
1 * 4 + 2 * 8 + 3 * 12 + 4 * 16,
|
||||||
|
}, {mat1:transform_4d(1, 2, 3, 4)})
|
||||||
|
end)
|
||||||
|
it("position", function()
|
||||||
|
assert.equals(vector.new(
|
||||||
|
1 * 1 + 2 * 5 + 3 * 9,
|
||||||
|
1 * 2 + 2 * 6 + 3 * 10,
|
||||||
|
1 * 3 + 2 * 7 + 3 * 11
|
||||||
|
):offset(13, 14, 15), mat1:transform_position(vector.new(1, 2, 3)))
|
||||||
|
end)
|
||||||
|
it("direction", function()
|
||||||
|
assert.equals(vector.new(
|
||||||
|
1 * 1 + 2 * 5 + 3 * 9,
|
||||||
|
1 * 2 + 2 * 6 + 3 * 10,
|
||||||
|
1 * 3 + 2 * 7 + 3 * 11
|
||||||
|
), mat1:transform_direction(vector.new(1, 2, 3)))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
|
||||||
|
local mat2 = Matrix4.new(
|
||||||
|
16, 15, 14, 13,
|
||||||
|
12, 11, 10, 9,
|
||||||
|
8, 7, 6, 5,
|
||||||
|
4, 3, 2, 1
|
||||||
|
)
|
||||||
|
|
||||||
|
describe("composition", function()
|
||||||
|
it("identity for empty argument list", function()
|
||||||
|
assert(Matrix4.identity():equals(Matrix4.compose()))
|
||||||
|
end)
|
||||||
|
it("same matrix for single argument", function()
|
||||||
|
local mat = Matrix4.new(
|
||||||
|
1, 2, 3, 4,
|
||||||
|
5, 6, 7, 8,
|
||||||
|
9, 10, 11, 12,
|
||||||
|
13, 14, 15, 16
|
||||||
|
)
|
||||||
|
assert(mat:equals(mat:compose()))
|
||||||
|
end)
|
||||||
|
it("matrix multiplication for two arguments", function()
|
||||||
|
local composition = mat1:compose(mat2)
|
||||||
|
assert.equals(Matrix4.new(
|
||||||
|
386, 444, 502, 560,
|
||||||
|
274, 316, 358, 400,
|
||||||
|
162, 188, 214, 240,
|
||||||
|
50, 60, 70, 80
|
||||||
|
), composition)
|
||||||
|
assert.same({mat1:transform_4d(mat2:transform_4d(1, 2, 3, 4))},
|
||||||
|
{composition:transform_4d(1, 2, 3, 4)})
|
||||||
|
end)
|
||||||
|
it("supports multiple arguments", function()
|
||||||
|
local fib = Matrix4.new(
|
||||||
|
0, 1, 0, 0, -- x' = y
|
||||||
|
1, 1, 0, 0, -- y' = x + y
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
)
|
||||||
|
local function rep(v, n)
|
||||||
|
if n == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
return v, rep(v, n - 1)
|
||||||
|
end
|
||||||
|
local result = Matrix4.compose(rep(fib, 10))
|
||||||
|
assert.equals(55, result:get(2, 1))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function random_matrix4()
|
||||||
|
local t = {}
|
||||||
|
for i = 1, 16 do
|
||||||
|
t[i] = math.random()
|
||||||
|
end
|
||||||
|
return Matrix4.new(unpack(t))
|
||||||
|
end
|
||||||
|
|
||||||
|
it("determinant", function()
|
||||||
|
assert.equal(42, Matrix4.scale(vector.new(2, 3, 7)):determinant())
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("inversion", function()
|
||||||
|
it("simple permutation", function()
|
||||||
|
assert_close(Matrix4.new(
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
1, 0, 0, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
), Matrix4.new(
|
||||||
|
0, 0, 1, 0,
|
||||||
|
1, 0, 0, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
):invert())
|
||||||
|
end)
|
||||||
|
it("random matrices", function()
|
||||||
|
for _ = 1, 100 do
|
||||||
|
local mat = random_matrix4()
|
||||||
|
if math.abs(mat:determinant()) > 1e-3 then
|
||||||
|
assert_close(Matrix4.identity(), mat:invert():compose(mat))
|
||||||
|
assert_close(Matrix4.identity(), mat:compose(mat:invert()))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("transpose", function()
|
||||||
|
assert.equals(Matrix4.new(
|
||||||
|
1, 5, 9, 13,
|
||||||
|
2, 6, 10, 14,
|
||||||
|
3, 7, 11, 15,
|
||||||
|
4, 8, 12, 16
|
||||||
|
), mat1:transpose())
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("affine transform constructors", function()
|
||||||
|
it("translation", function()
|
||||||
|
assert.equals(Matrix4.new(
|
||||||
|
1, 0, 0, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
1, 2, 3, 1
|
||||||
|
), Matrix4.translation(vector.new(1, 2, 3)))
|
||||||
|
end)
|
||||||
|
it("scale", function()
|
||||||
|
assert.equals(Matrix4.new(
|
||||||
|
-1, 0, 0, 0,
|
||||||
|
0, 2, 0, 0,
|
||||||
|
0, 0, 3, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
), Matrix4.scale(vector.new(-1, 2, 3)))
|
||||||
|
end)
|
||||||
|
it("rotation", function()
|
||||||
|
assert_close(Matrix4.new(
|
||||||
|
0, -1, 0, 0,
|
||||||
|
1, 0, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
), Matrix4.rotation(Rotation.z(math.pi / 2)))
|
||||||
|
end)
|
||||||
|
it("reflection", function()
|
||||||
|
assert_close(Matrix4.identity() - 2/(1^2 + 2^2 + 3^2) * Matrix4.new(
|
||||||
|
1, 2, 3, 0,
|
||||||
|
2, 4, 6, 0,
|
||||||
|
3, 6, 9, 0,
|
||||||
|
0, 0, 0, 0
|
||||||
|
), Matrix4.reflection(vector.new(1, 2, 3)))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("affine transform methods", function()
|
||||||
|
local t = vector.new(4, 5, 6)
|
||||||
|
local r = Rotation.z(math.pi / 2)
|
||||||
|
local s = vector.new(1, 2, 3)
|
||||||
|
local trs = Matrix4.compose(
|
||||||
|
Matrix4.translation(t),
|
||||||
|
Matrix4.rotation(r),
|
||||||
|
Matrix4.scale(s)
|
||||||
|
)
|
||||||
|
it("is affine", function()
|
||||||
|
assert(trs:is_affine_transform())
|
||||||
|
assert(not mat1:is_affine_transform())
|
||||||
|
end)
|
||||||
|
it("get translation", function()
|
||||||
|
assert.equals(t, trs:get_translation())
|
||||||
|
end)
|
||||||
|
it("get rotation & scale", function()
|
||||||
|
print(trs)
|
||||||
|
local rotation, scale = trs:get_rs()
|
||||||
|
print(rotation, scale)
|
||||||
|
print(r)
|
||||||
|
assert(r:angle_to(rotation) < 1e-4)
|
||||||
|
assert(s:distance(scale) < 1e-4)
|
||||||
|
end)
|
||||||
|
it("set translation", function()
|
||||||
|
local mat = trs:copy()
|
||||||
|
local v = vector.new(1, 2, 3)
|
||||||
|
mat:set_translation(v)
|
||||||
|
assert.equals(v, mat:get_translation())
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("metamethods", function()
|
||||||
|
it("addition", function()
|
||||||
|
assert.equals(Matrix4.all(17), mat1 + mat2)
|
||||||
|
end)
|
||||||
|
it("subtraction", function()
|
||||||
|
assert.equals(Matrix4.all(0), mat1 - mat1)
|
||||||
|
end)
|
||||||
|
it("unary minus", function()
|
||||||
|
assert.equals(-1 * mat1, -mat1)
|
||||||
|
end)
|
||||||
|
it("scalar multiplication", function()
|
||||||
|
assert.equals(2 * mat1, mat1 * 2)
|
||||||
|
assert.equals(2 * mat1, mat1:compose(Matrix4.new(
|
||||||
|
2, 0, 0, 0,
|
||||||
|
0, 2, 0, 0,
|
||||||
|
0, 0, 2, 0,
|
||||||
|
0, 0, 0, 2
|
||||||
|
)))
|
||||||
|
end)
|
||||||
|
it("equals", function()
|
||||||
|
local mat1 = Matrix4.identity()
|
||||||
|
local mat2 = Matrix4.identity()
|
||||||
|
assert(mat1:equals(mat2))
|
||||||
|
mat2:set(1, 1, 0)
|
||||||
|
assert(not mat1:equals(mat2))
|
||||||
|
mat2:set(1, 1, 0.999)
|
||||||
|
assert(mat1:equals(mat2, 0.01))
|
||||||
|
end)
|
||||||
|
it("tostring", function()
|
||||||
|
assert(tostring(Matrix4.scale(vector.new(12345, 0, 0))):find"12345")
|
||||||
|
end)
|
||||||
|
end)
|
67
games/devtest/mods/unittests/rotation.lua
Normal file
67
games/devtest/mods/unittests/rotation.lua
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
local function describe(_, func)
|
||||||
|
func()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function it(section_name, func)
|
||||||
|
print("Running test: " .. section_name)
|
||||||
|
func()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function assert_close(expected, actual)
|
||||||
|
assert(expected:angle_to(actual) < 1e-4)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe("constructors", function()
|
||||||
|
it("identity", function()
|
||||||
|
local rot = Rotation.identity()
|
||||||
|
assert.same({0, 0, 0, 1}, {rot:to_quaternion()})
|
||||||
|
end)
|
||||||
|
it("quaternion", function()
|
||||||
|
local rot = Rotation.quaternion(0, 0, 0, 1)
|
||||||
|
assert_close(rot, Rotation.identity())
|
||||||
|
end)
|
||||||
|
it("axis angle", function() end)
|
||||||
|
it("axis-angle shorthands", function()
|
||||||
|
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("composition", function()
|
||||||
|
it("is the identity for an empty argument list", function()
|
||||||
|
assert_close(Rotation.identity(), Rotation.compose())
|
||||||
|
end)
|
||||||
|
it("is the same rotation for a single argument", function()
|
||||||
|
local rot = Rotation.x(math.pi / 2)
|
||||||
|
assert_close(rot, rot:compose())
|
||||||
|
end)
|
||||||
|
it("is consistent with application", function()
|
||||||
|
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local function random_quaternion()
|
||||||
|
local x = math.random()
|
||||||
|
local y = math.random()
|
||||||
|
local z = math.random()
|
||||||
|
local w = math.random()
|
||||||
|
return Rotation.quaternion(x, y, z, w)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe("inversion", function()
|
||||||
|
it("random quaternions", function()
|
||||||
|
for _ = 1, 100 do
|
||||||
|
local rot = random_quaternion()
|
||||||
|
assert_close(Rotation.identity(), rot:inverse():compose(rot))
|
||||||
|
assert_close(Rotation.identity(), rot:compose(rot:inverse()))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
it("inverts the angle", function()
|
||||||
|
for _ = 1, 100 do
|
||||||
|
local rot = random_quaternion()
|
||||||
|
local axis, angle = rot:axis_angle()
|
||||||
|
local inv_axis, inv_angle = rot:inverse():axis_angle()
|
||||||
|
assert(axis:distance(inv_axis) < 1e-4)
|
||||||
|
assert(math.abs(angle + inv_angle) < 1e-4)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
|
@ -12,6 +12,7 @@
|
||||||
#include "aabbox3d.h"
|
#include "aabbox3d.h"
|
||||||
#include "rect.h"
|
#include "rect.h"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
namespace irr
|
namespace irr
|
||||||
{
|
{
|
||||||
|
@ -213,6 +214,9 @@ public:
|
||||||
//! Get Scale
|
//! Get Scale
|
||||||
vector3d<T> getScale() const;
|
vector3d<T> getScale() const;
|
||||||
|
|
||||||
|
//! Scale the matrix rows ("axes") by the components of a vector
|
||||||
|
void scaleAxes(const vector3d<T> &v);
|
||||||
|
|
||||||
//! Translate a vector by the inverse of the translation part of this matrix.
|
//! Translate a vector by the inverse of the translation part of this matrix.
|
||||||
void inverseTranslateVect(vector3df &vect) const;
|
void inverseTranslateVect(vector3df &vect) const;
|
||||||
|
|
||||||
|
@ -220,6 +224,7 @@ public:
|
||||||
[[nodiscard]] vector3d<T> scaleThenInvRotVect(const vector3d<T> &vect) const;
|
[[nodiscard]] vector3d<T> scaleThenInvRotVect(const vector3d<T> &vect) const;
|
||||||
|
|
||||||
//! Rotate and scale a vector. Applies both rotation & scale part of the matrix.
|
//! Rotate and scale a vector. Applies both rotation & scale part of the matrix.
|
||||||
|
// TODO rename to transformDirection
|
||||||
[[nodiscard]] vector3d<T> rotateAndScaleVect(const vector3d<T> &vect) const;
|
[[nodiscard]] vector3d<T> rotateAndScaleVect(const vector3d<T> &vect) const;
|
||||||
|
|
||||||
//! Transforms the vector by this matrix
|
//! Transforms the vector by this matrix
|
||||||
|
@ -426,6 +431,16 @@ public:
|
||||||
//! Compare two matrices using the equal method
|
//! Compare two matrices using the equal method
|
||||||
bool equals(const CMatrix4<T> &other, const T tolerance = (T)ROUNDING_ERROR_f64) const;
|
bool equals(const CMatrix4<T> &other, const T tolerance = (T)ROUNDING_ERROR_f64) const;
|
||||||
|
|
||||||
|
//! Check whether matrix is a 3d affine transform (last column is approximately 0, 0, 0, 1)
|
||||||
|
bool isAffine(const T tolerance = (T)ROUNDING_ERROR_f64) const
|
||||||
|
{
|
||||||
|
const auto &m = *this;
|
||||||
|
return core::equals(m(0, 3), (T) 0) &&
|
||||||
|
core::equals(m(1, 3), (T) 0) &&
|
||||||
|
core::equals(m(2, 3), (T) 0) &&
|
||||||
|
core::equals(m(3, 3), (T) 1);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <bool degrees>
|
template <bool degrees>
|
||||||
vector3d<T> getRotation(const vector3d<T> &scale) const;
|
vector3d<T> getRotation(const vector3d<T> &scale) const;
|
||||||
|
@ -751,6 +766,20 @@ inline vector3d<T> CMatrix4<T>::getScale() const
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
void CMatrix4<T>::scaleAxes(const vector3d<T> &v)
|
||||||
|
{
|
||||||
|
auto scale_row = [this](int row, T scale) {
|
||||||
|
auto &m = *this;
|
||||||
|
m(row, 0) *= scale;
|
||||||
|
m(row, 1) *= scale;
|
||||||
|
m(row, 2) *= scale;
|
||||||
|
};
|
||||||
|
scale_row(0, v.X);
|
||||||
|
scale_row(1, v.Y);
|
||||||
|
scale_row(2, v.Z);
|
||||||
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
inline CMatrix4<T> &CMatrix4<T>::setRotationDegrees(const vector3d<T> &rotation)
|
inline CMatrix4<T> &CMatrix4<T>::setRotationDegrees(const vector3d<T> &rotation)
|
||||||
{
|
{
|
||||||
|
@ -1631,6 +1660,19 @@ inline void CMatrix4<T>::getTransposed(CMatrix4<T> &o) const
|
||||||
o[15] = M[15];
|
o[15] = M[15];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
std::ostream& operator<<(std::ostream& os, const CMatrix4<T>& matrix)
|
||||||
|
{
|
||||||
|
for (int row = 0; row < 4; ++row) {
|
||||||
|
for (int col = 0; col < 4; ++col) {
|
||||||
|
os << "\t";
|
||||||
|
os << matrix(row, col);
|
||||||
|
}
|
||||||
|
os << "\n";
|
||||||
|
}
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
// used to scale <-1,-1><1,1> to viewport
|
// used to scale <-1,-1><1,1> to viewport
|
||||||
template <class T>
|
template <class T>
|
||||||
inline CMatrix4<T> &CMatrix4<T>::buildNDCToDCMatrix(const rect<s32> &viewport, f32 zScale)
|
inline CMatrix4<T> &CMatrix4<T>::buildNDCToDCMatrix(const rect<s32> &viewport, f32 zScale)
|
||||||
|
|
|
@ -91,6 +91,12 @@ public:
|
||||||
//! Calculates the dot product
|
//! Calculates the dot product
|
||||||
inline f32 dotProduct(const quaternion &other) const;
|
inline f32 dotProduct(const quaternion &other) const;
|
||||||
|
|
||||||
|
//! Calculates the (unsigned) angle between two quaternions
|
||||||
|
inline f32 angleTo(const quaternion &other) const
|
||||||
|
{
|
||||||
|
return acosf(std::abs(dotProduct(other)));
|
||||||
|
}
|
||||||
|
|
||||||
//! Sets new quaternion
|
//! Sets new quaternion
|
||||||
inline quaternion &set(f32 x, f32 y, f32 z, f32 w);
|
inline quaternion &set(f32 x, f32 y, f32 z, f32 w);
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
// Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
// Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
||||||
|
|
||||||
|
#include "irrTypes.h"
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <lauxlib.h>
|
#include <lauxlib.h>
|
||||||
}
|
}
|
||||||
|
@ -36,13 +37,44 @@ int LuaHelper::readParam(lua_State *L, int index)
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
float LuaHelper::readParam(lua_State *L, int index)
|
f32 LuaHelper::readParam(lua_State *L, int index)
|
||||||
{
|
{
|
||||||
lua_Number v = luaL_checknumber(L, index);
|
f64 v = luaL_checknumber(L, index);
|
||||||
if (std::isnan(v) && std::isinf(v))
|
return static_cast<f32>(v);
|
||||||
throw LuaError("Invalid float value (NaN or infinity)");
|
}
|
||||||
|
|
||||||
return static_cast<float>(v);
|
template <>
|
||||||
|
f64 LuaHelper::readParam(lua_State *L, int index)
|
||||||
|
{
|
||||||
|
return luaL_checknumber(L, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
LuaHelper::Finite<f32> LuaHelper::readParam(lua_State *L, int index)
|
||||||
|
{
|
||||||
|
f64 original_value = luaL_checknumber(L, index);
|
||||||
|
f32 v = static_cast<f32>(original_value);
|
||||||
|
if (std::isfinite(v))
|
||||||
|
return {v};
|
||||||
|
if (std::isnan(original_value))
|
||||||
|
luaL_argerror(L, index, "number is NaN");
|
||||||
|
if (!std::isfinite(original_value))
|
||||||
|
luaL_argerror(L, index, "number is not finite");
|
||||||
|
assert(!std::isfinite(v));
|
||||||
|
luaL_argerror(L, index, "number is out-of-bounds for a 32-bit float");
|
||||||
|
IRR_CODE_UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
LuaHelper::Finite<f64> LuaHelper::readParam(lua_State *L, int index)
|
||||||
|
{
|
||||||
|
f64 v = luaL_checknumber(L, index);
|
||||||
|
if (std::isfinite(v))
|
||||||
|
return {v};
|
||||||
|
if (std::isnan(v))
|
||||||
|
luaL_argerror(L, index, "number is NaN");
|
||||||
|
luaL_argerror(L, index, "number is not finite");
|
||||||
|
IRR_CODE_UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -24,6 +25,12 @@ protected:
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static T readParam(lua_State *L, int index);
|
static T readParam(lua_State *L, int index);
|
||||||
|
|
||||||
|
/// Type to represent a restriction to finite floats
|
||||||
|
template<typename T>
|
||||||
|
struct Finite {
|
||||||
|
T value;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a value using a template type T from Lua state L at index
|
* Read a value using a template type T from Lua state L at index
|
||||||
*
|
*
|
||||||
|
|
|
@ -24,6 +24,8 @@ set(common_SCRIPT_LUA_API_SRCS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_storage.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/l_storage.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_util.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/l_util.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/l_vmanip.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/l_vmanip.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/l_rotation.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/l_matrix4.cpp
|
||||||
PARENT_SCOPE)
|
PARENT_SCOPE)
|
||||||
|
|
||||||
set(client_SCRIPT_LUA_API_SRCS
|
set(client_SCRIPT_LUA_API_SRCS
|
||||||
|
|
458
src/script/lua_api/l_matrix4.cpp
Normal file
458
src/script/lua_api/l_matrix4.cpp
Normal file
|
@ -0,0 +1,458 @@
|
||||||
|
// Luanti
|
||||||
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
// Copyright (C) 2025 Lars Müller
|
||||||
|
|
||||||
|
#include "common/c_converter.h"
|
||||||
|
#include "irrTypes.h"
|
||||||
|
#include "irr_v3d.h"
|
||||||
|
#include "lua_api/l_rotation.h"
|
||||||
|
#include "matrix4.h"
|
||||||
|
|
||||||
|
#include "lua_api/l_matrix4.h"
|
||||||
|
#include "common/c_packer.h"
|
||||||
|
#include "lua_api/l_internal.h"
|
||||||
|
#include "quaternion.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
#include <lua.h>
|
||||||
|
#include <luajit-2.1/lauxlib.h>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
template<int max = 4>
|
||||||
|
static int read_index(lua_State *L, int index)
|
||||||
|
{
|
||||||
|
f64 value = luaL_checknumber(L, index);
|
||||||
|
if (std::floor(value) != value)
|
||||||
|
luaL_argerror(L, index, "index must be integer");
|
||||||
|
if (value < 1 || value > max)
|
||||||
|
luaL_argerror(L, index, "index out of range");
|
||||||
|
return static_cast<int>(value) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
core::matrix4 &LuaMatrix4::check(lua_State *L, int index)
|
||||||
|
{
|
||||||
|
return static_cast<LuaMatrix4 *>(luaL_checkudata(L, index, LuaMatrix4::className))->matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline core::matrix4 &LuaMatrix4::create(lua_State *L)
|
||||||
|
{
|
||||||
|
auto *mat = static_cast<LuaMatrix4 *>(lua_newuserdata(L, sizeof(LuaMatrix4)));
|
||||||
|
luaL_getmetatable(L, LuaMatrix4::className);
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
return mat->matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_identity(lua_State *L)
|
||||||
|
{
|
||||||
|
create(L) = core::IdentityMatrix;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_all(lua_State *L)
|
||||||
|
{
|
||||||
|
f32 v = luaL_checknumber(L, 1);
|
||||||
|
create(L) = v;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_new(lua_State *L)
|
||||||
|
{
|
||||||
|
if (lua_gettop(L) != 16)
|
||||||
|
luaL_error(L, "expected 16 arguments");
|
||||||
|
core::matrix4 &matrix = create(L);
|
||||||
|
for (int i = 0; i < 16; ++i)
|
||||||
|
matrix[i] = luaL_checknumber(L, 1 + i);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_translation(lua_State *L)
|
||||||
|
{
|
||||||
|
v3f translation = readParam<v3f>(L, 1);
|
||||||
|
core::matrix4 &matrix = create(L);
|
||||||
|
matrix = core::IdentityMatrix;
|
||||||
|
matrix.setTranslation(translation);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_rotation(lua_State *L)
|
||||||
|
{
|
||||||
|
const core::quaternion &rotation = LuaRotation::check(L, 1);
|
||||||
|
core::matrix4 &matrix = create(L);
|
||||||
|
rotation.getMatrix_transposed(matrix);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_scale(lua_State *L)
|
||||||
|
{
|
||||||
|
v3f scale = readParam<v3f>(L, 1);
|
||||||
|
core::matrix4 &matrix = create(L);
|
||||||
|
matrix = core::IdentityMatrix;
|
||||||
|
matrix.setScale(scale);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_reflection(lua_State *L)
|
||||||
|
{
|
||||||
|
v3f normal = readParam<v3f>(L, 1);
|
||||||
|
normal.normalize();
|
||||||
|
core::matrix4 &matrix = create(L);
|
||||||
|
matrix = core::IdentityMatrix;
|
||||||
|
// TODO move to CMatrix4
|
||||||
|
f32 factor = 2.0f / normal.getLengthSQ();
|
||||||
|
auto subtract_scaled_row = [&](int i, f32 scalar) {
|
||||||
|
v3f scaled = (factor * scalar) * normal;
|
||||||
|
matrix(i, 0) -= scaled.X;
|
||||||
|
matrix(i, 1) -= scaled.Y;
|
||||||
|
matrix(i, 2) -= scaled.Z;
|
||||||
|
};
|
||||||
|
subtract_scaled_row(0, normal.X);
|
||||||
|
subtract_scaled_row(1, normal.Y);
|
||||||
|
subtract_scaled_row(2, normal.Z);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container utils
|
||||||
|
|
||||||
|
int LuaMatrix4::l_get(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
int row = read_index(L, 2);
|
||||||
|
int col = read_index(L, 3);
|
||||||
|
lua_pushnumber(L, matrix(row, col));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_set(lua_State *L)
|
||||||
|
{
|
||||||
|
auto &matrix = check(L, 1);
|
||||||
|
int row = read_index(L, 2);
|
||||||
|
int col = read_index(L, 3);
|
||||||
|
f64 value = luaL_checknumber(L, 4);
|
||||||
|
matrix(row, col) = value;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_get_row(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
int row = read_index(L, 2);
|
||||||
|
for (int col = 0; col < 4; ++col)
|
||||||
|
lua_pushnumber(L, matrix(row, col));
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_set_row(lua_State *L)
|
||||||
|
{
|
||||||
|
auto &matrix = check(L, 1);
|
||||||
|
int row = read_index(L, 2);
|
||||||
|
f32 x = luaL_checknumber(L, 3);
|
||||||
|
f32 y = luaL_checknumber(L, 4);
|
||||||
|
f32 z = luaL_checknumber(L, 5);
|
||||||
|
f32 w = luaL_checknumber(L, 6);
|
||||||
|
matrix(row, 0) = x;
|
||||||
|
matrix(row, 1) = y;
|
||||||
|
matrix(row, 2) = z;
|
||||||
|
matrix(row, 3) = w;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_get_column(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
int col = read_index(L, 2);
|
||||||
|
for (int row = 0; row < 4; ++row)
|
||||||
|
lua_pushnumber(L, matrix(row, col));
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_set_column(lua_State *L)
|
||||||
|
{
|
||||||
|
auto &matrix = check(L, 1);
|
||||||
|
int col = read_index(L, 2);
|
||||||
|
f32 x = luaL_checknumber(L, 3);
|
||||||
|
f32 y = luaL_checknumber(L, 4);
|
||||||
|
f32 z = luaL_checknumber(L, 5);
|
||||||
|
f32 w = luaL_checknumber(L, 6);
|
||||||
|
matrix(0, col) = x;
|
||||||
|
matrix(1, col) = y;
|
||||||
|
matrix(2, col) = z;
|
||||||
|
matrix(3, col) = w;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_copy(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
create(L) = matrix;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_unpack(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
lua_createtable(L, 16, 0);
|
||||||
|
for (int i = 0; i < 16; ++i)
|
||||||
|
lua_pushnumber(L, matrix[i]);
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linear algebra
|
||||||
|
|
||||||
|
int LuaMatrix4::l_transform_4d(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
f32 vec4[4];
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
vec4[i] = luaL_checknumber(L, i + 2);
|
||||||
|
f32 res[4];
|
||||||
|
matrix.transformVec4(res, vec4);
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
lua_pushnumber(L, res[i]);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_transform_position(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
v3f vec = readParam<v3f>(L, 2);
|
||||||
|
matrix.transformVect(vec);
|
||||||
|
push_v3f(L, vec);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_transform_direction(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
v3f vec = readParam<v3f>(L, 2);
|
||||||
|
v3f res = matrix.rotateAndScaleVect(vec);
|
||||||
|
push_v3f(L, res);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_compose(lua_State *L)
|
||||||
|
{
|
||||||
|
int n_args = lua_gettop(L);
|
||||||
|
if (n_args == 0)
|
||||||
|
return LuaMatrix4::l_identity(L);
|
||||||
|
const auto &first = check(L, 1);
|
||||||
|
auto &product = create(L);
|
||||||
|
product = first;
|
||||||
|
for (int i = 2; i <= n_args; ++i) {
|
||||||
|
product *= check(L, i);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_transpose(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
create(L) = matrix.getTransposed();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_determinant(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
lua_pushnumber(L, matrix.determinant());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_invert(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
core::matrix4 inverse;
|
||||||
|
if (!matrix.getInverse(inverse)) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
create(L) = inverse;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_equals(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &a = check(L, 1);
|
||||||
|
const auto &b = check(L, 2);
|
||||||
|
f32 tol = luaL_optnumber(L, 3, 0.0);
|
||||||
|
lua_pushboolean(L, a.equals(b, tol));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_is_affine_transform(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
f32 tol = luaL_optnumber(L, 3, 0.0);
|
||||||
|
lua_pushboolean(L, matrix.isAffine(tol));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Affine transform helpers
|
||||||
|
|
||||||
|
int LuaMatrix4::l_get_translation(lua_State *L)
|
||||||
|
{
|
||||||
|
auto matrix = check(L, 1);
|
||||||
|
push_v3f(L, matrix.getTranslation());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_get_rs(lua_State *L)
|
||||||
|
{
|
||||||
|
// TODO ? should check that it is, in fact, a rotation matrix;
|
||||||
|
// not a fake rotation (axis flip) or a shear matrix
|
||||||
|
auto matrix = check(L, 1);
|
||||||
|
v3f scale = matrix.getScale();
|
||||||
|
if (scale.X == 0.0f || scale.Y == 0.0f || scale.Z == 0.0f) {
|
||||||
|
LuaRotation::create(L, core::quaternion());
|
||||||
|
push_v3f(L, scale);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
matrix.scaleAxes(v3f(1.0f) / scale);
|
||||||
|
LuaRotation::create(L, core::quaternion(matrix));
|
||||||
|
push_v3f(L, scale);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::l_set_translation(lua_State *L)
|
||||||
|
{
|
||||||
|
auto &matrix = check(L, 1);
|
||||||
|
v3f translation = readParam<v3f>(L, 2);
|
||||||
|
matrix.setTranslation(translation);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::mt_add(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &a = check(L, 1);
|
||||||
|
const auto &b = check(L, 2);
|
||||||
|
auto &res = create(L);
|
||||||
|
res = a;
|
||||||
|
res += b;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::mt_sub(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &a = check(L, 1);
|
||||||
|
const auto &b = check(L, 2);
|
||||||
|
auto &res = create(L);
|
||||||
|
res = a;
|
||||||
|
res -= b;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::mt_unm(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
auto &res = create(L);
|
||||||
|
res = matrix;
|
||||||
|
res *= -1.0f;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::mt_mul(lua_State *L)
|
||||||
|
{
|
||||||
|
if (lua_isnumber(L, 1)) {
|
||||||
|
f32 scalar = luaL_checknumber(L, 1);
|
||||||
|
const auto &matrix = check(L, 2);
|
||||||
|
create(L) = scalar * matrix;
|
||||||
|
} else {
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
f32 scalar = luaL_checknumber(L, 2);
|
||||||
|
create(L) = matrix * scalar;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::mt_eq(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &a = check(L, 1);
|
||||||
|
const auto &b = check(L, 2);
|
||||||
|
lua_pushboolean(L, a == b);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaMatrix4::mt_tostring(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &matrix = check(L, 1);
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << matrix;
|
||||||
|
std::string str = ss.str();
|
||||||
|
lua_pushlstring(L, str.c_str(), str.size());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *LuaMatrix4::packIn(lua_State *L, int idx)
|
||||||
|
{
|
||||||
|
return new core::matrix4(check(L, idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaMatrix4::packOut(lua_State *L, void *ptr)
|
||||||
|
{
|
||||||
|
auto *matrix = static_cast<core::matrix4 *>(ptr);
|
||||||
|
if (L)
|
||||||
|
create(L) = *matrix;
|
||||||
|
delete matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaMatrix4::Register(lua_State *L)
|
||||||
|
{
|
||||||
|
static const luaL_Reg metamethods[] = {
|
||||||
|
{"__tostring", mt_tostring},
|
||||||
|
{"__add", mt_add},
|
||||||
|
{"__sub", mt_sub},
|
||||||
|
{"__unm", mt_unm},
|
||||||
|
{"__mul", mt_mul},
|
||||||
|
{"__eq", mt_eq},
|
||||||
|
{0, 0}
|
||||||
|
};
|
||||||
|
registerClass<LuaMatrix4>(L, methods, metamethods);
|
||||||
|
|
||||||
|
lua_createtable(L, 0, 0);
|
||||||
|
int Matrix4 = lua_gettop(L);
|
||||||
|
#define CONSTRUCTOR(name) \
|
||||||
|
lua_pushcfunction(L, l_##name); \
|
||||||
|
lua_setfield(L, Matrix4, #name); \
|
||||||
|
|
||||||
|
CONSTRUCTOR(new)
|
||||||
|
CONSTRUCTOR(identity)
|
||||||
|
CONSTRUCTOR(all)
|
||||||
|
|
||||||
|
CONSTRUCTOR(translation)
|
||||||
|
CONSTRUCTOR(rotation)
|
||||||
|
CONSTRUCTOR(scale)
|
||||||
|
CONSTRUCTOR(reflection)
|
||||||
|
CONSTRUCTOR(compose)
|
||||||
|
#undef CONSTRUCTOR
|
||||||
|
lua_setglobal(L, className);
|
||||||
|
|
||||||
|
script_register_packer(L, className, packIn, packOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char LuaMatrix4::className[] = "Matrix4";
|
||||||
|
#define METHOD(name) luamethod(LuaMatrix4, name)
|
||||||
|
const luaL_Reg LuaMatrix4::methods[] = {
|
||||||
|
METHOD(get),
|
||||||
|
METHOD(set),
|
||||||
|
METHOD(get_row),
|
||||||
|
METHOD(set_row),
|
||||||
|
METHOD(get_column),
|
||||||
|
METHOD(set_column),
|
||||||
|
METHOD(copy),
|
||||||
|
METHOD(unpack),
|
||||||
|
METHOD(transform_4d),
|
||||||
|
METHOD(transform_position),
|
||||||
|
METHOD(transform_direction),
|
||||||
|
METHOD(compose),
|
||||||
|
METHOD(determinant),
|
||||||
|
METHOD(invert),
|
||||||
|
METHOD(transpose),
|
||||||
|
METHOD(equals),
|
||||||
|
METHOD(is_affine_transform),
|
||||||
|
METHOD(get_translation),
|
||||||
|
METHOD(get_rs),
|
||||||
|
METHOD(set_translation),
|
||||||
|
{0,0}
|
||||||
|
};
|
||||||
|
#undef METHOD
|
114
src/script/lua_api/l_matrix4.h
Normal file
114
src/script/lua_api/l_matrix4.h
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
// Luanti
|
||||||
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
// Copyright (C) 2025 Lars Müller
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "lua_api/l_base.h"
|
||||||
|
#include "matrix4.h"
|
||||||
|
#include <lua.h>
|
||||||
|
|
||||||
|
class LuaMatrix4 : public ModApiBase
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
core::matrix4 matrix;
|
||||||
|
|
||||||
|
static const luaL_Reg methods[];
|
||||||
|
|
||||||
|
// Exported functions
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
// identity()
|
||||||
|
static int l_identity(lua_State *L);
|
||||||
|
// all(number)
|
||||||
|
static int l_all(lua_State *L);
|
||||||
|
// translation(vec)
|
||||||
|
static int l_translation(lua_State *L);
|
||||||
|
// rotation(rot)
|
||||||
|
static int l_rotation(lua_State *L);
|
||||||
|
// reflection(normal)
|
||||||
|
static int l_reflection(lua_State *L);
|
||||||
|
// scale(vec)
|
||||||
|
static int l_scale(lua_State *L);
|
||||||
|
// new(a11, a12, ..., a44)
|
||||||
|
static int l_new(lua_State *L);
|
||||||
|
|
||||||
|
// Misc. container utils
|
||||||
|
|
||||||
|
// get(self, row, column)
|
||||||
|
static int l_get(lua_State *L);
|
||||||
|
// set(self, row, column, value)
|
||||||
|
static int l_set(lua_State *L);
|
||||||
|
// x, y, z, w = get_row(self, row)
|
||||||
|
static int l_get_row(lua_State *L);
|
||||||
|
// set_row(self, row, x, y, z, w)
|
||||||
|
static int l_set_row(lua_State *L);
|
||||||
|
// x, y, z, w = get_column(self, column)
|
||||||
|
static int l_get_column(lua_State *L);
|
||||||
|
// set_column(self, column, x, y, z, w)
|
||||||
|
static int l_set_column(lua_State *L);
|
||||||
|
// copy(self)
|
||||||
|
static int l_copy(lua_State *L);
|
||||||
|
// unpack(self)
|
||||||
|
static int l_unpack(lua_State *L);
|
||||||
|
|
||||||
|
// x, y, z, w = transform_4d(self, x, y, z, w)
|
||||||
|
static int l_transform_4d(lua_State *L);
|
||||||
|
// transform_position(self, vector)
|
||||||
|
static int l_transform_position(lua_State *L);
|
||||||
|
// transform_direction(self, vector)
|
||||||
|
static int l_transform_direction(lua_State *L);
|
||||||
|
// compose(self, other)
|
||||||
|
static int l_compose(lua_State *L);
|
||||||
|
// determinant(self)
|
||||||
|
static int l_determinant(lua_State *L);
|
||||||
|
// invert(self)
|
||||||
|
static int l_invert(lua_State *L);
|
||||||
|
// transpose(self)
|
||||||
|
static int l_transpose(lua_State *L);
|
||||||
|
// equals(self, other, [tolerance])
|
||||||
|
static int l_equals(lua_State *L);
|
||||||
|
// is_affine_transform(self)
|
||||||
|
static int l_is_affine_transform(lua_State *L);
|
||||||
|
|
||||||
|
// get_translation(self)
|
||||||
|
static int l_get_translation(lua_State *L);
|
||||||
|
// rotation, scale = get_rs(self)
|
||||||
|
static int l_get_rs(lua_State *L);
|
||||||
|
|
||||||
|
// set_translation(self, translation)
|
||||||
|
static int l_set_translation(lua_State *L);
|
||||||
|
|
||||||
|
// set_rotation and set_scale are deliberately omitted
|
||||||
|
// to nudge users not to decompose and recompose matrices.
|
||||||
|
// Instead they should store TRS transforms and construct matrices from them.
|
||||||
|
|
||||||
|
// m1 + m2
|
||||||
|
static int mt_add(lua_State *L);
|
||||||
|
// m1 - m2
|
||||||
|
static int mt_sub(lua_State *L);
|
||||||
|
// -m
|
||||||
|
static int mt_unm(lua_State *L);
|
||||||
|
// scalar * m; m * scalar
|
||||||
|
static int mt_mul(lua_State *L);
|
||||||
|
// m1 == m2
|
||||||
|
static int mt_eq(lua_State *L);
|
||||||
|
// tostring(m)
|
||||||
|
static int mt_tostring(lua_State *L);
|
||||||
|
|
||||||
|
static void *packIn(lua_State *L, int idx);
|
||||||
|
static void packOut(lua_State *L, void *ptr);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Constructor. Leaves the value on top of the stack.
|
||||||
|
// Returns a reference that *must* be overwritten.
|
||||||
|
[[nodiscard]] static inline core::matrix4 &create(lua_State *L);
|
||||||
|
|
||||||
|
[[nodiscard]] static core::matrix4 &check(lua_State *L, int index);
|
||||||
|
|
||||||
|
static void Register(lua_State *L);
|
||||||
|
|
||||||
|
static const char className[];
|
||||||
|
};
|
239
src/script/lua_api/l_rotation.cpp
Normal file
239
src/script/lua_api/l_rotation.cpp
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
// Luanti
|
||||||
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
// Copyright (C) 2025 Lars Müller
|
||||||
|
|
||||||
|
#include "lua_api/l_rotation.h"
|
||||||
|
#include "common/c_packer.h"
|
||||||
|
#include "lua_api/l_internal.h"
|
||||||
|
|
||||||
|
#include "common/c_converter.h"
|
||||||
|
#include "irr_v3d.h"
|
||||||
|
#include "quaternion.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
#include <lua.h>
|
||||||
|
|
||||||
|
core::quaternion &LuaRotation::check(lua_State *L, int index)
|
||||||
|
{
|
||||||
|
return static_cast<LuaRotation *>(luaL_checkudata(L, index, className))->quaternion;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaRotation::create(lua_State *L, const core::quaternion &quaternion)
|
||||||
|
{
|
||||||
|
auto *rot = static_cast<LuaRotation *>(lua_newuserdata(L, sizeof(LuaRotation)));
|
||||||
|
rot->quaternion = quaternion;
|
||||||
|
luaL_getmetatable(L, LuaRotation::className);
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
int LuaRotation::l_identity(lua_State *L)
|
||||||
|
{
|
||||||
|
create(L, core::quaternion());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaRotation::l_quaternion(lua_State *L)
|
||||||
|
{
|
||||||
|
// TODO be more strict.
|
||||||
|
f64 x = luaL_checknumber(L, 1);
|
||||||
|
f64 y = luaL_checknumber(L, 2);
|
||||||
|
f64 z = luaL_checknumber(L, 3);
|
||||||
|
f64 w = luaL_checknumber(L, 4);
|
||||||
|
// Note: Converted to f32
|
||||||
|
core::quaternion q(x, y, z, w);
|
||||||
|
q.normalize();
|
||||||
|
create(L, q);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaRotation::l_axis_angle(lua_State *L)
|
||||||
|
{
|
||||||
|
v3f axis = readParam<v3f>(L, 1);
|
||||||
|
f64 angle = luaL_checknumber(L, 2);
|
||||||
|
core::quaternion quaternion;
|
||||||
|
// Note: Axis converted to f32
|
||||||
|
axis.normalize();
|
||||||
|
quaternion.fromAngleAxis(angle, axis);
|
||||||
|
create(L, quaternion);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<float v3f::* C>
|
||||||
|
int LuaRotation::l_fixed_axis_angle(lua_State *L)
|
||||||
|
{
|
||||||
|
f64 angle = luaL_checknumber(L, 1);
|
||||||
|
// Note: Angle converted to f32
|
||||||
|
v3f axis;
|
||||||
|
axis.*C = 1.0f;
|
||||||
|
create(L, core::quaternion().fromAngleAxis(angle, axis));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaRotation::l_euler_angles(lua_State *L)
|
||||||
|
{
|
||||||
|
v3f euler = readParam<v3f>(L, 1);
|
||||||
|
core::quaternion quaternion;
|
||||||
|
// Note: Euler angles converted to f32
|
||||||
|
quaternion.set(euler.X, euler.Y, euler.Z);
|
||||||
|
create(L, quaternion);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaRotation::l_to_quaternion(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &q = check(L, 1);
|
||||||
|
lua_pushnumber(L, q.X);
|
||||||
|
lua_pushnumber(L, q.Y);
|
||||||
|
lua_pushnumber(L, q.Z);
|
||||||
|
lua_pushnumber(L, q.W);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaRotation::l_to_axis_angle(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto q = check(L, 1);
|
||||||
|
core::vector3df axis;
|
||||||
|
f32 angle;
|
||||||
|
q.toAngleAxis(angle, axis);
|
||||||
|
push_v3f(L, axis);
|
||||||
|
lua_pushnumber(L, angle);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaRotation::l_to_euler_angles(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &q = check(L, 1);
|
||||||
|
core::vector3df euler;
|
||||||
|
q.toEuler(euler);
|
||||||
|
lua_pushnumber(L, euler.X);
|
||||||
|
lua_pushnumber(L, euler.Y);
|
||||||
|
lua_pushnumber(L, euler.Z);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Math
|
||||||
|
|
||||||
|
int LuaRotation::l_apply(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &q = check(L, 1);
|
||||||
|
v3f vec = readParam<v3f>(L, 2);
|
||||||
|
push_v3f(L, q * vec);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaRotation::l_compose(lua_State *L)
|
||||||
|
{
|
||||||
|
int n_args = lua_gettop(L);
|
||||||
|
if (n_args == 0)
|
||||||
|
return LuaRotation::l_identity(L);
|
||||||
|
auto product = check(L, 1);
|
||||||
|
for (int i = 2; i <= n_args; ++i) {
|
||||||
|
product *= check(L, i);
|
||||||
|
}
|
||||||
|
create(L, product);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaRotation::l_invert(lua_State *L)
|
||||||
|
{
|
||||||
|
create(L, check(L, 1).makeInverse());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaRotation::l_slerp(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &from = check(L, 1);
|
||||||
|
const auto &to = check(L, 2);
|
||||||
|
f32 time = readParam<Finite<f32>>(L, 3).value;
|
||||||
|
core::quaternion result;
|
||||||
|
result.slerp(from, to, time);
|
||||||
|
create(L, result);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaRotation::l_angle_to(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &from = check(L, 1);
|
||||||
|
const auto &to = check(L, 2);
|
||||||
|
f32 angle = from.angleTo(to);
|
||||||
|
lua_pushnumber(L, angle);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialization
|
||||||
|
|
||||||
|
int LuaRotation::mt_tostring(lua_State *L)
|
||||||
|
{
|
||||||
|
const auto &q = check(L, 1);
|
||||||
|
lua_pushfstring(L, "(%f\t%f\t%f\t%f)", q.X, q.Y, q.Z, q.W);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *LuaRotation::packIn(lua_State *L, int idx)
|
||||||
|
{
|
||||||
|
return new core::quaternion(check(L, idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaRotation::packOut(lua_State *L, void *ptr)
|
||||||
|
{
|
||||||
|
auto *quat = static_cast<core::quaternion *>(ptr);
|
||||||
|
if (L)
|
||||||
|
create(L, *quat);
|
||||||
|
delete quat;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaRotation::Register(lua_State *L)
|
||||||
|
{
|
||||||
|
static const luaL_Reg metamethods[] = {
|
||||||
|
{"__tostring", mt_tostring},
|
||||||
|
{0, 0}
|
||||||
|
};
|
||||||
|
registerClass<LuaRotation>(L, methods, metamethods);
|
||||||
|
|
||||||
|
lua_createtable(L, 0, 0);
|
||||||
|
int Matrix4 = lua_gettop(L);
|
||||||
|
|
||||||
|
#define SET_CONSTRUCTOR(name, method) \
|
||||||
|
lua_pushcfunction(L, method); \
|
||||||
|
lua_setfield(L, Matrix4, name); \
|
||||||
|
|
||||||
|
#define CONSTRUCTOR(name) SET_CONSTRUCTOR(#name, l_##name)
|
||||||
|
|
||||||
|
CONSTRUCTOR(identity)
|
||||||
|
CONSTRUCTOR(quaternion)
|
||||||
|
CONSTRUCTOR(axis_angle)
|
||||||
|
CONSTRUCTOR(euler_angles)
|
||||||
|
CONSTRUCTOR(compose)
|
||||||
|
|
||||||
|
#undef CONSTRUCTOR
|
||||||
|
|
||||||
|
SET_CONSTRUCTOR("x", l_fixed_axis_angle<&v3f::X>)
|
||||||
|
SET_CONSTRUCTOR("y", l_fixed_axis_angle<&v3f::Y>)
|
||||||
|
SET_CONSTRUCTOR("z", l_fixed_axis_angle<&v3f::Z>)
|
||||||
|
|
||||||
|
#undef SET_CONSTRUCTOR
|
||||||
|
|
||||||
|
lua_setglobal(L, className);
|
||||||
|
|
||||||
|
script_register_packer(L, className, packIn, packOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char LuaRotation::className[] = "Rotation";
|
||||||
|
|
||||||
|
#define METHOD(name) luamethod(LuaRotation, name)
|
||||||
|
const luaL_Reg LuaRotation::methods[] = {
|
||||||
|
METHOD(to_quaternion),
|
||||||
|
METHOD(to_axis_angle),
|
||||||
|
METHOD(to_euler_angles),
|
||||||
|
METHOD(apply),
|
||||||
|
METHOD(compose),
|
||||||
|
METHOD(invert),
|
||||||
|
METHOD(slerp),
|
||||||
|
METHOD(angle_to),
|
||||||
|
{0,0}
|
||||||
|
};
|
||||||
|
#undef METHOD
|
69
src/script/lua_api/l_rotation.h
Normal file
69
src/script/lua_api/l_rotation.h
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// Luanti
|
||||||
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
// Copyright (C) 2025 Lars Müller
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "irr_v3d.h"
|
||||||
|
#include "lua_api/l_base.h"
|
||||||
|
#include "quaternion.h"
|
||||||
|
|
||||||
|
class LuaRotation : public ModApiBase
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
core::quaternion quaternion;
|
||||||
|
|
||||||
|
static const luaL_Reg methods[];
|
||||||
|
|
||||||
|
// Conversions
|
||||||
|
// Note: Matrix conversions are in l_matrix.h
|
||||||
|
|
||||||
|
// self = identity()
|
||||||
|
static int l_identity(lua_State *L);
|
||||||
|
// self = quaternion(x, y, z, w)
|
||||||
|
static int l_quaternion(lua_State *L);
|
||||||
|
// self = axis_angle(axis, angle)
|
||||||
|
static int l_axis_angle(lua_State *L);
|
||||||
|
// self = x(angle); self = y(angle); self = z(angle)
|
||||||
|
template<float v3f::* C>
|
||||||
|
static int l_fixed_axis_angle(lua_State *L);
|
||||||
|
// self = euler_angles(pitch, yaw, roll)
|
||||||
|
static int l_euler_angles(lua_State *L);
|
||||||
|
|
||||||
|
// x, y, z, w = to_quaternion(self)
|
||||||
|
static int l_to_quaternion(lua_State *L);
|
||||||
|
// axis, angle = to_axis_angle(self)
|
||||||
|
static int l_to_axis_angle(lua_State *L);
|
||||||
|
// pitch, yaw, roll = to_euler_angles(self)
|
||||||
|
static int l_to_euler_angles(lua_State *L);
|
||||||
|
|
||||||
|
// rotated_vector = apply(self, vector)
|
||||||
|
static int l_apply(lua_State *L);
|
||||||
|
// composition = compose(self, other)
|
||||||
|
static int l_compose(lua_State *L);
|
||||||
|
// inverse = invert(self)
|
||||||
|
static int l_invert(lua_State *L);
|
||||||
|
|
||||||
|
// slerped = slerp(from, to, time)
|
||||||
|
static int l_slerp(lua_State *L);
|
||||||
|
// angle = angle_to(to)
|
||||||
|
static int l_angle_to(lua_State *L);
|
||||||
|
|
||||||
|
// tostring(self)
|
||||||
|
static int mt_tostring(lua_State *L);
|
||||||
|
|
||||||
|
static void *packIn(lua_State *L, int idx);
|
||||||
|
static void packOut(lua_State *L, void *ptr);
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Constructor. Leaves the value on top of the stack.
|
||||||
|
static void create(lua_State *L, const core::quaternion &quaternion);
|
||||||
|
|
||||||
|
[[nodiscard]] static core::quaternion &check(lua_State *L, int index);
|
||||||
|
|
||||||
|
static void Register(lua_State *L);
|
||||||
|
|
||||||
|
static const char className[];
|
||||||
|
};
|
|
@ -3,6 +3,8 @@
|
||||||
// Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
// Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
|
||||||
#include "scripting_server.h"
|
#include "scripting_server.h"
|
||||||
|
#include "lua_api/l_rotation.h"
|
||||||
|
#include "lua_api/l_matrix4.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
@ -141,6 +143,8 @@ void ServerScripting::InitializeModApi(lua_State *L, int top)
|
||||||
LuaRaycast::Register(L);
|
LuaRaycast::Register(L);
|
||||||
LuaSecureRandom::Register(L);
|
LuaSecureRandom::Register(L);
|
||||||
LuaVoxelManip::Register(L);
|
LuaVoxelManip::Register(L);
|
||||||
|
LuaRotation::Register(L);
|
||||||
|
LuaMatrix4::Register(L);
|
||||||
NodeMetaRef::Register(L);
|
NodeMetaRef::Register(L);
|
||||||
NodeTimerRef::Register(L);
|
NodeTimerRef::Register(L);
|
||||||
ObjectRef::Register(L);
|
ObjectRef::Register(L);
|
||||||
|
@ -179,6 +183,8 @@ void ServerScripting::InitializeAsync(lua_State *L, int top)
|
||||||
LuaSecureRandom::Register(L);
|
LuaSecureRandom::Register(L);
|
||||||
LuaVoxelManip::Register(L);
|
LuaVoxelManip::Register(L);
|
||||||
LuaSettings::Register(L);
|
LuaSettings::Register(L);
|
||||||
|
LuaRotation::Register(L);
|
||||||
|
LuaMatrix4::Register(L);
|
||||||
|
|
||||||
// globals data
|
// globals data
|
||||||
auto *data = ModApiBase::getServer(L)->m_lua_globals_data.get();
|
auto *data = ModApiBase::getServer(L)->m_lua_globals_data.get();
|
||||||
|
|
|
@ -96,6 +96,22 @@ SECTION("matrix-quaternion roundtrip") {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("matrix-quaternion roundtrip") {
|
||||||
|
v3f rad(0, 0, irr::core::PI / 2);
|
||||||
|
// test_euler_angles_rad([](v3f rad) {
|
||||||
|
quaternion q;
|
||||||
|
q.set(rad);
|
||||||
|
matrix4 mat;
|
||||||
|
q.getMatrix(mat);
|
||||||
|
quaternion q2(mat);
|
||||||
|
// q2.makeInverse();
|
||||||
|
matrix4 mat2;
|
||||||
|
q2.getMatrix(mat2);
|
||||||
|
CHECK(matrix_equals(mat, mat2));
|
||||||
|
// CHECK(q.angleTo(q2) < 1e-4);
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("matrix-euler roundtrip") {
|
SECTION("matrix-euler roundtrip") {
|
||||||
test_euler_angles_rad([](v3f rad) {
|
test_euler_angles_rad([](v3f rad) {
|
||||||
matrix4 mat, mat2;
|
matrix4 mat, mat2;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue