#include "luabindings.hpp" #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwphysics/raycasting.hpp" #include "luamanagerimp.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()); }); api["COLLISION_TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ {"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", MWPhysics::CollisionType_Default}, {"AnyPhysical", MWPhysics::CollisionType_AnyPhysical}, {"Camera", MWPhysics::CollisionType_CameraOnly}, {"VisualOnly", MWPhysics::CollisionType_VisualOnly}, })); api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { MWWorld::Ptr ignore; int collisionType = MWPhysics::CollisionType_Default; 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); } }; // TODO: async raycasting /*api["asyncCastRay"] = [luaManager = context.mLuaManager]( const Callback& luaCallback, const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { std::function callback = luaManager->wrapLuaCallback(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(), 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()}; api["containers"] = LObjectList{worldView->getContainersInScene()}; api["doors"] = LObjectList{worldView->getDoorsInScene()}; api["items"] = LObjectList{worldView->getItemsInScene()}; api["NAVIGATOR_FLAGS"] = LuaUtil::makeStrictReadOnly( context.mLua->tableFromPairs({ {"Walk", DetourNavigator::Flag_walk}, {"Swim", DetourNavigator::Flag_swim}, {"OpenDoor", DetourNavigator::Flag_openDoor}, {"UsePathgrid", DetourNavigator::Flag_usePathgrid}, })); api["COLLISION_SHAPE_TYPE"] = LuaUtil::makeStrictReadOnly( context.mLua->tableFromPairs({ {"Aabb", DetourNavigator::CollisionShapeType::Aabb}, {"RotatingBox", DetourNavigator::CollisionShapeType::RotatingBox}, {"Cylinder", DetourNavigator::CollisionShapeType::Cylinder}, })); api["FIND_PATH_STATUS"] = LuaUtil::makeStrictReadOnly( context.mLua->tableFromPairs({ {"Success", DetourNavigator::Status::Success}, {"PartialPath", DetourNavigator::Status::PartialPath}, {"NavMeshNotFound", DetourNavigator::Status::NavMeshNotFound}, {"StartPolygonNotFound", DetourNavigator::Status::StartPolygonNotFound}, {"EndPolygonNotFound", DetourNavigator::Status::EndPolygonNotFound}, {"MoveAlongSurfaceFailed", DetourNavigator::Status::MoveAlongSurfaceFailed}, {"FindPathOverPolygonsFailed", DetourNavigator::Status::FindPathOverPolygonsFailed}, {"InitNavMeshQueryFailed", DetourNavigator::Status::InitNavMeshQueryFailed}, })); static const DetourNavigator::AgentBounds defaultAgentBounds { DetourNavigator::toCollisionShapeType(Settings::Manager::getInt("actor collision shape type", "Game")), Settings::Manager::getVector3("default actor pathfind half extents", "Game"), }; static const float defaultStepSize = 2 * std::max(defaultAgentBounds.mHalfExtents.x(), defaultAgentBounds.mHalfExtents.y()); static constexpr DetourNavigator::Flags defaultIncludeFlags = DetourNavigator::Flag_walk | DetourNavigator::Flag_swim | DetourNavigator::Flag_openDoor | DetourNavigator::Flag_usePathgrid; api["findPath"] = [] (const osg::Vec3f& source, const osg::Vec3f& destination, const sol::optional& options) { DetourNavigator::AgentBounds agentBounds = defaultAgentBounds; float stepSize = defaultStepSize; DetourNavigator::Flags includeFlags = defaultIncludeFlags; DetourNavigator::AreaCosts areaCosts {}; float destinationTolerance = 1; if (options.has_value()) { if (const auto& t = options->get>("agentBounds")) { if (const auto& v = t->get>("shapeType")) agentBounds.mShapeType = *v; if (const auto& v = t->get>("halfExtents")) { agentBounds.mHalfExtents = *v; stepSize = 2 * std::max(v->x(), v->y()); } } if (const auto& v = options->get>("stepSize")) stepSize = *v; if (const auto& v = options->get>("includeFlags")) includeFlags = *v; if (const auto& t = options->get>("areaCosts")) { if (const auto& v = t->get>("water")) areaCosts.mWater = *v; if (const auto& v = t->get>("door")) areaCosts.mDoor = *v; if (const auto& v = t->get>("pathgrid")) areaCosts.mPathgrid = *v; if (const auto& v = t->get>("ground")) areaCosts.mGround = *v; } if (const auto& v = options->get>("destinationTolerance")) destinationTolerance = *v; } std::vector result; const DetourNavigator::Status status = DetourNavigator::findPath( *MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, stepSize, source, destination, includeFlags, areaCosts, destinationTolerance, std::back_inserter(result)); return std::make_tuple(status, std::move(result)); }; api["findRandomPointAroundCircle"] = [] (const osg::Vec3f& position, float maxRadius, const sol::optional& options) { DetourNavigator::AgentBounds agentBounds = defaultAgentBounds; DetourNavigator::Flags includeFlags = defaultIncludeFlags; if (options.has_value()) { if (const auto& t = options->get>("agentBounds")) { if (const auto& v = t->get>("shapeType")) agentBounds.mShapeType = *v; if (const auto& v = t->get>("halfExtents")) agentBounds.mHalfExtents = *v; } if (const auto& v = options->get>("includeFlags")) includeFlags = *v; } constexpr auto getRandom = [] { return Misc::Rng::rollProbability(MWBase::Environment::get().getWorld()->getPrng()); }; return DetourNavigator::findRandomPointAroundCircle(*MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, position, maxRadius, includeFlags, getRandom); }; api["castNavigationRay"] = [] (const osg::Vec3f& from, const osg::Vec3f& to, const sol::optional& options) { DetourNavigator::AgentBounds agentBounds = defaultAgentBounds; DetourNavigator::Flags includeFlags = defaultIncludeFlags; if (options.has_value()) { if (const auto& t = options->get>("agentBounds")) { if (const auto& v = t->get>("shapeType")) agentBounds.mShapeType = *v; if (const auto& v = t->get>("halfExtents")) agentBounds.mHalfExtents = *v; } if (const auto& v = options->get>("includeFlags")) includeFlags = *v; } return DetourNavigator::raycast(*MWBase::Environment::get().getWorld()->getNavigator(), agentBounds, from, to, includeFlags); }; return LuaUtil::makeReadOnly(api); } }