1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-07-08 05:48:07 +02:00

HitTest class for use in area scanning

This commit is contained in:
Daniel Evans 2018-11-18 12:58:12 +00:00
parent 465f0ab056
commit 97609fcb5e
5 changed files with 197 additions and 0 deletions

View File

@ -49,6 +49,8 @@ set(RWENGINE_SOURCES
src/dynamics/CollisionInstance.cpp
src/dynamics/CollisionInstance.hpp
src/dynamics/HitTest.cpp
src/dynamics/HitTest.hpp
src/dynamics/RaycastCallbacks.hpp
src/engine/Animator.cpp

View File

@ -0,0 +1,64 @@
#include "HitTest.hpp"
#include <objects/GameObject.hpp>
#ifdef _MSC_VER
#pragma warning(disable : 4305 5033)
#endif
#include <btBulletDynamicsCommon.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
#ifdef _MSC_VER
#pragma warning(default : 4305 5033)
#endif
namespace {
HitTest::TestResult HitTestWorld(btDiscreteDynamicsWorld& world, btPairCachingGhostObject& tester)
{
world.addCollisionObject(&tester);
HitTest::TestResult result;
result.reserve(static_cast<unsigned long>(tester.getNumOverlappingObjects()));
for (auto i = 0; i < tester.getNumOverlappingObjects(); ++i)
{
auto overlapping = tester.getOverlappingObject(i);
HitTest::Hit hit{};
hit.body = overlapping;
if (auto object = static_cast<GameObject*>(overlapping->getUserPointer()))
{
hit.object = object;
}
result.push_back(hit);
}
world.removeCollisionObject(&tester);
return result;
}
HitTest::TestResult HitTestWithShape(btDiscreteDynamicsWorld& world, btCollisionShape& shape,
const glm::vec3& center, const glm::quat& rotation) {
btPairCachingGhostObject ghost{};
btTransform xform{};
xform.setOrigin({center.x, center.y, center.z});
xform.setRotation({rotation.x, rotation.y, rotation.z, rotation.w});
ghost.setWorldTransform(xform);
ghost.setCollisionShape(&shape);
return HitTestWorld(world, ghost);
}
} // namespace
HitTest::TestResult HitTest::sphereTest(const glm::vec3& center, float radius) {
btSphereShape sphere {radius};
return HitTestWithShape(_world, sphere, center, {});
}
HitTest::TestResult HitTest::boxTest(const glm::vec3 &center, const glm::vec3 &size, const glm::quat& rotation) {
btBoxShape box {{size.x, size.y, size.z}};
return HitTestWithShape(_world, box, center, rotation);
}

View File

@ -0,0 +1,39 @@
#ifndef _RWENGINE_HITTEST_HPP_
#define _RWENGINE_HITTEST_HPP_
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <vector>
#include <memory>
class btDiscreteDynamicsWorld;
class btCollisionObject;
class GameObject;
/**
* Utility for performing collision tests against the world.
*/
class HitTest {
public:
struct Hit {
btCollisionObject* body;
GameObject* object;
};
using TestResult = std::vector<Hit>;
explicit HitTest(btDiscreteDynamicsWorld& world)
: _world(world)
{}
~HitTest() = default;
TestResult sphereTest(const glm::vec3& center, float radius);
TestResult boxTest(const glm::vec3& center, const glm::vec3& size, const glm::quat& rotation = {1.f, 0.f, 0.f, 0.f});
private:
btDiscreteDynamicsWorld& _world;
};
#endif

View File

@ -11,6 +11,7 @@ set(TESTS
GameData
GameWorld
Garage
HitTest
Input
Items
Lifetime

91
tests/test_HitTest.cpp Normal file
View File

@ -0,0 +1,91 @@
#include <boost/test/unit_test.hpp>
#include "test_Globals.hpp"
#include <dynamics/HitTest.hpp>
#include <engine/GameWorld.hpp>
#include <dynamics/CollisionInstance.hpp>
#ifdef _MSC_VER
#pragma warning(disable : 4305 5033)
#endif
#include <btBulletDynamicsCommon.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
#ifdef _MSC_VER
#pragma warning(default : 4305 5033)
#endif
namespace {
struct HitTestFixture {
btDefaultCollisionConfiguration collisionConfig;
btCollisionDispatcher collisionDispatcher;
btDbvtBroadphase broadphase;
btGhostPairCallback overlappingPairCallback;
btSequentialImpulseConstraintSolver solver;
btDiscreteDynamicsWorld dynamicsWorld;
HitTest hitTest;
HitTestFixture()
: collisionDispatcher{&collisionConfig}
, dynamicsWorld{&collisionDispatcher, &broadphase, &solver, &collisionConfig}
, hitTest{dynamicsWorld}
{
broadphase.getOverlappingPairCache()->setInternalGhostPairCallback(
&overlappingPairCallback);
}
};
struct WithSphere : public HitTestFixture {
btSphereShape shape {0.5f};
std::unique_ptr<btRigidBody> target;
GameObject* object {reinterpret_cast<GameObject*>(0xDEADBEEF)};
WithSphere()
{
btDefaultMotionState ms;
btRigidBody::btRigidBodyConstructionInfo info {1.f, &ms, &shape};
target = std::make_unique<btRigidBody>(info);
target->setUserPointer(object);
dynamicsWorld.addRigidBody(target.get());
}
~WithSphere() {
dynamicsWorld.removeRigidBody(target.get());
}
};
}
BOOST_AUTO_TEST_SUITE(HitTestTests)
BOOST_FIXTURE_TEST_CASE(test_creation, HitTestFixture) {
HitTest test {dynamicsWorld};
}
BOOST_FIXTURE_TEST_CASE(sphereTest_returns_result, WithSphere) {
const auto result = hitTest.sphereTest({0.f, 0.f, 0.f}, 1.f);
BOOST_CHECK_EQUAL(result.size(), 1);
}
BOOST_FIXTURE_TEST_CASE(boxTest_returns_result, WithSphere) {
auto sphereBoundingBoxEdge = glm::vec3{ 0.f, 0.f, shape.getRadius() };
const auto result = hitTest.boxTest(sphereBoundingBoxEdge, {0.01f, 0.01f, 0.01f});
BOOST_CHECK_EQUAL(result.size(), 1);
}
BOOST_FIXTURE_TEST_CASE(non_overlapping_test_returns_nothing, WithSphere) {
auto sphereBoundingBoxEdge = glm::vec3{ shape.getRadius() };
const auto result = hitTest.boxTest(sphereBoundingBoxEdge * 2.f, {0.01f, 0.01f, 0.01f});
BOOST_CHECK(result.empty());
}
BOOST_FIXTURE_TEST_CASE(result_contains_body, WithSphere) {
const auto result = hitTest.sphereTest({0.f, 0.f, 0.f}, 1.f);
BOOST_ASSERT(result.size() == 1);
BOOST_CHECK_EQUAL(result[0].body, target.get());
}
BOOST_FIXTURE_TEST_CASE(test_result_contains_object, WithSphere) {
const auto result = hitTest.sphereTest({0.f, 0.f, 0.f}, 1.f);
BOOST_ASSERT(result.size() == 1);
BOOST_CHECK_EQUAL(result[0].object, object);
}
BOOST_AUTO_TEST_SUITE_END()