From c7a7c8ddf2b210e6de5c2122c3c56cc254c8404a Mon Sep 17 00:00:00 2001 From: MaxYari Date: Wed, 5 Mar 2025 09:38:59 +0100 Subject: [PATCH 1/5] WIP --- apps/openmw/mwlua/types/actor.cpp | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 413a656e90..75e1f86bbf 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -2,6 +2,8 @@ #include +#include + #include #include #include @@ -170,6 +172,29 @@ namespace MWLua MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(*it); } + static std::optional findBoneWorldTransform( + const osg::ref_ptr parentNode, const std::string_view boneName) + { + if (!parentNode) + return; + const osg::Group* parentNodeGroup = parentNode->asGroup(); + if (!parentNodeGroup) + return; + + for (unsigned int i = 0; i < parentNodeGroup->getNumChildren(); ++i) + { + osg::ref_ptr child = parentNodeGroup->getChild(i); + + // asMatrixTransform will break if its not a bone + if (child->getName() == boneName) + { + return osg::computeLocalToWorld(child->getParentalNodePaths()[0]); + } + + findBoneWorldTransform(child, boneName); + } + } + void addActorBindings(sol::table actor, const Context& context) { sol::state_view lua = context.sol(); @@ -195,7 +220,17 @@ namespace MWLua { "CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight }, { "CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft }, { "Ammunition", MWWorld::InventoryStore::Slot_Ammunition } })); + actor["getBonePosition"] = [](const SelfObject& self, std::string_view boneName) { + const MWWorld::Class& cls = self.ptr().getClass(); + if (!cls.isActor()) + throw std::runtime_error("Actor expected"); + std::optional boneTransform + = findBoneWorldTransform(self.ptr().getRefData().getBaseNode()->asGroup(), boneName); + return boneTransform; + // return o.ptr().getRefData().getPosition().asVec3(); + // return Misc::Convert::makeOsgQuat(pos.rot); + }; actor["getStance"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); if (cls.isActor()) From 8c3538d3775bcf918c7e1860a5b6f3b031e463da Mon Sep 17 00:00:00 2001 From: MaxYari Date: Wed, 5 Mar 2025 09:48:21 +0100 Subject: [PATCH 2/5] lua bindings almost done --- apps/openmw/mwlua/types/actor.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 75e1f86bbf..82c3b085b9 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "apps/openmw/mwbase/environment.hpp" @@ -226,9 +227,25 @@ namespace MWLua throw std::runtime_error("Actor expected"); std::optional boneTransform - = findBoneWorldTransform(self.ptr().getRefData().getBaseNode()->asGroup(), boneName); - return boneTransform; - // return o.ptr().getRefData().getPosition().asVec3(); + = findBoneWorldTransform(self.ptr().getRefData().getBaseNode(), boneName); + + if (!boneTransform.has_value()) + return sol::nil; + + return boneTransform.value().getTrans(); + }; + actor["getBoneRotation"] = [](const SelfObject& self, std::string_view boneName) { + const MWWorld::Class& cls = self.ptr().getClass(); + if (!cls.isActor()) + throw std::runtime_error("Actor expected"); + + std::optional boneTransform + = findBoneWorldTransform(self.ptr().getRefData().getBaseNode(), boneName); + + if (!boneTransform.has_value()) + return sol::nil; + + return boneTransform.value().getRotate(); // return Misc::Convert::makeOsgQuat(pos.rot); }; actor["getStance"] = [](const Object& o) { From 5750be2ad12d9b67ac8efe1f300e0e377ebbdc27 Mon Sep 17 00:00:00 2001 From: MaxYari Date: Wed, 5 Mar 2025 10:23:40 +0100 Subject: [PATCH 3/5] Seems to be working --- apps/openmw/mwlua/types/actor.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 82c3b085b9..98a846559e 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -177,14 +177,14 @@ namespace MWLua const osg::ref_ptr parentNode, const std::string_view boneName) { if (!parentNode) - return; - const osg::Group* parentNodeGroup = parentNode->asGroup(); + return std::nullopt; + osg::Group* parentNodeGroup = parentNode->asGroup(); if (!parentNodeGroup) - return; + return std::nullopt; for (unsigned int i = 0; i < parentNodeGroup->getNumChildren(); ++i) { - osg::ref_ptr child = parentNodeGroup->getChild(i); + osg::ref_ptr child = parentNodeGroup->getChild(i); // asMatrixTransform will break if its not a bone if (child->getName() == boneName) @@ -192,8 +192,10 @@ namespace MWLua return osg::computeLocalToWorld(child->getParentalNodePaths()[0]); } - findBoneWorldTransform(child, boneName); + return findBoneWorldTransform(child, boneName); } + + return std::nullopt; } void addActorBindings(sol::table actor, const Context& context) @@ -221,7 +223,7 @@ namespace MWLua { "CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight }, { "CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft }, { "Ammunition", MWWorld::InventoryStore::Slot_Ammunition } })); - actor["getBonePosition"] = [](const SelfObject& self, std::string_view boneName) { + actor["getBonePosition"] = [](const SelfObject& self, std::string_view boneName) -> sol::optional { const MWWorld::Class& cls = self.ptr().getClass(); if (!cls.isActor()) throw std::runtime_error("Actor expected"); @@ -230,11 +232,11 @@ namespace MWLua = findBoneWorldTransform(self.ptr().getRefData().getBaseNode(), boneName); if (!boneTransform.has_value()) - return sol::nil; + return sol::nullopt; return boneTransform.value().getTrans(); }; - actor["getBoneRotation"] = [](const SelfObject& self, std::string_view boneName) { + actor["getBoneRotation"] = [](const SelfObject& self, std::string_view boneName) -> sol::optional { const MWWorld::Class& cls = self.ptr().getClass(); if (!cls.isActor()) throw std::runtime_error("Actor expected"); @@ -243,7 +245,7 @@ namespace MWLua = findBoneWorldTransform(self.ptr().getRefData().getBaseNode(), boneName); if (!boneTransform.has_value()) - return sol::nil; + return sol::nullopt; return boneTransform.value().getRotate(); // return Misc::Convert::makeOsgQuat(pos.rot); From c0f733cdcecbcc29faaf51db34c446df3bf822c5 Mon Sep 17 00:00:00 2001 From: MaxYari Date: Fri, 7 Mar 2025 21:09:16 +0100 Subject: [PATCH 4/5] Changed to global for testing. Since it a getter - probably should change to regular object. --- apps/openmw/mwlua/types/actor.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 98a846559e..1a89d64dcf 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -223,26 +223,27 @@ namespace MWLua { "CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight }, { "CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft }, { "Ammunition", MWWorld::InventoryStore::Slot_Ammunition } })); - actor["getBonePosition"] = [](const SelfObject& self, std::string_view boneName) -> sol::optional { - const MWWorld::Class& cls = self.ptr().getClass(); - if (!cls.isActor()) - throw std::runtime_error("Actor expected"); + actor["getBonePosition"] = [](const GObject& o, std::string_view boneName) -> sol::optional { + const MWWorld::Class& cls = o.ptr().getClass(); + // Need to accept self OR a global object + /*if (!cls.isActor()) + throw std::runtime_error("Actor expected");*/ std::optional boneTransform - = findBoneWorldTransform(self.ptr().getRefData().getBaseNode(), boneName); + = findBoneWorldTransform(o.ptr().getRefData().getBaseNode(), boneName); if (!boneTransform.has_value()) return sol::nullopt; - return boneTransform.value().getTrans(); + return static_cast(boneTransform.value().getTrans()); }; - actor["getBoneRotation"] = [](const SelfObject& self, std::string_view boneName) -> sol::optional { - const MWWorld::Class& cls = self.ptr().getClass(); + actor["getBoneRotation"] = [](const GObject& o, std::string_view boneName) -> sol::optional { + const MWWorld::Class& cls = o.ptr().getClass(); if (!cls.isActor()) throw std::runtime_error("Actor expected"); std::optional boneTransform - = findBoneWorldTransform(self.ptr().getRefData().getBaseNode(), boneName); + = findBoneWorldTransform(o.ptr().getRefData().getBaseNode(), boneName); if (!boneTransform.has_value()) return sol::nullopt; From a668179a1eaec204cb0993b9ebaf474604cdaad8 Mon Sep 17 00:00:00 2001 From: MaxYari Date: Sun, 9 Mar 2025 17:21:42 +0100 Subject: [PATCH 5/5] Visual move method on lua self --- apps/openmw/engine.cpp | 4 ++++ apps/openmw/mwlua/localscripts.cpp | 15 +++++++++++++++ apps/openmw/mwlua/luamanagerimp.cpp | 18 ++++++++++++++++++ apps/openmw/mwlua/luamanagerimp.hpp | 9 +++++++++ components/lua/scriptscontainer.cpp | 1 + components/lua/scriptscontainer.hpp | 5 +++++ 6 files changed, 52 insertions(+) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 2736f339e4..6cde3b5538 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -348,6 +348,10 @@ bool OMW::Engine::frame(unsigned frameNumber, float frametime) mWorld->updateWindowManager(); } + // Synchronised update that can safely be used for scene modifications that affect rendering, without using a + // delayed action. + mLuaManager->preRenderSynchronizedUpdate(); + // if there is a separate Lua thread, it starts the update now mLuaWorker->allowUpdate(frameStart, frameNumber, *stats); diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 7a3e9ff23a..ae49d6c1a9 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -6,6 +6,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" + #include "../mwmechanics/aicombat.hpp" #include "../mwmechanics/aiescort.hpp" #include "../mwmechanics/aifollow.hpp" @@ -19,7 +21,10 @@ #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" +#include "../mwrender/renderingmanager.hpp" + #include "context.hpp" +#include "luamanagerimp.hpp" namespace sol { @@ -71,6 +76,16 @@ namespace MWLua { { "NoAttack", MWMechanics::AttackType::NoAttack }, { "Any", MWMechanics::AttackType::Any }, { "Chop", MWMechanics::AttackType::Chop }, { "Slash", MWMechanics::AttackType::Slash }, { "Thrust", MWMechanics::AttackType::Thrust } })); + selfAPI["visualMoveTo"] = [context](const SelfObject& self, const osg::Vec3f& pos) { + if (!context.mLuaManager->isInPreRenderUpdate()) + throw "visualMoveTo can only be used in onPreRender engine handler."; + MWBase::Environment::get().getWorld()->getRenderingManager()->moveObject(self.ptr(), pos); + }; + selfAPI["visualRotate"] = [context](const SelfObject& self, const osg::Quat& rot) { + if (!context.mLuaManager->isInPreRenderUpdate()) + throw "visualMoveTo can only be used in onPreRender engine handler."; + MWBase::Environment::get().getWorld()->getRenderingManager()->rotateObject(self.ptr(), rot); + }; using AiPackage = MWMechanics::AiPackage; sol::usertype aiPackage = lua.new_usertype("AiPackage"); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 5fa2d9867c..cdf542625b 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -239,6 +239,24 @@ namespace MWLua } } + void LuaManager::preRenderSynchronizedUpdate() + { + mLua.protectedCall([&](LuaUtil::LuaView&) { preRenderSynchronizedUpdateUnsafe(); }); + } + + void LuaManager::preRenderSynchronizedUpdateUnsafe() + { + mInPreRenderUpdate = true; + + float frameDuration = MWBase::Environment::get().getFrameDuration(); + for (LocalScripts* scripts : mActiveLocalScripts) + scripts->preRenderUpdate(frameDuration); + if (mGlobalScriptsStarted) + mGlobalScripts.preRenderUpdate(frameDuration); + + mInPreRenderUpdate = false; + } + void LuaManager::synchronizedUpdate() { mLua.protectedCall([&](LuaUtil::LuaView&) { synchronizedUpdateUnsafe(); }); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 3f2135e9c9..c732e59673 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -62,6 +62,12 @@ namespace MWLua // Can use the scene graph and applies the actions queued during update() void synchronizedUpdate(); + // \brief Executes latency-critical and scene graph related Lua logic. + // + // Called by engine.cpp from the main thread between after the complete world update, right before rendering and + // onUpdate. Can use the scene graph + void preRenderSynchronizedUpdate(); + // Normally it is called by `synchronizedUpdate` every frame. // Additionally must be called before making a save to ensure that there are no pending delayed // actions and the world is in a consistent state. @@ -159,6 +165,7 @@ namespace MWLua LuaUi::ResourceManager* uiResourceManager() { return &mUiResourceManager; } bool isProcessingInputEvents() const { return mProcessingInputEvents; } + bool isInPreRenderUpdate() const { return mInPreRenderUpdate; } void reportStats(unsigned int frameNumber, osg::Stats& stats) const; std::string formatResourceUsageStats() const override; @@ -172,10 +179,12 @@ namespace MWLua std::optional autoStartConf = std::nullopt); void reloadAllScriptsImpl(); void synchronizedUpdateUnsafe(); + void preRenderSynchronizedUpdateUnsafe(); bool mInitialized = false; bool mGlobalScriptsStarted = false; bool mProcessingInputEvents = false; + bool mInPreRenderUpdate = false; bool mApplyingDelayedActions = false; bool mNewGameStarted = false; bool mReloadAllScriptsRequested = false; diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 5eff211894..c24fff222d 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -37,6 +37,7 @@ namespace LuaUtil { sInstanceCount++; registerEngineHandlers({ &mUpdateHandlers }); + registerEngineHandlers({ &mPreRenderUpdateHandlers }); if (load) { LoadedData& data = mData.emplace(); diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index 956b4d4317..e88d4519bd 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -107,6 +107,10 @@ namespace LuaUtil // Handlers are called in the same order as scripts were added. void update(float dt) { callEngineHandlers(mUpdateHandlers, dt); } + // Calls `onUpdate` (if present) for every script in the container. + // Handlers are called in the same order as scripts were added. + void preRenderUpdate(float dt) { callEngineHandlers(mPreRenderUpdateHandlers, dt); } + // Calls event handlers `eventName` (if present) for every script. // If several scripts register handlers for `eventName`, they are called in reverse order. // If some handler returns `false`, all remaining handlers are ignored. Any other return value @@ -289,6 +293,7 @@ namespace LuaUtil LoadedData& ensureLoaded(); EngineHandlerList mUpdateHandlers{ "onUpdate" }; + EngineHandlerList mPreRenderUpdateHandlers{ "onPreRender" }; std::map mEngineHandlers; std::variant mData; int64_t mTemporaryCallbackCounter = 0;