From 51845e95536bdb29c86569c3c6481c06446215d4 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 18 Mar 2022 00:27:16 +0100 Subject: [PATCH] Rendering raycasts in Lua --- apps/openmw/mwbase/world.hpp | 4 ++++ apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 2 ++ apps/openmw/mwlua/luamanagerimp.hpp | 3 +++ apps/openmw/mwlua/nearbybindings.cpp | 22 ++++++++++++++++++++++ apps/openmw/mwphysics/raycasting.hpp | 11 ++++++----- apps/openmw/mwworld/worldimp.cpp | 19 +++++++++++++++++++ apps/openmw/mwworld/worldimp.hpp | 3 +++ files/lua_api/openmw/nearby.lua | 17 +++++++++++++++++ 9 files changed, 77 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 625758d640..f90794fc52 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -57,6 +57,7 @@ namespace ESM namespace MWPhysics { + class RayCastingResult; class RayCastingInterface; } @@ -331,6 +332,9 @@ namespace MWBase virtual bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) = 0; + virtual bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, + bool ignorePlayer, bool ignoreActors) = 0; + virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index c88045ae84..3c2023ff5f 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -41,7 +41,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 20; + api["API_REVISION"] = 21; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 692b089bdb..aa2d5baa6f 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -226,6 +226,7 @@ namespace MWLua return; // The game is not started yet. // We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency. + mProcessingInputEvents = true; PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (playerScripts && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)) { @@ -235,6 +236,7 @@ namespace MWLua mInputEvents.clear(); if (playerScripts && !mWorldView.isPaused()) playerScripts->inputUpdate(MWBase::Environment::get().getFrameDuration()); + mProcessingInputEvents = false; MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); for (const std::string& message : mUIMessages) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 43f5d9c3b6..fd9ec8b172 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -111,12 +111,15 @@ namespace MWLua LuaUi::ResourceManager* uiResourceManager() { return &mUiResourceManager; } + bool isProcessingInputEvents() const { return mProcessingInputEvents; } + private: void initConfiguration(); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags); bool mInitialized = false; bool mGlobalScriptsStarted = false; + bool mProcessingInputEvents = false; LuaUtil::ScriptsConfiguration mConfiguration; LuaUtil::LuaState mLua; LuaUi::ResourceManager mUiResourceManager; diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 05bc52c5cc..6ce78e569f 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -6,6 +6,7 @@ #include "../mwbase/world.hpp" #include "../mwphysics/raycasting.hpp" +#include "luamanagerimp.hpp" #include "worldview.hpp" namespace sol @@ -91,6 +92,27 @@ namespace MWLua // and use this callback from the main thread at the beginning of the next frame processing. rayCasting->asyncCastRay(callback, from, to, ignore, std::vector(), collisionType); };*/ + api["castRenderingRay"] = [manager=context.mLuaManager](const osg::Vec3f& from, const osg::Vec3f& to) + { + if (!manager->isProcessingInputEvents()) + { + throw std::logic_error("castRenderingRay can be used only in player scripts during processing of input events; " + "use asyncCastRenderingRay instead."); + } + MWPhysics::RayCastingResult res; + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + return res; + }; + api["asyncCastRenderingRay"] = + [manager=context.mLuaManager](const LuaUtil::Callback& callback, const osg::Vec3f& from, const osg::Vec3f& to) + { + manager->addAction([manager, callback, from, to] + { + MWPhysics::RayCastingResult res; + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + manager->queueCallback(callback, sol::make_object(callback.mFunc.lua_state(), res)); + }); + }; api["activators"] = LObjectList{worldView->getActivatorsInScene()}; api["actors"] = LObjectList{worldView->getActorsInScene()}; diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index d00f23e2c4..848f17a01a 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -9,12 +9,13 @@ namespace MWPhysics { - struct RayCastingResult + class RayCastingResult { - bool mHit; - osg::Vec3f mHitPos; - osg::Vec3f mHitNormal; - MWWorld::Ptr mHitObject; + public: + bool mHit; + osg::Vec3f mHitPos; + osg::Vec3f mHitNormal; + MWWorld::Ptr mHitObject; }; class RayCastingInterface diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 7f652d9520..4d3f4aa7ec 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2042,6 +2042,25 @@ namespace MWWorld return facedObject; } + bool World::castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, + bool ignorePlayer, bool ignoreActors) + { + MWRender::RenderingManager::RayResult rayRes = mRendering->castRay(from, to, ignorePlayer, ignoreActors); + res.mHit = rayRes.mHit; + res.mHitPos = rayRes.mHitPointWorld; + res.mHitNormal = rayRes.mHitNormalWorld; + res.mHitObject = rayRes.mHitObject; + if (res.mHitObject.isEmpty() && rayRes.mHitRefnum.isSet()) + { + for (CellStore* cellstore : mWorldScene->getActiveCells()) + { + res.mHitObject = cellstore->searchViaRefNum(rayRes.mHitRefnum); + if (!res.mHitObject.isEmpty()) break; + } + } + return res.mHit; + } + bool World::isCellExterior() const { const CellStore *currentCell = mWorldScene->getCurrentCell(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 399b7232de..8cf83eb3aa 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -421,6 +421,9 @@ namespace MWWorld bool castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore) override; + bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, + bool ignorePlayer, bool ignoreActors) override; + void setActorCollisionMode(const Ptr& ptr, bool internal, bool external) override; bool isActorCollisionEnabled(const Ptr& ptr) override; diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index 3ba3d7e6fb..ba27269700 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -68,5 +68,22 @@ -- radius = 10, -- }) +--- +-- Cast ray from one point to another and find the first visual intersection with anything in the scene. +-- As opposite to `castRay` can find an intersection with an object without collisions. +-- In order to avoid threading issues can be used only in player scripts only in `onInputUpdate` or +-- in engine handlers for user input. In other cases use `asyncCastRenderingRay` instead. +-- @function [parent=#nearby] castRenderingRay +-- @param openmw.util#Vector3 from Start point of the ray. +-- @param openmw.util#Vector3 to End point of the ray. +-- @return #RayCastingResult + +--- +-- Asynchronously cast ray from one point to another and find the first visual intersection with anything in the scene. +-- @function [parent=#nearby] asyncCastRenderingRay +-- @param openmw.async#Callback callback The callback to pass the result to (should accept a single argument @{openmw.nearby#RayCastingResult}). +-- @param openmw.util#Vector3 from Start point of the ray. +-- @param openmw.util#Vector3 to End point of the ray. + return nil