From a7b7f99d72b54f1d1b26114697cc6190f68b2dbd Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Thu, 18 May 2023 11:10:10 -0700 Subject: [PATCH] Expose game object's bounding box in lua api --- apps/openmw/mwlua/objectbindings.cpp | 13 ++++ components/CMakeLists.txt | 1 + components/lua/shapes/box.cpp | 45 +++++++++++++ components/lua/shapes/box.hpp | 31 +++++++++ components/lua/utilpackage.cpp | 31 +++++++++ .../sceneutil/cullsafeboundsvisitor.hpp | 64 +++++++++++++++++++ files/lua_api/openmw/core.lua | 5 ++ files/lua_api/openmw/util.lua | 23 +++++++ 8 files changed, 213 insertions(+) create mode 100644 components/lua/shapes/box.cpp create mode 100644 components/lua/shapes/box.hpp create mode 100644 components/sceneutil/cullsafeboundsvisitor.hpp diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 6ab6776d7a..893618cdf8 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" @@ -10,6 +13,8 @@ #include "../mwworld/player.hpp" #include "../mwworld/scene.hpp" +#include "../mwrender/vismask.hpp" + #include "../mwmechanics/creaturestats.hpp" #include "luaevents.hpp" @@ -132,6 +137,14 @@ namespace MWLua [](const ObjectT& o) -> osg::Vec3f { return o.ptr().getRefData().getPosition().asVec3(); }); objectT["rotation"] = sol::readonly_property( [](const ObjectT& o) -> osg::Vec3f { return o.ptr().getRefData().getPosition().asRotationVec3(); }); + objectT["getBoundingBox"] = [](const ObjectT& o) { + const MWWorld::Ptr& ptr = o.ptr(); + SceneUtil::CullSafeBoundsVisitor computeBounds; + computeBounds.setTraversalMask(~(MWRender::Mask_ParticleSystem | MWRender::Mask_Effect)); + ptr.getRefData().getBaseNode()->accept(computeBounds); + osg::BoundingBox bb = computeBounds.mBoundingBox; + return LuaUtil::Box{ bb.center(), bb._max - bb.center() }; + }; objectT["type"] = sol::readonly_property( [types = getTypeToPackageTable(context.mLua->sol())]( diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 1140ddcf2e..9ae1c3dc33 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -35,6 +35,7 @@ endif (GIT_CHECKOUT) add_component_dir (lua luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage + shapes/box ) add_component_dir (l10n diff --git a/components/lua/shapes/box.cpp b/components/lua/shapes/box.cpp new file mode 100644 index 0000000000..896edb53f5 --- /dev/null +++ b/components/lua/shapes/box.cpp @@ -0,0 +1,45 @@ +#include "box.hpp" + +namespace LuaUtil +{ + Box::Box(const osg::Vec3f& center, const osg::Vec3f& halfSize, const osg::Quat& rotation) + : mCenter(center) + , mHalfSize(halfSize) + , mRotation(rotation) + { + } + + Box::Box(const osg::Matrix& transform) + { + osg::Quat _; + transform.decompose(mCenter, mRotation, mHalfSize, _); + } + + std::array Box::vertices() const + { + return { + mCenter + mRotation * osg::Vec3f(-mHalfSize.x(), -mHalfSize.y(), -mHalfSize.z()), + mCenter + mRotation * osg::Vec3f(mHalfSize.x(), -mHalfSize.y(), -mHalfSize.z()), + mCenter + mRotation * osg::Vec3f(mHalfSize.x(), mHalfSize.y(), -mHalfSize.z()), + mCenter + mRotation * osg::Vec3f(-mHalfSize.x(), mHalfSize.y(), -mHalfSize.z()), + mCenter + mRotation * osg::Vec3f(-mHalfSize.x(), -mHalfSize.y(), mHalfSize.z()), + mCenter + mRotation * osg::Vec3f(mHalfSize.x(), -mHalfSize.y(), mHalfSize.z()), + mCenter + mRotation * osg::Vec3f(mHalfSize.x(), mHalfSize.y(), mHalfSize.z()), + mCenter + mRotation * osg::Vec3f(-mHalfSize.x(), mHalfSize.y(), mHalfSize.z()), + }; + } + + osg::Matrix Box::asTransform() const + { + osg::Matrix transform; + transform.preMultTranslate(mCenter); + transform.preMultRotate(mRotation); + transform.preMultScale(mHalfSize); + return transform; + } + + bool Box::operator==(const Box& other) const + { + return mCenter == other.mCenter && mHalfSize == other.mHalfSize && mRotation == other.mRotation; + } +} \ No newline at end of file diff --git a/components/lua/shapes/box.hpp b/components/lua/shapes/box.hpp new file mode 100644 index 0000000000..0e502e1159 --- /dev/null +++ b/components/lua/shapes/box.hpp @@ -0,0 +1,31 @@ +#ifndef COMPONENTS_LUA_SHAPES_BOX_H +#define COMPONENTS_LUA_SHAPES_BOX_H + +#include + +#include +#include +#include + +namespace LuaUtil +{ + class Box + { + public: + Box(const osg::Vec3f& center, const osg::Vec3f& halfSize, const osg::Quat& rotation = osg::Quat()); + Box(const osg::Matrix& transform); + + std::array vertices() const; + + osg::Matrix asTransform() const; + + // TODO: Add `contains` and `intersects` methods + + bool operator==(const Box& other) const; + + osg::Vec3f mCenter; + osg::Vec3f mHalfSize; + osg::Quat mRotation; + }; +} +#endif // COMPONENTS_LUA_SHAPES_BOX_H diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index 932eb21d50..5fbd1fe8b0 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -9,6 +9,8 @@ #include "luastate.hpp" +#include "shapes/box.hpp" + namespace sol { template <> @@ -40,6 +42,11 @@ namespace sol struct is_automagical : std::false_type { }; + + template <> + struct is_automagical : std::false_type + { + }; } namespace LuaUtil @@ -121,6 +128,30 @@ namespace LuaUtil vec4Type["w"] = sol::readonly_property([](const Vec4& v) -> float { return v.w(); }); addVectorMethods(vec4Type); + // Lua bindings for Box + util["box"] = sol::overload([](const Vec3& center, const Vec3& halfSize) { return Box(center, halfSize); }, + [](const TransformM& transform) { return Box(transform.mM); }); + sol::usertype boxType = lua.new_usertype("Box"); + boxType["center"] = sol::readonly_property([](const Box& b) { return b.mCenter; }); + boxType["halfSize"] = sol::readonly_property([](const Box& b) { return b.mHalfSize; }); + boxType["transform"] = sol::readonly_property([](const Box& b) { return TransformM{ b.asTransform() }; }); + boxType["vertices"] = sol::readonly_property([lua](const Box& b) { + sol::table table(lua, sol::create); + const auto vertices = b.vertices(); + for (size_t i = 0; i < vertices.size(); ++i) + table[i + 1] = vertices[i]; + return table; + }); + boxType[sol::meta_function::equal_to] = [](const Box& a, const Box& b) { return a == b; }; + boxType[sol::meta_function::to_string] = [](const Box& b) { + std::stringstream ss; + ss << "Box{ "; + ss << "center(" << b.mCenter.x() << ", " << b.mCenter.y() << ", " << b.mCenter.z() << ") "; + ss << "halfSize(" << b.mHalfSize.x() << ", " << b.mHalfSize.y() << ", " << b.mHalfSize.z() << ")"; + ss << " }"; + return ss.str(); + }; + // Lua bindings for Color sol::usertype colorType = lua.new_usertype("Color"); colorType["r"] = sol::readonly_property([](const Misc::Color& c) { return c.r(); }); diff --git a/components/sceneutil/cullsafeboundsvisitor.hpp b/components/sceneutil/cullsafeboundsvisitor.hpp new file mode 100644 index 0000000000..02fe21b6bd --- /dev/null +++ b/components/sceneutil/cullsafeboundsvisitor.hpp @@ -0,0 +1,64 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_CULLSAFEBOUNDSVISITOR_H +#define OPENMW_COMPONENTS_SCENEUTIL_CULLSAFEBOUNDSVISITOR_H + +#include + +#include +#include +#include + +#include +#include + +namespace SceneUtil +{ + // Computes local bounding box of a node without dirtying itself or any of its children + struct CullSafeBoundsVisitor : osg::NodeVisitor + { + CullSafeBoundsVisitor(osg::NodeVisitor::TraversalMode traversalMode = TRAVERSE_ALL_CHILDREN) + : osg::NodeVisitor(traversalMode) + { + } + + void reset() override + { + mMatrixStack.clear(); + mBoundingBox.init(); + } + + void apply(osg::Drawable& drawable) + { + osg::BoundingBox bbox = drawable.getInitialBound(); + bbox.expandBy(drawable.computeBoundingBox()); + applyBoundingBox(bbox); + } + + void apply(osg::Transform& transform) + { + osg::Matrix matrix; + if (!mMatrixStack.empty()) + matrix = mMatrixStack.back(); + + mMatrixStack.push_back(matrix); + traverse(transform); + mMatrixStack.pop_back(); + } + + void applyBoundingBox(const osg::BoundingBox& bbox) + { + if (mMatrixStack.empty()) + { + mBoundingBox.expandBy(bbox); + } + else if (bbox.valid()) + { + for (int i = 0; i < 8; ++i) + mBoundingBox.expandBy(bbox.corner(i) * mMatrixStack.back()); + } + } + + osg::BoundingBox mBoundingBox; + std::vector mMatrixStack; + }; +} +#endif // OPENMW_COMPONENTS_SCENEUTIL_CULLSAFEBOUNDSVISITOR_H \ No newline at end of file diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 0312ba8706..61104fd667 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -203,6 +203,11 @@ -- @usage -- take 50 coins from `money` and put to the container `cont` -- money:split(50):moveInto(types.Container.content(cont)) +--- +-- The axis aligned bounding box in local coordinates. +-- @function [parent=#GameObject] getBoundingBox +-- @param self +-- @return openmw.util#Box --- -- List of GameObjects. Implements [iterables#List](iterables.html#List) of #GameObject diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index a868b0f522..b8c50bba2a 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -397,6 +397,29 @@ -- @param #Vector4 v -- @return #Vector4 +--- +-- Immutable box. +-- @type Box +-- @field #Vector3 center The center of the box +-- @field #Vector3 halfSize The half sizes of the box along each axis +-- @field #Transform transform A transformation which encapsulates the boxes center pointer (translation), half sizes (scale), and rotation. +-- @field #table vertices Table of the 8 vertices which comprise the box, taking rotation into account + +--- +-- Creates a new Box with a given center and half sizes. Boxes are immutable and can not be changed after creation. +-- @function [parent=#util] Box +-- @param #Vector3 center +-- @param #Vector3 halfSize in each dimension (x, y, z) +-- @return #Box + +--- +-- Creates a new Box from a given transformation. Boxes are immutable and can not be changed after creation. +-- @function [parent=#util] Box +-- @param #Transform transform A transformation which encapsulates the boxes center pointer (translation), half sizes (scale), and rotation. +-- @return #Box +-- @usage +-- -- Creates a 1x1x1 length box centered at the origin +-- util.box(util.transform.scale(util.vector3(0.5, 0.5, 0.5))) --- -- Color in RGBA format. All of the component values are in the range [0, 1].