1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00
This commit is contained in:
Lars Mueller 2025-05-29 02:13:52 +02:00
parent 513532a93c
commit f7067644a3
8 changed files with 107 additions and 92 deletions

View file

@ -4154,19 +4154,22 @@ For example:
Rotations
=========
As abusing vectors of euler angles is discouraged as error-prone,
Luanti provides a proper helper class for working with 3d rotations.
Using vectors of euler angles instead is discouraged as it is error-prone.
You must not rely on the specific type or imprecision of the current implementation.
The precision of the implementation may change (improve) in the future.
Adhering to Luanti and Irrlicht conventions, rotations use **left-handed** conventions
with a rotation order of **XYZ** (X first, then Y, then Z).
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)
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
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:
@ -4175,16 +4178,15 @@ Constructors
* `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.
* Mathematically equivalent to `Rotation.compose(Rotation.z(roll), Rotation.y(yaw), Rotation.x(pitch))`.
* Consistent with the euler angles that can be used for bones or attachments.
Conversions
-----------
Corresponding to the constructors, quaternions can be converted
Corresponding to the constructors, rotations 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:
you merely get values that produce a (roughly) equivalent rotation when passed to the corresponding constructor:
* `x, y, z, w = Rotation:to_quaternion()`
* Returns the normalized quaternion representation.
@ -4194,8 +4196,8 @@ you merely get values that produce the same rotation when passed to the correspo
* `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 -->
Rotations can also be converted to matrices using `Matrix4.rotation(rot)`.
Methods
-------
@ -4212,11 +4214,14 @@ Methods
* `Rotation:angle_to(other)`: Returns the absolute angle between two quaternions.
* Useful to measure similarity.
Rotations implement `__tostring`. The format is only intended for human-readability,
not serialization, and may thus change.
Matrices
========
Luanti uses 4x4 matrices to represent transformations of 3d vectors.
Luanti uses 4x4 matrices to represent transformations of 3d vectors (embedded into 4d space).
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.
@ -4231,6 +4236,8 @@ 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.
Row and column indices range from `1` to `4`.
Constructors
------------
@ -4250,22 +4257,19 @@ 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
* `Matrix4:get(row, col)`
* `Matrix4:set(row, col, number)`
* `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.
* `Matrix4:copy()`
* `... = Matrix4:unpack()`: Get the 16 numbers in the matrix in row-major order
(inverse of `Matrix4.new`).
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.
* Vector transformations:
* `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.
@ -4273,8 +4277,14 @@ Linear algebra:
* `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.compose(...)`: Returns the composition of the given matrices.
If `...` is empty, this is just the identity.
* `Matrix4:determinant()`: Returns the determinant.
* `Matrix4:invert()`: Returns a newly created inverse, or `nil` if the matrix is (close to being) singular.
* `Matrix4:transpose()`: Returns a transposed copy of the matrix.
* `Matrix4:equals(other, [tolerance = 0])`:
Returns whether all components differ in absolute value at most by the given tolerance.
* `m1 == m2`: Returns whether `m1` and `m2` are identical (`tolerance = 0`).
* `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.
@ -4282,36 +4292,33 @@ Linear algebra:
For working with affine transforms, the following methods are available:
* `Matrix4:get_translation()`:
Returns the translation as a vector.
* `Matrix4:set_translation(vec)`
* `Matrix4:get_translation()`: Returns the translation as a vector.
* `Matrix4:set_translation(vec)`: Sets (overwrites) the translation in the last row.
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`.
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:
Similar to vectors, matrices define some element-wise 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`: Returns the additive inverse.
* `m * s` or `s * m`: Returns the matrix `m` scaled by the scalar `s`.
* Note: *All* entries are scaled, including the last row.
* Note: *All* entries are scaled, including the last column:
The matrix may not be an affine transform afterwards.
Matrices also define a `__tostring` metamethod.
This is only intended for human readability and not for serialization.
Helper functions
================

View file

@ -1663,6 +1663,7 @@ inline void CMatrix4<T>::getTransposed(CMatrix4<T> &o) const
template <class T>
std::ostream& operator<<(std::ostream& os, const CMatrix4<T>& matrix)
{
os << "(\n";
for (int row = 0; row < 4; ++row) {
for (int col = 0; col < 4; ++col) {
os << "\t";
@ -1670,6 +1671,7 @@ std::ostream& operator<<(std::ostream& os, const CMatrix4<T>& matrix)
}
os << "\n";
}
os << ")";
return os;
}

View file

@ -9,6 +9,8 @@
#include "matrix4.h"
#include "vector3d.h"
#include <ostream>
// NOTE: You *only* need this when updating an application from Irrlicht before 1.8 to Irrlicht 1.8 or later.
// Between Irrlicht 1.7 and Irrlicht 1.8 the quaternion-matrix conversions changed.
// Before the fix they had mixed left- and right-handed rotations.
@ -215,6 +217,12 @@ public:
f32 W; // real part
};
std::ostream& operator<<(std::ostream& os, const quaternion& q)
{
os << q.X << "\t" << q.Y << "\t" << q.Z << "\t" << q.W;
return os;
}
// Constructor which converts Euler angles to a quaternion
inline quaternion::quaternion(f32 x, f32 y, f32 z)
{

View file

@ -13,7 +13,6 @@ extern "C" {
#include <irr_v3d.h>
#include <string_view>
#include "c_converter.h"
#include "c_types.h"
/*
* Read template functions
@ -50,12 +49,12 @@ f64 LuaHelper::readParam(lua_State *L, int index)
}
template <>
LuaHelper::Finite<f32> LuaHelper::readParam(lua_State *L, int index)
f32 LuaHelper::readFiniteParam(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};
return v;
if (std::isnan(original_value))
luaL_argerror(L, index, "number is NaN");
if (!std::isfinite(original_value))
@ -66,11 +65,11 @@ LuaHelper::Finite<f32> LuaHelper::readParam(lua_State *L, int index)
}
template <>
LuaHelper::Finite<f64> LuaHelper::readParam(lua_State *L, int index)
f64 LuaHelper::readFiniteParam(lua_State *L, int index)
{
f64 v = luaL_checknumber(L, index);
if (std::isfinite(v))
return {v};
return v;
if (std::isnan(v))
luaL_argerror(L, index, "number is NaN");
luaL_argerror(L, index, "number is not finite");

View file

@ -4,7 +4,6 @@
#pragma once
#include <cmath>
#include <string_view>
extern "C" {
@ -25,11 +24,12 @@ protected:
template <typename T>
static T readParam(lua_State *L, int index);
/// Type to represent a restriction to finite floats
/**
* @brief Read a value, but restrict to finite floats.
* @see readParam
*/
template <typename T>
struct Finite {
T value;
};
static T readFiniteParam(lua_State *L, int index);
/**
* Read a value using a template type T from Lua state L at index

View file

@ -14,19 +14,18 @@
#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)
template<int MAX>
int LuaMatrix4::readIndex(lua_State *L, int index)
{
f64 value = luaL_checknumber(L, index);
f64 value = readParam<f64>(L, index);
if (std::floor(value) != value)
luaL_argerror(L, index, "index must be integer");
if (value < 1 || value > max)
if (value < 1 || value > MAX)
luaL_argerror(L, index, "index out of range");
return static_cast<int>(value) - 1;
}
@ -52,7 +51,7 @@ int LuaMatrix4::l_identity(lua_State *L)
int LuaMatrix4::l_all(lua_State *L)
{
f32 v = luaL_checknumber(L, 1);
f32 v = readParam<f32>(L, 1);
create(L) = v;
return 1;
}
@ -63,7 +62,7 @@ int LuaMatrix4::l_new(lua_State *L)
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);
matrix[i] = readParam<f32>(L, 1 + i);
return 1;
}
@ -118,8 +117,8 @@ int LuaMatrix4::l_reflection(lua_State *L)
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);
int row = readIndex(L, 2);
int col = readIndex(L, 3);
lua_pushnumber(L, matrix(row, col));
return 1;
}
@ -127,9 +126,9 @@ int LuaMatrix4::l_get(lua_State *L)
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);
int row = readIndex(L, 2);
int col = readIndex(L, 3);
f64 value = readParam<f64>(L, 4);
matrix(row, col) = value;
return 0;
}
@ -137,7 +136,7 @@ int LuaMatrix4::l_set(lua_State *L)
int LuaMatrix4::l_get_row(lua_State *L)
{
const auto &matrix = check(L, 1);
int row = read_index(L, 2);
int row = readIndex(L, 2);
for (int col = 0; col < 4; ++col)
lua_pushnumber(L, matrix(row, col));
return 4;
@ -146,11 +145,11 @@ int LuaMatrix4::l_get_row(lua_State *L)
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);
int row = readIndex(L, 2);
f32 x = readParam<f32>(L, 3);
f32 y = readParam<f32>(L, 4);
f32 z = readParam<f32>(L, 5);
f32 w = readParam<f32>(L, 6);
matrix(row, 0) = x;
matrix(row, 1) = y;
matrix(row, 2) = z;
@ -161,7 +160,7 @@ int LuaMatrix4::l_set_row(lua_State *L)
int LuaMatrix4::l_get_column(lua_State *L)
{
const auto &matrix = check(L, 1);
int col = read_index(L, 2);
int col = readIndex(L, 2);
for (int row = 0; row < 4; ++row)
lua_pushnumber(L, matrix(row, col));
return 4;
@ -170,11 +169,11 @@ int LuaMatrix4::l_get_column(lua_State *L)
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);
int col = readIndex(L, 2);
f32 x = readParam<f32>(L, 3);
f32 y = readParam<f32>(L, 4);
f32 z = readParam<f32>(L, 5);
f32 w = readParam<f32>(L, 6);
matrix(0, col) = x;
matrix(1, col) = y;
matrix(2, col) = z;
@ -205,7 +204,7 @@ 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);
vec4[i] = readParam<f32>(L, i + 2);
f32 res[4];
matrix.transformVec4(res, vec4);
for (int i = 0; i < 4; ++i)
@ -354,12 +353,12 @@ int LuaMatrix4::mt_unm(lua_State *L)
int LuaMatrix4::mt_mul(lua_State *L)
{
if (lua_isnumber(L, 1)) {
f32 scalar = luaL_checknumber(L, 1);
f32 scalar = readParam<f32>(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);
f32 scalar = readParam<f32>(L, 2);
create(L) = matrix * scalar;
}
return 1;

View file

@ -100,6 +100,9 @@ private:
static void *packIn(lua_State *L, int idx);
static void packOut(lua_State *L, void *ptr);
template<int max = 4>
static int readIndex(lua_State *L, int index);
public:
// Constructor. Leaves the value on top of the stack.

View file

@ -10,10 +10,9 @@
#include "irr_v3d.h"
#include "quaternion.h"
#include <cmath>
#include <cstring>
#include <lauxlib.h>
#include <lua.h>
#include <sstream>
core::quaternion &LuaRotation::check(lua_State *L, int index)
{
@ -38,12 +37,10 @@ int LuaRotation::l_identity(lua_State *L)
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
f32 x = readFiniteParam<f32>(L, 1);
f32 y = readFiniteParam<f32>(L, 2);
f32 z = readFiniteParam<f32>(L, 3);
f32 w = readFiniteParam<f32>(L, 4);
core::quaternion q(x, y, z, w);
q.normalize();
create(L, q);
@ -53,9 +50,8 @@ int LuaRotation::l_quaternion(lua_State *L)
int LuaRotation::l_axis_angle(lua_State *L)
{
v3f axis = readParam<v3f>(L, 1);
f64 angle = luaL_checknumber(L, 2);
f32 angle = readFiniteParam<f32>(L, 2);
core::quaternion quaternion;
// Note: Axis converted to f32
axis.normalize();
quaternion.fromAngleAxis(angle, axis);
create(L, quaternion);
@ -65,8 +61,7 @@ int LuaRotation::l_axis_angle(lua_State *L)
template<float v3f::* C>
int LuaRotation::l_fixed_axis_angle(lua_State *L)
{
f64 angle = luaL_checknumber(L, 1);
// Note: Angle converted to f32
f32 angle = readFiniteParam<f32>(L, 1);
v3f axis;
axis.*C = 1.0f;
create(L, core::quaternion().fromAngleAxis(angle, axis));
@ -77,7 +72,6 @@ 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;
@ -148,7 +142,7 @@ 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;
f32 time = readFiniteParam<f32>(L, 3);
core::quaternion result;
result.slerp(from, to, time);
create(L, result);
@ -169,7 +163,10 @@ int LuaRotation::l_angle_to(lua_State *L)
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);
std::stringstream ss;
ss << q;
std::string str = ss.str();
lua_pushlstring(L, str.c_str(), str.size());
return 1;
}