-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Add matrix & rotation Lua APIs (2nd try) #16870
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
4511658
1bbf0a9
5cd05c5
f5f5e0b
520562e
2b53ad8
7008ad7
9f3a7d2
1cee968
a91aab0
c1f81f4
fc78638
a80ffcb
7aff109
6f10600
dde62da
ed83284
b40dd9e
69ea311
4f74d23
fffe3b3
305d6fa
6ede96a
b36db6f
545bfd2
3db0766
8110803
f42c657
caba0fa
63af28a
2fad015
808793e
4828bd5
e2378fb
6cc50b2
bbc1da8
2db2bc4
30642e7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3051,7 +3051,7 @@ Elements | |||||||||
| * `textures`: The mesh textures to use according to the mesh materials. | ||||||||||
| Texture names must be separated by commas. | ||||||||||
| * `rotation` (Optional): Initial rotation of the camera, format `x,y`. | ||||||||||
| The axes are euler angles in degrees. | ||||||||||
| The axes are Euler angles in degrees. | ||||||||||
| * `continuous` (Optional): Whether the rotation is continuous. Default `false`. | ||||||||||
| * `mouse control` (Optional): Whether the model can be controlled with the mouse. Default `true`. | ||||||||||
| * `frame loop range` (Optional): Range of the animation frames. | ||||||||||
|
|
@@ -3942,7 +3942,7 @@ It means that when you're pointing in +Z direction in-game ("forward"), +X is to | |||||||||
|
|
||||||||||
| Consistently, rotation is [**left-handed**](https://en.wikipedia.org/w/index.php?title=Right-hand_rule) as well. | ||||||||||
| Luanti uses [Tait-Bryan angles](https://en.wikipedia.org/wiki/Euler_angles#Tait%E2%80%93Bryan_angles) for rotations, | ||||||||||
| often referred to simply as "euler angles" (even though they are not "proper" euler angles). | ||||||||||
| often referred to simply as "Euler angles" (even though they are not "proper" Euler angles). | ||||||||||
| The rotation order is extrinsic X-Y-Z: | ||||||||||
| First rotation around the (unrotated) X-axis is applied, | ||||||||||
| then rotation around the (unrotated) Y-axis follows, | ||||||||||
|
|
@@ -4036,8 +4036,8 @@ For the following functions (and subchapters), | |||||||||
| `s` is a scalar (a number), | ||||||||||
| vectors are written like this: `(x, y, z)`: | ||||||||||
|
|
||||||||||
| * `vector.new([a[, b, c]])`: | ||||||||||
| * Returns a new vector `(a, b, c)`. | ||||||||||
| * `vector.new(x, y, z)`: | ||||||||||
| * Returns a new vector `(x, y, z)`. | ||||||||||
| * Deprecated: `vector.new()` does the same as `vector.zero()` and | ||||||||||
| `vector.new(v)` does the same as `vector.copy(v)` | ||||||||||
| * `vector.zero()`: | ||||||||||
|
|
@@ -4046,6 +4046,8 @@ vectors are written like this: `(x, y, z)`: | |||||||||
| * Returns a new vector of length 1, pointing into a direction chosen uniformly at random. | ||||||||||
| * `vector.copy(v)`: | ||||||||||
| * Returns a copy of the vector `v`. | ||||||||||
| * `vector.unpack(v)`: | ||||||||||
| * Returns the `x`, `y` and `z` components of the vector individually | ||||||||||
| * `vector.from_string(s[, init])`: | ||||||||||
| * Returns `v, np`, where `v` is a vector read from the given string `s` and | ||||||||||
| `np` is the next position in the string after the vector. | ||||||||||
|
|
@@ -4095,7 +4097,9 @@ vectors are written like this: `(x, y, z)`: | |||||||||
| * `vector.dot(v1, v2)`: | ||||||||||
| * Returns the dot product of `v1` and `v2`. | ||||||||||
| * `vector.cross(v1, v2)`: | ||||||||||
| * Returns the cross product of `v1` and `v2`. | ||||||||||
| * Returns the *right-handed* cross product of `v1` and `v2`. | ||||||||||
| * To get the left-handed cross product | ||||||||||
| (e.g. for use with rotations), swap `v1` and `v2`. | ||||||||||
| * `vector.offset(v, x, y, z)`: | ||||||||||
| * Returns the sum of the vectors `v` and `(x, y, z)`. | ||||||||||
| * `vector.check(v)`: | ||||||||||
|
|
@@ -4156,6 +4160,10 @@ For the following functions `a` is an angle in radians and `r` is a rotation | |||||||||
| vector (`{x = <pitch>, y = <yaw>, z = <roll>}`) where pitch, yaw and roll are | ||||||||||
| angles in radians. | ||||||||||
|
|
||||||||||
| Use of these functions is **discouraged** because they use | ||||||||||
| deprecated rotation conventions. | ||||||||||
| Prefer to use `Rotation` objects when possible. | ||||||||||
|
|
||||||||||
| * `vector.rotate(v, r)`: | ||||||||||
| * Applies the rotation `r` to `v` and returns the result. | ||||||||||
| * Uses (extrinsic) Z-X-Y rotation order and is right-handed, consistent with `ObjectRef:set_rotation`. | ||||||||||
|
|
@@ -4183,7 +4191,215 @@ For example: | |||||||||
| * `core.hash_node_position` (Only works on node positions.) | ||||||||||
| * `core.dir_to_wallmounted` (Involves wallmounted param2 values.) | ||||||||||
|
|
||||||||||
| Rotations | ||||||||||
| ========= | ||||||||||
|
|
||||||||||
| Luanti provides a proper helper class for working with 3d rotations. | ||||||||||
| Using vectors of Euler angles instead is discouraged as it is error-prone. | ||||||||||
| This class was added in Luanti 5.17.0. | ||||||||||
|
|
||||||||||
| The precision of the implementation may change (improve) in the future. | ||||||||||
| Rotations currently use 32-bit floats (*less* precision than the Lua number type). | ||||||||||
|
|
||||||||||
| Rotations use **left-handed** conventions. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also here. |
||||||||||
|
|
||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Would be helpful info (and grep endpoint) imo for users who want to use quaternions.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I think they would hit the "quaternion" constructor anyways. But sure, I can add this so nobody goes "where are my quaternions!?" 😅 |
||||||||||
| 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)`: | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ( Also here, sounds like it's measuring the axis of an angle (yes this makes no sense, but that's how I read x), the thinking comes afterwards). Similar for other ctors. Well, I guess one can get accustomed to this kind of naming. )
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is something I considered, but it makes things a little more verbose and I don't like that. I'm open to changing it though. 🤷 |
||||||||||
| Constructs a rotation around the given axis by the given angle. | ||||||||||
| * `axis` is a nonzero vector, which need not be normalized | ||||||||||
| * `angle` is in radians | ||||||||||
| * Example: `Rotation.axis_angle(vector.new(1, 0, 1), math.pi/2)` | ||||||||||
| is a half-turn around the bisector between the X and Z axes. | ||||||||||
| * There are shorthands for rotations around the cardinal axes: | ||||||||||
| * `Rotation.x(pitch)` | ||||||||||
| * `Rotation.y(yaw)` | ||||||||||
| * `Rotation.z(roll)` | ||||||||||
| * `Rotation.euler_xyz(pitch, yaw, roll)` | ||||||||||
| * All angles in radians. | ||||||||||
| * Uses X-Y-Z rotation order, 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. | ||||||||||
| * `Rotation.euler_zxy(pitch, yaw, roll)` | ||||||||||
| * Same as `euler_xyz`, but uses Z-X-Y rotation order. | ||||||||||
| * This is consistent with the Euler angles that can be used for entities. | ||||||||||
| You can do `Rotation.euler_zxy((-rotation):unpack())` | ||||||||||
| to convert an entity rotation vector (note the handedness conversion). | ||||||||||
| * `Rotation.mapsto(dir_from, dir_to)`: | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sound like a function that returns a bool. Also, why no snake case?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think of "mapsto" as a single symbol, but you're right that our naming convention requires an underscore here. |
||||||||||
| Construct a rotation that maps `dir_from` to `dir_to`. | ||||||||||
| * `dir_from` and `dir_to` are nonzero direction vectors. | ||||||||||
| * The given rotation only rotates in the plane spanned by the two vectors. It is thus uniquely defined. | ||||||||||
| * `Rotation.compose(...)`: Returns the composition of the given rotations. | ||||||||||
| * `Rotation.compose()` is an alias for `Rotation.identity()`. | ||||||||||
| * `Rotation.compose(rot)` copies the rotation. | ||||||||||
| * `Rotation.compose(...)` for at least two rotations composes the given rotations | ||||||||||
| in right-to-left order. This means that `Rotation.compose(second, first):apply(v)` | ||||||||||
| is equivalent to `second:apply(first:apply(v))`: | ||||||||||
| The composed rotation first applies `first`, then `second` to a vector. | ||||||||||
|
|
||||||||||
| Conversions | ||||||||||
| ----------- | ||||||||||
|
|
||||||||||
| Corresponding to the constructors, rotations can be converted | ||||||||||
| to different representations. | ||||||||||
| Note that this conversion is not guaranteed to produce the same values you put in. | ||||||||||
| It is only guaranteed that the values produce an approximately equivalent rotation | ||||||||||
| when passed to the corresponding constructor. | ||||||||||
|
|
||||||||||
| * `x, y, z, w = rot:to_quaternion()` | ||||||||||
| * Returns the normalized quaternion representation. | ||||||||||
| * `axis, angle = rot:to_axis_angle()` | ||||||||||
| * `axis` is a normalized vector that can point in any direction. | ||||||||||
| * `angle` is the rotation about this axis as an angle in radians. | ||||||||||
| * `pitch, yaw, roll = rot:to_euler_xyz()` | ||||||||||
| * Angles are all in radians. | ||||||||||
| * `pitch`, `yaw`, `roll`: Rotation around the X-, Y-, and Z-axis respectively. | ||||||||||
| * Inverse of `Rotation.euler_xyz`. | ||||||||||
| * `pitch, yaw, roll = rot:to_euler_zxy()` | ||||||||||
| * Same as `to_euler_xyz`, except uses Z-X-Y rotation order. | ||||||||||
| * To obtain a rotation for an entity, you can do | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
(Where dings and W-W-W are templates.) |
||||||||||
| `-vector.new(rot:to_euler_xyz())`. | ||||||||||
|
|
||||||||||
| Rotations can also be converted to matrices using `Matrix4.rotation(rot)`. | ||||||||||
|
|
||||||||||
| Methods | ||||||||||
| ------- | ||||||||||
|
|
||||||||||
| * `rot:compose(...)`: Shorthand for `Rotation.compose(rot, ...)`. | ||||||||||
| * `rot:apply(vec)`: Returns the result of applying the rotation to the given vector. | ||||||||||
| * `rot:invert()`: Returns the inverse rotation. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Could be "inverse" for noun naming.)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I think it reads better if we don't stick too rigidly to noun naming. I don't want "compose" to be "composition" etc. For another example, if we stuck strictly to output-based noun naming, it'd have to be |
||||||||||
| * `from:slerp(to, time)`: Interpolate from one rotation to another. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "In-place" or "returns interpolated"? Also, if latter, can we have
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Returns interpolated. We can have that. In general, I wonder whether all methods should be accessible as |
||||||||||
| * `time = 0` is all `from`, `time = 1` is all `to`. | ||||||||||
| * `rot: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 in the future. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Btw, maybe we should also implement concat, also for vectors. (I didn't back then for vectors and I'm not sure how to feel about it. But
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel it's better not to have it, because I think that users usually ought to be explicit in which formatting they want, and this helps enforce that. Often, you probably want to round many digits away (how many?). Other times, you might want the data to be preserved exactly. No default fits all well here, it's just a foot gun. Users should be forced to make an explicit decision. See also #16943 for related discussion. |
||||||||||
|
|
||||||||||
|
|
||||||||||
| Matrices | ||||||||||
| ======== | ||||||||||
|
|
||||||||||
| Luanti uses 4x4 matrices to represent linear transformations of 3D vectors. | ||||||||||
| For this, 3D vectors are embedded into 4D space. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. into 3D projective space using homogeneous coordinates. |
||||||||||
| This class was added in Luanti 5.17.0. | ||||||||||
|
|
||||||||||
| The matrices use column-major conventions. | ||||||||||
| Vectors are treated as column vectors. | ||||||||||
| This means the first column is the image of the vector (1, 0, 0, 0), | ||||||||||
| the second column is the image of (0, 1, 0, 0), and so on. | ||||||||||
| Thus the translation is in the last column. | ||||||||||
|
|
||||||||||
| You must account for loss of precision in matrix calculations. | ||||||||||
| Matrices currently use 32-bit floats (*less* precision than the Lua number type). | ||||||||||
| However, your code should not expect imprecisions either. | ||||||||||
| Matrices may carry out computations more precisely in the future. | ||||||||||
|
Comment on lines
+4299
to
+4300
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Upper sentence is kinda confusing, imo.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will clarify |
||||||||||
|
|
||||||||||
| You should not rely on the internal representation or type of matrices. | ||||||||||
| You should only interact with matrices through the interface documented below. | ||||||||||
| This allows us to replace the implementation in the future. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/should/must/ Would also be good to explicitly mention the Also, Rotations should have the same paragraph.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was sort of planning to explicitly not mention the userdata type. Why should the user care after I've just told them explicitly not to? x)
Hmm. At some point some of this should probably be a general note on "classes" to not be repeated on every new "class" we introduce. |
||||||||||
|
|
||||||||||
| Matrices are very suitable for constructing, composing and applying | ||||||||||
| linear transformations; they are not useful for exact storage of | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Linear 4D transformations.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. indeed |
||||||||||
| TRS transformations if the properties need to be handled separately: | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please spell out TRS at least once. |
||||||||||
| Decomposition into rotation and scale will be expensive and inexact. | ||||||||||
| You should instead store the translation, rotation and scale. | ||||||||||
|
|
||||||||||
| 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.full(number)`: Constructs a matrix where all entries are the given number. | ||||||||||
sfan5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
| * `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(s)`: Constructs a matrix that applies the given | ||||||||||
| component-wise scaling factors to vectors. | ||||||||||
| `s` can be a vector or a number. | ||||||||||
| * `Matrix4.trs([t], [r], [s])`: Shorthand for | ||||||||||
| `Matrix4.compose(Matrix4.translation(t), Matrix4.rotation(r), Matrix4.scale(s))`. | ||||||||||
| All parameters are optional and default to identity transforms. | ||||||||||
| * `Matrix4.reflection(normal)`: Constructs a matrix that reflects vectors | ||||||||||
| at the plane with the given plane normal vector (which need not be normalized). | ||||||||||
| * `Matrix4.compose(...)`: Variadic composition of the given matrices. | ||||||||||
| As is common in mathematics, matrices are applied in left-to-right order. | ||||||||||
| * `Matrix4.compose(...)`: Returns the composition of the given matrices. | ||||||||||
| * `Matrix4.compose()` is an alias for `Matrix4.identity()`. | ||||||||||
| * `Matrix4.compose(mat)` copies the matrix. | ||||||||||
| * `Matrix4.compose(...)` for at least two rotations composes the given matrices | ||||||||||
| in right-to-left order. This means that `Matrix4.compose(second, first):apply(v)` | ||||||||||
| is equivalent to `second:apply(first:apply(v))`: | ||||||||||
| The composed matrix first applies `first`, then `second` to a vector. | ||||||||||
|
|
||||||||||
| Container utilities: | ||||||||||
|
|
||||||||||
| For all of the below methods, `row` and `col`umn indices range from `1` to `4`. | ||||||||||
|
|
||||||||||
| * `mat:get(row, col)`: Returns the number in the given row and column. | ||||||||||
| * `mat:set(row, col, number)`: Set the entry in the given row and column to the given number. | ||||||||||
| * `x, y, z, w = mat:get_row(row)`: Get the 4 numbers in the given row. | ||||||||||
| * `mat:set_row(row, x, y, z, w)`: Set the 4 numbers in the given row. | ||||||||||
| * `x, y, z, w = mat:get_col(col)`: Get the 4 numbers in the given column. | ||||||||||
| * `mat:set_col(col, x, y, z, w)`: Set the 4 numbers in the given column. | ||||||||||
| * `mat:copy()`: Returns a new matrix containing the same numbers. | ||||||||||
| * `r1c1, r1c2, ..., r4c4 = mat:unpack()`: | ||||||||||
| Get the 16 numbers in the matrix in row-major order. | ||||||||||
| `Matrix4.new(mat:unpack())` is thus equivalent to `mat:copy()`. | ||||||||||
|
|
||||||||||
| Linear algebra: | ||||||||||
|
|
||||||||||
| * Vector transformations: | ||||||||||
| * `x, y, z, w = mat:transform_4d(x, y, z, w)`: Apply the matrix to a 4d vector. | ||||||||||
| * `mat:transform_pos(pos)`: | ||||||||||
| * Apply the matrix to a vector representing a position. | ||||||||||
| * Applies the transformation as if w = 1 and discards the resulting w component. | ||||||||||
|
Comment on lines
+4358
to
+4360
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So ... does this effectively do
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you take |
||||||||||
| * `mat:transform_dir(dir)`: | ||||||||||
| * Apply the matrix to a vector representing a direction. | ||||||||||
| * Ignores the fourth row and column; does not apply the translation (w = 0). | ||||||||||
| * `mat:compose(...)`: Shorthand for `Matrix4.compose(mat, ...)`. | ||||||||||
| * `mat:determinant()`: Returns the determinant. | ||||||||||
| * `mat:invert()`: Returns a newly created inverse, or `nil` if the matrix is (close to being) singular. | ||||||||||
| * `mat:transpose()`: Returns a transposed copy of the matrix. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ( Would like to also have the adjucate (adj) (essentially a cheaper but scaled inverse). (And combination of adj and transposed.) ) |
||||||||||
| * `mat: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`). | ||||||||||
| * `mat: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 row is approximately 0, 0, 0, 1.) | ||||||||||
|
|
||||||||||
| For working with affine transforms, the following methods are available: | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if not, methods will still exist, right? UB? Or unspecified result? (I.e. may it crash?)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep, will still exist. basically UB. in practice likely bunch of NaNs, maybe SIGFPE? |
||||||||||
|
|
||||||||||
| * `mat:get_translation()`: Returns the translation as a vector. | ||||||||||
| * `mat:set_translation(vec)`: Sets (overwrites) the translation in the last row. | ||||||||||
|
|
||||||||||
| For TRS transforms specifically, | ||||||||||
| let `mat = Matrix4.compose(Matrix4.translation(t), Matrix4.rotation(r), Matrix4.scale(s))`. | ||||||||||
| Then we can decompose `mat` further. Note that `mat` must not shear or reflect. | ||||||||||
|
|
||||||||||
| * `rotation, scale = mat:get_rs()`: | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When reading
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will give this a longer name. |
||||||||||
| Extracts a `Rotation` equivalent to `r`, | ||||||||||
| along with the corresponding component-wise scaling factors as a vector. | ||||||||||
|
|
||||||||||
| Operators | ||||||||||
| --------- | ||||||||||
|
|
||||||||||
| Similar to vectors, matrices define some element-wise arithmetic operators: | ||||||||||
|
|
||||||||||
| * `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 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 | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| -- A simple substitute for a busted-like unit test interface | ||
|
|
||
| local bustitute = {} | ||
|
|
||
| local test_env = setmetatable({}, {__index = _G}) | ||
|
|
||
| test_env.assert = setmetatable({}, {__call = function(_, ...) | ||
| return assert(...) | ||
| end}) | ||
|
|
||
| function test_env.assert.equals(expected, got) | ||
| if expected ~= got then | ||
| error("expected " .. dump(expected) .. ", got " .. dump(got)) | ||
| end | ||
| end | ||
|
|
||
| local function same(a, b) | ||
| if a == b then | ||
| return true | ||
| end | ||
| if type(a) ~= "table" or type(b) ~= "table" then | ||
| return false | ||
| end | ||
| for k, v in pairs(a) do | ||
| if not same(b[k], v) then | ||
| return false | ||
| end | ||
| end | ||
| for k, v in pairs(b) do | ||
| if a[k] == nil then -- if a[k] is present, we already compared them above | ||
| return false | ||
| end | ||
| end | ||
| return true | ||
| end | ||
|
|
||
| function test_env.assert.same(expected, got) | ||
| if not same(expected, got) then | ||
| error("expected " .. dump(expected) .. ", got " .. dump(got)) | ||
| end | ||
| end | ||
|
|
||
| local full_test_name = {} | ||
|
|
||
| function test_env.describe(name, func) | ||
| table.insert(full_test_name, name) | ||
| func() | ||
| table.remove(full_test_name) | ||
| end | ||
|
|
||
| function test_env.it(name, func) | ||
| table.insert(full_test_name, name) | ||
| unittests.register(table.concat(full_test_name, " "), func, {random = true}) | ||
| table.remove(full_test_name) | ||
| end | ||
|
|
||
| function bustitute.register(name) | ||
| local modpath = core.get_modpath(core.get_current_modname()) | ||
| local chunk = assert(loadfile(modpath .. "/" .. name .. ".lua")) | ||
| setfenv(chunk, test_env) | ||
| test_env.describe(name, chunk) | ||
| end | ||
|
|
||
| return bustitute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cross((1,0,0), (0,1,0)) is (0,0,1).
In a right-handed coord system this is right-handed.
But luanti worlds use a left-handed coord system, so it's left-handed.
Doc needs to be clear. Just saying "right-handed" is ambiguous.