Merge branch 'lua_raycasting' into 'master'

Lua raycasting

Closes #6098

See merge request OpenMW/openmw!1175
pull/3113/head^2
psi29a 3 years ago
commit 5794a3b346

@ -58,7 +58,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua add_openmw_dir (mwlua
luamanagerimp actions object worldview userdataserializer eventqueue query luamanagerimp actions object worldview userdataserializer eventqueue query
luabindings localscripts objectbindings cellbindings asyncbindings settingsbindings luabindings localscripts objectbindings cellbindings asyncbindings settingsbindings
camerabindings uibindings inputbindings camerabindings uibindings inputbindings nearbybindings
) )
add_openmw_dir (mwsound add_openmw_dir (mwsound

@ -1,5 +1,7 @@
#include "luabindings.hpp" #include "luabindings.hpp"
#include "luamanagerimp.hpp"
namespace sol namespace sol
{ {
template <> template <>
@ -48,11 +50,16 @@ namespace MWLua
asyncId.mContainer->setupUnsavableTimer( asyncId.mContainer->setupUnsavableTimer(
TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScript, std::move(callback)); TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScript, std::move(callback));
}; };
api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn)
{
return Callback{std::move(fn), asyncId.mHiddenData};
};
auto initializer = [](sol::table hiddenData) auto initializer = [](sol::table hiddenData)
{ {
LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY]; LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY];
return AsyncPackageId{id.mContainer, id.mPath}; hiddenData[Callback::SCRIPT_NAME_KEY] = id.toString();
return AsyncPackageId{id.mContainer, id.mPath, hiddenData};
}; };
return sol::make_object(context.mLua->sol(), initializer); return sol::make_object(context.mLua->sol(), initializer);
} }

@ -25,7 +25,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"] = 5; api["API_REVISION"] = 6;
api["quit"] = [lua]() api["quit"] = [lua]()
{ {
std::string traceback = lua->sol()["debug"]["traceback"]().get<std::string>(); std::string traceback = lua->sol()["debug"]["traceback"]().get<std::string>();
@ -110,36 +110,6 @@ namespace MWLua
return LuaUtil::makeReadOnly(api); return LuaUtil::makeReadOnly(api);
} }
sol::table initNearbyPackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
WorldView* worldView = context.mWorldView;
api["activators"] = LObjectList{worldView->getActivatorsInScene()};
api["actors"] = LObjectList{worldView->getActorsInScene()};
api["containers"] = LObjectList{worldView->getContainersInScene()};
api["doors"] = LObjectList{worldView->getDoorsInScene()};
api["items"] = LObjectList{worldView->getItemsInScene()};
api["selectObjects"] = [context](const Queries::Query& query)
{
ObjectIdList list;
WorldView* worldView = context.mWorldView;
if (query.mQueryType == "activators")
list = worldView->getActivatorsInScene();
else if (query.mQueryType == "actors")
list = worldView->getActorsInScene();
else if (query.mQueryType == "containers")
list = worldView->getContainersInScene();
else if (query.mQueryType == "doors")
list = worldView->getDoorsInScene();
else if (query.mQueryType == "items")
list = worldView->getItemsInScene();
return LObjectList{selectObjectsFromList(query, list, context)};
// TODO: Maybe use sqlite
// return LObjectList{worldView->selectObjects(query, true)};
};
return LuaUtil::makeReadOnly(api);
}
sol::table initQueryPackage(const Context& context) sol::table initQueryPackage(const Context& context)
{ {
Queries::registerQueryBindings(context.mLua->sol()); Queries::registerQueryBindings(context.mLua->sol());

@ -21,11 +21,13 @@ namespace MWLua
sol::table initCorePackage(const Context&); sol::table initCorePackage(const Context&);
sol::table initWorldPackage(const Context&); sol::table initWorldPackage(const Context&);
sol::table initNearbyPackage(const Context&);
sol::table initQueryPackage(const Context&); sol::table initQueryPackage(const Context&);
sol::table initFieldGroup(const Context&, const QueryFieldGroup&); sol::table initFieldGroup(const Context&, const QueryFieldGroup&);
// Implemented in nearbybindings.cpp
sol::table initNearbyPackage(const Context&);
// Implemented in objectbindings.cpp // Implemented in objectbindings.cpp
void initObjectBindingsForLocalScripts(const Context&); void initObjectBindingsForLocalScripts(const Context&);
void initObjectBindingsForGlobalScripts(const Context&); void initObjectBindingsForGlobalScripts(const Context&);
@ -45,9 +47,9 @@ namespace MWLua
// Implemented in asyncbindings.cpp // Implemented in asyncbindings.cpp
struct AsyncPackageId struct AsyncPackageId
{ {
// TODO: add ObjectId mLocalObject;
LuaUtil::ScriptsContainer* mContainer; LuaUtil::ScriptsContainer* mContainer;
std::string mScript; std::string mScript;
sol::table mHiddenData;
}; };
sol::function getAsyncPackageInitializer(const Context&); sol::function getAsyncPackageInitializer(const Context&);

@ -74,6 +74,16 @@ namespace MWLua
mInitialized = true; mInitialized = true;
} }
void Callback::operator()(sol::object arg) const
{
if (mHiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY] != sol::nil)
LuaUtil::call(mFunc, std::move(arg));
else
{
Log(Debug::Debug) << "Ignored callback to removed script " << mHiddenData.get<std::string>(SCRIPT_NAME_KEY);
}
}
void LuaManager::update(bool paused, float dt) void LuaManager::update(bool paused, float dt)
{ {
ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry(); ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry();
@ -126,6 +136,11 @@ namespace MWLua
<< ". Object not found or has no attached scripts"; << ". Object not found or has no attached scripts";
} }
// Run queued callbacks
for (CallbackWithData& c : mQueuedCallbacks)
c.mCallback(c.mArg);
mQueuedCallbacks.clear();
// Engine handlers in local scripts // Engine handlers in local scripts
PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts()); PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
if (playerScripts) if (playerScripts)

