From 0bd1c22e241f78d529019558bb544eb5deaf576b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 28 Aug 2021 10:47:57 +0200 Subject: [PATCH] Raycasting in Lua --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/luabindings.cpp | 32 +------- apps/openmw/mwlua/luabindings.hpp | 4 +- apps/openmw/mwlua/nearbybindings.cpp | 108 +++++++++++++++++++++++++++ files/lua_api/openmw/nearby.lua | 42 +++++++++++ 5 files changed, 155 insertions(+), 33 deletions(-) create mode 100644 apps/openmw/mwlua/nearbybindings.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 5605ff229e..82a91699a9 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,7 +58,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp actions object worldview userdataserializer eventqueue query luabindings localscripts objectbindings cellbindings asyncbindings settingsbindings - camerabindings uibindings inputbindings + camerabindings uibindings inputbindings nearbybindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index e37241d692..6526a18367 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -25,7 +25,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 5; + api["API_REVISION"] = 6; api["quit"] = [lua]() { std::string traceback = lua->sol()["debug"]["traceback"]().get(); @@ -110,36 +110,6 @@ namespace MWLua 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) { Queries::registerQueryBindings(context.mLua->sol()); diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index c58b556a3d..d34ff3d727 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -21,11 +21,13 @@ namespace MWLua sol::table initCorePackage(const Context&); sol::table initWorldPackage(const Context&); - sol::table initNearbyPackage(const Context&); sol::table initQueryPackage(const Context&); sol::table initFieldGroup(const Context&, const QueryFieldGroup&); + // Implemented in nearbybindings.cpp + sol::table initNearbyPackage(const Context&); + // Implemented in objectbindings.cpp void initObjectBindingsForLocalScripts(const Context&); void initObjectBindingsForGlobalScripts(const Context&); diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp new file mode 100644 index 0000000000..16e55bd6b1 --- /dev/null +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -0,0 +1,108 @@ +#include "luabindings.hpp" + +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwphysics/raycasting.hpp" + +#include "worldview.hpp" + +namespace sol +{ + template <> + struct is_automagical : 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 rayResult = + context.mLua->sol().new_usertype("RayCastingResult"); + rayResult["hit"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) { return r.mHit; }); + rayResult["hitPos"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional + { + if (r.mHit) + return r.mHitPos; + else + return sol::nullopt; + }); + rayResult["hitNormal"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional + { + if (r.mHit) + return r.mHitNormal; + else + return sol::nullopt; + }); + rayResult["hitObject"] = sol::readonly_property([worldView](const MWPhysics::RayCastingResult& r) -> sol::optional + { + 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 options) + { + MWWorld::Ptr ignore; + int collisionType = defaultCollisionType; + float radius = 0; + if (options) + { + sol::optional ignoreObj = options->get>("ignore"); + if (ignoreObj) ignore = ignoreObj->ptr(); + collisionType = options->get>("collisionType").value_or(collisionType); + radius = options->get>("radius").value_or(0); + } + const MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); + if (radius <= 0) + return rayCasting->castRay(from, to, ignore, std::vector(), 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); + } + }; + + 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); + } +} diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index c24d5e3d10..f1aeb07b24 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -32,5 +32,47 @@ -- @param openmw.query#Query query -- @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