Lua: change obj.rotation from Euler angles to Quaternion

revert-6246b479
Petr Mikheev 2 years ago
parent 1d5b73f20a
commit 3b43cc2aea

@ -111,7 +111,7 @@ namespace MWLua
{ {
auto* lua = context.mLua; auto* lua = context.mLua;
sol::table api(lua->sol(), sol::create); sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = 39; api["API_REVISION"] = 40;
api["quit"] = [lua]() { api["quit"] = [lua]() {
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
MWBase::Environment::get().getStateManager()->requestQuit(); MWBase::Environment::get().getStateManager()->requestQuit();

@ -4,6 +4,9 @@
#include <components/esm3/loadnpc.hpp> #include <components/esm3/loadnpc.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/shapes/box.hpp> #include <components/lua/shapes/box.hpp>
#include <components/lua/utilpackage.hpp>
#include <components/misc/convert.hpp>
#include <components/misc/mathutil.hpp>
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
@ -141,6 +144,28 @@ namespace MWLua
listT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get<sol::function>(); listT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get<sol::function>();
} }
osg::Vec3f toEulerRotation(const sol::object& transform, bool isActor)
{
if (transform.is<LuaUtil::TransformQ>())
{
const osg::Quat& q = transform.as<LuaUtil::TransformQ>().mQ;
return isActor ? Misc::toEulerAnglesXZ(q) : Misc::toEulerAnglesZYX(q);
}
else
{
const osg::Matrixf& m = LuaUtil::cast<LuaUtil::TransformM>(transform).mM;
return isActor ? Misc::toEulerAnglesXZ(m) : Misc::toEulerAnglesZYX(m);
}
}
osg::Quat toQuat(const ESM::Position& pos, bool isActor)
{
if (isActor)
return osg::Quat(pos.rot[0], osg::Vec3(-1, 0, 0)) * osg::Quat(pos.rot[2], osg::Vec3(0, 0, -1));
else
return Misc::Convert::makeOsgQuat(pos.rot);
}
template <class ObjectT> template <class ObjectT>
void addBasicBindings(sol::usertype<ObjectT>& objectT, const Context& context) void addBasicBindings(sol::usertype<ObjectT>& objectT, const Context& context)
{ {
@ -166,12 +191,14 @@ namespace MWLua
[](const ObjectT& o) -> osg::Vec3f { return o.ptr().getRefData().getPosition().asVec3(); }); [](const ObjectT& o) -> osg::Vec3f { return o.ptr().getRefData().getPosition().asVec3(); });
objectT["scale"] objectT["scale"]
= sol::readonly_property([](const ObjectT& o) -> float { return o.ptr().getCellRef().getScale(); }); = sol::readonly_property([](const ObjectT& o) -> float { return o.ptr().getCellRef().getScale(); });
objectT["rotation"] = sol::readonly_property( objectT["rotation"] = sol::readonly_property([](const ObjectT& o) -> LuaUtil::TransformQ {
[](const ObjectT& o) -> osg::Vec3f { return o.ptr().getRefData().getPosition().asRotationVec3(); }); return { toQuat(o.ptr().getRefData().getPosition(), o.ptr().getClass().isActor()) };
});
objectT["startingPosition"] = sol::readonly_property( objectT["startingPosition"] = sol::readonly_property(
[](const ObjectT& o) -> osg::Vec3f { return o.ptr().getCellRef().getPosition().asVec3(); }); [](const ObjectT& o) -> osg::Vec3f { return o.ptr().getCellRef().getPosition().asVec3(); });
objectT["startingRotation"] = sol::readonly_property( objectT["startingRotation"] = sol::readonly_property([](const ObjectT& o) -> LuaUtil::TransformQ {
[](const ObjectT& o) -> osg::Vec3f { return o.ptr().getCellRef().getPosition().asRotationVec3(); }); return { toQuat(o.ptr().getCellRef().getPosition(), o.ptr().getClass().isActor()) };
});
objectT["getBoundingBox"] = [](const ObjectT& o) { objectT["getBoundingBox"] = [](const ObjectT& o) {
MWRender::RenderingManager* renderingManager MWRender::RenderingManager* renderingManager
= MWBase::Environment::get().getWorld()->getRenderingManager(); = MWBase::Environment::get().getWorld()->getRenderingManager();
@ -401,12 +428,14 @@ namespace MWLua
throw std::runtime_error("Object is either removed or already in the process of teleporting"); throw std::runtime_error("Object is either removed or already in the process of teleporting");
osg::Vec3f rot = ptr.getRefData().getPosition().asRotationVec3(); osg::Vec3f rot = ptr.getRefData().getPosition().asRotationVec3();
bool placeOnGround = false; bool placeOnGround = false;
if (options.is<osg::Vec3f>()) if (LuaUtil::isTransform(options))
rot = options.as<osg::Vec3f>(); rot = toEulerRotation(options, ptr.getClass().isActor());
else if (options != sol::nil) else if (options != sol::nil)
{ {
sol::table t = LuaUtil::cast<sol::table>(options); sol::table t = LuaUtil::cast<sol::table>(options);
rot = LuaUtil::getValueOrDefault(t["rotation"], rot); sol::object rotationArg = t["rotation"];
if (rotationArg != sol::nil)
rot = toEulerRotation(rotationArg, ptr.getClass().isActor());
placeOnGround = LuaUtil::getValueOrDefault(t["onGround"], placeOnGround); placeOnGround = LuaUtil::getValueOrDefault(t["onGround"], placeOnGround);
} }
if (ptr.getContainerStore()) if (ptr.getContainerStore())

@ -2,6 +2,8 @@
#include <components/esm3/loaddoor.hpp> #include <components/esm3/loaddoor.hpp>
#include <components/esm4/loaddoor.hpp> #include <components/esm4/loaddoor.hpp>
#include <components/lua/utilpackage.hpp>
#include <components/misc/convert.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -41,8 +43,9 @@ namespace MWLua
door["isTeleport"] = [](const Object& o) { return doorPtr(o).getCellRef().getTeleport(); }; door["isTeleport"] = [](const Object& o) { return doorPtr(o).getCellRef().getTeleport(); };
door["destPosition"] door["destPosition"]
= [](const Object& o) -> osg::Vec3f { return doorPtr(o).getCellRef().getDoorDest().asVec3(); }; = [](const Object& o) -> osg::Vec3f { return doorPtr(o).getCellRef().getDoorDest().asVec3(); };
door["destRotation"] door["destRotation"] = [](const Object& o) -> LuaUtil::TransformQ {
= [](const Object& o) -> osg::Vec3f { return doorPtr(o).getCellRef().getDoorDest().asRotationVec3(); }; return { Misc::Convert::makeOsgQuat(doorPtr(o).getCellRef().getDoorDest().rot) };
};
door["destCell"] = [](sol::this_state lua, const Object& o) -> sol::object { door["destCell"] = [](sol::this_state lua, const Object& o) -> sol::object {
const MWWorld::CellRef& cellRef = doorPtr(o).getCellRef(); const MWWorld::CellRef& cellRef = doorPtr(o).getCellRef();
if (!cellRef.getTeleport()) if (!cellRef.getTeleport())
@ -80,8 +83,9 @@ namespace MWLua
door["isTeleport"] = [](const Object& o) { return door4Ptr(o).getCellRef().getTeleport(); }; door["isTeleport"] = [](const Object& o) { return door4Ptr(o).getCellRef().getTeleport(); };
door["destPosition"] door["destPosition"]
= [](const Object& o) -> osg::Vec3f { return door4Ptr(o).getCellRef().getDoorDest().asVec3(); }; = [](const Object& o) -> osg::Vec3f { return door4Ptr(o).getCellRef().getDoorDest().asVec3(); };
door["destRotation"] door["destRotation"] = [](const Object& o) -> LuaUtil::TransformQ {
= [](const Object& o) -> osg::Vec3f { return door4Ptr(o).getCellRef().getDoorDest().asRotationVec3(); }; return { Misc::Convert::makeOsgQuat(door4Ptr(o).getCellRef().getDoorDest().rot) };
};
door["destCell"] = [](sol::this_state lua, const Object& o) -> sol::object { door["destCell"] = [](sol::this_state lua, const Object& o) -> sol::object {
const MWWorld::CellRef& cellRef = door4Ptr(o).getCellRef(); const MWWorld::CellRef& cellRef = door4Ptr(o).getCellRef();
if (!cellRef.getTeleport()) if (!cellRef.getTeleport())

@ -157,7 +157,7 @@ namespace
EXPECT_EQ(getAsString(lua, "moveAndScale:apply(v(300, 200, 100))"), "(156, 222, 68)"); EXPECT_EQ(getAsString(lua, "moveAndScale:apply(v(300, 200, 100))"), "(156, 222, 68)");
EXPECT_THAT(getAsString(lua, "moveAndScale"), EXPECT_THAT(getAsString(lua, "moveAndScale"),
AllOf(StartsWith("TransformM{ move(6, 22, 18) scale(0.5, 1, 0.5) "), EndsWith(" }"))); AllOf(StartsWith("TransformM{ move(6, 22, 18) scale(0.5, 1, 0.5) "), EndsWith(" }")));
EXPECT_EQ(getAsString(lua, "T.identity"), "TransformM{ }"); EXPECT_EQ(getAsString(lua, "T.identity"), "TransformQ{ rotation(angle=0, axis=(0, 0, 1)) }");
lua.safe_script("rx = T.rotateX(-math.pi / 2)"); lua.safe_script("rx = T.rotateX(-math.pi / 2)");
lua.safe_script("ry = T.rotateY(-math.pi / 2)"); lua.safe_script("ry = T.rotateY(-math.pi / 2)");
lua.safe_script("rz = T.rotateZ(-math.pi / 2)"); lua.safe_script("rz = T.rotateZ(-math.pi / 2)");

@ -175,7 +175,7 @@ namespace LuaUtil
sol::table transforms(lua, sol::create); sol::table transforms(lua, sol::create);
util["transform"] = LuaUtil::makeReadOnly(transforms); util["transform"] = LuaUtil::makeReadOnly(transforms);
transforms["identity"] = sol::make_object(lua, TransformM{ osg::Matrixf::identity() }); transforms["identity"] = sol::make_object(lua, TransformQ{ osg::Quat() });
transforms["move"] = sol::overload([](const Vec3& v) { return TransformM{ osg::Matrixf::translate(v) }; }, transforms["move"] = sol::overload([](const Vec3& v) { return TransformM{ osg::Matrixf::translate(v) }; },
[](float x, float y, float z) { return TransformM{ osg::Matrixf::translate(x, y, z) }; }); [](float x, float y, float z) { return TransformM{ osg::Matrixf::translate(x, y, z) }; });
transforms["scale"] = sol::overload([](const Vec3& v) { return TransformM{ osg::Matrixf::scale(v) }; }, transforms["scale"] = sol::overload([](const Vec3& v) { return TransformM{ osg::Matrixf::scale(v) }; },
@ -223,6 +223,22 @@ namespace LuaUtil
throw std::runtime_error("This Transform is not invertible"); throw std::runtime_error("This Transform is not invertible");
return res; return res;
}; };
transMType["getYaw"] = [](const TransformM& m) {
osg::Vec3f angles = Misc::toEulerAnglesXZ(m.mM);
return angles.z();
};
transMType["getPitch"] = [](const TransformM& m) {
osg::Vec3f angles = Misc::toEulerAnglesXZ(m.mM);
return angles.x();
};
transMType["getAnglesXZ"] = [](const TransformM& m) {
osg::Vec3f angles = Misc::toEulerAnglesXZ(m.mM);
return std::make_tuple(angles.x(), angles.z());
};
transMType["getAnglesZYX"] = [](const TransformM& m) {
osg::Vec3f angles = Misc::toEulerAnglesXZ(m.mM);
return std::make_tuple(angles.z(), angles.y(), angles.x());
};
transQType[sol::meta_function::multiplication] transQType[sol::meta_function::multiplication]
= sol::overload([](const TransformQ& a, const Vec3& b) { return a.mQ * b; }, = sol::overload([](const TransformQ& a, const Vec3& b) { return a.mQ * b; },
@ -243,6 +259,22 @@ namespace LuaUtil
}; };
transQType["apply"] = [](const TransformQ& a, const Vec3& b) { return a.mQ * b; }, transQType["apply"] = [](const TransformQ& a, const Vec3& b) { return a.mQ * b; },
transQType["inverse"] = [](const TransformQ& q) { return TransformQ{ q.mQ.inverse() }; }; transQType["inverse"] = [](const TransformQ& q) { return TransformQ{ q.mQ.inverse() }; };
transQType["getYaw"] = [](const TransformQ& q) {
osg::Vec3f angles = Misc::toEulerAnglesXZ(q.mQ);
return angles.z();
};
transQType["getPitch"] = [](const TransformQ& q) {
osg::Vec3f angles = Misc::toEulerAnglesXZ(q.mQ);
return angles.x();
};
transQType["getAnglesXZ"] = [](const TransformQ& q) {
osg::Vec3f angles = Misc::toEulerAnglesXZ(q.mQ);
return std::make_tuple(angles.x(), angles.z());
};
transQType["getAnglesZYX"] = [](const TransformQ& q) {
osg::Vec3f angles = Misc::toEulerAnglesXZ(q.mQ);
return std::make_tuple(angles.z(), angles.y(), angles.x());
};
// Utility functions // Utility functions
util["clamp"] = [](double value, double from, double to) { return std::clamp(value, from, to); }; util["clamp"] = [](double value, double from, double to) { return std::clamp(value, from, to); };

@ -2,6 +2,7 @@
#define COMPONENTS_LUA_UTILPACKAGE_H #define COMPONENTS_LUA_UTILPACKAGE_H
#include <osg/Matrix> #include <osg/Matrix>
#include <osg/Quat>
#include <osg/Vec2> #include <osg/Vec2>
#include <osg/Vec3> #include <osg/Vec3>
#include <osg/Vec4> #include <osg/Vec4>
@ -34,6 +35,11 @@ namespace LuaUtil
return { q }; return { q };
} }
inline bool isTransform(const sol::object& obj)
{
return obj.is<TransformM>() || obj.is<TransformQ>();
}
sol::table initUtilPackage(lua_State*); sol::table initUtilPackage(lua_State*);
} }

@ -2,7 +2,10 @@
#define MISC_MATHUTIL_H #define MISC_MATHUTIL_H
#include <osg/Math> #include <osg/Math>
#include <osg/Matrixf>
#include <osg/Quat>
#include <osg/Vec2f> #include <osg/Vec2f>
#include <osg/Vec3f>
namespace Misc namespace Misc
{ {
@ -22,6 +25,44 @@ namespace Misc
return osg::Vec2f(vec.x() * c + vec.y() * -s, vec.x() * s + vec.y() * c); return osg::Vec2f(vec.x() * c + vec.y() * -s, vec.x() * s + vec.y() * c);
} }
inline osg::Vec3f toEulerAnglesXZ(osg::Vec3f forward)
{
float x = -asin(forward.z());
float z = atan2(forward.x(), forward.y());
return osg::Vec3f(x, 0, z);
}
inline osg::Vec3f toEulerAnglesXZ(osg::Quat quat)
{
return toEulerAnglesXZ(quat * osg::Vec3f(0, 1, 0));
}
inline osg::Vec3f toEulerAnglesXZ(osg::Matrixf m)
{
osg::Vec3f forward(m(1, 0), m(1, 1), m(1, 2));
forward.normalize();
return toEulerAnglesXZ(forward);
}
inline osg::Vec3f toEulerAnglesZYX(osg::Vec3f forward, osg::Vec3f up)
{
float y = -asin(up.x());
float x = atan2(up.y(), up.z());
osg::Vec3f forwardZ = (osg::Quat(x, osg::Vec3f(1, 0, 0)) * osg::Quat(y, osg::Vec3f(0, 1, 0))) * forward;
float z = atan2(forwardZ.x(), forwardZ.y());
return osg::Vec3f(x, y, z);
}
inline osg::Vec3f toEulerAnglesZYX(osg::Quat quat)
{
return toEulerAnglesZYX(quat * osg::Vec3f(0, 1, 0), quat * osg::Vec3f(0, 0, 1));
}
inline osg::Vec3f toEulerAnglesZYX(osg::Matrixf m)
{
osg::Vec3f forward(m(1, 0), m(1, 1), m(1, 2));
osg::Vec3f up(m(2, 0), m(2, 1), m(2, 2));
forward.normalize();
up.normalize();
return toEulerAnglesZYX(forward, up);
}
inline bool isPowerOfTwo(int x) inline bool isPowerOfTwo(int x)
{ {
return ((x > 0) && ((x & (x - 1)) == 0)); return ((x > 0) && ((x & (x - 1)) == 0));

@ -34,7 +34,7 @@ function M.onUpdate(dt)
return return
end end
if camera.getMode() == camera.MODE.ThirdPerson and camera.getThirdPersonDistance() < limitSwitch if camera.getMode() == camera.MODE.ThirdPerson and camera.getThirdPersonDistance() < limitSwitch
and math.abs(util.normalizeAngle(camera.getYaw() - self.rotation.z)) < math.rad(10) then and math.abs(util.normalizeAngle(camera.getYaw() - self.rotation:getYaw())) < math.rad(10) then
if castRayBackward() <= limitSwitch then if castRayBackward() <= limitSwitch then
camera.setMode(camera.MODE.FirstPerson, true) camera.setMode(camera.MODE.FirstPerson, true)
forcedFirstPerson = true forcedFirstPerson = true

@ -43,11 +43,11 @@ function M.onFrame(dt)
if camera.getMode() == MODE.Preview and not input.isActionPressed(input.ACTION.TogglePOV) then if camera.getMode() == MODE.Preview and not input.isActionPressed(input.ACTION.TogglePOV) then
camera.showCrosshair(camera.getFocalPreferredOffset():length() > 5) camera.showCrosshair(camera.getFocalPreferredOffset():length() > 5)
local move = util.vector2(self.controls.sideMovement, self.controls.movement) local move = util.vector2(self.controls.sideMovement, self.controls.movement)
local yawDelta = camera.getYaw() - self.rotation.z local yawDelta = camera.getYaw() - self.rotation:getYaw()
move = move:rotate(-yawDelta) move = move:rotate(-yawDelta)
self.controls.sideMovement = move.x self.controls.sideMovement = move.x
self.controls.movement = move.y self.controls.movement = move.y
self.controls.pitchChange = camera.getPitch() * math.cos(yawDelta) - self.rotation.x self.controls.pitchChange = camera.getPitch() * math.cos(yawDelta) - self.rotation:getPitch()
if move:length() > 0.05 then if move:length() > 0.05 then
local delta = math.atan2(move.x, move.y) local delta = math.atan2(move.x, move.y)
local maxDelta = math.max(delta, 1) * M.turnSpeed * dt local maxDelta = math.max(delta, 1) * M.turnSpeed * dt
@ -68,7 +68,7 @@ function M.onInputAction(action)
end end
if action == input.ACTION.ZoomIn and camera.getMode() == MODE.Preview if action == input.ACTION.ZoomIn and camera.getMode() == MODE.Preview
and I.Camera.getBaseThirdPersonDistance() == 30 then and I.Camera.getBaseThirdPersonDistance() == 30 then
self.controls.yawChange = camera.getYaw() - self.rotation.z self.controls.yawChange = camera.getYaw() - self.rotation:getYaw()
camera.setMode(MODE.FirstPerson) camera.setMode(MODE.FirstPerson)
elseif action == input.ACTION.ZoomOut and camera.getMode() == MODE.FirstPerson then elseif action == input.ACTION.ZoomOut and camera.getMode() == MODE.FirstPerson then
camera.setMode(MODE.Preview) camera.setMode(MODE.Preview)

@ -152,9 +152,9 @@
-- @field #boolean enabled Whether the object is enabled or disabled. Global scripts can set the value. Items in containers or inventories can't be disabled. -- @field #boolean enabled Whether the object is enabled or disabled. Global scripts can set the value. Items in containers or inventories can't be disabled.
-- @field openmw.util#Vector3 position Object position. -- @field openmw.util#Vector3 position Object position.
-- @field #number scale Object scale. -- @field #number scale Object scale.
-- @field openmw.util#Vector3 rotation Object rotation (ZXY order). -- @field openmw.util#Transform rotation Object rotation.
-- @field openmw.util#Vector3 startingPosition The object original position -- @field openmw.util#Vector3 startingPosition The object original position
-- @field openmw.util#Vector3 startingRotation The object original rotation -- @field openmw.util#Transform startingRotation The object original rotation
-- @field #string ownerRecordId NPC who owns the object (nil if missing). Global and self scripts can set the value. -- @field #string ownerRecordId NPC who owns the object (nil if missing). Global and self scripts can set the value.
-- @field #string ownerFactionId Faction who owns the object (nil if missing). Global and self scripts can set the value. -- @field #string ownerFactionId Faction who owns the object (nil if missing). Global and self scripts can set the value.
-- @field #number ownerFactionRank Rank required to be allowed to pick up the object. Global and self scripts can set the value. -- @field #number ownerFactionRank Rank required to be allowed to pick up the object. Global and self scripts can set the value.
@ -228,12 +228,12 @@
-- @param #any cellOrName A cell to define the destination worldspace; can be either #Cell, or cell name, or an empty string (empty string means the default exterior worldspace). -- @param #any cellOrName A cell to define the destination worldspace; can be either #Cell, or cell name, or an empty string (empty string means the default exterior worldspace).
-- If the worldspace has multiple cells (i.e. an exterior), the destination cell is calculated using `position`. -- If the worldspace has multiple cells (i.e. an exterior), the destination cell is calculated using `position`.
-- @param openmw.util#Vector3 position New position. -- @param openmw.util#Vector3 position New position.
-- @param #TeleportOptions options (optional) Either table @{#TeleportOptions} or @{openmw.util#Vector3} rotation. -- @param #TeleportOptions options (optional) Either table @{#TeleportOptions} or @{openmw.util#Transform} rotation.
--- ---
-- Either table with options or @{openmw.util#Vector3} rotation. -- Either table with options or @{openmw.util#Vector3} rotation.
-- @type TeleportOptions -- @type TeleportOptions
-- @field openmw.util#Vector3 rotation New rotation; if missing, then the current rotation is used. -- @field openmw.util#Transform rotation New rotation; if missing, then the current rotation is used.
-- @field #boolean onGround If true, adjust destination position to the ground. -- @field #boolean onGround If true, adjust destination position to the ground.
--- ---

@ -1333,7 +1333,7 @@
-- Destination rotation (only if a teleport door). -- Destination rotation (only if a teleport door).
-- @function [parent=#Door] destRotation -- @function [parent=#Door] destRotation
-- @param openmw.core#GameObject object -- @param openmw.core#GameObject object
-- @return openmw.util#Vector3 -- @return openmw.util#Transform
--- ---
-- Destination cell (only if a teleport door). -- Destination cell (only if a teleport door).
@ -1443,7 +1443,7 @@
-- Destination rotation (only if a teleport door). -- Destination rotation (only if a teleport door).
-- @function [parent=#ESM4Door] destRotation -- @function [parent=#ESM4Door] destRotation
-- @param openmw.core#GameObject object -- @param openmw.core#GameObject object
-- @return openmw.util#Vector3 -- @return openmw.util#Transform
--- ---
-- Destination cell (only if a teleport door). -- Destination cell (only if a teleport door).

@ -501,6 +501,33 @@
-- @param #Vector3 v -- @param #Vector3 v
-- @return #Vector3 -- @return #Vector3
---
-- Get yaw angle (radians)
-- @function [parent=#Transform] getYaw
-- @param self
-- @return #number
---
-- Get pitch angle (radians)
-- @function [parent=#Transform] getPitch
-- @param self
-- @return #number
---
-- Get Euler angles for XZ rotation order (pitch and yaw; radians)
-- @function [parent=#Transform] getAnglesXZ
-- @param self
-- @return #number pitch (rotation around X axis)
-- @return #number yaw (rotation around Z axis)
---
-- Get Euler angles for ZYX rotation order (radians)
-- @function [parent=#Transform] getAnglesZYX
-- @param self
-- @return #number rotation around Z axis (first rotation)
-- @return #number rotation around Y axis (second rotation)
-- @return #number rotation around X axis (third rotation)
--- ---
-- @type TRANSFORM -- @type TRANSFORM
-- @field [parent=#TRANSFORM] #Transform identity Empty transform. -- @field [parent=#TRANSFORM] #Transform identity Empty transform.

@ -21,10 +21,10 @@ testing.registerLocalTest('playerRotation',
self.controls.run = true self.controls.run = true
self.controls.movement = 0 self.controls.movement = 0
self.controls.sideMovement = 0 self.controls.sideMovement = 0
self.controls.yawChange = util.normalizeAngle(math.rad(90) - self.rotation.z) * 0.5 self.controls.yawChange = util.normalizeAngle(math.rad(90) - self.rotation:getYaw()) * 0.5
coroutine.yield() coroutine.yield()
end end
testing.expectEqualWithDelta(self.rotation.z, math.rad(90), 0.05, 'Incorrect rotation') testing.expectEqualWithDelta(self.rotation:getYaw(), math.rad(90), 0.05, 'Incorrect rotation')
end) end)
testing.registerLocalTest('playerForwardRunning', testing.registerLocalTest('playerForwardRunning',

@ -36,23 +36,29 @@ local function testTimers()
end end
local function testTeleport() local function testTeleport()
player:teleport('', util.vector3(100, 50, 0), util.vector3(0, 0, math.rad(-90))) player:teleport('', util.vector3(100, 50, 500), util.transform.rotateZ(math.rad(90)))
coroutine.yield() coroutine.yield()
testing.expect(player.cell.isExterior, 'teleport to exterior failed') testing.expect(player.cell.isExterior, 'teleport to exterior failed')
testing.expectEqualWithDelta(player.position.x, 100, 1, 'incorrect position after teleporting') testing.expectEqualWithDelta(player.position.x, 100, 1, 'incorrect position after teleporting')
testing.expectEqualWithDelta(player.position.y, 50, 1, 'incorrect position after teleporting') testing.expectEqualWithDelta(player.position.y, 50, 1, 'incorrect position after teleporting')
testing.expectEqualWithDelta(player.rotation.z, math.rad(-90), 0.05, 'incorrect rotation after teleporting') testing.expectEqualWithDelta(player.position.z, 500, 1, 'incorrect position after teleporting')
testing.expectEqualWithDelta(player.rotation:getYaw(), math.rad(90), 0.05, 'incorrect rotation after teleporting')
player:teleport('', player.position, {rotation=util.transform.rotateZ(math.rad(-90)), onGround=true})
coroutine.yield()
testing.expectEqualWithDelta(player.rotation:getYaw(), math.rad(-90), 0.05, 'options.rotation is not working')
testing.expectLessOrEqual(player.position.z, 400, 'options.onGround is not working')
player:teleport('', util.vector3(50, -100, 0)) player:teleport('', util.vector3(50, -100, 0))
coroutine.yield() coroutine.yield()
testing.expect(player.cell.isExterior, 'teleport to exterior failed') testing.expect(player.cell.isExterior, 'teleport to exterior failed')
testing.expectEqualWithDelta(player.position.x, 50, 1, 'incorrect position after teleporting') testing.expectEqualWithDelta(player.position.x, 50, 1, 'incorrect position after teleporting')
testing.expectEqualWithDelta(player.position.y, -100, 1, 'incorrect position after teleporting') testing.expectEqualWithDelta(player.position.y, -100, 1, 'incorrect position after teleporting')
testing.expectEqualWithDelta(player.rotation.z, math.rad(-90), 0.05, 'teleporting changes rotation') testing.expectEqualWithDelta(player.rotation:getYaw(), math.rad(-90), 0.05, 'teleporting changes rotation')
end end
local function initPlayer() local function initPlayer()
player:teleport('', util.vector3(4096, 4096, 867.237), util.vector3(0, 0, 0)) player:teleport('', util.vector3(4096, 4096, 867.237), util.transform.identity)
coroutine.yield() coroutine.yield()
end end

Loading…
Cancel
Save