@ -19,6 +19,19 @@
namespace MWLua namespace MWLua
{ {
// Wrapper for a single-argument Lua function.
// Holds information about the script the function belongs to.
// Needed to prevent callback calls if the script was removed.
struct Callback
{
static constexpr std::string_view SCRIPT_NAME_KEY = "name";
sol::function mFunc;
sol::table mHiddenData;
void operator()(sol::object arg) const;
};
class LuaManager : public MWBase::LuaManager class LuaManager : public MWBase::LuaManager
{ {
public: public:
@ -67,6 +80,18 @@ namespace MWLua
// Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script.
void reloadAllScripts() override; void reloadAllScripts() override;
// Used to call Lua callbacks from C++
void queueCallback(Callback callback, sol::object arg) { mQueuedCallbacks.push_back({std::move(callback), std::move(arg)}); }
// Wraps Lua callback into an std::function.
// NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or
// any other Lua-related function is running.
template <class Arg>
std::function<void(Arg)> wrapLuaCallback(const Callback& c)
{
return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); };
}
private: private:
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr);
@ -100,6 +125,13 @@ namespace MWLua
std::vector<MWBase::LuaManager::InputEvent> mInputEvents; std::vector<MWBase::LuaManager::InputEvent> mInputEvents;
std::vector<ObjectId> mActorAddedEvents; std::vector<ObjectId> mActorAddedEvents;
struct CallbackWithData
{
Callback mCallback;
sol::object mArg;
};
std::vector<CallbackWithData> mQueuedCallbacks;
struct LocalEngineEvent struct LocalEngineEvent
{ {
ObjectId mDest; ObjectId mDest;

@ -0,0 +1,122 @@
#include "luabindings.hpp"
#include <components/lua/luastate.hpp>
#include <components/queries/luabindings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwphysics/raycasting.hpp"
#include "worldview.hpp"
namespace sol
{
template <>
struct is_automagical<MWPhysics::RayCastingResult> : std::false_type {};
}
namespace MWLua
{
sol::table initNearbyPackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
WorldView* worldView = context.mWorldView;
sol::usertype<MWPhysics::RayCastingResult> rayResult =
context.mLua->sol().new_usertype<MWPhysics::RayCastingResult>("RayCastingResult");
rayResult["hit"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) { return r.mHit; });
rayResult["hitPos"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional<osg::Vec3f>
{
if (r.mHit)
return r.mHitPos;
else
return sol::nullopt;
});
rayResult["hitNormal"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional<osg::Vec3f>
{
if (r.mHit)
return r.mHitNormal;
else
return sol::nullopt;
});
rayResult["hitObject"] = sol::readonly_property([worldView](const MWPhysics::RayCastingResult& r) -> sol::optional<LObject>
{
if (r.mHitObject.isEmpty())
return sol::nullopt;
else
return LObject(getId(r.mHitObject), worldView->getObjectRegistry());
});
constexpr int defaultCollisionType = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap |
MWPhysics::CollisionType_Actor | MWPhysics::CollisionType_Door;
api["COLLISION_TYPE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
"World", MWPhysics::CollisionType_World,
"Door", MWPhysics::CollisionType_Door,
"Actor", MWPhysics::CollisionType_Actor,
"HeightMap", MWPhysics::CollisionType_HeightMap,
"Projectile", MWPhysics::CollisionType_Projectile,
"Water", MWPhysics::CollisionType_Water,
"Default", defaultCollisionType));
api["castRay"] = [defaultCollisionType](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional<sol::table> options)
{
MWWorld::Ptr ignore;
int collisionType = defaultCollisionType;
float radius = 0;
if (options)
{
sol::optional<LObject> ignoreObj = options->get<sol::optional<LObject>>("ignore");
if (ignoreObj) ignore = ignoreObj->ptr();
collisionType = options->get<sol::optional<int>>("collisionType").value_or(collisionType);
radius = options->get<sol::optional<float>>("radius").value_or(0);
}
const MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting();
if (radius <= 0)
return rayCasting->castRay(from, to, ignore, std::vector<MWWorld::Ptr>(), collisionType);
else
{
if (!ignore.isEmpty()) throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0");
return rayCasting->castSphere(from, to, radius, collisionType);
}
};
// TODO: async raycasting
/*api["asyncCastRay"] = [luaManager = context.mLuaManager, defaultCollisionType](
const Callback& luaCallback, const osg::Vec3f& from, const osg::Vec3f& to, sol::optional<sol::table> options)
{
std::function<void(MWPhysics::RayCastingResult)> callback =
luaManager->wrapLuaCallback<MWPhysics::RayCastingResult>(luaCallback);
MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting();
// Handle options the same way as in `castRay`.
// NOTE: `callback` is not thread safe. If MWPhysics works in separate thread, it must put results to a queue
// and use this callback from the main thread at the beginning of the next frame processing.
rayCasting->asyncCastRay(callback, from, to, ignore, std::vector<MWWorld::Ptr>(), collisionType);
};*/
api["activators"] = LObjectList{worldView->getActivatorsInScene()};
api["actors"] = LObjectList{worldView->getActorsInScene()};
api["containers"] = LObjectList{worldView->getContainersInScene()};
api["doors"] = LObjectList{worldView->getDoorsInScene()};
api["items"] = LObjectList{worldView->getItemsInScene()};
api["selectObjects"] = [context](const Queries::Query& query)
{
ObjectIdList list;
WorldView* worldView = context.mWorldView;
if (query.mQueryType == "activators")
list = worldView->getActivatorsInScene();
else if (query.mQueryType == "actors")
list = worldView->getActorsInScene();
else if (query.mQueryType == "containers")
list = worldView->getContainersInScene();
else if (query.mQueryType == "doors")
list = worldView->getDoorsInScene();
else if (query.mQueryType == "items")
list = worldView->getItemsInScene();
return LObjectList{selectObjectsFromList(query, list, context)};
// TODO: Maybe use sqlite
// return LObjectList{worldView->selectObjects(query, true)};
};
return LuaUtil::makeReadOnly(api);
}
}

