From 2c7a244aa2f0e4f06c27111b3ec91e99edb3f5f5 Mon Sep 17 00:00:00 2001 From: FatalErr42O <58855799+FatalError42O@users.noreply.github.com> Date: Sat, 30 Nov 2024 21:02:40 -0800 Subject: [PATCH] implement luanti and irrlicht transformations --- docs/index.html | 2 +- docs/modules/bound2.html | 2 +- docs/modules/bound3.html | 2 +- docs/modules/bvh.html | 2 +- docs/modules/color.html | 2 +- docs/modules/constants.html | 2 +- docs/modules/intersect.html | 2 +- docs/modules/mat4.html | 341 +++++++++++++++++++++++++++++++- docs/modules/mesh.html | 2 +- docs/modules/octree.html | 2 +- docs/modules/quat.html | 308 ++++++++++++++++++++++------- docs/modules/simplex.html | 2 +- docs/modules/utils.html | 2 +- docs/modules/vec2.html | 2 +- docs/modules/vec3.html | 2 +- docs/topics/readme.md.html | 4 +- init.lua | 111 ++++++----- ldoc/windows_quick_generate.bat | 2 - modules/mat4.lua | 200 +++++++++++++++++-- modules/quat.lua | 279 +++++++++++++++++++------- 20 files changed, 1045 insertions(+), 226 deletions(-) diff --git a/docs/index.html b/docs/index.html index 6c89e44..dcdebf3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -130,7 +130,7 @@
generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
diff --git a/docs/modules/bound2.html b/docs/modules/bound2.html index 5912a42..f60cefa 100644 --- a/docs/modules/bound2.html +++ b/docs/modules/bound2.html @@ -662,7 +662,7 @@
generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
diff --git a/docs/modules/bound3.html b/docs/modules/bound3.html index 69eb6de..67cf934 100644 --- a/docs/modules/bound3.html +++ b/docs/modules/bound3.html @@ -662,7 +662,7 @@
generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
diff --git a/docs/modules/bvh.html b/docs/modules/bvh.html index b4603d8..292c0f5 100644 --- a/docs/modules/bvh.html +++ b/docs/modules/bvh.html @@ -77,7 +77,7 @@
generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
diff --git a/docs/modules/color.html b/docs/modules/color.html index 7956d20..c976b1f 100644 --- a/docs/modules/color.html +++ b/docs/modules/color.html @@ -730,7 +730,7 @@
generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
diff --git a/docs/modules/constants.html b/docs/modules/constants.html index 7c15874..a4335a2 100644 --- a/docs/modules/constants.html +++ b/docs/modules/constants.html @@ -118,7 +118,7 @@
generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
diff --git a/docs/modules/intersect.html b/docs/modules/intersect.html index 6d27e16..c137a22 100644 --- a/docs/modules/intersect.html +++ b/docs/modules/intersect.html @@ -77,7 +77,7 @@
generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
diff --git a/docs/modules/mat4.html b/docs/modules/mat4.html index 5cf2dbf..7242ca0 100644 --- a/docs/modules/mat4.html +++ b/docs/modules/mat4.html @@ -89,6 +89,10 @@ Create a matrix from a quaternion. + set_rot_from_quaternion (q) + set the rotation of a matrix from a quaternion. + + from_direction (direction, up) Create a matrix from a direction/up pair. @@ -97,6 +101,38 @@ Create a matrix from a transform. + set_rot_zxy (pitch, yaw, roll) + set the rotation of a given matrix in euler in the ZXY application order. + + + set_rot_luanti_entity (pitch, yaw, roll) + alias of set_rot_zxy. + + + get_rot_zxy (the) + get the ZXY euler rotation of the given matrix. + + + get_rot_luanti_entity (the) + Alias of get_rot_zxy. + + + set_rot_xyz (pitch, yaw, roll) + set the rotation of a given matrix in euler in the XYZ application order. + + + set_rot_irrlicht_bone (pitch, yaw, roll) + alias of set_rot_xyz. + + + get_rot_xyz (the) + Get the XYZ euler rotation of the given matrix. + + + get_rot_irrlicht_bone (the) + Alias of get_rot_zxy. + + from_ortho (left, right, top, bottom, near, far) Create matrix from orthogonal. @@ -307,6 +343,29 @@ + +
+ + set_rot_from_quaternion (q) +
+
+ set the rotation of a matrix from a quaternion. Not sure at all where i got this code from, but it works... + i refactored so it works with https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/index.html + I think I got it from https://github.com/minetest/irrlicht/blob/7173c2c62997b6416f17b90f9a50bff11fef1c4c/include/quaternion.h#L367 + + +

Parameters:

+ + + + + +
@@ -373,6 +432,286 @@ + +
+ + set_rot_zxy (pitch, yaw, roll) +
+
+ set the rotation of a given matrix in euler in the ZXY application order. This is the order that minetest entities are rotated in. + + +

Parameters:

+ + +

Returns:

+
    + + matrix + + + +
+ + + + +
+
+ + set_rot_luanti_entity (pitch, yaw, roll) +
+
+ alias of set_rot_zxy. Sets the rotation of a given matrix in euler in the ZXY application order. This is the order that minetest entities are rotated in. + + +

Parameters:

+ + +

Returns:

