added convex hull colliders (mesh colliders with fundamental differences), also fixed mesh v mesh because of some dumb assumption that the BVH was in worldspace

This commit is contained in:
ecker 2026-04-29 23:19:54 -05:00
parent b523878912
commit 9c25bf9a9a
20 changed files with 721 additions and 185 deletions

View File

@ -6,12 +6,12 @@
"metadata": {
"holdable": true,
"physics": {
"gravity": [ 0, 0, 0 ],
// "gravity": [ 0, -1, 0 ],
"mass": 100,
"inertia": false,
"mass": 1,
"type": "aabb",
"recenter": false
// "type": "bounding box"
"type": "mesh"
// "type": "hull"
}
}
}

View File

@ -10,6 +10,7 @@
"inertia": false,
"type": "bounding box"
// "type": "mesh"
// "type": "mesh"
}
}
}

View File

@ -22,6 +22,7 @@ namespace pod {
CAPSULE,
TRIANGLE,
MESH,
CONVEX_HULL,
};
struct SupportPoint {
@ -107,6 +108,12 @@ namespace pod {
const uf::Mesh* mesh;
};
// MIGHT contain additional data, so far mirrors the above
struct ConvexHullBVH {
pod::BVH* bvh;
const uf::Mesh* mesh;
};
typedef uint32_t CollisionMask;
@ -147,6 +154,7 @@ namespace pod {
pod::Capsule capsule;
pod::TriangleWithNormal triangle;
pod::MeshBVH mesh;
pod::ConvexHullBVH convexHull;
};
};
@ -176,7 +184,8 @@ namespace pod {
bool isStatic = false;
float mass = 1.0f;
float inverseMass = 1.0f;
float inverseMass = 1.0f; // for fast division
int32_t viewIndex = -1; // -1 means it's not an aliased view
/*alignas(16)*/ pod::Vector3f offset = {};
@ -232,9 +241,9 @@ namespace pod {
bool warmupSolver = true; // cache manifold data to warm up the solver
bool blockContactSolver = true; // use BlockNxN solvers (where N = number of contacts for a manifold)
bool psgContactSolver = true; // use PSG contact solver
bool useGjk = false; // currently don't have a way to broadphase mesh => narrowphase tri via GJK
bool useGjk = true; // currently don't have a way to broadphase mesh => narrowphase tri via GJK
bool fixedStep = true; // run physics simulation with a fixed delta time (with accumulation), rather than rely on actual engine deltatime
uint32_t substeps = 1; // number of substeps per frame tick
uint32_t substeps = 4; // number of substeps per frame tick
uint32_t reserveCount = 32; // amount of elements to reserve for vectors used in this system, to-do: have it tie to a memory pool allocator
// increasing these make things lag for reasons I can imagine why
@ -333,7 +342,7 @@ namespace uf {
pod::PhysicsBody& UF_API create( uf::Object& object, const pod::Sphere& sphere, float mass = 0.0f, const pod::Vector3f& = {} );
pod::PhysicsBody& UF_API create( uf::Object& object, const pod::Plane& plane, float mass = 0.0f, const pod::Vector3f& = {} );
pod::PhysicsBody& UF_API create( uf::Object& object, const pod::Capsule& capsule, float mass = 0.0f, const pod::Vector3f& = {} );
pod::PhysicsBody& UF_API create( uf::Object&, const uf::Mesh& mesh, float mass = 0.0f, const pod::Vector3f& = {} );
pod::PhysicsBody& UF_API create( uf::Object&, const uf::Mesh& mesh, float mass = 0.0f, const pod::Vector3f& = {}, bool convex = false );
pod::PhysicsBody& UF_API create( pod::World&, uf::Object&, float mass = 0.0f, const pod::Vector3f& = {} );
pod::PhysicsBody& UF_API create( pod::World&, uf::Object& object, const pod::AABB& aabb, float mass = 0.0f, const pod::Vector3f& = {} );
@ -341,7 +350,7 @@ namespace uf {
pod::PhysicsBody& UF_API create( pod::World&, uf::Object& object, const pod::Plane& plane, float mass = 0.0f, const pod::Vector3f& = {} );
pod::PhysicsBody& UF_API create( pod::World&, uf::Object& object, const pod::Capsule& capsule, float mass = 0.0f, const pod::Vector3f& = {} );
pod::PhysicsBody& UF_API create( pod::World&, uf::Object& object, const pod::TriangleWithNormal& tri, float mass = 0.0f, const pod::Vector3f& = {} );
pod::PhysicsBody& UF_API create( pod::World&, uf::Object&, const uf::Mesh& mesh, float mass = 0.0f, const pod::Vector3f& = {} );
pod::PhysicsBody& UF_API create( pod::World&, uf::Object&, const uf::Mesh& mesh, float mass = 0.0f, const pod::Vector3f& = {}, bool convex = false );
void UF_API destroy( uf::Object& );
void UF_API destroy( pod::PhysicsBody& );

View File

@ -56,7 +56,7 @@ namespace uf {
template<typename T> pod::Transform<T> /*UF_API*/ inverse(const pod::Transform<T>& t);
template<typename T> pod::Vector3t<T> /*UF_API*/ apply( const pod::Transform<T>& transform, const pod::Vector3t<T>& point );
template<typename T> pod::Vector3t<T> /*UF_API*/ applyInverse(const pod::Transform<T>& t, const pod::Vector3t<T>& worldPoint);
template<typename T> pod::Transform<T> /*UF_API*/ relative(const pod::Transform<T>& a, const pod::Transform<T>& b);
template<typename T> uf::stl::string /*UF_API*/ toString( const pod::Transform<T>&, bool flatten = true );
template<typename T> ext::json::Value /*UF_API*/ encode( const pod::Transform<T>&, bool flatten = true, const ext::json::EncodingSettings& = {} );

View File

@ -1,4 +1,5 @@
template<typename T> pod::Transform<T>& /*UF_API*/ uf::transform::initialize( pod::Transform<T>& transform ) {
template<typename T>
pod::Transform<T>& /*UF_API*/ uf::transform::initialize( pod::Transform<T>& transform ) {
transform.position = {0, 0, 0};
transform.scale = {1, 1, 1};
@ -7,17 +8,20 @@ template<typename T> pod::Transform<T>& /*UF_API*/ uf::transform::initialize( po
transform.forward = {0, 0, 1};
transform.orientation = {0, 0, 0, 1};
transform.model = uf::matrix::identity();
transform.reference = NULL;
transform.reference = nullptr;
return transform;
}
template<typename T> pod::Transform<T> /*UF_API*/ uf::transform::initialize() {
template<typename T>
pod::Transform<T> /*UF_API*/ uf::transform::initialize() {
pod::Transform<T> transform;
return transform = uf::transform::initialize(transform);
return uf::transform::initialize(transform);
}
template<typename T> pod::Transform<T>& /*UF_API*/ uf::transform::lookAt( pod::Transform<T>& transform, const pod::Vector3t<T>& at ) {
template<typename T>
pod::Transform<T>& /*UF_API*/ uf::transform::lookAt( pod::Transform<T>& transform, const pod::Vector3t<T>& at ) {
pod::Vector3t<T> forward = uf::vector::normalize( at - transform.position );
pod::Vector3t<T> right = uf::vector::normalize(uf::vector::cross( forward, transform.up ));
pod::Vector3t<T> up = uf::vector::normalize(uf::vector::cross(at, right));
@ -25,132 +29,139 @@ template<typename T> pod::Transform<T>& /*UF_API*/ uf::transform::lookAt( pod::T
transform.up = up;
transform.right = right;
transform.forward = forward;
transform.orientation = uf::quaternion::lookAt( at, up );
return transform;
}
template<typename T> pod::Transform<T>& /*UF_API*/ uf::transform::move( pod::Transform<T>& transform, const pod::Vector3t<T>& axis, pod::Math::num_t delta ) {
template<typename T>
pod::Transform<T>& /*UF_API*/ uf::transform::move( pod::Transform<T>& transform, const pod::Vector3t<T>& axis, pod::Math::num_t delta ) {
transform.position += (axis * delta);
return transform;
}
template<typename T> pod::Transform<T>& /*UF_API*/ uf::transform::move( pod::Transform<T>& transform, const pod::Vector3t<T>& delta ) {
template<typename T>
pod::Transform<T>& /*UF_API*/ uf::transform::move( pod::Transform<T>& transform, const pod::Vector3t<T>& delta ) {
transform.position += delta;
return transform;
}
template<typename T> pod::Transform<T>& /*UF_API*/ uf::transform::reorient( pod::Transform<T>& transform ) {
/*
transform.up = uf::vector::normalize(uf::quaternion::rotate(transform.orientation, pod::Vector3{0.0f, 1.0f, 0.0f}));
transform.right = uf::vector::normalize(uf::quaternion::rotate(transform.orientation, pod::Vector3{1.0f, 0.0f, 0.0f}));
transform.forward = uf::vector::normalize(uf::quaternion::rotate(transform.orientation, pod::Vector3{0.0f, 0.0f, 1.0f}));
*/
template<typename T>
pod::Transform<T>& /*UF_API*/ uf::transform::reorient( pod::Transform<T>& transform ) {
pod::Quaternion<T> q = transform.orientation;
transform.forward = { 2 * (q.x * q.z + q.w * q.y), 2 * (q.y * q.x - q.w * q.x), 1 - 2 * (q.x * q.x + q.y * q.y) };
transform.up = { 2 * (q.x * q.y - q.w * q.z), 1 - 2 * (q.x * q.x + q.z * q.z), 2 * (q.y * q.z + q.w * q.x)};
transform.right = { 1 - 2 * (q.y * q.y + q.z * q.z), 2 * (q.x * q.y + q.w * q.z), 2 * (q.x * q.z - q.w * q.y)};
return transform;
}
template<typename T> pod::Transform<T> /*UF_API*/ uf::transform::reorient( const pod::Transform<T>& _transform ) {
/*
transform.up = uf::vector::normalize(uf::quaternion::rotate(transform.orientation, pod::Vector3{0.0f, 1.0f, 0.0f}));
transform.right = uf::vector::normalize(uf::quaternion::rotate(transform.orientation, pod::Vector3{1.0f, 0.0f, 0.0f}));
transform.forward = uf::vector::normalize(uf::quaternion::rotate(transform.orientation, pod::Vector3{0.0f, 0.0f, 1.0f}));
*/
template<typename T>
pod::Transform<T> /*UF_API*/ uf::transform::reorient( const pod::Transform<T>& _transform ) {
pod::Transform<T> transform = _transform;
pod::Quaternion<T> q = transform.orientation;
transform.forward = { 2 * (q.x * q.z + q.w * q.y), 2 * (q.y * q.x - q.w * q.x), 1 - 2 * (q.x * q.x + q.y * q.y) };
transform.up = { 2 * (q.x * q.y - q.w * q.z), 1 - 2 * (q.x * q.x + q.z * q.z), 2 * (q.y * q.z + q.w * q.x)};
transform.right = { 1 - 2 * (q.y * q.y + q.z * q.z), 2 * (q.x * q.y + q.w * q.z), 2 * (q.x * q.z - q.w * q.y)};
return transform;
return uf::transform::reorient(transform);
}
template<typename T> pod::Transform<T>& /*UF_API*/ uf::transform::rotate( pod::Transform<T>& transform, const pod::Vector3t<T>& axis, pod::Math::num_t delta ) {
template<typename T>
pod::Transform<T>& /*UF_API*/ uf::transform::rotate( pod::Transform<T>& transform, const pod::Vector3t<T>& axis, pod::Math::num_t delta ) {
pod::Quaternion<> quat = uf::quaternion::axisAngle( axis, delta );
transform.orientation = uf::vector::normalize(uf::quaternion::multiply(quat, transform.orientation));
transform = uf::transform::reorient(transform);
return transform;
return uf::transform::reorient(transform);
}
template<typename T> pod::Transform<T>& /*UF_API*/ uf::transform::rotate( pod::Transform<T>& transform, const pod::Quaternion<T>& quat ) {
template<typename T>
pod::Transform<T>& /*UF_API*/ uf::transform::rotate( pod::Transform<T>& transform, const pod::Quaternion<T>& quat ) {
transform.orientation = uf::vector::normalize(uf::quaternion::multiply(quat, transform.orientation));
transform = uf::transform::reorient(transform);
return transform;
return uf::transform::reorient(transform);
}
template<typename T> pod::Transform<T>& /*UF_API*/ uf::transform::scale( pod::Transform<T>& transform, const pod::Vector3t<T>& factor ) {
template<typename T>
pod::Transform<T>& /*UF_API*/ uf::transform::scale( pod::Transform<T>& transform, const pod::Vector3t<T>& factor ) {
transform.scale = factor;
return transform;
}
template<typename T> inline pod::Transform<T> /*UF_API*/ uf::transform::condense( const pod::Transform<T>& transform ) {
template<typename T>
inline pod::Transform<T> /*UF_API*/ uf::transform::condense( const pod::Transform<T>& transform ) {
return uf::transform::fromMatrix( uf::transform::model( transform, false ) );
}
template<typename T> pod::Transform<T> /*UF_API*/ uf::transform::flatten( const pod::Transform<T>& transform, size_t depth ) {
template<typename T>
pod::Transform<T> /*UF_API*/ uf::transform::flatten( const pod::Transform<T>& transform, size_t depth ) {
if ( !transform.reference ) return transform;
// if ( depth == SIZE_MAX ) return uf::transform::condense( transform );
pod::Transform<T> combined = transform;
combined.reference = NULL;
combined.reference = nullptr;
const pod::Transform<T>* pointer = transform.reference;
while ( pointer && depth-- > 0 ) {
// for some reason I can't just += for the camera
combined.position = {
combined.position.x + pointer->position.x,
combined.position.y + pointer->position.y,
combined.position.z + pointer->position.z,
};
pod::Quaternion<T> orientation = pointer->orientation;
combined.orientation = uf::quaternion::multiply( orientation, combined.orientation );
combined.orientation = uf::quaternion::multiply( pointer->orientation, combined.orientation );
combined.scale = {
combined.scale.x * pointer->scale.x,
combined.scale.y * pointer->scale.y,
combined.scale.z * pointer->scale.z,
};
combined.model = pointer->model * combined.model;
if ( pointer == pointer->reference ) break;
pointer = pointer->reference;
}
return combined = uf::transform::reorient(combined);
return uf::transform::reorient(combined);
}
template<typename T> pod::Matrix4t<T> /*UF_API*/ uf::transform::model( const pod::Transform<T>& transform, bool flatten, size_t depth ) {
template<typename T>
pod::Matrix4t<T> /*UF_API*/ uf::transform::model( const pod::Transform<T>& transform, bool flatten, size_t depth ) {
if ( flatten ) {
pod::Transform<T> flatten = uf::transform::flatten(transform, depth);
return uf::matrix::translate( uf::matrix::identity(), flatten.position ) *
uf::quaternion::matrix( flatten.orientation ) *
uf::matrix::scale( uf::matrix::identity(), flatten.scale ) *
flatten.model;
pod::Transform<T> flat = uf::transform::flatten(transform, depth);
return uf::matrix::translate( uf::matrix::identity(), flat.position ) *
uf::quaternion::matrix( flat.orientation ) *
uf::matrix::scale( uf::matrix::identity(), flat.scale ) *
flat.model;
}
pod::Matrix4t<T> matrix = uf::matrix::identity();
const pod::Transform<T>* pointer = &transform;
do {
pod::Matrix4t<T> model = uf::matrix::translate( uf::matrix::identity(), pointer->position ) *
pod::Matrix4t<T> modelMat = uf::matrix::translate( uf::matrix::identity(), pointer->position ) *
uf::quaternion::matrix( pointer->orientation ) *
uf::matrix::scale( uf::matrix::identity(), pointer->scale ) *
pointer->model;
matrix = model * matrix;
matrix = modelMat * matrix;
if ( pointer == pointer->reference ) break;
pointer = pointer->reference;
} while ( pointer && --depth >= 0 );
} while ( pointer && --depth > 0 );
return matrix;
}
template<typename T> pod::Transform<T> uf::transform::fromMatrix( const pod::Matrix4t<T>& matrix ) {
template<typename T>
pod::Transform<T> uf::transform::fromMatrix( const pod::Matrix4t<T>& matrix ) {
pod::Transform<T> transform;
transform.position = uf::matrix::multiply<float>( matrix, pod::Vector4f{ 0, 0, 0, 1 } );
transform.orientation = uf::quaternion::fromMatrix( matrix );
transform.model = matrix;
return transform = reorient( transform );
return reorient( transform );
}
template<typename T> pod::Transform<T>& /*UF_API*/ uf::transform::reference( pod::Transform<T>& transform, const pod::Transform<T>& parent, bool reorient ) {
template<typename T>
pod::Transform<T>& /*UF_API*/ uf::transform::reference( pod::Transform<T>& transform, const pod::Transform<T>& parent, bool reorient ) {
transform.reference = const_cast<pod::Transform<T>*>(&parent);
if ( !reorient ) {
transform.reference = const_cast<pod::Transform<T>*>(&parent);
return transform;
}
transform.position = parent.position - transform.position;
// reorient orientation here too
transform.reference = const_cast<pod::Transform<T>*>(&parent);
return transform;
}
template<typename T> pod::Transform<T> /*UF_API*/ uf::transform::interpolate( const pod::Transform<T>& from, const pod::Transform<T>& to, float factor, bool reorient ) {
template<typename T>
pod::Transform<T> /*UF_API*/ uf::transform::interpolate( const pod::Transform<T>& from, const pod::Transform<T>& to, float factor, bool reorient ) {
pod::Transform transform = to;
transform.position = uf::vector::lerp( from.position, to.position, factor );
transform.orientation = uf::quaternion::slerp( from.orientation, to.orientation, factor );
@ -159,48 +170,50 @@ template<typename T> pod::Transform<T> /*UF_API*/ uf::transform::interpolate( co
template<typename T>
pod::Transform<T> uf::transform::inverse(const pod::Transform<T>& t) {
pod::Transform<T> inv;
pod::Transform<T> inv;
// Inverse orientation = conjugate of normalized quaternion
pod::Quaternion<T> qInv = uf::quaternion::conjugate(uf::vector::normalize(t.orientation));
pod::Quaternion<T> qInv = uf::quaternion::conjugate(uf::vector::normalize(t.orientation));
// Inverse scale is reciprocal (with protection against zero)
pod::Vector3t<T> sInv {
(fabs(t.scale.x) > 1e-8) ? (1.0f / t.scale.x) : 0.0f,
(fabs(t.scale.y) > 1e-8) ? (1.0f / t.scale.y) : 0.0f,
(fabs(t.scale.z) > 1e-8) ? (1.0f / t.scale.z) : 0.0f
};
pod::Vector3t<T> sInv {
(fabs(t.scale.x) > 1e-8) ? (1.0f / t.scale.x) : 0.0f,
(fabs(t.scale.y) > 1e-8) ? (1.0f / t.scale.y) : 0.0f,
(fabs(t.scale.z) > 1e-8) ? (1.0f / t.scale.z) : 0.0f
};
inv.scale = sInv;
inv.orientation = qInv;
inv.scale = sInv;
inv.orientation = qInv;
// To invert translation: apply inverse orientation/scale
pod::Vector3t<T> negPos = -t.position;
inv.position = uf::quaternion::rotate(qInv, pod::Vector3f{ negPos.x * sInv.x,
negPos.y * sInv.y,
negPos.z * sInv.z });
pod::Vector3t<T> negPos = -t.position;
inv.position = uf::quaternion::rotate(qInv, pod::Vector3f{
negPos.x * sInv.x,
negPos.y * sInv.y,
negPos.z * sInv.z
});
// Update basis vectors
inv = uf::transform::reorient(inv);
return inv;
return uf::transform::reorient(inv);
}
template<typename T> // Normalizes a vector
pod::Vector3t<T> /*UF_API*/ uf::transform::apply( const pod::Transform<T>& transform, const pod::Vector3t<T>& point ) {
// return uf::transform::model( transform ) * point;
return uf::matrix::multiply( uf::transform::model( transform ), point );
}
// Apply inverse transform to a point
template<typename T>
pod::Vector3t<T> uf::transform::applyInverse(const pod::Transform<T>& t, const pod::Vector3t<T>& worldPoint) {
pod::Transform<T> inv = inverse(t);
return uf::transform::apply(inv, worldPoint);
pod::Vector3t<T> /*UF_API*/ uf::transform::apply( const pod::Transform<T>& t, const pod::Vector3t<T>& p ) {
return uf::quaternion::rotate( t.orientation, p * t.scale ) + t.position;
}
template<typename T> // Normalizes a vector
template<typename T>
pod::Vector3t<T> uf::transform::applyInverse( const pod::Transform<T>& t, const pod::Vector3t<T>& worldPoint ) {
pod::Vector3t<T> translated = worldPoint - t.position;
return uf::quaternion::rotate(uf::quaternion::inverse(t.orientation), translated) / t.scale;
}
template<typename T>
pod::Transform<T> uf::transform::relative( const pod::Transform<T>& a, const pod::Transform<T>& b ) {
pod::Transform<T> rel;
auto invOriA = uf::quaternion::inverse(a.orientation);
rel.position = uf::quaternion::rotate(invOriA, b.position - a.position);
rel.orientation = invOriA * b.orientation;
return rel;
}
template<typename T>
uf::stl::string /*UF_API*/ uf::transform::toString( const pod::Transform<T>& t, bool flatten ) {
pod::Transform<T> transform = flatten ? uf::transform::flatten(t) : t;
uf::stl::stringstream ss;
@ -218,46 +231,26 @@ ext::json::Value /*UF_API*/ uf::transform::encode( const pod::Transform<T>& t, b
json["model"] = uf::matrix::encode(transform.model, settings);
return json;
}
template<typename T>
pod::Transform<T>& /*UF_API*/ uf::transform::decode( const ext::json::Value& _json, pod::Transform<T>& transform ) {
ext::json::Value json = _json;
// transform.position = uf::vector::decode<T, 3>(json["position"], transform.position);
// transform.scale = uf::vector::decode<T, 3>(json["scale"], transform.scale);
// transform.orientation = uf::vector::decode<T, 4>(json["orientation"], transform.orientation);
// if ( ext::json::isArray(json["position"]) || ext::json::isObject(json["position"]) )
transform.position = uf::vector::decode<T, 3>(json["position"], transform.position);
// if ( ext::json::isArray(json["scale"]) || ext::json::isObject(json["scale"]) )
transform.scale = uf::vector::decode<T, 3>(json["scale"], transform.scale);
// if ( ext::json::isArray(json["orientation"]) || ext::json::isObject(json["orientation"]) )
transform.orientation = uf::vector::decode<T, 4>(json["orientation"], transform.orientation);
ext::json::Value json = _json; // cringe null behavior
transform.position = uf::vector::decode<T, 3>(json["position"], transform.position);
transform.scale = uf::vector::decode<T, 3>(json["scale"], transform.scale);
transform.orientation = uf::vector::decode<T, 4>(json["orientation"], transform.orientation);
if ( ext::json::isObject(json["rotation"]) && !ext::json::isNull(json["rotation"]["axis"]) && !ext::json::isNull(json["rotation"]["angle"]) ) {
pod::Vector3t<T> axis = uf::vector::decode<T, 3>(json["rotation"]["axis"]);
T angle = json["rotation"]["angle"].as<T>();
transform.orientation = uf::quaternion::axisAngle( axis, angle );
}
// if ( ext::json::isArray(json["model"]) )
transform.model = uf::matrix::decode<T, 4, 4>(json["model"], transform.model);
transform.model = uf::matrix::decode<T, 4, 4>(json["model"], transform.model);
return transform;
}
template<typename T>
pod::Transform<T> /*UF_API*/ uf::transform::decode( const ext::json::Value& _json, const pod::Transform<T>& t ) {
ext::json::Value json = _json;
pod::Transform<T> /*UF_API*/ uf::transform::decode( const ext::json::Value& json, const pod::Transform<T>& t ) {
pod::Transform<T> transform = t;
// transform.position = uf::vector::decode<T, 3>(json["position"], transform.position);
// transform.scale = uf::vector::decode<T, 3>(json["scale"], transform.scale);
// transform.orientation = uf::vector::decode<T, 4>(json["orientation"], transform.orientation);
// if ( ext::json::isArray(json["position"]) || ext::json::isObject(json["position"]) )
transform.position = uf::vector::decode<T, 3>(json["position"], transform.position);
// if ( ext::json::isArray(json["scale"]) || ext::json::isObject(json["scale"]) )
transform.scale = uf::vector::decode<T, 3>(json["scale"], transform.scale);
// if ( ext::json::isArray(json["orientation"]) || ext::json::isObject(json["orientation"]) )
transform.orientation = uf::vector::decode<T, 4>(json["orientation"], transform.orientation);
if ( ext::json::isObject(json["rotation"]) && !ext::json::isNull(json["rotation"]["axis"]) && !ext::json::isNull(json["rotation"]["angle"]) ) {
pod::Vector3t<T> axis = uf::vector::decode<T, 3>(json["rotation"]["axis"]);
T angle = json["rotation"]["angle"].as<T>();
transform.orientation = uf::quaternion::axisAngle( axis, angle );
}
// if ( ext::json::isArray(json["model"]) )
transform.model = uf::matrix::decode<T, 4, 4>(json["model"], transform.model);
return transform;
return uf::transform::decode(json, transform);
}

View File

@ -1317,7 +1317,8 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
if ( ext::json::isObject( phyziks ) ) {
uf::stl::string type = phyziks["type"].as<uf::stl::string>();
if ( type != "mesh" ) {
bool isMesh = type == "mesh" || type == "hull";
if ( !isMesh ) {
auto min = bounds.min; // uf::matrix::multiply<float>( model, bounds.min, 1.0f );
auto max = bounds.max; // uf::matrix::multiply<float>( model, bounds.max, 1.0f );
@ -1330,7 +1331,7 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
if ( ext::json::isNull( metadataJson["physics"]["max"] ) ) metadataJson["physics"]["max"] = uf::vector::encode( max );
}
#if !UF_GRAPH_EXTENDED
if ( type == "mesh" ) {
if ( isMesh ) {
auto& physicsBody = entity.getComponent<pod::PhysicsBody>();
float mass = phyziks["mass"].as(physicsBody.mass);
@ -1338,8 +1339,9 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
physicsBody.material.restitution = phyziks["restitution"].as(physicsBody.material.restitution);
physicsBody.inertiaTensor = uf::vector::decode( phyziks["inertia"], physicsBody.inertiaTensor );
physicsBody.gravity = uf::vector::decode( phyziks["gravity"], physicsBody.gravity );
uf::physics::impl::create( entity.as<uf::Object>(), mesh, mass );
auto center = uf::vector::decode( phyziks["center"], pod::Vector3f{} );
uf::physics::impl::create( entity.as<uf::Object>(), mesh, mass, center, type != "mesh" );
}
#endif
}
@ -1938,15 +1940,16 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
}
}
// bind mesh to physics state
// to-do: figure out why the mesh just suddenly breaks when re-streamed in dreamcast (could just be the version of reactphysics)
// to-do: test if this works in the internal physics system (the entire reason why I wrote it was because of this mess)
{
auto phyziks = tag["physics"];
if ( !ext::json::isObject( phyziks ) ) phyziks = metadataJson["physics"];
else metadataJson["physics"] = phyziks;
if ( ext::json::isObject( phyziks ) ) {
uf::stl::string type = phyziks["type"].as<uf::stl::string>();
if ( type == "mesh" ) {
uf::stl::string type = phyziks["type"].as<uf::stl::string>();
bool isMesh = type == "mesh" || type == "hull";
if ( isMesh ) {
bool exists = entity.hasComponent<pod::PhysicsBody>();
if ( exists ) {
uf::physics::impl::destroy( entity );
@ -1959,8 +1962,9 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
physicsBody.material.restitution = phyziks["restitution"].as(physicsBody.material.restitution);
physicsBody.inertiaTensor = uf::vector::decode( phyziks["inertia"], physicsBody.inertiaTensor );
physicsBody.gravity = uf::vector::decode( phyziks["gravity"], physicsBody.gravity );
auto center = uf::vector::decode( phyziks["center"], pod::Vector3f{} );
uf::physics::impl::create( entity.as<uf::Object>(), mesh, mass );
uf::physics::impl::create( entity.as<uf::Object>(), mesh, mass, center, type != "mesh" );
}
}
}

View File

@ -30,6 +30,16 @@ namespace {
};
}
pod::AABB computeConvexHullAABB( const uf::Mesh::View& view, const uf::Mesh::AttributeView& positions, pod::AABB bounds = { { FLT_MAX, FLT_MAX, FLT_MAX }, { -FLT_MAX, -FLT_MAX, -FLT_MAX } } ) {
for ( size_t i = 0; i < view.vertex.count; ++i ) {
pod::Vector3f v = ::getVertex( view, positions, i );
bounds.min = uf::vector::min( bounds.min, v );
bounds.max = uf::vector::max( bounds.max, v );
}
return bounds;
}
FORCE_INLINE pod::AABB mergeAabb( const pod::AABB& a, const pod::AABB& b ) {
return {
uf::vector::min( a.min, b.min ),
@ -106,6 +116,21 @@ namespace {
transform.position + body.collider.mesh.bvh->bounds[0].min,
transform.position + body.collider.mesh.bvh->bounds[0].max,
};
const auto& meshData = *body.collider.mesh.mesh;
pod::AABB bounds = { { FLT_MAX, FLT_MAX, FLT_MAX }, { -FLT_MAX, -FLT_MAX, -FLT_MAX } };
for ( const auto& view : meshData.buffer_views ) ::computeConvexHullAABB( view, view["position"], bounds );
return ::transformAabbToWorld( bounds, transform );
} break;
case pod::ShapeType::CONVEX_HULL: {
if ( body.collider.convexHull.bvh && !body.collider.convexHull.bvh->bounds.empty() )
return {
transform.position + body.collider.convexHull.bvh->bounds[0].min,
transform.position + body.collider.convexHull.bvh->bounds[0].max,
};
const auto& meshData = *body.collider.convexHull.mesh;
pod::AABB bounds = { { FLT_MAX, FLT_MAX, FLT_MAX }, { -FLT_MAX, -FLT_MAX, -FLT_MAX } };
for ( const auto& view : meshData.buffer_views ) ::computeConvexHullAABB( view, view["position"], bounds );
return ::transformAabbToWorld( bounds, transform );
} break;
default: {
} break;
@ -280,4 +305,8 @@ namespace {
ASSERT_COLLIDER_TYPES( AABB, MESH );
REVERSE_COLLIDER( a, b, meshAabb );
}
bool aabbHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( AABB, CONVEX_HULL );
REVERSE_COLLIDER( a, b, hullAabb );
}
}

View File

@ -6,6 +6,7 @@ namespace {
void queryFlatOverlaps( const pod::BVH& bvh, pod::BVH::pairs_t& outPairs );
void queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH::pairs_t& outPairs );
void queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, const pod::Transform<>& relTransform, pod::BVH::pairs_t& outPairs );
void postprocessPairs( pod::BVH::pairs_t& pairs ) {
std::sort(pairs.begin(), pairs.end());
@ -263,6 +264,42 @@ namespace {
// mark as clean
bvh.dirty = false;
}
void buildConvexHullBVH( pod::BVH& bvh, const uf::Mesh& mesh, pod::BVH::index_t capacity = 1 ) {
const auto& views = mesh.buffer_views;
UF_ASSERT( !views.empty() );
uint32_t hullCount = views.size();
bvh.indices.clear();
bvh.nodes.clear();
bvh.indices.reserve( hullCount );
// stores bounds
uf::stl::vector<pod::AABB> bounds;
bounds.reserve( hullCount );
// populate initial indices and bounds
for ( size_t viewID = 0; viewID < hullCount; ++viewID ) {
const auto& view = views[viewID];
auto aabb = ::computeConvexHullAABB( view, view["position"] );
bounds.emplace_back( aabb );
bvh.indices.emplace_back( viewID );
}
UF_ASSERT( !bounds.empty() );
// recursively build BVH from indices
if ( uf::physics::impl::settings.useBvhSahMeshes ) ::buildBVHNode_SAH( bvh, bounds, 0, bvh.indices.size(), capacity );
else ::buildBVHNode( bvh, bounds, 0, bvh.indices.size(), capacity );
// flatten if requested
if ( uf::physics::impl::settings.flattenBvhMeshes ) ::flattenBVH( bvh, 0 );
// mark as clean
bvh.dirty = false;
}
}
namespace {
@ -528,6 +565,43 @@ namespace {
}
}
void traverseNodePair( const pod::BVH& bvhA, pod::BVH::index_t nodeAID, const pod::BVH& bvhB, pod::BVH::index_t nodeBID, const pod::Transform<>& relTransform, pod::BVH::pairs_t& pairs ) {
const auto& nodeA = bvhA.nodes[nodeAID];
const auto& nodeB = bvhB.nodes[nodeBID];
if ( nodeA.isAsleep() && nodeB.isAsleep() ) return;
pod::AABB boundsB_in_A = ::transformAabbToWorld(bvhB.bounds[nodeBID], relTransform);
if ( !::aabbOverlap( bvhA.bounds[nodeAID], boundsB_in_A ) ) return;
if ( nodeA.getCount() > 0 && nodeB.getCount() > 0 ) {
for ( auto i = 0; i < nodeA.getCount(); ++i ) {
for ( auto j = 0; j < nodeB.getCount(); ++j ) {
pairs.emplace_back(bvhA.indices[nodeA.start + i], bvhB.indices[nodeB.start + j]);
}
}
return;
}
if ( nodeA.getCount() == 0 && nodeB.getCount() == 0 ) {
if ( ::aabbSurfaceArea(bvhA.bounds[nodeAID]) > ::aabbSurfaceArea(boundsB_in_A) ) {
::traverseNodePair( bvhA, nodeA.left, bvhB, nodeBID, relTransform, pairs );
::traverseNodePair( bvhA, nodeA.right, bvhB, nodeBID, relTransform, pairs );
} else {
::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.left, relTransform, pairs );
::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.right, relTransform, pairs );
}
}
else if ( nodeA.getCount() == 0 ) {
::traverseNodePair( bvhA, nodeA.left, bvhB, nodeBID, relTransform, pairs );
::traverseNodePair( bvhA, nodeA.right, bvhB, nodeBID, relTransform, pairs );
}
else if ( nodeB.getCount() == 0 ) {
::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.left, relTransform, pairs );
::traverseNodePair( bvhA, nodeAID, bvhB, nodeB.right, relTransform, pairs );
}
}
void traverseBVH( const pod::BVH& bvh, pod::BVH::index_t nodeID, pod::BVH::pairs_t& pairs ) {
const auto& node = bvh.nodes[nodeID];
@ -568,6 +642,17 @@ namespace {
::postprocessPairs( outPairs );
}
void queryOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, const pod::Transform<>& relTransform, pod::BVH::pairs_t& outPairs ) {
if ( !bvhA.flattened.empty() && !bvhB.flattened.empty() ) return ::queryFlatOverlaps( bvhA, bvhB, relTransform, outPairs );
if ( bvhA.nodes.empty() || bvhB.nodes.empty() ) return;
outPairs.reserve(uf::physics::impl::settings.reserveCount);
::traverseNodePair(bvhA, 0, bvhB, 0, relTransform, outPairs);
UF_EXCEPTION("unimplemented");
::postprocessPairs( outPairs );
}
}
namespace {
@ -775,6 +860,77 @@ namespace {
::postprocessPairs( outPairs );
}
void queryFlatOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, const pod::Transform<>& relTransform, pod::BVH::pairs_t& outPairs ) {
auto& nodesA = bvhA.flattened;
auto& boundsA = bvhA.flatBounds;
auto& indicesA = bvhA.indices;
auto& nodesB = bvhB.flattened;
auto& boundsB = bvhB.flatBounds;
auto& indicesB = bvhB.indices;
if ( nodesA.empty() || nodesB.empty() ) return;
outPairs.reserve(uf::physics::impl::settings.reserveCount);
static thread_local uf::stl::vector<std::pair<pod::BVH::index_t, pod::BVH::index_t>> stack;
stack.clear();
stack.emplace_back(0, 0);
while ( !stack.empty() ) {
auto [a, b] = stack.back();
stack.pop_back();
const auto& nodeA = bvhA.flattened[a];
const auto& nodeB = bvhB.flattened[b];
if ( nodeA.isAsleep() && nodeB.isAsleep() ) continue;
/*
pod::AABB worldBoundsA = ::transformAabbToWorld( boundsA[a], tA );
pod::AABB worldBoundsB = ::transformAabbToWorld( boundsB[b], tB );
if ( !::aabbOverlap( worldBoundsA, worldBoundsB ) ) continue;
*/
pod::AABB boundsB_in_A = ::transformAabbToWorld(boundsB[b], relTransform);
if ( !::aabbOverlap( boundsA[a], boundsB_in_A ) ) continue;
bool isLeafA = (nodeA.getCount() > 0);
bool isLeafB = (nodeB.getCount() > 0);
if ( isLeafA && isLeafB ) {
for ( pod::BVH::index_t ia = 0; ia < nodeA.getCount(); ++ia ) {
for ( pod::BVH::index_t ib = 0; ib < nodeB.getCount(); ++ib ) {
auto indexA = bvhA.indices[nodeA.start + ia];
auto indexB = bvhB.indices[nodeB.start + ib];
// if ( indexA > indexB ) std::swap( indexA, indexB );
outPairs.emplace_back(indexA, indexB);
}
}
}
else if ( isLeafA ) {
pod::BVH::index_t rightB = bvhB.flattened[b + 1].skipIndex;
stack.emplace_back(a, b + 1);
stack.emplace_back(a, rightB);
}
else if ( isLeafB ) {
pod::BVH::index_t rightA = bvhA.flattened[a + 1].skipIndex;
stack.emplace_back(a + 1, b);
stack.emplace_back(rightA, b);
}
else {
pod::BVH::index_t rightA = bvhA.flattened[a + 1].skipIndex;
pod::BVH::index_t rightB = bvhB.flattened[b + 1].skipIndex;
stack.emplace_back(a + 1, b + 1);
stack.emplace_back(a + 1, rightB);
stack.emplace_back(rightA, b + 1);
stack.emplace_back(rightA, rightB);
}
}
::postprocessPairs( outPairs );
}
void queryFlatBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector<pod::BVH::index_t>& outIndices ) {
auto& nodes = bvh.flattened;
auto& indices = bvh.indices;

View File

@ -94,4 +94,8 @@ namespace {
ASSERT_COLLIDER_TYPES( CAPSULE, MESH );
REVERSE_COLLIDER( a, b, meshCapsule );
}
bool capsuleHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( CAPSULE, CONVEX_HULL );
REVERSE_COLLIDER( a, b, hullCapsule );
}
}

View File

@ -0,0 +1,86 @@
namespace {
bool hullGeneric( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
const auto& hull = a;
const auto& body = b;
const auto& bvh = *hull.collider.convexHull.bvh;
const auto& meshData = *hull.collider.convexHull.mesh;
// transform to local space for BVH query
auto bounds = ::transformAabbToLocal( body.bounds, ::getTransform( hull ) );
static thread_local uf::stl::vector<pod::BVH::index_t> candidates;
candidates.clear();
::queryBVH( bvh, bounds, candidates );
bool hit = false;
// do collision per hull
for ( auto hullID : candidates ) {
auto hullView = ::physicsBodyHullView( hull, hullID );
pod::Simplex simplex;
if ( !::gjk( hullView, body, simplex ) ) continue;
auto result = ::epa( hullView, body, simplex );
if ( !uf::vector::isValid(result.point) ) continue;
manifold.points.emplace_back(result);
hit = true;
}
return hit;
}
}
namespace {
bool hullAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( CONVEX_HULL, AABB );
return hullGeneric(a, b, manifold, eps);
}
bool hullSphere( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( CONVEX_HULL, SPHERE );
return hullGeneric(a, b, manifold, eps);
}
bool hullPlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( CONVEX_HULL, PLANE );
return hullGeneric(a, b, manifold, eps);
}
bool hullCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( CONVEX_HULL, CAPSULE );
return hullGeneric(a, b, manifold, eps);
}
bool hullMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( CONVEX_HULL, MESH );
REVERSE_COLLIDER( a, b, meshHull );
}
bool hullHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( CONVEX_HULL, CONVEX_HULL );
const auto& bvhA = *a.collider.convexHull.bvh;
const auto& bvhB = *b.collider.convexHull.bvh;
auto tA = ::getTransform( a );
auto tB = ::getTransform( b );
auto relTransform = uf::transform::relative( tA, tB );
static thread_local pod::BVH::pairs_t pairs;
pairs.clear();
::queryOverlaps( bvhA, bvhB, relTransform, pairs );
bool hit = false;
for (auto [ viewIdA, viewIdB ] : pairs ) {
auto viewA = ::physicsBodyHullView( a, viewIdA );
auto viewB = ::physicsBodyHullView( b, viewIdB );
pod::Simplex simplex;
if ( !::gjk( viewA, viewB, simplex ) ) continue;
auto result = ::epa( viewA, viewB, simplex );
if ( !uf::vector::isValid(result.point) ) continue;
manifold.points.emplace_back(result);
hit = true;
}
return hit;
}
}

View File

@ -80,7 +80,7 @@ namespace {
}
}
pod::Contact epa( const pod::PhysicsBody& a, const pod::PhysicsBody& b, const pod::Simplex& simplex, uint32_t maxIterations = 64, float eps = EPS ) {
pod::Contact epa( const pod::PhysicsBody& a, const pod::PhysicsBody& b, const pod::Simplex& simplex, uint32_t maxIterations, float eps ) {
UF_ASSERT( ::isValidSimplex(simplex) );
auto faces = ::initialPolytope(simplex);

View File

@ -12,6 +12,18 @@ namespace {
( dir.z >= 0.0f ) ? body.bounds.max.z : body.bounds.min.z
};
} break;
case pod::ShapeType::PLANE: {
const auto& plane = body.collider.plane;
pod::Vector3f n = plane.normal;
float d = plane.offset;
pod::Vector3f basePoint = n * d;
float dot = uf::vector::dot( dir, n );
if ( std::fabs(dot) > 0.9999f ) return basePoint;
pod::Vector3f tangent = uf::vector::normalize( dir - (n * dot) );
return basePoint + tangent * 100000.0f;
} break;
case pod::ShapeType::CAPSULE: {
auto up = uf::quaternion::rotate( transform.orientation, pod::Vector3f{0,1,0} );
auto p1 = transform.position + up * body.collider.capsule.halfHeight;
@ -29,8 +41,34 @@ namespace {
if ( d1 > d2 ) return tri.points[1];
return tri.points[2];
} break;
case pod::ShapeType::CONVEX_HULL: {
const auto transform = ::getTransform( body );
const auto& mesh = *body.collider.convexHull.mesh;
auto selectedViewIdx = body.viewIndex;
pod::Vector3f localDir = uf::quaternion::rotate( uf::quaternion::inverse(transform.orientation), dir );
float maxDist = -FLT_MAX;
pod::Vector3f furthestVertex;
for ( auto viewIdx = 0; viewIdx < mesh.buffer_views.size(); ++viewIdx ) {
if ( 0 <= selectedViewIdx && selectedViewIdx != viewIdx ) continue; // cringe, but saves code duplication (could just alter the bounds above)
const auto& view = mesh.buffer_views[viewIdx];
auto& positions = view["position"];
for ( size_t i = 0; i < view.vertex.count; ++i ) {
pod::Vector3f v = ::getVertex( view, positions, i );
float dist = uf::vector::dot( v, localDir );
if ( dist > maxDist ) {
maxDist = dist;
furthestVertex = v;
}
}
}
return uf::transform::apply( transform, furthestVertex );
} break;
default: {
UF_EXCEPTION("unsupported shape: {}", (int)body.collider.type);
} break;
}
return {};
@ -150,7 +188,7 @@ namespace {
return false;
}
bool gjk( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Simplex& simplex, int maxIterations = 20, float eps = EPS ) {
bool gjk( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Simplex& simplex, int maxIterations, float eps ) {
const float eps2 = eps * eps;
auto dir = ::getPosition( b ) - ::getPosition( a );
if ( uf::vector::magnitude( dir ) < eps2 ) dir = {1,0,0}; // fallback direction
@ -205,4 +243,70 @@ namespace {
return !simplex.pts.empty();
#endif
}
}
// GJK ray-cast
namespace {
pod::Vector3f closestPointOnSimplex( const pod::Vector3f& x, pod::Vector3f* simplex, int& sCount ) {
if ( sCount == 1 ) {
return simplex[0];
} else if ( sCount == 2 ) {
auto p = ::closestPointOnSegment( x, simplex[0], simplex[1] );
return p;
} else if ( sCount == 3 ) {
auto p = ::closestPointOnTriangle( x, simplex[0], simplex[1], simplex[2] );
return p;
}
return x;
}
bool gjk( const pod::Ray& ray, const pod::PhysicsBody& body, float maxDist, float& outT, pod::Vector3f& outNormal, float eps ) {
float t = 0.0f;
pod::Vector3f x = ray.origin;
pod::Vector3f n = {0, 0, 0};
pod::Vector3f supportPoint = ::support( body, -ray.direction );
pod::Vector3f v = x - supportPoint;
pod::Vector3f simplex[4];
int sCount = 0;
for ( int iter = 0; iter < 32; ++iter ) {
float vSq = uf::vector::dot( v, v );
// origin was inside the shape to begin with, or we perfectly converged
if ( vSq < eps * eps ) break;
pod::Vector3f dir = -v;
pod::Vector3f p = ::support( body, dir );
pod::Vector3f w = x - p;
if ( uf::vector::dot( v, w ) > 0.0f ) {
float vDotD = uf::vector::dot( v, ray.direction );
if ( vDotD >= 0.0f ) return false; // miss
float dt = uf::vector::dot( v, w ) / vDotD;
t = t - dt;
if ( t > maxDist ) return false;
x = ray.origin + ray.direction * t;
n = uf::vector::normalize( v );
// reset
sCount = 0;
}
simplex[sCount++] = p;
pod::Vector3f closest = ::closestPointOnSimplex( x, simplex, sCount );
v = x - closest;
if ( sCount == 4 ) break; // collided
}
outT = t;
outNormal = n;
return true;
}
}

View File

@ -11,30 +11,35 @@ namespace {
bool aabbPlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool aabbCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool aabbMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool aabbHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool sphereSphere( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool sphereAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool spherePlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool sphereCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool sphereMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool sphereHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool planeAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool planeSphere( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool planePlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool planeCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool planeMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool planeHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool capsuleCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool capsuleAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool capsulePlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool capsuleSphere( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool capsuleMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool capsuleHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool meshAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool meshSphere( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool meshPlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool meshCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool meshMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool meshHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool triangleTriangle( const pod::TriangleWithNormal& a, const pod::TriangleWithNormal& b, pod::Manifold& manifold, float eps = EPS );
bool triangleAabb( const pod::TriangleWithNormal& tri, const pod::PhysicsBody& body, pod::Manifold& manifold, float eps = EPS );
@ -42,10 +47,23 @@ namespace {
bool trianglePlane( const pod::TriangleWithNormal& tri, const pod::PhysicsBody& body, pod::Manifold& manifold, float eps = EPS );
bool triangleCapsule( const pod::TriangleWithNormal& tri, const pod::PhysicsBody& body, pod::Manifold& manifold, float eps = EPS );
bool hullAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool hullSphere( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool hullPlane( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool hullCapsule( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool hullMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
bool hullHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps = EPS );
// ugh
FORCE_INLINE bool aabbOverlap( const pod::AABB& a, const pod::AABB& b, float eps = EPS );
pod::Vector3f aabbCenter( const pod::AABB& aabb );
pod::Vector3f getVertex( const uf::Mesh::View& view, const uf::Mesh::AttributeView& positions, size_t index );
pod::TriangleWithNormal fetchTriangle( const uf::Mesh& mesh, size_t triID, const pod::PhysicsBody& body, bool fast = false );
// to-do: define maxIterations as a setting
bool gjk( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Simplex& simplex, int maxIterations = 20, float eps = EPS );
pod::Contact epa( const pod::PhysicsBody& a, const pod::PhysicsBody& b, const pod::Simplex& simplex, uint32_t maxIterations = 64, float eps = EPS );
bool gjk( const pod::Ray& ray, const pod::PhysicsBody& body, float maxDist, float& outT, pod::Vector3f& outNormal, float eps = EPS );
void queryBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector<pod::BVH::index_t>& indices );
void queryBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector<pod::BVH::index_t>& indices, pod::BVH::index_t nodeID );
@ -53,6 +71,7 @@ namespace {
void queryBVH( const pod::BVH& bvh, const pod::Ray& ray, uf::stl::vector<pod::BVH::index_t>& indices, pod::BVH::index_t nodeID, float maxDist = FLT_MAX );
void queryOverlaps( const pod::BVH& bvh, pod::BVH::pairs_t& outPairs );
void queryOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, pod::BVH::pairs_t& outPairs );
void queryOverlaps( const pod::BVH& bvhA, const pod::BVH& bvhB, const pod::Transform<>& relTransform, pod::BVH::pairs_t& outPairs );
}
namespace {
@ -107,7 +126,7 @@ namespace {
}
// returns an absolute transform while also allowing offsetting the collision body
// to-do: find a succint way to explain this madness
// to-do: find a succinct way to explain this madness
pod::Transform<> getTransform( const pod::PhysicsBody& body ) {
pod::Transform<> t;
t.position = body.offset;
@ -120,6 +139,26 @@ namespace {
return ::getTransform( body ).position;
}
pod::PhysicsBody physicsBodyHullView( const pod::PhysicsBody& body, int32_t index = -1 ) {
pod::PhysicsBody view = body;
view.viewIndex = index;
return view;
}
pod::PhysicsBody physicsBodyTriView( const pod::PhysicsBody& body, const pod::TriangleWithNormal triangle ) {
pod::PhysicsBody view = body;
view.collider.type = pod::ShapeType::TRIANGLE;
view.collider.triangle = triangle;
// assume triangle is already transformed
view.offset = {};
view.transform = NULL;
return view;
}
pod::PhysicsBody physicsBodyTriView( const pod::PhysicsBody& body, size_t triID ) {
auto tri = ::fetchTriangle( *body.collider.mesh.mesh, triID, body );
return ::physicsBodyTriView( body, tri );
}
bool shouldCollide( const pod::Collider& a, const pod::Collider& b ) {
return ( a.category & b.mask ) && ( b.category & a.mask );
}

View File

@ -16,6 +16,7 @@
#include "capsule.inl"
#include "triangle.inl"
#include "mesh.inl"
#include "convexHull.inl"
#include "ray.inl"
#include "bvh.inl"
#include "gjk.inl"
@ -149,7 +150,7 @@ void uf::physics::impl::step( pod::World& world, float dt ) {
// do not do it for planes
bool shouldReorient = true;
if ( a.collider.type == pod::ShapeType::MESH || b.collider.type == pod::ShapeType::MESH ) shouldReorient = false;
if ( a.collider.type == pod::ShapeType::PLANE || b.collider.type == pod::ShapeType::PLANE ) shouldReorient = false;
//if ( a.collider.type == pod::ShapeType::PLANE || b.collider.type == pod::ShapeType::PLANE ) shouldReorient = false;
if ( shouldReorient ) {
for ( auto& c : manifold.points ) c.normal = ::orientNormalToAB( a, b, c.normal );
}
@ -440,14 +441,23 @@ pod::PhysicsBody& uf::physics::impl::create( pod::World& world, uf::Object& obje
uf::physics::impl::updateInertia( body );
return body;
}
pod::PhysicsBody& uf::physics::impl::create( pod::World& world, uf::Object& object, const uf::Mesh& mesh, float mass, const pod::Vector3f& offset ) {
pod::PhysicsBody& uf::physics::impl::create( pod::World& world, uf::Object& object, const uf::Mesh& mesh, float mass, const pod::Vector3f& offset, bool convex ) {
auto& body = uf::physics::impl::create( world, object, mass, offset );
body.collider.type = pod::ShapeType::MESH;
body.collider.mesh.mesh = &mesh;
body.collider.mesh.bvh = new pod::BVH;
auto& bvh = *body.collider.mesh.bvh;
::buildMeshBVH( bvh, mesh, uf::physics::impl::settings.meshBvhCapacity );
if ( !convex ) {
body.collider.type = pod::ShapeType::MESH;
body.collider.mesh.mesh = &mesh;
body.collider.mesh.bvh = new pod::BVH;
auto& bvh = *body.collider.mesh.bvh;
::buildMeshBVH( bvh, mesh, uf::physics::impl::settings.meshBvhCapacity );
} else {
body.collider.type = pod::ShapeType::CONVEX_HULL;
body.collider.convexHull.mesh = &mesh;
body.collider.convexHull.bvh = new pod::BVH;
auto& bvh = *body.collider.convexHull.bvh;
::buildConvexHullBVH( bvh, mesh/*, uf::physics::impl::settings.meshBvhCapacity*/ );
}
body.bounds = ::computeAABB( body );
uf::physics::impl::updateInertia( body );
@ -483,10 +493,10 @@ pod::PhysicsBody& uf::physics::impl::create( uf::Object& object, const pod::Caps
auto& world = scene.getComponent<pod::World>();
return create( world, object, capsule, mass, offset );
}
pod::PhysicsBody& uf::physics::impl::create( uf::Object& object, const uf::Mesh& mesh, float mass, const pod::Vector3f& offset ) {
pod::PhysicsBody& uf::physics::impl::create( uf::Object& object, const uf::Mesh& mesh, float mass, const pod::Vector3f& offset, bool convex ) {
auto& scene = uf::scene::getCurrentScene();
auto& world = scene.getComponent<pod::World>();
return create( world, object, mesh, mass, offset );
return create( world, object, mesh, mass, offset, convex );
}
void uf::physics::impl::destroy( uf::Object& object ) {
@ -539,6 +549,7 @@ pod::RayQuery uf::physics::impl::rayCast( const pod::Ray& ray, const pod::World&
case pod::ShapeType::PLANE: rayPlane( ray, *b, rayHit ); break;
case pod::ShapeType::CAPSULE: rayCapsule( ray, *b, rayHit ); break;
case pod::ShapeType::MESH: rayMesh( ray, *b, rayHit ); break;
case pod::ShapeType::CONVEX_HULL: rayHull( ray, *b, rayHit ); break;
}
}

View File

@ -69,7 +69,10 @@ namespace {
}
bool generateContacts( pod::PhysicsBody& a, pod::PhysicsBody& b, pod::Manifold& manifold, float dt ) {
if ( uf::physics::impl::settings.useGjk ) return generateContactsGjk( a, b, manifold, dt );
bool useGjk = uf::physics::impl::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 );
::bindManifold( a, b, manifold, dt );
#define CHECK_CONTACT( A, B, fun )\
@ -80,30 +83,44 @@ namespace {
CHECK_CONTACT( AABB, PLANE, aabbPlane );
CHECK_CONTACT( AABB, CAPSULE, aabbCapsule );
CHECK_CONTACT( AABB, MESH, aabbMesh );
CHECK_CONTACT( AABB, CONVEX_HULL, aabbHull );
CHECK_CONTACT( SPHERE, AABB, sphereAabb );
CHECK_CONTACT( SPHERE, SPHERE, sphereSphere );
CHECK_CONTACT( SPHERE, PLANE, spherePlane );
CHECK_CONTACT( SPHERE, CAPSULE, sphereCapsule );
CHECK_CONTACT( SPHERE, MESH, sphereMesh );
CHECK_CONTACT( SPHERE, CONVEX_HULL, sphereHull );
CHECK_CONTACT( PLANE, AABB, planeAabb );
CHECK_CONTACT( PLANE, SPHERE, planeSphere );
CHECK_CONTACT( PLANE, PLANE, planePlane );
CHECK_CONTACT( PLANE, CAPSULE, planeCapsule );
CHECK_CONTACT( PLANE, MESH, planeMesh );
CHECK_CONTACT( PLANE, CONVEX_HULL, planeHull );
CHECK_CONTACT( CAPSULE, AABB, capsuleAabb );
CHECK_CONTACT( CAPSULE, SPHERE, capsuleSphere );
CHECK_CONTACT( CAPSULE, PLANE, capsulePlane );
CHECK_CONTACT( CAPSULE, CAPSULE, capsuleCapsule );
CHECK_CONTACT( CAPSULE, MESH, capsuleMesh );
CHECK_CONTACT( CAPSULE, CONVEX_HULL, capsuleHull );
CHECK_CONTACT( MESH, AABB, meshAabb );
CHECK_CONTACT( MESH, SPHERE, meshSphere );
CHECK_CONTACT( MESH, PLANE, meshPlane );
CHECK_CONTACT( MESH, CAPSULE, meshCapsule );
CHECK_CONTACT( MESH, MESH, meshMesh );
CHECK_CONTACT( MESH, CONVEX_HULL, meshHull );
CHECK_CONTACT( CONVEX_HULL, AABB, hullAabb );
CHECK_CONTACT( CONVEX_HULL, SPHERE, hullSphere );
CHECK_CONTACT( CONVEX_HULL, PLANE, hullPlane );
CHECK_CONTACT( CONVEX_HULL, CAPSULE, hullCapsule );
CHECK_CONTACT( CONVEX_HULL, MESH, hullMesh );
CHECK_CONTACT( CONVEX_HULL, CONVEX_HULL, hullHull );
UF_EXCEPTION("unregistered contact: {} vs {}", (int) a.collider.type, (int) b.collider.type );
return false;
}

View File

@ -1,17 +1,3 @@
/*
// transform capsule line segments to local space
p1 = uf::transform::applyInverse( mesh.transform, p1 );
p2 = uf::transform::applyInverse( mesh.transform, p2 );
*/
/*
contact = uf::transform::apply( mesh.transform, contact );
normal = uf::quaternion::rotate( mesh.transform.orientation, normal );
*/
//
namespace {
bool meshAabb( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( MESH, AABB );
@ -113,7 +99,7 @@ namespace {
return hit;
}
// to-do
bool meshMesh( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( MESH, MESH );
@ -123,29 +109,61 @@ namespace {
const auto& bvhB = *b.collider.mesh.bvh;
const auto& meshB = *b.collider.mesh.mesh;
auto tA = ::getTransform( a );
auto tB = ::getTransform( b );
auto relTransform = uf::transform::relative( tA, tB );
// compute overlaps between one BVH and another BVH
static thread_local pod::BVH::pairs_t pairs;
pairs.clear();
//UF_TIMER_MULTITRACE_START("Colliding {} ({} indices) <=> {} ({} indices)", a.object->getName(), bvhA.indices.size(), b.object->getName(), bvhB.indices.size());
//UF_TIMER_MULTITRACE("Querying overlaps...");
::queryOverlaps( bvhA, bvhB, pairs );
//UF_TIMER_MULTITRACE("Queried overlaps.");
::queryOverlaps( bvhA, bvhB, relTransform, pairs );
bool hit = false;
// do collision per triangle
//UF_TIMER_MULTITRACE("Colliding triangles (pairs={})...", pairs.size());
for (auto [idA, idB] : pairs ) {
auto tA = ::fetchTriangle( meshA, idA, a ); // transform triangles to world space
auto tB = ::fetchTriangle( meshB, idB, b );
auto triA = ::fetchTriangle( meshA, idA, a ); // transform triangles to world space
auto triB = ::fetchTriangle( meshB, idB, b );
bool collides = ::triangleTriangle( triA, triB, manifold, eps );
if ( !collides ) continue;
hit = true;
}
return hit;
}
bool meshHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( MESH, CONVEX_HULL );
const auto& mesh = a;
const auto& hull = b;
const auto& bvhA = *a.collider.mesh.bvh;
const auto& meshA = *a.collider.mesh.mesh;
const auto& bvhB = *b.collider.convexHull.bvh;
const auto& meshB = *b.collider.convexHull.mesh;
auto tA = ::getTransform( a );
auto tB = ::getTransform( b );
auto relTransform = uf::transform::relative( tA, tB );
// compute overlaps between one BVH and another BVH
static thread_local pod::BVH::pairs_t pairs;
pairs.clear();
::queryOverlaps( bvhA, bvhB, relTransform, pairs );
bool hit = false;
// do collision per hull and triangle
for (auto [ triID, hullID ] : pairs ) {
auto triView = ::physicsBodyTriView( mesh, triID );
auto hullView = ::physicsBodyHullView( hull, hullID );
bool collides = ::triangleHull( triView, hullView, manifold, eps );
bool collides = ::triangleTriangle( tA, tB, manifold, eps );
if ( !collides ) continue;
hit = true;
}
//UF_TIMER_MULTITRACE("Collided triangles.");
//UF_TIMER_MULTITRACE_END("Collided mesh.");
return hit;
}
}

View File

@ -54,4 +54,8 @@ namespace {
ASSERT_COLLIDER_TYPES( PLANE, MESH );
REVERSE_COLLIDER( a, b, meshPlane );
}
bool planeHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( PLANE, CONVEX_HULL );
REVERSE_COLLIDER( a, b, hullPlane );
}
}

View File

@ -229,4 +229,38 @@ namespace {
return rayHit.hit;
}
bool rayHull( const pod::Ray& r, const pod::PhysicsBody& body, pod::RayQuery& rayHit ) {
const auto& bvh = *body.collider.convexHull.bvh;
const auto& meshData = *body.collider.convexHull.mesh;
const auto transform = ::getTransform( body );
pod::Ray ray;
ray.origin = uf::transform::applyInverse( transform, r.origin );
ray.direction = uf::quaternion::rotate( uf::quaternion::inverse( transform.orientation ), r.direction );
thread_local uf::stl::vector<pod::BVH::index_t> candidates;
candidates.clear();
::queryBVH( bvh, ray, candidates );
for ( auto hullID : candidates ) {
auto hullView = ::physicsBodyHullView( body, hullID );
float t;
pod::Vector3f normal;
if ( !::gjk( ray, hullView, rayHit.contact.penetration, t, normal ) ) continue;
if ( t >= rayHit.contact.penetration ) continue;
rayHit.hit = true;
rayHit.body = &body;
rayHit.contact.point = uf::transform::apply( transform, ray.origin + ray.direction * t );
rayHit.contact.normal = uf::quaternion::rotate( transform.orientation, normal );
rayHit.contact.penetration = t;
}
return rayHit.hit;
}
}

View File

@ -56,4 +56,8 @@ namespace {
ASSERT_COLLIDER_TYPES( SPHERE, MESH );
REVERSE_COLLIDER( a, b, meshSphere );
}
bool sphereHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( SPHERE, CONVEX_HULL );
REVERSE_COLLIDER( a, b, hullSphere );
}
}

View File

@ -120,7 +120,7 @@ namespace {
}
// if body is a mesh, apply its transform to the triangles, else reorient the normal with respect to the body
pod::TriangleWithNormal fetchTriangle( const uf::Mesh& mesh, size_t triID, const pod::PhysicsBody& body, bool fast = false ) {
pod::TriangleWithNormal fetchTriangle( const uf::Mesh& mesh, size_t triID, const pod::PhysicsBody& body, bool fast ) {
auto tri = ::fetchTriangle( mesh, triID );
auto transform = ::getTransform( body );
@ -566,4 +566,27 @@ namespace {
ASSERT_COLLIDER_TYPES( TRIANGLE, CAPSULE );
return ::triangleCapsule( a.collider.triangle, b, manifold, eps );
}
bool triangleHull( const pod::PhysicsBody& a, const pod::PhysicsBody& b, pod::Manifold& manifold, float eps ) {
ASSERT_COLLIDER_TYPES( TRIANGLE, CONVEX_HULL );
const auto& tri = a;
const auto& hull = b;
pod::Simplex simplex;
if ( !::gjk( tri, hull, simplex ) ) return false;
auto result = ::epa( tri, hull, simplex );
if ( !uf::vector::isValid(result.point) ) return false;
manifold.points.emplace_back( result );
return true;
}
bool triangleHull( const pod::TriangleWithNormal& tri, const pod::PhysicsBody& body, pod::Manifold& manifold, float eps ) {
pod::PhysicsBody triView = {};
triView.collider.type = pod::ShapeType::TRIANGLE;
triView.collider.triangle = tri;
return ::triangleHull( triView, body, manifold, eps );
}
}