@ -16,6 +16,15 @@ namespace LuaUtil
static constexpr std::string_view REGISTERED_TIMER_CALLBACKS = "_timers"; static constexpr std::string_view REGISTERED_TIMER_CALLBACKS = "_timers";
static constexpr std::string_view TEMPORARY_TIMER_CALLBACKS = "_temp_timers"; static constexpr std::string_view TEMPORARY_TIMER_CALLBACKS = "_temp_timers";
std::string ScriptsContainer::ScriptId::toString() const
{
std::string res = mContainer->mNamePrefix;
res.push_back('[');
res.append(mPath);
res.push_back(']');
return res;
}
ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix) : mNamePrefix(namePrefix), mLua(*lua) ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix) : mNamePrefix(namePrefix), mLua(*lua)
{ {
registerEngineHandlers({&mUpdateHandlers}); registerEngineHandlers({&mUpdateHandlers});
@ -81,6 +90,7 @@ namespace LuaUtil
auto scriptIter = mScripts.find(path); auto scriptIter = mScripts.find(path);
if (scriptIter == mScripts.end()) if (scriptIter == mScripts.end())
return false; // no such script return false; // no such script
scriptIter->second.mHiddenData[ScriptId::KEY] = sol::nil;
sol::object& script = scriptIter->second.mInterface; sol::object& script = scriptIter->second.mInterface;
if (getFieldOrNil(script, INTERFACE_NAME) != sol::nil) if (getFieldOrNil(script, INTERFACE_NAME) != sol::nil)
{ {
@ -320,6 +330,8 @@ namespace LuaUtil
void ScriptsContainer::removeAllScripts() void ScriptsContainer::removeAllScripts()
{ {
for (auto& [_, script] : mScripts)
script.mHiddenData[ScriptId::KEY] = sol::nil;
mScripts.clear(); mScripts.clear();
mScriptOrder.clear(); mScriptOrder.clear();
for (auto& [_, handlers] : mEngineHandlers) for (auto& [_, handlers] : mEngineHandlers)

@ -66,6 +66,8 @@ namespace LuaUtil
ScriptsContainer* mContainer; ScriptsContainer* mContainer;
std::string mPath; std::string mPath;
std::string toString() const;
}; };
using TimeUnit = ESM::LuaTimer::TimeUnit; using TimeUnit = ESM::LuaTimer::TimeUnit;
@ -73,7 +75,7 @@ namespace LuaUtil
ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix); ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix);
ScriptsContainer(const ScriptsContainer&) = delete; ScriptsContainer(const ScriptsContainer&) = delete;
ScriptsContainer(ScriptsContainer&&) = delete; ScriptsContainer(ScriptsContainer&&) = delete;
virtual ~ScriptsContainer() {} virtual ~ScriptsContainer() { removeAllScripts(); }
// Adds package that will be available (via `require`) for all scripts in the container. // Adds package that will be available (via `require`) for all scripts in the container.
// Automatically applies LuaUtil::makeReadOnly to the package. // Automatically applies LuaUtil::makeReadOnly to the package.

@ -48,5 +48,12 @@
-- @param #number delay -- @param #number delay
-- @param #function func -- @param #function func
-------------------------------------------------------------------------------
-- Wraps Lua function with `Callback` object that can be used in async API calls.
-- @function [parent=#async] callback
-- @param self
-- @param #function func
-- @return #Callback
return nil return nil

@ -32,5 +32,47 @@
-- @param openmw.query#Query query -- @param openmw.query#Query query
-- @return openmw.core#ObjectList -- @return openmw.core#ObjectList
-------------------------------------------------------------------------------
-- @type COLLISION_TYPE
-- @field [parent=#COLLISION_TYPE] #number World
-- @field [parent=#COLLISION_TYPE] #number Door
-- @field [parent=#COLLISION_TYPE] #number Actor
-- @field [parent=#COLLISION_TYPE] #number HeightMap
-- @field [parent=#COLLISION_TYPE] #number Projectile
-- @field [parent=#COLLISION_TYPE] #number Water
-- @field [parent=#COLLISION_TYPE] #number Default Used by deafult: World+Door+Actor+HeightMap
-------------------------------------------------------------------------------
-- Collision types that are used in `castRay`.
-- Several types can be combined with '+'.
-- @field [parent=#nearby] #COLLISION_TYPE COLLISION_TYPE
-------------------------------------------------------------------------------
-- Result of raycasing
-- @type RayCastingResult
-- @field [parent=#RayCastingResult] #boolean hit Is there a collision? (true/false)
-- @field [parent=#RayCastingResult] openmw.util#Vector3 hitPos Position of the collision point (nil if no collision)
-- @field [parent=#RayCastingResult] openmw.util#Vector3 hitNormal Normal to the surface in the collision point (nil if no collision)
-- @field [parent=#RayCastingResult] openmw.core#GameObject hitObject The object the ray has collided with (can be nil)
-------------------------------------------------------------------------------
-- Cast ray from one point to another and return the first collision.
-- @function [parent=#nearby] castRay
-- @param openmw.util#Vector3 from Start point of the ray.
-- @param openmw.util#Vector3 to End point of the ray.
-- @param #table options An optional table with additional optional arguments. Can contain:
-- `ignore` - an object to ignore (specify here the source of the ray);
-- `collisionType` - object types to work with (see @{openmw.nearby#COLLISION_TYPE}), several types can be combined with '+';
-- `radius` - the radius of the ray (zero by default). If not zero then castRay actually casts a sphere with given radius.
-- NOTE: currently `ignore` is not supported if `radius>0`.
-- @return #RayCastingResult
-- @usage if nearby.castRay(pointA, pointB).hit then print('obstacle between A and B') end
-- @usage local res = nearby.castRay(self.position, enemy.position, {ignore=self})
-- if res.hitObject and res.hitObject ~= enemy then obstacle = res.hitObject end
-- @usage local res = nearby.castRay(self.position, targetPos, {
-- collisionType=nearby.COLLISION_TYPE.HeightMap + nearby.COLLISION_TYPE.Water,
-- radius = 10,
-- })
return nil return nil

Loading…
Cancel
Save