+
    + + matrix + + + +
+ + + + +
+
+ + get_rot_zxy (the) +
+
+ get the ZXY euler rotation of the given matrix. This is the order that minetest entities are rotated in. + + +

Parameters:

+ + +

Returns:

+
    +
  1. + float + pitch
  2. +
  3. + float + yaw
  4. +
  5. + float + roll
  6. +
+ + + + +
+
+ + get_rot_luanti_entity (the) +
+
+ Alias of get_rot_zxy. Gets the ZXY euler rotation of the given matrix. This is the order that minetest entities are rotated in. + + +

Parameters:

+ + +

Returns:

+
    +
  1. + float + pitch
  2. +
  3. + float + yaw
  4. +
  5. + float + roll
  6. +
+ + + + +
+
+ + set_rot_xyz (pitch, yaw, roll) +
+
+ set the rotation of a given matrix in euler in the XYZ application order. This is the rotation order irrlicht uses (i.e. for bones in Luanti) + + +

Parameters:

+ + +

Returns:

+
    + + matrix + + + +
+ + + + +
+
+ + set_rot_irrlicht_bone (pitch, yaw, roll) +
+
+ alias of set_rot_xyz. Sets the rotation of a given matrix in euler in the XYZ application order. This is the rotation order irrlicht uses (i.e. for bones in Luanti) + + +

Parameters:

+ + +

Returns:

+
    + + matrix + + + +
+ + + + +
+
+ + get_rot_xyz (the) +
+
+ Get the XYZ euler rotation of the given matrix. This is the rotation order irrlicht uses (i.e. for bones in Luanti) + + +

Parameters:

+ + +

Returns:

+
    +
  1. + float + pitch
  2. +
  3. + float + yaw
  4. +
  5. + float + roll
  6. +
+ + + + +
+
+ + get_rot_irrlicht_bone (the) +
+
+ Alias of get_rot_zxy. Gets the XYZ euler rotation of the given matrix. This is the rotation order irrlicht uses (i.e. for bones in Luanti). + + +

Parameters:

+ + +

Returns:

+
    +
  1. + float + pitch
  2. +
  3. + float + yaw
  4. +
  5. + float + roll
  6. +
+ + + +
@@ -1166,7 +1505,7 @@
generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
diff --git a/docs/modules/mesh.html b/docs/modules/mesh.html index 41a8eed..fd3f367 100644 --- a/docs/modules/mesh.html +++ b/docs/modules/mesh.html @@ -77,7 +77,7 @@
generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
diff --git a/docs/modules/octree.html b/docs/modules/octree.html index 543d6a5..92f9cd2 100644 --- a/docs/modules/octree.html +++ b/docs/modules/octree.html @@ -703,7 +703,7 @@
generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
diff --git a/docs/modules/quat.html b/docs/modules/quat.html index 2b576cf..b452ef1 100644 --- a/docs/modules/quat.html +++ b/docs/modules/quat.html @@ -98,11 +98,7 @@ Subtract a quaternion from another. - mul (a, b) - Multiply two quaternions. - - - mul_vec3 (a, b) + mul_vec3 (a, v) Multiply a quaternion and a vec3. @@ -131,7 +127,7 @@ rotate (angle, axis, y, z) - Alias of fromangleaxis. + Alias of from_angle_axis. conjugate (a) @@ -186,12 +182,40 @@ Convert a quaternion into an angle/axis pair. - to_euler_angles_unpack (a) - Convert a quaternion into euler angle components + set_matrix_rot (quaternion, the) + set a matrix's rotation fields from a quaternion. - to_euler_angles (a) - Convert a quaternion into euler angles + from_matrix (the) + create a new quaternion from a matrix. + + + get_rot_luanti_entity () + alias of get_euler_zxy + + + from_euler_zxy (X, Y, Z) + create a quaternion from euler angles in the ZXY rotation order. + + + get_rot_luanti_entity () + alias of from_euler_zxy + + + get_euler_xyz (quaternion) + convert a quaternion to an xyz euler angles. + + + get_rot_irrlicht_bone () + alias of get_euler_xyz + + + from_euler_xyz (X, Y, Z) + create a quaternion from euler angles in the xyz rotation order. + + + get_rot_irrlicht_bone () + alias of quat.from_euler_zxy to_vec3 (a) @@ -298,7 +322,7 @@ from_direction (normal, up)
- Create a quaternion from a normal/up vector pair. + Create a quaternion from a normal/up vector pair. (accepts minetest vectors)

Parameters:

@@ -414,44 +438,13 @@ -
-
- - mul (a, b) -
-
- Multiply two quaternions. - - -

Parameters:

- - -

Returns:

-
    - - quat - quaternion equivalent to "apply b, then a" -
- - - -
- mul_vec3 (a, b) + mul_vec3 (a, v)
- Multiply a quaternion and a vec3. + Multiply a quaternion and a vec3. Equivalent to rotating the vector (a) by the quaternion (v)

Parameters:

