retrieval of collision events

This commit is contained in:
ecker 2026-05-23 15:34:36 -05:00
parent 52b972167a
commit 3a2dfb0833
10 changed files with 299 additions and 194 deletions

View File

@ -75,166 +75,33 @@ end
local useDistance = 6
local pullDistance = useDistance * 4
-- on tick
ent:bind( "tick", function(self)
local inControl = scene:globalFindByName("Gui: Menu"):uid() == 0
local keyE = inputs.key("E") or inputs.key("R_Y")
local keyF = inputs.key("F")
local mouse1 = inputs.key("Mouse1") or inputs.key("L_TRIGGER");
local mouse2 = inputs.key("Mouse2") or inputs.key("R_TRIGGER");
local mouse3 = inputs.key("Mouse3");
local wheel = inputs.analog("MouseWheel")
if not inControl then
keyE = false
keyF = false
mouse1 = false
mouse2 = false
mouse3 = false
wheel = 0
end
-- eye transform
local flattenedTransform = nil
if fixedCamera then
flattenedTransform = transform:flatten()
else
flattenedTransform = cameraTransform:flatten()
end
-- toggle flashlight
local function tickFlashlight( transform, inputs )
-- update light position
if light.enabled then
local center = flattenedTransform.position
local direction = flattenedTransform.forward * 8
local center = transform.position
local direction = transform.forward * 8
local offset = 0.25
local _, depth = physicsBody:rayCast( center, direction )
if depth >= 0.5 then
depth = 0.5
elseif depth < 0 then
depth = 0.5
end
local _, depth = physicsBody:rayCast(center, direction)
depth = math.clamp(depth, 0, 0.5)
light.transform.position = center + direction * (depth - offset)
end
if timers.flashlight:elapsed() > 0.5 and keyF then
-- toggle
if timers.flashlight:elapsed() > 0.5 and inputs["F"] then
timers.flashlight:reset()
if light.metadata.power ~= light.power then
light.metadata.power = light.power
light.enabled = true
else
light.metadata.power = 0
light.enabled = false
end
light.enabled = (light.metadata.power ~= light.power)
light.metadata.power = light.enabled and light.power or 0
playSound("flashlight")
end
end
-- fire use ray
if timers.use:elapsed() > 0.5 and keyE and inControl then
timers.use:reset()
local center = flattenedTransform.position
local direction = flattenedTransform.forward * useDistance
local prop, depth = physicsBody:rayCast( center, direction )
local payload = {
user = ent:uid(),
uid = prop and prop:uid() or 0,
depth = depth,
}
if prop then
prop:lazyCallHook("entity:Use.%UID%", payload)
end
ent:lazyCallHook("entity:Use.%UID%", payload)
end
-- update HOLP
if heldObject.uid == 0 then
if mouse2 then
--[[
local center = transform.position + cameraTransform.position
local direction = transform.forward + Vector3f( 0, cameraTransform.forward.y, 0 )
direction = direction:normalize() * 4
]]
local center = flattenedTransform.position
local direction = flattenedTransform.forward * pullDistance
local prop, depth = physicsBody:rayCast( center, direction )
if depth >= 0 and prop and not string.matched( prop:name(), "/^worldspawn/" ) then
local heldObjectTransform = prop:getComponent("Transform")
local heldObjectPhysicsBody = prop:getComponent("PhysicsBody")
local strength = 500
local distanceSquared = (heldObjectTransform.position - flattenedTransform.position):magnitude()
heldObjectPhysicsBody:applyImpulse( flattenedTransform.forward * -heldObjectPhysicsBody:getMass() * strength / distanceSquared )
if timers.physcannon:elapsed() > 1.0 then
timers.physcannon:reset()
playSound("phys_tooHeavy")
end
end
end
else
if wheel ~= 0 then
heldObject.distance = heldObject.distance + (wheel / 120 * heldObject.scrollSpeed) * time.delta()
end
if mouse3 then
heldObject.rotate = not heldObject.rotate
end
local prop = entities.get( heldObject.uid )
local heldObjectTransform = prop:getComponent("Transform")
local heldObjectPhysicsBody = prop:getComponent("PhysicsBody")
if mouse1 and timers.physcannon:elapsed() > 0.5 then
timers.physcannon:reset()
heldObject.uid = 0
heldObjectPhysicsBody:enableGravity(true)
heldObjectPhysicsBody:applyImpulse( flattenedTransform.forward * heldObjectPhysicsBody:getMass() * 50 )
playSound("phys_launch"..math.random(1,4))
else
if heldObject.rotate then
heldObjectTransform.orientation = Quaternion.lookAt( (heldObjectTransform.position - flattenedTransform.position):normalize(), transform.up )
end
local forward = flattenedTransform.forward * heldObject.distance --flattenedTransform.orientation:rotate( Vector3f(0,0,1) )
if heldObject.smoothSpeed ~= 0 then
local heldObjectFlattened = heldObjectTransform:flatten()
local target = flattenedTransform.position + forward
local offset = target - heldObjectFlattened.position
local stiffness = 15.0
local damping = 2.0
local currentVelocity = heldObjectPhysicsBody:getVelocity()
local mass = heldObjectPhysicsBody:getMass()
local springForce = offset * stiffness
local dampingForce = currentVelocity * -damping
heldObjectPhysicsBody:applyImpulse((springForce + dampingForce) * mass * time.delta())
else
heldObjectTransform.position = flattenedTransform.position + forward
end
end
end
end )
-- on use
ent:addHook( "entity:Use.%UID%", function( payload )
if payload.user ~= ent:uid() then return end
local function onUse( payload )
local validUse = false
-- not currently holding anything, and hit something
if heldObject.uid == 0 and payload.depth > 0 then
local prop = entities.get( payload.uid )
local propMetadata = prop:getComponent("Metadata")
-- entity is holdable, pick it up
if propMetadata["holdable"] then
validUse = true
local heldObjectTransform = prop:getComponent("Transform")
@ -246,11 +113,10 @@ ent:addHook( "entity:Use.%UID%", function( payload )
local heldObjectPhysicsBody = prop:getComponent("PhysicsBody")
heldObjectPhysicsBody:enableGravity(false)
print( prop:name() )
else
validUse = not string.matched( prop:name(), "/^worldspawn/" )
end
-- currently holding something, drop it
elseif heldObject.uid ~= 0 then
validUse = true
local prop = entities.get( heldObject.uid )
@ -263,9 +129,149 @@ ent:addHook( "entity:Use.%UID%", function( payload )
heldObject.momentum = Vector3f(0,0,0)
end
if validUse then
playSound("select")
else
playSound("deny")
playSound(validUse and "select" or "deny")
end
local function tickUse( transform, inputs )
-- trigger use
if timers.use:elapsed() > 0.5 and inputs["E"] then
timers.use:reset()
local center = transform.position
local direction = transform.forward * useDistance
local prop, depth = physicsBody:rayCast(center, direction)
local payload = {
user = ent:uid(),
uid = prop and prop:uid() or 0,
depth = depth,
}
if prop then prop:lazyCallHook("entity:Use.%UID%", payload) end
ent:lazyCallHook("entity:Use.%UID%", payload)
end
end
local function tickGravGun( transform, inputs )
-- not holding anything
if heldObject.uid == 0 then
-- try and launch object in sights
if inputs["mouse2"] then
local center = transform.position
local direction = transform.forward * pullDistance
local prop, depth = physicsBody:rayCast( center, direction )
if depth >= 0 and prop and not string.matched( prop:name(), "/^worldspawn/" ) then
local heldObjectTransform = prop:getComponent("Transform")
local heldObjectPhysicsBody = prop:getComponent("PhysicsBody")
local strength = 500
local distanceSquared = (heldObjectTransform.position - transform.position):magnitude()
heldObjectPhysicsBody:applyImpulse( transform.forward * -heldObjectPhysicsBody:getMass() * strength / distanceSquared )
if timers.physcannon:elapsed() > 1.0 then
timers.physcannon:reset()
playSound("phys_tooHeavy")
end
end
end
-- holding something
else
-- adjust hold distance
if inputs["wheel"] ~= 0 then
heldObject.distance = heldObject.distance + (inputs["wheel"] / 120 * heldObject.scrollSpeed) * time.delta()
end
-- update rotation mode
if inputs["mouse3"] then
heldObject.rotate = not heldObject.rotate
end
local prop = entities.get( heldObject.uid )
local heldObjectTransform = prop:getComponent("Transform")
local heldObjectPhysicsBody = prop:getComponent("PhysicsBody")
-- launch held object
if inputs["mouse1"] and timers.physcannon:elapsed() > 0.5 then
timers.physcannon:reset()
heldObject.uid = 0
heldObjectPhysicsBody:enableGravity(true)
heldObjectPhysicsBody:applyImpulse( transform.forward * heldObjectPhysicsBody:getMass() * 50 )
playSound("phys_launch"..math.random(1,4))
else
-- update rotation
if heldObject.rotate then
heldObjectTransform.orientation = Quaternion.lookAt( (heldObjectTransform.position - transform.position):normalize(), transform.up )
end
-- move held object
local forward = transform.forward * heldObject.distance
if heldObject.smoothSpeed ~= 0 then
local heldObjectFlattened = heldObjectTransform:flatten()
local target = transform.position + forward
local offset = target - heldObjectFlattened.position
local stiffness = 15.0
local damping = 2.0
local currentVelocity = heldObjectPhysicsBody:getVelocity()
local mass = heldObjectPhysicsBody:getMass()
local springForce = offset * stiffness
local dampingForce = currentVelocity * -damping
heldObjectPhysicsBody:applyImpulse((springForce + dampingForce) * mass * time.delta())
else
heldObjectTransform.position = transform.position + forward
end
end
end
end
-- on tick
ent:bind( "tick", function(self)
local inControl = scene:globalFindByName("Gui: Menu"):uid() == 0
local inputs = {
E = inputs.key("E") or inputs.key("R_Y"),
F = inputs.key("F"),
mouse1 = inputs.key("Mouse1") or inputs.key("L_TRIGGER"),
mouse2 = inputs.key("Mouse2") or inputs.key("R_TRIGGER"),
mouse3 = inputs.key("Mouse3"),
wheel = inputs.analog("MouseWheel"),
}
if not inControl then
inputs["E"] = false
inputs["F"] = false
inputs["mouse1"] = false
inputs["mouse2"] = false
inputs["mouse3"] = false
inputs["wheel"] = 0
end
-- eye transform
local flattenedTransform = fixedCamera and transform:flatten() or cameraTransform:flatten()
-- update flashlight
tickFlashlight( flattenedTransform, inputs )
-- update use
tickUse( flattenedTransform, inputs )
-- update HOLP
tickGravGun( flattenedTransform, inputs )
-- get collision events
--[[
local collisionEvents = physicsBody:getCollisionEvents()
for i, event in ipairs(collisionEvents) do
print( event.state, event.a, event.b, event.point, event.normal, event.impulse )
end
]]
end )
-- on use
ent:addHook( "entity:Use.%UID%", function( payload )
if payload.user ~= ent:uid() then return end
onUse( payload )
end )

View File

@ -14,6 +14,7 @@
namespace impl {
void solveConstraint( pod::Constraint& constraint, float dt );
void solveConstraints( uf::stl::vector<pod::Constraint*>& constraint, float dt );
}
namespace uf {

View File

@ -4,19 +4,21 @@
namespace impl {
void bindManifold( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold& manifold, float dt = 0 );
bool generateContactsGjk( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold& manifold, float dt );
bool generateContacts( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold& manifold, float dt );
void computeLocalContacts( pod::Manifold& manifold );
bool generateManifoldGjk( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold& manifold, float dt );
bool generateManifold( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold& manifold, float dt );
void computeLocalManifold( pod::Manifold& manifold );
bool similarContact( const pod::Contact& a, const pod::Contact& b, float distSqThreshold = 1.0e-2f, float normThreshold = 0.9f );
void reduceContacts( pod::Manifold& manifold );
void mergeContacts( pod::Manifold& manifold );
void retrieveContacts( pod::Manifold& current, const pod::Manifold& previous, float distanceThreshold = 0.1f, float separationThreshold = 0.1f, float decay = 0.85f );
void reduceManifold( pod::Manifold& manifold );
void mergeManifold( pod::Manifold& manifold );
void retrieveManifold( pod::Manifold& current, const pod::Manifold& previous, float distanceThreshold = 0.1f, float separationThreshold = 0.1f, float decay = 0.85f );
void prepareManifoldCache( uf::stl::unordered_map<size_t, pod::Manifold>& cache, const uf::stl::vector<pod::Island>& islands, const uf::stl::vector<pod::PhysicsBody*>& bodies );
void updateManifoldCache( const uf::stl::vector<pod::Manifold>& manifolds, uf::stl::unordered_map<size_t, pod::Manifold>& cache );
void pruneManifoldCache( uf::stl::unordered_map<size_t, pod::Manifold>& cache );
void warmupContacts( pod::PhysicsBody& a, pod::PhysicsBody& b, const pod::Contact& c, float dt );
void warmupContact( pod::PhysicsBody& a, pod::PhysicsBody& b, const pod::Contact& c, float dt );
void warmupManifold( pod::PhysicsBody& a, pod::PhysicsBody& b, const pod::Manifold& manifold, float dt );
void resolveManifold( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold& manifold, float dt );
void solveContacts( uf::stl::vector<pod::Manifold>& manifolds, float dt );
void solveManifold( uf::stl::vector<pod::Manifold>& manifolds, float dt );
void dispatchManifold( pod::Manifold& manifold, pod::CollisionEvent::events_t& events, pod::CollisionEvent::map_t& active, const pod::CollisionEvent::map_t& previous );
}

View File

@ -55,6 +55,8 @@ namespace uf {
void UF_API applyRotation( pod::PhysicsBody& body, const pod::Vector3f& axis, float angle );
pod::World& UF_API getWorld();
const pod::CollisionEvent::events_t& UF_API getCollisionEvents( const pod::World& world );
pod::CollisionEvent::events_t UF_API getCollisionEvents( const pod::PhysicsBody& body );
pod::PhysicsBody& UF_API create( uf::Object&, float mass = 0.0f, const pod::Vector3f& = {} );
pod::PhysicsBody& UF_API create( pod::World&, uf::Object&, float mass = 0.0f, const pod::Vector3f& = {} );

View File

@ -376,6 +376,25 @@ namespace pod {
static constexpr float linearSleepEpsilon = 0.2f; // m/s
static constexpr float angularSleepEpsilon = 0.2f; // rad/s
};
enum class CollisionState {
ENTER,
SUSTAIN,
EXIT
};
struct CollisionEvent {
typedef std::pair<pod::PhysicsBody*, pod::PhysicsBody*> pairs_t;
typedef uf::stl::vector<pod::CollisionEvent> events_t;
typedef uf::stl::unordered_map<uint64_t, pod::CollisionEvent::pairs_t> map_t;
pod::CollisionState state = {};
pod::PhysicsBody* a = NULL;
pod::PhysicsBody* b = NULL;
pod::Vector3f point = {};
pod::Vector3f normal = {};
float impulse = 0;
};
}
namespace pod {
@ -473,6 +492,9 @@ namespace pod {
pod::Vector3f gravity = { 0, -9.81f, 0 };
pod::BVH dynamicBvh;
pod::BVH staticBvh;
pod::CollisionEvent::events_t collisionEvents;
pod::CollisionEvent::map_t activeCollisions;
};
}

View File

@ -573,6 +573,16 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
}
}
#if 0
{
int count = 0;
auto events = uf::physics::getCollisionEvents( physicsBody );
for ( const auto& event : events ) {
// do something
}
UF_MSG_DEBUG("count={}", events.size());
}
#endif
#if UF_USE_OPENAL
if ( stats.floored && !stats.noclipped ) {
if ( stats.walking ) {

View File

@ -36,6 +36,11 @@ namespace binds {
return uf::transform::flatten( t );
}
auto getCollisionEvents( const pod::PhysicsBody& body ) {
auto events = uf::physics::getCollisionEvents( body );
return sol::as_table( events );
}
std::tuple<uf::Object*, float> rayCast( pod::PhysicsBody& self, const pod::Vector3f& center, const pod::Vector3f& direction ) {
pod::RayQuery query = uf::physics::rayCast( pod::Ray{center, uf::vector::normalize( direction )}, self, uf::vector::norm( direction ) );
uf::Object* object = query.hit ? query.body->object : NULL;
@ -102,6 +107,14 @@ namespace binds {
#include <uf/ext/lua/component.h>
UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(pod::CollisionEvent,
UF_LUA_REGISTER_USERTYPE_MEMBER(pod::CollisionEvent::state),
UF_LUA_REGISTER_USERTYPE_MEMBER(pod::CollisionEvent::a),
UF_LUA_REGISTER_USERTYPE_MEMBER(pod::CollisionEvent::b),
UF_LUA_REGISTER_USERTYPE_MEMBER(pod::CollisionEvent::point),
UF_LUA_REGISTER_USERTYPE_MEMBER(pod::CollisionEvent::normal),
UF_LUA_REGISTER_USERTYPE_MEMBER(pod::CollisionEvent::impulse)
)
UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(pod::AABB,
sol::call_constructor, sol::initializers(
[]( pod::AABB& self ) {
@ -206,6 +219,7 @@ UF_LUA_REGISTER_USERTYPE_AND_COMPONENT(pod::PhysicsBody,
UF_LUA_REGISTER_USERTYPE_DEFINE( applyImpulse, UF_LUA_C_FUN(uf::physics::applyImpulse) ),
UF_LUA_REGISTER_USERTYPE_DEFINE( applyRotation, UF_LUA_C_FUN(::binds::body::applyRotation) ),
UF_LUA_REGISTER_USERTYPE_DEFINE( getTransform, UF_LUA_C_FUN(::binds::body::getTransform) ),
UF_LUA_REGISTER_USERTYPE_DEFINE( getCollisionEvents, UF_LUA_C_FUN(::binds::body::getCollisionEvents) ),
UF_LUA_REGISTER_USERTYPE_DEFINE( setGravity, UF_LUA_C_FUN(::binds::body::setGravity) ),
UF_LUA_REGISTER_USERTYPE_DEFINE( enableGravity, UF_LUA_C_FUN(::binds::body::enableGravity) ),
UF_LUA_REGISTER_USERTYPE_DEFINE( rayCast, UF_LUA_C_FUN(::binds::body::rayCast) ),

View File

@ -1,6 +1,11 @@
#include <uf/utils/math/physics/common.h>
#include <uf/utils/math/physics/constraints.h>
void impl::solveConstraints( uf::stl::vector<pod::Constraint*>& constraints, float dt ) {
for ( uint32_t i = 0; i < uf::physics::settings.solverIterations; ++i ) {
for ( auto* constraint : constraints ) impl::solveConstraint( *constraint, dt );
}
}
void impl::solveConstraint( pod::Constraint& constraint, float dt ) {
switch ( constraint.type ) {
case pod::ConstraintType::BALL_AND_SOCKET: {

View File

@ -11,7 +11,7 @@ void impl::bindManifold( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold
manifold.points.reserve(4);
}
bool impl::generateContactsGjk( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold& manifold, float dt ) {
bool impl::generateManifoldGjk( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold& manifold, float dt ) {
impl::bindManifold( a, b, manifold, dt );
pod::Simplex simplex;
@ -23,11 +23,11 @@ bool impl::generateContactsGjk( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::M
return true;
}
bool impl::generateContacts( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold& manifold, float dt ) {
bool impl::generateManifold( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold& manifold, float dt ) {
bool useGjk = uf::physics::settings.useGjk;
if ( a.collider.type == pod::ShapeType::MESH || b.collider.type == pod::ShapeType::MESH ) useGjk = false;
//if ( a.collider.type == pod::ShapeType::PLANE || b.collider.type == pod::ShapeType::PLANE ) useGjk = false;
if ( useGjk ) return generateContactsGjk( a, b, manifold, dt );
if ( useGjk ) return generateManifoldGjk( a, b, manifold, dt );
impl::bindManifold( a, b, manifold, dt );
#define CHECK_CONTACT( A, B, fun )\
@ -94,7 +94,7 @@ bool impl::generateContacts( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Mani
return false;
}
void impl::computeLocalContacts( pod::Manifold& manifold ) {
void impl::computeLocalManifold( pod::Manifold& manifold ) {
if ( manifold.points.empty() ) return;
auto& a = *manifold.a;
@ -113,7 +113,7 @@ bool impl::similarContact( const pod::Contact& a, const pod::Contact& b, float d
return uf::vector::distanceSquared(a.point, b.point) < distSqThreshold && uf::vector::dot(a.normal, b.normal) > normThreshold;
}
void impl::reduceContacts( pod::Manifold& manifold ) {
void impl::reduceManifold( pod::Manifold& manifold ) {
if ( manifold.points.size() <= 4 ) return;
#if 1
@ -210,7 +210,7 @@ void impl::reduceContacts( pod::Manifold& manifold ) {
#endif
}
void impl::mergeContacts( pod::Manifold& manifold ) {
void impl::mergeManifold( pod::Manifold& manifold ) {
STATIC_THREAD_LOCAL(uf::stl::vector<pod::Contact>, result);
result.reserve(4);
@ -231,7 +231,7 @@ void impl::mergeContacts( pod::Manifold& manifold ) {
manifold.points = result;
}
void impl::retrieveContacts( pod::Manifold& current, const pod::Manifold& previous, float distanceThreshold, float separationThreshold, float decay ) {
void impl::retrieveManifold( pod::Manifold& current, const pod::Manifold& previous, float distanceThreshold, float separationThreshold, float decay ) {
auto& a = *current.a;
auto& b = *current.b;
@ -320,7 +320,7 @@ void impl::pruneManifoldCache( uf::stl::unordered_map<size_t, pod::Manifold>& ca
}
}
void impl::warmupContacts( pod::PhysicsBody& a, pod::PhysicsBody& b, const pod::Contact& c, float dt ) {
void impl::warmupContact( pod::PhysicsBody& a, pod::PhysicsBody& b, const pod::Contact& c, float dt ) {
if ( !c.lifetime ) return; // too new
// build relative offsets
@ -337,7 +337,7 @@ void impl::warmupContacts( pod::PhysicsBody& a, pod::PhysicsBody& b, const pod::
}
void impl::warmupManifold( pod::PhysicsBody& a, pod::PhysicsBody& b, const pod::Manifold& manifold, float dt ) {
for ( auto& contact : manifold.points ) {
impl::warmupContacts( a, b, contact, dt );
impl::warmupContact( a, b, contact, dt );
}
}
@ -348,7 +348,25 @@ void impl::resolveManifold( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manif
for ( auto& contact : manifold.points ) impl::iterativeImpulseSolver( a, b, contact, dt );
}
void impl::solveContacts( uf::stl::vector<pod::Manifold>& manifolds, float dt ) {
void impl::solveManifold( uf::stl::vector<pod::Manifold>& manifolds, float dt ) {
if ( uf::physics::settings.warmupSolver ) for ( auto& manifold : manifolds ) impl::warmupManifold( *manifold.a, *manifold.b, manifold, dt );
for ( auto i = 0; i < uf::physics::settings.solverIterations; ++i ) for ( auto& manifold : manifolds ) impl::resolveManifold( *manifold.a, *manifold.b, manifold, dt );
}
void impl::dispatchManifold( pod::Manifold& manifold, pod::CollisionEvent::events_t& events, pod::CollisionEvent::map_t& active, const pod::CollisionEvent::map_t& previous ) {
auto pairKey = impl::makePairKey( *manifold.a, *manifold.b );
// find largest impulse
float maxImpulse = 0.0f;
for ( const auto& c : manifold.points ) maxImpulse = std::max( maxImpulse, c.accumulatedNormalImpulse );
// mark as an active collision
active[pairKey] = { manifold.a, manifold.b };
// dispatch
events.emplace_back(pod::CollisionEvent{
.state = previous.count( pairKey ) == 0 ? pod::CollisionState::ENTER : pod::CollisionState::SUSTAIN,
.a = manifold.a,
.b = manifold.b,
.point = manifold.points[0].point,
.normal = manifold.points[0].normal,
.impulse = maxImpulse,
});
}

View File

@ -90,6 +90,14 @@ void uf::physics::step( pod::World& world, float dt ) {
auto& constraints = world.constraints;
auto& dynamicBvh = world.dynamicBvh;
auto& staticBvh = world.staticBvh;
auto& activeCollisions = world.activeCollisions;
auto& collisionEvents = world.collisionEvents;
STATIC_THREAD_LOCAL(pod::CollisionEvent::events_t, previousCollisionEvents);
STATIC_THREAD_LOCAL(pod::CollisionEvent::map_t, previousCollisions);
std::swap( previousCollisions, activeCollisions );
std::swap( previousCollisionEvents, collisionEvents );
if ( bodies.empty() ) return;
@ -148,10 +156,10 @@ void uf::physics::step( pod::World& world, float dt ) {
pod::Manifold manifold;
// did not collide
if ( !impl::generateContacts( a, b, manifold, dt ) ) continue;
if ( !impl::generateManifold( a, b, manifold, dt ) ) continue;
// compute local points (for reprojection)
impl::computeLocalContacts( manifold );
impl::computeLocalManifold( manifold );
// bodies with meshes already reorient the normal to the triangle's center
bool shouldReorient = true;
@ -165,14 +173,14 @@ void uf::physics::step( pod::World& world, float dt ) {
// retrieve accumulated impulses
if ( uf::physics::settings.warmupSolver ) {
auto it = uf::physics::settings.manifoldsCache.find( impl::makePairKey( a, b ) );
if ( it != uf::physics::settings.manifoldsCache.end() ) impl::retrieveContacts( manifold, it->second );
if ( it != uf::physics::settings.manifoldsCache.end() ) impl::retrieveManifold( manifold, it->second );
}
// merge similar contacts from a mesh to ensure continuity
if ( a.collider.type == pod::ShapeType::MESH || b.collider.type == pod::ShapeType::MESH ) {
impl::mergeContacts( manifold );
impl::mergeManifold( manifold );
}
// keep four most important contacts
impl::reduceContacts( manifold );
impl::reduceManifold( manifold );
// no points remained, skip
if ( manifold.points.empty() ) continue;
// wake up bodies
@ -188,36 +196,42 @@ void uf::physics::step( pod::World& world, float dt ) {
}
// store manifold
manifolds.emplace_back(manifold);
manifolds.emplace_back( manifold );
}
// pass manifolds to solver
impl::solveContacts( manifolds, dt );
impl::solveManifold( manifolds, dt );
// do position correction
impl::solvePositions( manifolds, dt );
// solve constraints
for ( uint32_t i = 0; i < uf::physics::settings.solverIterations; ++i ) {
for ( auto& constraint : constraints ) {
impl::solveConstraint( *constraint, dt );
}
}
impl::solveConstraints( constraints, dt );
// cache manifold positions
if ( uf::physics::settings.warmupSolver ) {
impl::updateManifoldCache( manifolds, uf::physics::settings.manifoldsCache );
}
if ( uf::physics::settings.warmupSolver ) impl::updateManifoldCache( manifolds, uf::physics::settings.manifoldsCache );
});
uf::thread::execute( tasks );
// prune expired manifolds in the cache
if ( uf::physics::settings.warmupSolver ) impl::pruneManifoldCache( uf::physics::settings.manifoldsCache );
if ( uf::physics::settings.debugDraw ) {
for ( auto& island : islands ) for ( auto& manifold : island.manifolds ) impl::drawManifold( manifold );
for ( auto& island : islands ) for ( auto& manifold : island.manifolds ) {
// dispatch collision events
impl::dispatchManifold( manifold, collisionEvents, activeCollisions, previousCollisions );
// draw collision events
if ( uf::physics::settings.debugDraw ) impl::drawManifold( manifold );
}
// dispatch exiting collisions
for ( auto& [ key, pair ] : previousCollisions ) {
// sustained collision
if ( activeCollisions.count( key ) > 0 ) continue;
// mark as exiting
collisionEvents.emplace_back(pod::CollisionEvent{
.state = pod::CollisionState::EXIT,
.a = pair.first,
.b = pair.second,
});
}
for ( auto* b : bodies ) {
if ( b->inverseMass == 0.0f ) continue;
impl::snapVelocity( *b, dt );
}
// snap velocities of bodies
for ( auto* b : bodies ) impl::snapVelocity( *b, dt );
}
void uf::physics::setMass( pod::PhysicsBody& body, float mass ) {
@ -447,7 +461,18 @@ pod::World& uf::physics::getWorld() {
auto& scene = uf::scene::getCurrentScene();
return scene.getComponent<pod::World>();
}
const pod::CollisionEvent::events_t& uf::physics::getCollisionEvents( const pod::World& world ) {
return world.collisionEvents;
}
pod::CollisionEvent::events_t uf::physics::getCollisionEvents( const pod::PhysicsBody& body ) {
const auto& events = uf::physics::getCollisionEvents( *body.world );
pod::CollisionEvent::events_t res;
for ( auto& event : events ) {
if ( event.a != &body && event.b != &body ) continue;
res.emplace_back( event );
}
return res;
}
// body creation
pod::PhysicsBody& uf::physics::create( pod::World& world, uf::Object& object, float mass, const pod::Vector3f& offset ) {
auto& root = object.getComponent<pod::PhysicsBody>();