diff --git a/doc/lua_api.md b/doc/lua_api.md index 34311d33c..7080050c3 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -4209,7 +4209,7 @@ Methods `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. +* `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. diff --git a/games/devtest/mods/unittests/bustitute.lua b/games/devtest/mods/unittests/bustitute.lua index fb44308cc..85e5dea03 100644 --- a/games/devtest/mods/unittests/bustitute.lua +++ b/games/devtest/mods/unittests/bustitute.lua @@ -50,7 +50,7 @@ end function test_env.it(name, func) table.insert(full_test_name, name) - unittests.register(table.concat(full_test_name, " "), func, {}) + unittests.register(table.concat(full_test_name, " "), func, {random = true}) table.remove(full_test_name) end diff --git a/games/devtest/mods/unittests/init.lua b/games/devtest/mods/unittests/init.lua index 625457c1f..a6bf25ac7 100644 --- a/games/devtest/mods/unittests/init.lua +++ b/games/devtest/mods/unittests/init.lua @@ -205,6 +205,7 @@ dofile(modpath .. "/color.lua") local bustitute = dofile(modpath .. "/bustitute.lua") bustitute.register("matrix4") +bustitute.register("rotation") -------------- diff --git a/games/devtest/mods/unittests/rotation.lua b/games/devtest/mods/unittests/rotation.lua index 354fd6ddb..e13e3e4a1 100644 --- a/games/devtest/mods/unittests/rotation.lua +++ b/games/devtest/mods/unittests/rotation.lua @@ -1,14 +1,24 @@ -local function describe(_, func) - func() +local function assert_close(expected, got) + if expected:angle_to(got) > 1e-3 then + error("expected +-" .. tostring(expected) .. " got " .. tostring(got)) + end end -local function it(section_name, func) - print("Running test: " .. section_name) - func() +local function assert_close_vec(expected, got) + if expected:distance(got) > 1e-4 then + error("expected " .. tostring(expected) .. " got " .. tostring(got)) + end end -local function assert_close(expected, actual) - assert(expected:angle_to(actual) < 1e-4) +local function srandom(n) + if n == 0 then + return + end + return 2 * math.random() - 1, srandom(n - 1) +end + +local function random_rotation() + return Rotation.quaternion(srandom(4)) end describe("constructors", function() @@ -17,13 +27,37 @@ describe("constructors", function() 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()) + assert_close(Rotation.identity(), Rotation.quaternion(0, 0, 0, 1)) + end) + it("axis-angle", function() + assert_close(Rotation.quaternion(1, 1, 1, 0), + Rotation.axis_angle(vector.new(1, 1, 1), math.pi)) end) - it("axis angle", function() end) it("axis-angle shorthands", function() - + local angle = math.pi + assert_close(Rotation.quaternion(1, 0, 0, 0), Rotation.x(angle)) + assert_close(Rotation.quaternion(0, 1, 0, 0), Rotation.y(angle)) + assert_close(Rotation.quaternion(0, 0, 1, 0), Rotation.z(angle)) end) + it("euler angles", function() + local pitch, yaw, roll = 1, 2, 3 + assert_close(Rotation.compose(Rotation.z(roll), Rotation.y(yaw), Rotation.x(pitch)), + Rotation.euler_angles(pitch, yaw, roll)) + end) +end) + +describe("conversions", function() + local function test_roundtrip(name) + it(name, function() + for _ = 1, 100 do + local rot = random_rotation() + assert_close(rot, Rotation[name](rot["to_" .. name](rot))) + end + end) + end + test_roundtrip("quaternion") + test_roundtrip("axis_angle") + test_roundtrip("euler_angles") end) describe("composition", function() @@ -35,33 +69,29 @@ describe("composition", function() assert_close(rot, rot:compose()) end) it("is consistent with application", function() - + for _ = 1, 100 do + local r1, r2 = random_rotation(), random_rotation() + local v = vector.new(srandom(3)) + assert_close_vec(r1:apply(r2:apply(v)), r1:compose(r2):apply(v)) + end 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 +it("application", function() + assert_close_vec(vector.new(-2, 1, 3), Rotation.z(math.pi / 2):apply(vector.new(1, 2, 3))) +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) \ No newline at end of file +it("inversion", function() + assert_close(Rotation.x(-math.pi / 2), Rotation.x(math.pi / 2):invert()) +end) + +it("slerp", function() + local from, to = Rotation.identity(), Rotation.x(2) + assert_close(Rotation.identity(), from:slerp(to, 0)) + assert_close(Rotation.x(1), from:slerp(to, 0.5)) + assert_close(Rotation.x(2), from:slerp(to, 1)) +end) + +it("tostring", function() + assert(type(tostring(Rotation.identity())) == "string") +end) diff --git a/irr/include/quaternion.h b/irr/include/quaternion.h index e99e721e2..7ea12c127 100644 --- a/irr/include/quaternion.h +++ b/irr/include/quaternion.h @@ -219,7 +219,7 @@ public: inline std::ostream& operator<<(std::ostream& os, const quaternion& q) { - os << q.X << "\t" << q.Y << "\t" << q.Z << "\t" << q.W; + os << "(" << q.X << "\t" << q.Y << "\t" << q.Z << "\t" << q.W << ")"; return os; } diff --git a/src/script/lua_api/l_rotation.cpp b/src/script/lua_api/l_rotation.cpp index 35b9f3c65..deda7399d 100644 --- a/src/script/lua_api/l_rotation.cpp +++ b/src/script/lua_api/l_rotation.cpp @@ -70,9 +70,11 @@ int LuaRotation::l_fixed_axis_angle(lua_State *L) int LuaRotation::l_euler_angles(lua_State *L) { - v3f euler = readParam(L, 1); + f32 pitch = readFiniteParam(L, 1); + f32 yaw = readFiniteParam(L, 2); + f32 roll = readFiniteParam(L, 3); core::quaternion quaternion; - quaternion.set(euler.X, euler.Y, euler.Z); + quaternion.set(pitch, yaw, roll); create(L, quaternion); return 1; }