@@ -460,7 +453,7 @@ quat Left hand operand -
  • b +
  • v vec3 Right hand operand
  • @@ -656,7 +649,7 @@ rotate (angle, axis, y, z)
    - Alias of fromangleaxis. + Alias of from_angle_axis.

    Parameters:

    @@ -1084,36 +1077,32 @@
    - - to_euler_angles_unpack (a) + + set_matrix_rot (quaternion, the)
    - Convert a quaternion into euler angle components + set a matrix's rotation fields from a quaternion. Uses mat4.setrotfrom_quaternion

    Parameters:

    Returns:

      -
    1. - roll + + mat4 -
    2. -
    3. - pitch - -
    4. -
    5. - yaw - no idea if this shit really works, very well could not...
    @@ -1121,31 +1110,210 @@
    - - to_euler_angles (a) + + from_matrix (the)
    - Convert a quaternion into euler angles + create a new quaternion from a matrix. Uses mat4.to_quaternion

    Parameters:

    Returns:

      - result - a {roll, pitch, yaw} table + quat + + +
    +
    +
    + + get_rot_luanti_entity () +
    +
    + alias of get_euler_zxy + + + + + + + +
    +
    + + from_euler_zxy (X, Y, Z) +
    +
    + create a quaternion from euler angles in the ZXY rotation order. This is the rotation order Luanti Entities use + + +

    Parameters:

    + + +

    Returns:

    +
      + + quat + q +
    + + + + +
    +
    + + get_rot_luanti_entity () +
    +
    + alias of from_euler_zxy + + + + + + + +
    +
    + + get_euler_xyz (quaternion) +
    +
    + convert a quaternion to an xyz euler angles. This is the rotation order used by irrlicht bones. + + +

    Parameters:

    + + +

    Returns:

    +
      +
    1. + X + + +
    2. +
    3. + Y + + +
    4. +
    5. + Z + + +
    6. +
    + + + + +
    +
    + + get_rot_irrlicht_bone () +
    +
    + alias of get_euler_xyz + + + + + + + +
    +
    + + from_euler_xyz (X, Y, Z) +
    +
    + create a quaternion from euler angles in the xyz rotation order. This is the rotation order irrlicht bones use + + +

    Parameters:

    + + +

    Returns:

    +
      + + quat + q +
    + + + + +
    +
    + + get_rot_irrlicht_bone () +
    +
    + alias of quat.from_euler_zxy + + + + + + +
    @@ -1235,7 +1403,7 @@
    generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
    diff --git a/docs/modules/simplex.html b/docs/modules/simplex.html index 4302094..157fba3 100644 --- a/docs/modules/simplex.html +++ b/docs/modules/simplex.html @@ -77,7 +77,7 @@
    generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
    diff --git a/docs/modules/utils.html b/docs/modules/utils.html index e0a0490..f8fac61 100644 --- a/docs/modules/utils.html +++ b/docs/modules/utils.html @@ -550,7 +550,7 @@
    generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
    diff --git a/docs/modules/vec2.html b/docs/modules/vec2.html index fd88b05..c58d159 100644 --- a/docs/modules/vec2.html +++ b/docs/modules/vec2.html @@ -1117,7 +1117,7 @@
    generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
    diff --git a/docs/modules/vec3.html b/docs/modules/vec3.html index 0234cc4..6caf0c6 100644 --- a/docs/modules/vec3.html +++ b/docs/modules/vec3.html @@ -1025,7 +1025,7 @@
    generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
    diff --git a/docs/topics/readme.md.html b/docs/topics/readme.md.html index 770a0fe..cca493c 100644 --- a/docs/topics/readme.md.html +++ b/docs/topics/readme.md.html @@ -61,7 +61,7 @@

    Cirno's Perfect Math Library

    -

    Adapated for Minetest

    +

    Adapted for Minetest

    For best memory performance: have luaJIT & it's FFI library (this should be built into luaJIT), and add MTUL-CPML to your trusted list (so it can require() call the FFI library).

    Various useful bits of game math. 3D line intersections, ray casting, 2d/3d vectors, 4x4 matrices, quaternions, etc.

    @@ -80,7 +80,7 @@ Documentation can be found here:
    generated by LDoc 1.5.0 -Last updated 2024-01-06 19:06:14 +Last updated 2024-11-30 20:09:10
    diff --git a/init.lua b/init.lua index 03036f8..6e035a8 100644 --- a/init.lua +++ b/init.lua @@ -56,75 +56,82 @@ local files = { "bound3", } +--you will now witness the lua equivelant of a schizo rant. Have fun with this bullshit. + --initialize some variables mtul = mtul or { loaded_modules = {} } -mtul.math = mtul.math or {} --other modules (probably) have not initialized this. +mtul.math = mtul.math or {} mtul.loaded_modules.cpml = true -local modpath = minetest.get_modpath("mtul_cpml") -local loaded_modules = {} local old_require = require --just in case require is present (aka it's an insecure environment) -local ie = minetest.request_insecure_environment() - ---if require isn't present, allow us to load the modules through hackish means ---there's like 100s of require calls, it'd be insane to replace them. If you're farmiliar with require, the goal should be obvious. -modules = "" --this is just for Busted support, as it'll bitch about "attempt to concat a nil value" otherwise. ---modules is the path to modules local old_package_path -if not ie then - --if an insecure environment cannot be loaded, then we basically change how require works temporarily, so modules (which is referenced in all CPML files on require() has to be changed) - modules = modpath.."/modules/" -else - old_package_path = package.path - --get the real modpath and add it to the package.path string so we can find our modules in require() - ie.package.path = ie.package.path .. ";"..string.gsub(modpath, "\\bin\\%.%.", "").."?.lua" --add our path - modules = ".modules." -end +local modpath +--check that it's minetest and not a lua script running it. If it's not minetest we dont have to do all of this, but otherwise we dont know if +if minetest or (core and core.register_globalstep) then + modpath = minetest.get_modpath("mtul_cpml") + local ie = minetest.request_insecure_environment() - -if not ie then - function require(path) - if loaded_modules[path] then return loaded_modules[path] end - local ending = string.gsub(path:sub(#modules+1), "%.", "/")..".lua" - --[[if ending[1] ~= "/" then - ending = "/"..ending - end]] - path = modules..ending - loaded_modules[path] = dofile(path) - return loaded_modules[path] - end -else - require = ie.require -end ---print(require, ie.require) - -if type(jit) == "table" and jit.status() then - if ie then - if pcall(require, "ffi") then - minetest.log("verbose", "MTUL-CPML: loaded JIT FFI library. Memory efficiency with FFI enabled.") - print("mtul-cpml: JIT FFI loaded successfully.") + --since we can't use require, what we do instead is override require by some utterly offensive means. + modules = "" --path to modules. + if not ie then + --if an insecure environment cannot be loaded, then we basically change how require works temporarily, so modules (which is referenced in all CPML files on require() has to be changed) + modules = modpath.."/modules/" + function require(path) + local ending = string.gsub(path:sub(#modules+1), "%.", "/")..".lua" + path = modules..ending + return dofile(path) + end else - minetest.log("error", "MTUL-CPML: Failure to load JIT FFI library.") + old_package_path = package.path + --get the real modpath and add it to the package.path string so we can find our modules in require() + ie.package.path = ie.package.path .. ";"..string.gsub(modpath, "\\bin\\%.%.", "").."?.lua" --add our path + modules = ".modules." + require = ie.require end - else - minetest.log("error", "MTUL-CPML: insecure environment denied for MTUL-CPML. Add mtul-cpml to your trusted mods for JIT FFI support (memory efficiency & speed boost)") - end -else - minetest.log("verbose", "MTUL-CPML: JIT not present, skipped attempt to load JIT FFI library for acceleration and memory efficiency") -end ---load the files + if type(jit) == "table" and jit.status() then + if ie then + if pcall(require, "ffi") then + minetest.log("verbose", "MTUL-CPML: loaded JIT FFI library. Memory efficiency with FFI enabled.") + print("mtul-cpml: JIT FFI loaded successfully.") + else + minetest.log("error", "MTUL-CPML: Failure to load JIT FFI library.") + end + else + minetest.log("error", "MTUL-CPML: insecure environment denied for MTUL-CPML. Add mtul-cpml to your trusted mods for better performance") + end + else + minetest.log("verbose", "MTUL-CPML: JIT not present, skipped attempt to load JIT FFI library for acceleration and memory efficiency") + end +end + --load the files for _, file in ipairs(files) do mtul.math[file] = require(modules .. file) end --unset all the global shit we had to change for CPML to work properly. -if old_package_path then - ie.package.path = old_package_path +if modpath then + if ie then + ie.package.path = old_package_path + end + modules = nil + require = old_require end -modules = nil -require = old_require + +--dofile(modpath.."/unit_tests/quat_unit_test.lua") +if modpath then + print("MTUL CPML: BEGINNING UNIT TESTING FOR COMPLEX TYPES") + dofile(modpath.."/unit_tests/irrlicht_luanti_tests.lua") + dofile(modpath.."/unit_tests/matrix_unit_test.lua") + dofile(modpath.."/unit_tests/quat_unit_test.lua") +else + print("MTUL CPML: BEGINNING UNIT TESTING FOR COMPLEX TYPES") + require("/unit_tests/irrlicht_luanti_tests.lua") + require("/unit_tests/matrix_unit_test.lua") + require("/unit_tests/quat_unit_test.lua") +end + diff --git a/ldoc/windows_quick_generate.bat b/ldoc/windows_quick_generate.bat index a025533..f8004d5 100644 --- a/ldoc/windows_quick_generate.bat +++ b/ldoc/windows_quick_generate.bat @@ -1,3 +1 @@ -# literally just so I dont have to open powershell every time. -@echo off ldoc . \ No newline at end of file diff --git a/modules/mat4.lua b/modules/mat4.lua index 0229acd..30cccdf 100644 --- a/modules/mat4.lua +++ b/modules/mat4.lua @@ -3,10 +3,11 @@ local constants = require(modules .. "constants") local vec2 = require(modules .. "vec2") local vec3 = require(modules .. "vec3") -local quat = require(modules .. "quat") +--local quat = require(modules .. "quat") local utils = require(modules .. "utils") local precond = require(modules .. "_private_precond") local private = require(modules .. "_private_utils") +local DBL_EPSILON = constants.DBL_EPSILON local sqrt = math.sqrt local cos = math.cos local sin = math.sin @@ -58,7 +59,8 @@ local tv4 = { 0, 0, 0, 0 } -- table Length 4 (4 vec4s) -- nil -- @treturn mat4 out -function mat4.new(a) + +function mat4.new(a, ...) local out = new() -- 4x4 matrix @@ -128,7 +130,33 @@ end -- @tparam quat q Rotation quaternion -- @treturn mat4 out function mat4.from_quaternion(q) - return mat4.from_angle_axis(q:to_angle_axis()) + --q=q:normalize() + return mat4.set_rot_from_quaternion(identity(new()), q) +end + +--- set the rotation of a matrix from a quaternion. Not sure at all where i got this code from, but it works... +-- i refactored so it works with https://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/index.html +-- I think I got it from https://github.com/minetest/irrlicht/blob/7173c2c62997b6416f17b90f9a50bff11fef1c4c/include/quaternion.h#L367 +-- @tparam quat q rotation quaternion. only supports normal quaternion rotation (will normalize) +function mat4.set_rot_from_quaternion(m, q) + local qx,qy,qz,qw = q.x,q.y,q.z,q.w + --normalize the quaternion + --local s = 1/sqrt(qx * qx + qy * qy + qz * qz + qw * qw) + local s = 1/sqrt(qx * qx + qz * qz + qy * qy + qw * qw) + qx,qy,qz,qw = qx*s,qy*s,qz*s,qw*s + + m[1] = 1-2*(qy^2 + qz^2) + m[2] = 2*(qx*qy + qz*qw) + m[3] = 2*(qx*qz - qy*qw) + + m[5] = 2*(qx*qy - qz*qw) + m[6] = 1-2*(qx^2 + qz^2) + m[7] = 2*(qy*qz + qx*qw) + + m[9] = 2*(qx*qz + qy*qw) + m[10]= 2*(qy*qz - qx*qw) + m[11]= 1-2*(qx^2 + qy^2) + return m end --- Create a matrix from a direction/up pair. @@ -186,6 +214,151 @@ function mat4.from_transform(trans, rot, scale) return rsm end +local tau = 2*math.pi +local atan2 = math.atan2 +--- set the rotation of a given matrix in euler in the ZXY application order. This is the order that minetest entities are rotated in. +-- @tparam float pitch the clockwise pitch in radians +-- @tparam float yaw the clockwise yaw in radians +-- @tparam float roll the clockwise yaw in roll +-- @treturn matrix +function mat4.set_rot_zxy(M, pitch,yaw,roll) + --minetest numeric.h + local cr = cos(roll) + local sr = sin(roll) + local cp = cos(pitch) + local sp = sin(pitch); + local cy = cos(yaw) + local sy = sin(yaw); + + M[1] = sr * sp * sy + cr * cy + M[2] = sr * cp + M[3] = sr * sp * cy - cr * sy + + M[5] = cr * sp * sy - sr * cy + M[6] = cr * cp + M[7] = cr * sp * cy + sr * sy + + M[9] = cp * sy + M[10] = -sp + M[11] = cp * cy + return M +end +local asin = math.asin +local abs = math.abs + +--- alias of `set_rot_zxy`. Sets the rotation of a given matrix in euler in the ZXY application order. This is the order that minetest entities are rotated in. +-- @tparam float pitch the clockwise pitch in radians +-- @tparam float yaw the clockwise yaw in radians +-- @tparam float roll the clockwise yaw in roll +-- @treturn matrix +-- @function set_rot_luanti_entity +mat4.set_rot_luanti_entity = mat4.set_rot_zxy + + + + + + +--- get the ZXY euler rotation of the given matrix. This is the order that minetest entities are rotated in. +-- @tparam matrix the matrix to get the rotation of +-- @treturn float pitch +-- @treturn float yaw +-- @treturn float roll +function mat4.get_rot_zxy(M) + local X,Y,Z + if abs(M[10])-1 < DBL_EPSILON then --check if x is 90 or -90. If it is yaw will experience gimbal lock and there will therefore be infinite solutions. + Z = atan2(M[2], M[6]) --(cz*cx / sz*cx) = cz/cx = tz. + Y = atan2(M[9], M[11]) + X = atan2(-M[10], M[6]/cos(Z)) + else + Z = atan2(M[7], M[5]) + Y = 0 --pitch and roll are the same given x=90 or -90. + X = asin(-M[10]) + end + return X,Y,Z +end + +--- Alias of `get_rot_zxy`. Gets the ZXY euler rotation of the given matrix. This is the order that minetest entities are rotated in. +-- @tparam matrix the matrix to get the rotation of +-- @treturn float pitch +-- @treturn float yaw +-- @treturn float roll +-- @function get_rot_luanti_entity +mat4.get_rot_luanti_entity = mat4.get_rot_zxy + + + + + +--- set the rotation of a given matrix in euler in the XYZ application order. This is the rotation order irrlicht uses (i.e. for bones in Luanti) +-- @tparam float pitch the clockwise pitch in radians +-- @tparam float yaw the clockwise yaw in radians +-- @tparam float roll the clockwise yaw in roll +-- @treturn matrix +function mat4.set_rot_xyz(M, pitch,yaw,roll) + --standard euler rotation matrices applied in XYZ order (matrix transformations are applied in inverse) + local cp = cos(pitch) + local sp = sin(pitch) + local cy = cos(yaw) + local sy = sin(yaw) + local cr = cos(roll) + local sr = sin(roll) + + M[1] = (cy * cr) + M[2] = (cy * sr) + M[3] = (-sy) + + M[5] = (sp * sy * cr - cp * sr) + M[6] = (sp * sy * sr + cp * cr) + M[7] = (sp * cy) + + M[9] = (cp * sy * cr + sp * sr) + M[10] = (cp * sy * sr - sp * cr) + M[11] = (cp * cy) + return M +end + +--- alias of `set_rot_xyz`. Sets the rotation of a given matrix in euler in the XYZ application order. This is the rotation order irrlicht uses (i.e. for bones in Luanti) +-- @tparam float pitch the clockwise pitch in radians +-- @tparam float yaw the clockwise yaw in radians +-- @tparam float roll the clockwise yaw in roll +-- @treturn matrix +-- @function set_rot_irrlicht_bone +mat4.set_rot_irrlicht_bone = mat4.set_rot_xyz + + + +--- Get the XYZ euler rotation of the given matrix. This is the rotation order irrlicht uses (i.e. for bones in Luanti) +-- @tparam matrix the matrix to get the rotation of +-- @treturn float pitch +-- @treturn float yaw +-- @treturn float roll +function mat4.get_rot_xyz(M) + local X,Y,Z + if abs(M[3])-1 < DBL_EPSILON then --check if x is 90 or -90. If they are yaw will experience gimbal lock and there will therefore be infinite solutions. + Z = atan2(M[2], M[1]) + Y = atan2(-M[3], M[1]/cos(Z)) + X = atan2(M[7], M[11]) + else + --Z = atan2(M[], M[]) + Y = asin(M[3]) + X = atan2(M[5], M[7]) + Z = 0 + end + return X,Y,Z +end + +--- Alias of `get_rot_zxy`. Gets the XYZ euler rotation of the given matrix. This is the rotation order irrlicht uses (i.e. for bones in Luanti). +-- @tparam matrix the matrix to get the rotation of +-- @treturn float pitch +-- @treturn float yaw +-- @treturn float roll +-- @function get_rot_irrlicht_bone +mat4.get_rot_irrlicht_bone = mat4.get_rot_xyz + + + + --- Create matrix from orthogonal. -- @tparam number left -- @tparam number right @@ -773,19 +946,18 @@ end --- Convert a matrix to a quaternion. -- @tparam mat4 a Matrix to be converted -- @treturn quat out -function mat4.to_quat(a) - identity(tmp):transpose(a) - - local w = sqrt(1 + tmp[1] + tmp[6] + tmp[11]) / 2 - local scale = w * 4 - local q = quat.new( - tmp[10] - tmp[7] / scale, - tmp[3] - tmp[9] / scale, - tmp[5] - tmp[2] / scale, +local quat_new +function mat4.to_quaternion(m) + --I want to note that for no apparent reason at all the original matrix was transposed here + if not quat_new then quat_new = mtul.math.quat.new end + local w = math.sqrt(1 + m[1] + m[6] + m[11]) / 2 + local q=quat_new( + (m[7] - m[10]) /(4 * w), + (m[9] - m[3]) /(4 * w), + (m[2] - m[5]) /(4 * w), w ) - - return q:normalize(q) + return q end -- http://www.crownandcutlass.com/features/technicaldetails/frustum.html diff --git a/modules/quat.lua b/modules/quat.lua index f8447d2..1241e87 100644 --- a/modules/quat.lua +++ b/modules/quat.lua @@ -5,6 +5,8 @@ local constants = require(modules .. "constants") local vec3 = require(modules .. "vec3") local precond = require(modules .. "_private_precond") local private = require(modules .. "_private_utils") +local utils = require(modules .. "utils") +local mat4 = require(modules .. "mat4") local DOT_THRESHOLD = constants.DOT_THRESHOLD local DBL_EPSILON = constants.DBL_EPSILON local acos = math.acos @@ -36,10 +38,6 @@ if type(jit) == "table" and jit.status() then end end --- Statically allocate a temporary variable used in some of our functions. -local tmp = new() -local qv, uv, uuv = vec3(), vec3(), vec3() - --- Constants -- @table quat -- @field unit Unit quaternion @@ -79,6 +77,10 @@ function quat.new(x, y, z, w) return new(0, 0, 0, 1) end +--[[returns the required delta rotation to make a quaternion aim at a point +function quat.aim_at_point(quat) +end]] + --- Create a quaternion from an angle/axis pair. -- @tparam number angle Angle (in radians) -- @param axis/x -- Can be of two types, a vec3 axis, or the x component of that axis @@ -96,28 +98,6 @@ function quat.from_angle_axis(angle, axis, a3, a4) end end ---works in theory... probably. ---- Create a quaternion from an euler angle --- @tparam Vec3 (or xyz table) --- @treturn quat out -function quat.from_euler_rotation(rot) - local cr = cos(rot.z*.5) - local sr = sin(rot.z*.5) - - local cp = cos(rot.x*.5) - local sp = sin(rot.x*.5) - - local cy = cos(rot.y*.5) - local sy = sin(rot.y*.5) - return quat.new({ - w = cr * cp * cy + sr * sp * sy, - x = sr * cp * cy - cr * sp * sy, - y = cr * sp * cy + sr * cp * sy, - z = cr * cp * sy - sr * sp * cy - }) -end - - --- Create a quaternion from a normal/up vector pair. (accepts minetest vectors) -- @tparam vec3 normal -- @tparam vec3 up (optional) @@ -167,28 +147,48 @@ end -- @tparam quat a Left hand operand -- @tparam quat b Right hand operand -- @treturn quat quaternion equivalent to "apply b, then a" +local out = {} function quat.mul(a, b) return new( - a.x * b.w + a.w * b.x + a.y * b.z - a.z * b.y, - a.y * b.w + a.w * b.y + a.z * b.x - a.x * b.z, - a.z * b.w + a.w * b.z + a.x * b.y - a.y * b.x, - a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z + (a.x * b.w) + (a.w * b.x) + (a.y * b.z) - (a.z * b.y), + (a.y * b.w) + (a.w * b.y) + (a.z * b.x) - (a.x * b.z), + (b.w * a.z) + (b.z * a.w) + (b.y * a.x) - (b.x * a.y), + (a.w * b.w) - (a.x * b.x) - (a.y * b.y) - (a.z * b.z) ) end ---- Multiply a quaternion and a vec3. +-- Statically allocate a temporary variable used in some of our functions. +local tmp = new() +local u, uv, uuv = vec3(), vec3(), vec3() + +--- Multiply a quaternion and a vec3. Equivalent to rotating the vector (a) by the quaternion (v) -- @tparam quat a Left hand operand --- @tparam vec3 b Right hand operand +-- @tparam vec3 v Right hand operand -- @treturn vec3 out -function quat.mul_vec3(a, b) - qv.x = a.x - qv.y = a.y - qv.z = a.z - uv = qv:cross(b) - uuv = qv:cross(uv) - return b + ((uv * a.w) + uuv) * 2 +function quat.mul_vec3(a, v) + u.x = a.x + u.y = a.y + u.z = a.z + uv = u:cross(v) + uuv = u:cross(uv) + return v + ((uv * a.w) + uuv) * 2 end +--[[ does the same thing as above, which I did not know when i reimplemented it to check. +function quat.rotate_vec3(a, v) + u.x = a.x + u.y = a.y + u.z = a.z + local s = a.w + return + + (u*u:dot(v)*2) + + + (v*(s*s - u:dot(u))) + + + (u:cross(v)*s*2) +end]] + --- Raise a normalized quaternion to a scalar power. -- @tparam quat a Left hand operand (should be a unit quaternion) -- @tparam number s Right hand operand @@ -253,7 +253,7 @@ function quat.scale(a, s) ) end ---- Alias of from_angle_axis. +--- Alias of `from_angle_axis.` -- @tparam number angle Angle (in radians) -- @param axis/x -- Can be of two types, a vec3 axis, or the x component of that axis -- @param y axis -- y component of axis (optional, only if x component param used) @@ -445,42 +445,177 @@ function quat.to_angle_axis(a, identityAxis) return angle, vec3(x, y, z) end ---- Convert a quaternion into euler angle components --- @tparam quat a Quaternion to convert --- @treturn roll --- @treturn pitch --- @treturn yaw ---no idea if this shit really works, very well could not... -function quat.to_euler_angles_unpack(q) - -- roll (x-axis rotation) - local sinr_cosp = 2 * (q.w * q.x + q.y * q.z) - local cosr_cosp = 1 - 2 * (q.x * q.x + q.y * q.y) - local pitch = math.atan2(sinr_cosp, cosr_cosp) - - -- pitch (y-axis rotation) - local sinp = 2 * (q.w * q.y - q.z * q.x) - local yaw - if math.abs(sinp) >= 1 then - yaw = math.pi / 2 * ((sinp > 0) and 1 or -1) -- Use 90 degrees if out of range - else - yaw = math.asin(sinp) - end - - -- yaw (z-axis rotation) - local siny_cosp = 2 * (q.w * q.z + q.x * q.y) - local cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z) - local roll = math.atan2(siny_cosp, cosy_cosp) - - return pitch, yaw, roll +--- set a matrix's rotation fields from a quaternion. Uses mat4.set_rot_from_quaternion +-- @tparam quat quaternion to convert +-- @tparam mat4 the mat4 to apply to. +-- @treturn mat4 +function quat.set_matrix_rot(q, m) + m:set_rot_from_quaternion(q) + return m end ---- Convert a quaternion into euler angles --- @tparam quat a Quaternion to convert --- @treturn result a {roll, pitch, yaw} table -function quat.to_euler_angles(a) - return {quat.to_euler_angles_unpack(a)} +--- create a new quaternion from a matrix. Uses mat4.to_quaternion +-- @tparam mat4 the matrix to use +-- @treturn quat +function quat.from_matrix(m) + return m:to_quaternion() end + + +--- convert a quaternion to an ZXY euler angles. This is the rotation order used by Minetest/Luanti Entities. +-- @tparam quat quaternion to convert +-- @treturn float X +-- @treturn float Y +-- @treturn float Z +local atan2 = math.atan2 +local abs = math.abs +local asin = math.asin +function quat.get_euler_zxy(q) + local qx, qy, qz, qw = q.x, q.y, q.z, q.w + local s = 1/sqrt(qx * qx + qz * qz + qy * qy + qw * qw) + qx,qz,qy,qw = qx*s, qz*s, qy*s, qw*s + --convert to matrix but only grab the matrix indices we need. Basically this violently smashes together the matrix to zxy and quat to matrix code. + local m2 = 2*(qx*qy + qz*qw) + local m5 = 2*(qx*qy - qz*qw) + local m6 = 1-2*(qx^2 + qz^2) + local m7 = 2*(qy*qz + qx*qw) + local m9 = 2*(qx*qz + qy*qw) + local m10 = 2*(qy*qz - qx*qw) + local m11 = 1-2*(qx^2 + qy^2) + + local X,Y,Z + if abs(m10)-1 < DBL_EPSILON then --check if x is 90 or -90. If it is yaw will experience gimbal lock and there will therefore be infinite solutions. + Z = atan2(m2, m6) --(cz*cx / sz*cx) = cz/cx = tz. + Y = atan2(m9, m11) + X = atan2(-m10, m6/cos(Z)) + else + Z = atan2(m7, m5) + Y = 0 --pitch and roll are the same given x=90 or -90. + X = asin(-m10) + end + return X,Y,Z +end + +--- alias of `get_euler_zxy` +-- @function quat.get_rot_luanti_entity +quat.get_euler_luanti_entity = quat.get_euler_zxy + + + +--- create a quaternion from euler angles in the ZXY rotation order. This is the rotation order Luanti Entities use +-- @tparam float X +-- @tparam float Y +-- @tparam float Z +-- @treturn quat q +function quat.from_euler_zxy(X,Y,Z) + --I want to note that for no apparent reason at all the original matrix was transposed here + local cr = cos(Z) + local sr = sin(Z) + local cp = cos(X) + local sp = sin(X); + local cy = cos(Y) + local sy = sin(Y); + + local m1 = sr * sp * sy + cr * cy + local m2 = sr * cp + local m3 = sr * sp * cy - cr * sy + + local m5 = cr * sp * sy - sr * cy + local m6 = cr * cp + local m7 = cr * sp * cy + sr * sy + + local m9 = cp * sy + local m10 = -sp + local m11 = cp * cy + local w = math.sqrt(1 + m1 + m6 + m11) / 2 + return new( + (m7 - m10) /(4 * w), + (m9 - m3) /(4 * w), + (m2 - m5) /(4 * w), + w + ) +end + +--- alias of `from_euler_zxy` +-- @function quat.get_rot_luanti_entity +quat.from_euler_luanti_entity = quat.from_euler_zxy + + + +--- convert a quaternion to an xyz euler angles. This is the rotation order used by irrlicht bones. +-- @tparam quat quaternion to convert +-- @treturn X +-- @treturn Y +-- @treturn Z +function quat.get_euler_xyz(q) + local qx, qy, qz, qw = q.x, q.y, q.z, q.w + local s = 1/sqrt(qx * qx + qz * qz + qy * qy + qw * qw) + qx,qz,qy,qw = qx*s, qz*s, qy*s, qw*s + --convert to matrix but only grab the matrix indices we need. Basically this violently smashes together the matrix to zxy and quat to matrix code. + local m1 = 1-2*(qy^2 + qz^2) + local m2 = 2*(qx*qy + qz*qw) + local m3 = 2*(qx*qz - qy*qw) + local m5 = 2*(qx*qy - qz*qw) + local m7 = 2*(qy*qz + qx*qw) + local m11 = 1-2*(qx^2 + qy^2) + + local X,Y,Z + if abs(m3)-1 < DBL_EPSILON then --check if x is 90 or -90. If they are yaw will experience gimbal lock and there will therefore be infinite solutions. + Z = atan2(m2, m1) + Y = atan2(-m3, m1/cos(Z)) + X = atan2(m7, m11) + else + --Z = atan2(M[], M[]) + Y = asin[m3] + X = atan2(m5, m7) + Z = 0 + end + return X,Y,Z +end + +--- alias of `get_euler_xyz` +-- @function quat.get_rot_irrlicht_bone +quat.get_euler_irrlicht_bone = quat.get_euler_xyz + + + +--- create a quaternion from euler angles in the xyz rotation order. This is the rotation order irrlicht bones use +-- @tparam float X +-- @tparam float Y +-- @tparam float Z +-- @treturn quat q +function quat.from_euler_xyz(X,Y,Z) + local cp = cos(X) + local sp = sin(X) + local cy = cos(Y) + local sy = sin(Y) + local cr = cos(Z) + local sr = sin(Z) + + local m1 = (cy * cr) + local m2 = (cy * sr) + local m3 = (-sy) + + local m5 = (sp * sy * cr - cp * sr) + local m6 = (sp * sy * sr + cp * cr) + local m7 = (sp * cy) + + local m9 = (cp * sy * cr + sp * sr) + local m10 = (cp * sy * sr - sp * cr) + local m11 = (cp * cy) + local w = math.sqrt(1 + m1 + m6 + m11) / 2 + return new( + (m7 - m10) /(4 * w), + (m9 - m3) /(4 * w), + (m2 - m5) /(4 * w), + w + ) +end +--- alias of `quat.from_euler_zxy` +--@function quat.get_rot_irrlicht_bone +quat.from_euler_irrlicht_bone = quat.from_euler_xyz + --- Convert a quaternion into a vec3. -- @tparam quat a Quaternion to convert -- @treturn vec3 out