wasted a week on trying to roll out my own physics system just to be humbled when it almost works but doesn't work well enough (probably a bad solver, will probably revisit it at a later date)
This commit is contained in:
parent
65cf3887b7
commit
40da94c422
2
Makefile
2
Makefile
@ -66,7 +66,7 @@ ifneq (,$(findstring win64,$(ARCH)))
|
||||
REQ_DEPS += meshoptimizer toml xatlas curl ffx:fsr dc:texconv # vall_e cpptrace # openvr # ncurses draco discord bullet ultralight-ux
|
||||
FLAGS += -march=native -g # -flto # -g
|
||||
endif
|
||||
REQ_DEPS += $(RENDERER) json:nlohmann zlib luajit reactphysics simd ctti gltf imgui fmt freetype openal ogg wav
|
||||
REQ_DEPS += $(RENDERER) json:nlohmann zlib luajit reactphysics simd ctti gltf imgui fmt freetype openal ogg wav
|
||||
FLAGS += -DUF_ENV_WINDOWS -DUF_ENV_WIN64 -DWIN32_LEAN_AND_MEAN
|
||||
DEPS += -lgdi32 -ldwmapi
|
||||
LINKS += #-Wl,-subsystem,windows
|
||||
|
||||
@ -63,12 +63,12 @@
|
||||
"air": 0.1,
|
||||
|
||||
"crouch": 1,
|
||||
"jump": [ 0, 6, 0 ],
|
||||
"jump": [ 0, 8, 0 ],
|
||||
"look": 1,
|
||||
"floored": {
|
||||
"feet": [ 0, -1.5, 0 ],
|
||||
"feet": [ 0, -1, 0 ],
|
||||
// "floor": [ 0, -0.5, 0 ],
|
||||
"floor": [ 0, -1.0, 0 ],
|
||||
"floor": [ 0, -1, 0 ],
|
||||
"print": false
|
||||
},
|
||||
"strafe": true
|
||||
@ -77,10 +77,17 @@
|
||||
"gravity": [ 0, -9.81, 0 ],
|
||||
"inertia": [ 0, 0, 0 ],
|
||||
|
||||
// "type": "sphere",
|
||||
// "radius": 2,
|
||||
|
||||
"type": "capsule",
|
||||
"radius": 1,
|
||||
"height": 2,
|
||||
|
||||
// "type": "bounding box",
|
||||
// "min": [ -1.0, -2.0, -1.0 ],
|
||||
// "max": [ 1.0, 0.0, 1.0 ],
|
||||
|
||||
"mass": 100,
|
||||
"friction": 0.95,
|
||||
"restitution": 0.0,
|
||||
|
||||
@ -7,6 +7,14 @@
|
||||
"metadata": {
|
||||
"graph": {
|
||||
"tags": {
|
||||
" worldspawn": {
|
||||
// "physics": { "type": "bounding box", "static": true },
|
||||
"physics": { "type": "plane", "static": true, "direction": [ 0, 1, 0 ], "offset": 0 },
|
||||
"grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true },
|
||||
"optimize meshlets": { "simplify": 0.125, "print": false },
|
||||
"unwrap mesh": true
|
||||
},
|
||||
|
||||
"/^prop_/": { "action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } } },
|
||||
"/^func_/": { "action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } } }
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
clang
|
||||
gcc
|
||||
@ -67,6 +67,7 @@ namespace uf {
|
||||
|
||||
void setParent();
|
||||
void setParent( uf::Entity& parent );
|
||||
|
||||
uf::Entity& addChild( uf::Entity& child );
|
||||
void addChild( uf::Entity* child );
|
||||
void moveChild( uf::Entity& child );
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
#include <uf/engine/object/object.h>
|
||||
#include <uf/utils/math/transform.h>
|
||||
#include <uf/utils/mesh/mesh.h>
|
||||
#include <uf/utils/math/collision.h>
|
||||
#include <uf/engine/graph/graph.h>
|
||||
|
||||
#if UF_USE_REACTPHYSICS
|
||||
@ -25,21 +24,18 @@ namespace pod {
|
||||
rp3d::PhysicsWorld* world = NULL;
|
||||
|
||||
pod::Transform<> transform = {};
|
||||
|
||||
struct Linear {
|
||||
pod::Vector3f velocity;
|
||||
pod::Vector3f acceleration;
|
||||
} linear;
|
||||
struct Angular {
|
||||
pod::Quaternion<> velocity;
|
||||
pod::Quaternion<> acceleration;
|
||||
} rotational;
|
||||
pod::Vector3f velocity;
|
||||
pod::Vector3f acceleration;
|
||||
pod::Quaternion<> angularVelocity;
|
||||
pod::Quaternion<> angularAcceleration;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
pod::Transform<> transform = {};
|
||||
Linear linear;
|
||||
Angular rotational;
|
||||
pod::Vector3f velocity;
|
||||
pod::Vector3f acceleration;
|
||||
pod::Quaternion<> angularVelocity;
|
||||
pod::Quaternion<> angularAcceleration;
|
||||
} current, previous;
|
||||
} internal;
|
||||
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
#define UF_NS_GET_LAST(name) uf::string::namespaceGetLast(#name)
|
||||
#define UF_NS_REMOVE_FIRST(name) uf::string::namespaceRemoveFirst(#name)
|
||||
|
||||
#define S_1(x) #x
|
||||
#define S_2(x) S_1(x)
|
||||
|
||||
#define TIMER(x, ...)\
|
||||
static bool first = true;\
|
||||
static uf::Timer<long long> timer(false);\
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
#if UF_USE_UNUSED
|
||||
#include "./collision/gjk.h"
|
||||
#include "./collision/boundingbox.h"
|
||||
#include "./collision/sphere.h"
|
||||
#include "./collision/mesh.h"
|
||||
#include <uf/utils/memory/vector.h>
|
||||
|
||||
namespace uf {
|
||||
class UF_API Collider {
|
||||
public:
|
||||
typedef uf::stl::vector<pod::Collider*> container_t;
|
||||
protected:
|
||||
uf::Collider::container_t m_container;
|
||||
public:
|
||||
~Collider();
|
||||
void clear();
|
||||
|
||||
void add( pod::Collider* );
|
||||
uf::Collider::container_t& getContainer();
|
||||
const uf::Collider::container_t& getContainer() const;
|
||||
|
||||
std::size_t getSize() const;
|
||||
|
||||
uf::stl::vector<pod::Collider::Manifold> intersects( const uf::Collider&, bool = false ) const;
|
||||
uf::stl::vector<pod::Collider::Manifold> intersects( const pod::Collider&, bool = false ) const;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#if UF_USE_UNUSED
|
||||
#include "gjk.h"
|
||||
|
||||
namespace uf {
|
||||
class UF_API BoundingBox : public pod::Collider {
|
||||
protected:
|
||||
pod::Vector3 m_origin;
|
||||
pod::Vector3 m_corner;
|
||||
public:
|
||||
BoundingBox( const pod::Vector3& = {}, const pod::Vector3& = {} );
|
||||
|
||||
const pod::Vector3& getOrigin() const;
|
||||
const pod::Vector3& getCorner() const;
|
||||
void setOrigin( const pod::Vector3& );
|
||||
void setCorner( const pod::Vector3& );
|
||||
|
||||
pod::Vector3 min() const;
|
||||
pod::Vector3 max() const;
|
||||
pod::Vector3 closest( const pod::Vector3f& ) const;
|
||||
|
||||
virtual uf::stl::string type() const;
|
||||
virtual pod::Vector3* expand() const;
|
||||
virtual pod::Vector3 support( const pod::Vector3& ) const;
|
||||
pod::Collider::Manifold intersects( const uf::BoundingBox& ) const;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@ -1,90 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
|
||||
#if UF_USE_UNUSED
|
||||
#include <uf/utils/math/vector.h>
|
||||
#include <uf/utils/math/transform.h>
|
||||
|
||||
namespace pod {
|
||||
struct UF_API Simplex {
|
||||
public:
|
||||
struct UF_API SupportPoint {
|
||||
pod::Vector3 a, b, v;
|
||||
bool operator==( const pod::Simplex::SupportPoint& r ) const {
|
||||
return v == r.v;
|
||||
}
|
||||
pod::Vector3 operator-( const pod::Simplex::SupportPoint& r ) const {
|
||||
return v - r.v;
|
||||
}
|
||||
pod::Vector3 operator*( float r ) const {
|
||||
return v * r;
|
||||
}
|
||||
pod::Vector3 operator-( const pod::Vector3& r ) const {
|
||||
return v - r;
|
||||
}
|
||||
pod::Math::num_t dot( const pod::Vector3& r ) const {
|
||||
return uf::vector::dot(v, r);
|
||||
}
|
||||
pod::Vector3 cross( const pod::Simplex::SupportPoint& r ) const {
|
||||
return uf::vector::cross(v, r.v);
|
||||
}
|
||||
pod::Vector3 cross( const pod::Vector3& r ) const {
|
||||
return uf::vector::cross(v, r);
|
||||
}
|
||||
};
|
||||
int size = 0;
|
||||
pod::Simplex::SupportPoint b, c, d;
|
||||
/*
|
||||
pod::Vector3 b, c, d;
|
||||
Simplex( const pod::Vector3& = pod::Vector3(), const pod::Vector3& = pod::Vector3(), const pod::Vector3& = pod::Vector3() );
|
||||
void add( const pod::Vector3& );
|
||||
void set( const pod::Vector3& = pod::Vector3(), const pod::Vector3& = pod::Vector3(), const pod::Vector3& = pod::Vector3() );
|
||||
*/
|
||||
};
|
||||
}
|
||||
namespace pod {
|
||||
class UF_API Collider {
|
||||
protected:
|
||||
pod::Transform<> m_transform;
|
||||
public:
|
||||
struct UF_API Manifold {
|
||||
const pod::Collider* a;
|
||||
const pod::Collider* b;
|
||||
pod::Vector3 normal;
|
||||
float depth;
|
||||
bool colliding;
|
||||
|
||||
Manifold() {
|
||||
this->normal = pod::Vector3{0.0f, 0.0f, 0.0f};
|
||||
this->depth = -0.1f;
|
||||
this->colliding = false;
|
||||
}
|
||||
Manifold( const pod::Collider& a, const pod::Collider& b ) {
|
||||
this->a = &a;
|
||||
this->b = &b;
|
||||
this->normal = pod::Vector3{0.0f, 0.0f, 0.0f};
|
||||
this->depth = -0.1f;
|
||||
this->colliding = false;
|
||||
}
|
||||
Manifold( const pod::Collider& a, const pod::Collider& b, const pod::Vector3& normal, float depth, bool colliding ) {
|
||||
this->a = &a;
|
||||
this->b = &b;
|
||||
this->normal = normal;
|
||||
this->depth = depth;
|
||||
this->colliding = colliding;
|
||||
}
|
||||
};
|
||||
virtual ~Collider();
|
||||
virtual uf::stl::string type() const;
|
||||
virtual pod::Vector3* expand() const = 0;
|
||||
virtual pod::Vector3 support( const pod::Vector3& ) const = 0;
|
||||
pod::Collider::Manifold intersects( const pod::Collider& ) const;
|
||||
|
||||
pod::Vector3f getPosition() const;
|
||||
pod::Transform<>& getTransform();
|
||||
const pod::Transform<>& getTransform() const;
|
||||
void setTransform( const pod::Transform<>& );
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#if UF_USE_UNUSED
|
||||
#include "gjk.h"
|
||||
#include <uf/utils/mesh/mesh.h>
|
||||
|
||||
namespace uf {
|
||||
class UF_API MeshCollider : public pod::Collider {
|
||||
protected:
|
||||
uf::stl::vector<pod::Vector3> m_positions;
|
||||
public:
|
||||
MeshCollider( const pod::Transform<>& = {}, const uf::stl::vector<pod::Vector3>& = {} );
|
||||
|
||||
uf::stl::vector<pod::Vector3>& getPositions();
|
||||
const uf::stl::vector<pod::Vector3>& getPositions() const;
|
||||
|
||||
void setPositions( const uf::stl::vector<pod::Vector3>& );
|
||||
|
||||
#if 0
|
||||
template<typename T, typename U>
|
||||
void setPositions( const uf::Mesh<T, U>& mesh ) {
|
||||
this->m_positions.clear();
|
||||
this->m_positions.reserve( std::max( mesh.vertices.size(), mesh.indices.size() ) );
|
||||
if ( !mesh.indices.empty() ) {
|
||||
for ( auto& index : mesh.indices ) this->m_positions.push_back( mesh.vertices[index].position );
|
||||
} else {
|
||||
for ( auto& vertex : mesh.vertices ) this->m_positions.push_back( vertex.position );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
virtual uf::stl::string type() const;
|
||||
virtual pod::Vector3* expand() const;
|
||||
virtual pod::Vector3 support( const pod::Vector3& ) const;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#if UF_USE_UNUSED
|
||||
#include "gjk.h"
|
||||
#include <functional>
|
||||
|
||||
namespace uf {
|
||||
class UF_API ModularCollider : public pod::Collider {
|
||||
public:
|
||||
typedef std::function<pod::Vector3*()> function_expand_t;
|
||||
typedef std::function<pod::Vector3(const pod::Vector3&)> function_support_t;
|
||||
protected:
|
||||
uint m_len;
|
||||
pod::Vector3* m_container;
|
||||
|
||||
bool m_should_free;
|
||||
|
||||
uf::ModularCollider::function_expand_t m_function_expand;
|
||||
uf::ModularCollider::function_support_t m_function_support;
|
||||
public:
|
||||
ModularCollider( uint len = 0, pod::Vector3* = NULL, bool = false, const uf::ModularCollider::function_expand_t& = NULL, const uf::ModularCollider::function_support_t& = NULL );
|
||||
~ModularCollider();
|
||||
|
||||
void setExpand( const uf::ModularCollider::function_expand_t& = NULL );
|
||||
void setSupport( const uf::ModularCollider::function_support_t& = NULL );
|
||||
|
||||
pod::Vector3* getContainer();
|
||||
uint getSize() const;
|
||||
void setContainer( pod::Vector3*, uint );
|
||||
|
||||
virtual uf::stl::string type() const;
|
||||
virtual pod::Vector3* expand() const;
|
||||
virtual pod::Vector3 support( const pod::Vector3& ) const;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#if UF_USE_UNUSED
|
||||
#include "gjk.h"
|
||||
|
||||
namespace uf {
|
||||
class UF_API SphereCollider : public pod::Collider {
|
||||
protected:
|
||||
float m_radius;
|
||||
pod::Vector3 m_origin;
|
||||
public:
|
||||
SphereCollider( float = 1.0f, const pod::Vector3& = pod::Vector3{} );
|
||||
|
||||
float getRadius() const;
|
||||
const pod::Vector3& getOrigin() const;
|
||||
|
||||
void setRadius( float = 1.0f );
|
||||
void setOrigin( const pod::Vector3& );
|
||||
|
||||
virtual uf::stl::string type() const;
|
||||
virtual pod::Vector3* expand() const;
|
||||
virtual pod::Vector3 support( const pod::Vector3& ) const;
|
||||
pod::Collider::Manifold intersects( const uf::SphereCollider& ) const;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@ -5,12 +5,10 @@
|
||||
#include <uf/utils/time/time.h>
|
||||
#include <uf/engine/object/object.h>
|
||||
|
||||
#if UF_USE_BULLET
|
||||
#include <uf/ext/bullet/bullet.h>
|
||||
#elif UF_USE_REACTPHYSICS
|
||||
#if UF_USE_REACTPHYSICS
|
||||
#include <uf/ext/reactphysics/reactphysics.h>
|
||||
#else
|
||||
#include "physics/stub.h"
|
||||
#include "physics/impl.h"
|
||||
#endif
|
||||
|
||||
namespace uf {
|
||||
@ -25,9 +23,13 @@ namespace uf {
|
||||
void UF_API initialize( uf::Object& );
|
||||
void UF_API tick( uf::Object& );
|
||||
void UF_API terminate( uf::Object& );
|
||||
#if 0
|
||||
template<typename T> pod::Transform<T>& update( pod::Transform<T>& transform, pod::Physics& physics );
|
||||
template<typename T> pod::Transform<T>& update( pod::Physics& physics, pod::Transform<T>& transform );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#include "physics/pod.inl"
|
||||
#if 0
|
||||
#include "physics/pod.inl"
|
||||
#endif
|
||||
201
engine/inc/uf/utils/math/physics/impl.h
Normal file
201
engine/inc/uf/utils/math/physics/impl.h
Normal file
@ -0,0 +1,201 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/utils/math/vector.h>
|
||||
#include <uf/utils/math/quaternion.h>
|
||||
#include <uf/utils/math/matrix.h>
|
||||
#include <uf/utils/math/shapes.h>
|
||||
#include <uf/utils/math/transform.h>
|
||||
|
||||
#include <uf/utils/memory/vector.h>
|
||||
#include <uf/utils/memory/unordered_map.h>
|
||||
|
||||
#include <uf/engine/object/object.h>
|
||||
#include <cfloat>
|
||||
|
||||
namespace uf {
|
||||
class Mesh;
|
||||
}
|
||||
|
||||
namespace pod {
|
||||
enum class ShapeType {
|
||||
AABB,
|
||||
SPHERE,
|
||||
PLANE,
|
||||
CAPSULE,
|
||||
MESH,
|
||||
};
|
||||
|
||||
struct SupportPoint {
|
||||
pod::Vector3f p; // Minkowski difference point
|
||||
pod::Vector3f pA; // original support point in A
|
||||
pod::Vector3f pB; // original support point in B
|
||||
};
|
||||
|
||||
struct Simplex {
|
||||
//uf::stl::vector<pod::Vector3f> pts;
|
||||
uf::stl::vector<pod::SupportPoint> pts;
|
||||
};
|
||||
|
||||
struct Face {
|
||||
// pod::Vector3f a,b,c;
|
||||
pod::SupportPoint a, b, c;
|
||||
pod::Vector3f normal;
|
||||
float distance;
|
||||
};
|
||||
|
||||
struct BVH {
|
||||
typedef uf::stl::vector<std::pair<int,int>> pair_t;
|
||||
struct Node {
|
||||
pod::AABB bounds = {};
|
||||
int left = -1;
|
||||
int right = -1;
|
||||
int start = 0;
|
||||
int count = 0;
|
||||
};
|
||||
uf::stl::vector<size_t> indices;
|
||||
uf::stl::vector<pod::BVH::Node> nodes;
|
||||
};
|
||||
|
||||
struct MeshCollider {
|
||||
pod::BVH* bvh;
|
||||
const uf::Mesh* mesh;
|
||||
};
|
||||
|
||||
typedef uint32_t CollisionMask;
|
||||
|
||||
struct Collider {
|
||||
pod::ShapeType type;
|
||||
pod::CollisionMask mask = 0xFFFFFFFF;
|
||||
union {
|
||||
pod::Sphere sphere;
|
||||
pod::AABB aabb;
|
||||
pod::Plane plane;
|
||||
pod::Capsule capsule;
|
||||
pod::MeshCollider mesh;
|
||||
} u;
|
||||
};
|
||||
|
||||
struct PhysicsMaterial {
|
||||
float restitution = 0.2f;
|
||||
float staticFriction = 0.5f;
|
||||
float dynamicFriction = 0.3f;
|
||||
};
|
||||
|
||||
struct World; // forward declare
|
||||
|
||||
struct RigidBody {
|
||||
pod::World* world = NULL;
|
||||
uf::Object* object = NULL;
|
||||
pod::Transform<>* transform = NULL;
|
||||
|
||||
bool isStatic = false;
|
||||
|
||||
float mass = 1.0f;
|
||||
float inverseMass = 1.0f;
|
||||
|
||||
pod::Vector3f velocity = {};
|
||||
pod::Vector3f forceAccumulator = {};
|
||||
|
||||
pod::Vector3f angularVelocity = {};
|
||||
pod::Vector3f torqueAccumulator = {};
|
||||
|
||||
pod::Vector3f inertiaTensor = { 1, 1, 1 };
|
||||
pod::Vector3f inverseInertiaTensor = { 1, 1, 1 };
|
||||
|
||||
pod::AABB bounds;
|
||||
pod::Collider collider;
|
||||
pod::PhysicsMaterial material;
|
||||
};
|
||||
|
||||
struct Contact {
|
||||
pod::Vector3f point;
|
||||
pod::Vector3f normal;
|
||||
float penetration;
|
||||
|
||||
// Warm-start cached values
|
||||
int lifetime = 0;
|
||||
pod::Vector3f tangent = {};
|
||||
float accumulatedNormalImpulse = 0.0f;
|
||||
float accumulatedTangentImpulse = 0.0f;
|
||||
};
|
||||
|
||||
struct Manifold {
|
||||
pod::RigidBody* a = NULL;
|
||||
pod::RigidBody* b = NULL;
|
||||
float dt = 0;
|
||||
uf::stl::vector<pod::Contact> points;
|
||||
};
|
||||
|
||||
struct RayQuery {
|
||||
bool hit = false;
|
||||
const pod::RigidBody* body;
|
||||
pod::Contact contact = { pod::Vector3f{}, pod::Vector3f{}, FLT_MAX };
|
||||
};
|
||||
|
||||
struct World {
|
||||
uf::stl::vector<pod::RigidBody*> bodies;
|
||||
|
||||
pod::Vector3f gravity = { 0, -9.81f, 0 };
|
||||
pod::BVH bvh;
|
||||
};
|
||||
}
|
||||
|
||||
namespace uf {
|
||||
namespace physics {
|
||||
namespace impl {
|
||||
extern UF_API float timescale;
|
||||
extern UF_API bool interpolate;
|
||||
extern UF_API bool shared;
|
||||
extern UF_API bool globalStorage;
|
||||
|
||||
extern UF_API pod::World world;
|
||||
|
||||
void UF_API initialize();
|
||||
void UF_API initialize( uf::Object& );
|
||||
void UF_API initialize( pod::World& );
|
||||
|
||||
void UF_API tick( float = 0 );
|
||||
void UF_API tick( uf::Object&, float = 0 );
|
||||
void UF_API tick( pod::World&, float = 0 );
|
||||
|
||||
void UF_API terminate();
|
||||
void UF_API terminate( uf::Object& );
|
||||
void UF_API terminate( pod::World& );
|
||||
|
||||
void UF_API step( pod::World&, float dt );
|
||||
void UF_API substep( pod::World&, float dt, int substeps );
|
||||
|
||||
void UF_API setMass( pod::RigidBody& body, float mass = 0.0f );
|
||||
void UF_API setInertia( pod::RigidBody& body );
|
||||
void UF_API applyForce( pod::RigidBody& body, const pod::Vector3f& force );
|
||||
void UF_API applyForceAtPoint( pod::RigidBody body, const pod::Vector3f& force, const pod::Vector3f& point );
|
||||
void UF_API applyImpulse( pod::RigidBody& body, const pod::Vector3f& impulse );
|
||||
void UF_API applyTorque( pod::RigidBody& body, const pod::Vector3f& torque );
|
||||
|
||||
void UF_API setVelocity( pod::RigidBody& body, const pod::Vector3f& velocity );
|
||||
void UF_API applyRotation( pod::RigidBody& body, const pod::Quaternion<>& q );
|
||||
void UF_API applyRotation( pod::RigidBody& body, const pod::Vector3f& axis, float angle );
|
||||
|
||||
pod::RigidBody& UF_API create( uf::Object&, float mass = 0.0f );
|
||||
pod::RigidBody& UF_API create( uf::Object& object, const pod::AABB& aabb, float mass = 0 );
|
||||
pod::RigidBody& UF_API create( uf::Object& object, const pod::Sphere& sphere, float mass = 0 );
|
||||
pod::RigidBody& UF_API create( uf::Object& object, const pod::Plane& plane, float mass = 0 );
|
||||
pod::RigidBody& UF_API create( uf::Object& object, const pod::Capsule& capsule, float mass = 0 );
|
||||
pod::RigidBody& UF_API create( uf::Object&, const uf::Mesh& mesh, float mass = 0 );
|
||||
|
||||
pod::RigidBody& UF_API create( pod::World&, uf::Object&, float mass = 0.0f );
|
||||
pod::RigidBody& UF_API create( pod::World&, uf::Object& object, const pod::AABB& aabb, float mass = 0 );
|
||||
pod::RigidBody& UF_API create( pod::World&, uf::Object& object, const pod::Sphere& sphere, float mass = 0 );
|
||||
pod::RigidBody& UF_API create( pod::World&, uf::Object& object, const pod::Plane& plane, float mass = 0 );
|
||||
pod::RigidBody& UF_API create( pod::World&, uf::Object& object, const pod::Capsule& capsule, float mass = 0 );
|
||||
pod::RigidBody& UF_API create( pod::World&, uf::Object&, const uf::Mesh& mesh, float mass = 0 );
|
||||
|
||||
void UF_API destroy( uf::Object& );
|
||||
void UF_API destroy( pod::RigidBody& );
|
||||
|
||||
pod::RayQuery UF_API rayCast( const pod::Ray&, const pod::RigidBody&, float = FLT_MAX );
|
||||
pod::RayQuery UF_API rayCast( const pod::Ray&, const pod::World&, float = FLT_MAX );
|
||||
pod::RayQuery UF_API rayCast( const pod::Ray&, const pod::World&, const pod::RigidBody*, float = FLT_MAX );
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,14 +2,14 @@
|
||||
template<typename T> pod::Transform<T>& uf::physics::update( pod::Transform<T>& transform, pod::Physics& physics ) {
|
||||
// physics.internal.previous = transform;
|
||||
|
||||
if ( physics.linear.acceleration != pod::Vector3t<T>{0,0,0} )
|
||||
physics.linear.velocity += (physics.linear.acceleration * uf::physics::time::delta);
|
||||
if ( physics.rotational.acceleration != pod::Quaternion<T>{0,0,0,0} ) {
|
||||
physics.rotational.velocity = uf::quaternion::multiply(physics.rotational.velocity, physics.rotational.acceleration*uf::physics::time::delta);
|
||||
if ( physics.acceleration != pod::Vector3t<T>{0,0,0} )
|
||||
physics.velocity += (physics.acceleration * uf::physics::time::delta);
|
||||
if ( physics.angularAcceleration != pod::Quaternion<T>{0,0,0,0} ) {
|
||||
physics.angularVelocity = uf::quaternion::multiply(physics.angularVelocity, physics.angularAcceleration*uf::physics::time::delta);
|
||||
}
|
||||
|
||||
transform = uf::transform::move( transform, physics.linear.velocity*uf::physics::time::delta );
|
||||
transform = uf::transform::rotate( transform, uf::vector::normalize(physics.rotational.velocity*uf::physics::time::delta));
|
||||
transform = uf::transform::move( transform, physics.velocity*uf::physics::time::delta );
|
||||
transform = uf::transform::rotate( transform, uf::vector::normalize(physics.angularVelocity*uf::physics::time::delta));
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
@ -1,108 +0,0 @@
|
||||
// enough things to get it to compile without a physics lib
|
||||
namespace uf {
|
||||
struct Mesh;
|
||||
}
|
||||
|
||||
namespace pod {
|
||||
struct UF_API Physics {
|
||||
size_t uid = 0;
|
||||
uf::Object* object = NULL;
|
||||
|
||||
bool shared = false; // share control of the transform both in-engine and bullet, set to true if you're directly modifying the transform
|
||||
void* body = NULL;
|
||||
void* shape = NULL;
|
||||
|
||||
void* world = NULL;
|
||||
|
||||
pod::Transform<> transform = {};
|
||||
|
||||
struct Linear {
|
||||
pod::Vector3f velocity;
|
||||
pod::Vector3f acceleration;
|
||||
} linear;
|
||||
struct Angular {
|
||||
pod::Quaternion<> velocity;
|
||||
pod::Quaternion<> acceleration;
|
||||
} rotational;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
pod::Transform<> transform = {};
|
||||
Linear linear;
|
||||
Angular rotational;
|
||||
} current, previous;
|
||||
} internal;
|
||||
|
||||
|
||||
struct {
|
||||
uint32_t flags = 0;
|
||||
float mass = 0.0f;
|
||||
float friction = 0.8f;
|
||||
float restitution = 0.0f;
|
||||
pod::Vector3f inertia = {0, 0, 0};
|
||||
pod::Vector3f gravity = {0, 0, 0};
|
||||
} stats;
|
||||
};
|
||||
|
||||
typedef Physics PhysicsState;
|
||||
}
|
||||
|
||||
namespace uf {
|
||||
namespace physics {
|
||||
namespace impl {
|
||||
typedef void* WorldState;
|
||||
inline void UF_API initialize() {}
|
||||
inline void UF_API initialize( uf::Object& ) {}
|
||||
inline void UF_API tick( float = 0 ) {}
|
||||
inline void UF_API tick( uf::Object&, float = 0 ) {}
|
||||
inline void UF_API terminate() {}
|
||||
inline void UF_API terminate( uf::Object& ) {}
|
||||
|
||||
// base collider creation
|
||||
inline pod::PhysicsState& UF_API create( uf::Object& ) { static pod::PhysicsState null; return null; }
|
||||
|
||||
inline void UF_API destroy( uf::Object& ) { return; }
|
||||
inline void UF_API destroy( pod::PhysicsState& ) { return; }
|
||||
|
||||
inline void UF_API attach( pod::PhysicsState& ) { return; }
|
||||
inline void UF_API detach( pod::PhysicsState& ) { return; }
|
||||
|
||||
// collider for mesh (static or dynamic)
|
||||
inline pod::PhysicsState& create( uf::Object&, const uf::Mesh&, bool ) { static pod::PhysicsState null; return null; }
|
||||
// collider for boundingbox
|
||||
inline pod::PhysicsState& UF_API create( uf::Object&, const pod::Vector3f& ) { static pod::PhysicsState null; return null; }
|
||||
// collider for capsule
|
||||
inline pod::PhysicsState& UF_API create( uf::Object&, float, float ) { static pod::PhysicsState null; return null; }
|
||||
/*
|
||||
// synchronize engine transforms to bullet transforms
|
||||
inline void UF_API syncTo( ext::reactphysics::WorldState& ) { return; }
|
||||
// synchronize bullet transforms to engine transforms
|
||||
inline void UF_API syncFrom( ext::reactphysics::WorldState&, float = 1 ) { return; }
|
||||
*/
|
||||
// apply impulse
|
||||
inline void UF_API setImpulse( pod::PhysicsState&, const pod::Vector3f& = {} ) { return; }
|
||||
inline void UF_API applyImpulse( pod::PhysicsState&, const pod::Vector3f& ) { return; }
|
||||
// directly move a transform
|
||||
inline void UF_API applyMovement( pod::PhysicsState&, const pod::Vector3f& ) { return; }
|
||||
// directly apply a velocity
|
||||
inline void UF_API setVelocity( pod::PhysicsState&, const pod::Vector3f& ) { return; }
|
||||
inline void UF_API applyVelocity( pod::PhysicsState&, const pod::Vector3f& ) { return; }
|
||||
// directly rotate a transform
|
||||
inline void UF_API applyRotation( pod::PhysicsState&, const pod::Quaternion<>& ) { return; }
|
||||
inline void UF_API applyRotation( pod::PhysicsState&, const pod::Vector3f&, float ) { return; }
|
||||
|
||||
// ray casting
|
||||
inline uf::Object* UF_API rayCast( const pod::Vector3f&, const pod::Vector3f& ) { return NULL; }
|
||||
inline uf::Object* UF_API rayCast( pod::PhysicsState&, const pod::Vector3f&, const pod::Vector3f& ) { return NULL; }
|
||||
inline uf::Object* UF_API rayCast( pod::PhysicsState&, const pod::Vector3f&, const pod::Vector3f&, float& ) { return NULL; }
|
||||
inline uf::Object* UF_API rayCast( const pod::Vector3f&, const pod::Vector3f&, uf::Object*, float& ) { return NULL; }
|
||||
|
||||
// allows noclip
|
||||
inline void UF_API activateCollision( pod::PhysicsState&, bool = true ) { return; }
|
||||
|
||||
//
|
||||
inline float UF_API getMass( pod::PhysicsState& ) { return {}; }
|
||||
inline void UF_API setMass( pod::PhysicsState&, float ) { return; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,446 +6,39 @@
|
||||
namespace pod {
|
||||
struct Plane {
|
||||
pod::Vector3f normal;
|
||||
float distance;
|
||||
float offset;
|
||||
};
|
||||
|
||||
struct AABB {
|
||||
pod::Vector3f min;
|
||||
pod::Vector3f max;
|
||||
};
|
||||
|
||||
struct Sphere {
|
||||
pod::Vector3f origin;
|
||||
float radius;
|
||||
};
|
||||
|
||||
struct Cube {
|
||||
struct Capsule {
|
||||
float radius;
|
||||
float halfHeight;
|
||||
};
|
||||
|
||||
struct Ray {
|
||||
pod::Vector3f origin;
|
||||
pod::Vector3f size;
|
||||
pod::Vector3f direction;
|
||||
};
|
||||
|
||||
struct Triangle {
|
||||
pod::Vector3f points[3];
|
||||
};
|
||||
|
||||
struct TriangleWithNormal : Triangle { // to-do: find a better name
|
||||
pod::Vector3f normals[3];
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Polygon {
|
||||
uf::stl::vector<T> points;
|
||||
};
|
||||
|
||||
struct Cone {
|
||||
pod::Vector3f origin;
|
||||
pod::Vector3f direction;
|
||||
float height;
|
||||
float radius;
|
||||
};
|
||||
|
||||
struct Torus {
|
||||
pod::Vector3f origin;
|
||||
float majorRadius;
|
||||
float minorRadius;
|
||||
};
|
||||
|
||||
struct Cylinder {
|
||||
pod::Vector3f origin;
|
||||
pod::Vector3f direction;
|
||||
float height;
|
||||
float radius;
|
||||
};
|
||||
}
|
||||
|
||||
namespace uf {
|
||||
namespace shapes {
|
||||
inline float planePointSide( const pod::Plane& plane, const pod::Vector3f& point ) {
|
||||
return uf::vector::dot( point, plane.normal ) - plane.distance;
|
||||
}
|
||||
|
||||
inline float planeEdgeIntersection( const pod::Plane& plane, const pod::Vector3f& p1, const pod::Vector3f& p2 ) {
|
||||
return (plane.distance - uf::vector::dot(p1, plane.normal)) / uf::vector::dot(uf::vector::subtract(p2, p1), plane.normal);
|
||||
}
|
||||
|
||||
template<typename T, typename U = uint32_t>
|
||||
uf::stl::vector<pod::Polygon<T>> verticesPolygonate( const uf::stl::vector<T>& vertices, const uf::stl::vector<U>& indices ) {
|
||||
uf::stl::vector<pod::Polygon<T>> polygons;
|
||||
polygons.reserve( indices.size() / 3 );
|
||||
|
||||
for ( auto i = 0; i < indices.size(); i += 3 ) {
|
||||
polygons.emplace_back( pod::Polygon<T>{ { vertices[indices[i+0]], vertices[indices[i+1]], vertices[indices[i+2]] } });
|
||||
}
|
||||
|
||||
return polygons;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
uf::stl::vector<pod::Polygon<T>> verticesPolygonate( const uf::stl::vector<T>& vertices ) {
|
||||
uf::stl::vector<pod::Polygon<T>> polygons;
|
||||
polygons.reserve( vertices.size() / 3 );
|
||||
|
||||
for ( auto i = 0; i < vertices.size(); i += 3 ) {
|
||||
polygons.emplace_back( pod::Polygon<T>{ { vertices[i+0], vertices[i+1], vertices[i+2] } });
|
||||
}
|
||||
|
||||
return polygons;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
uf::stl::vector<T> polygonsTriangulate( const uf::stl::vector<pod::Polygon<T>>& polygons ) {
|
||||
uf::stl::vector<T> vertices;
|
||||
vertices.reserve( polygons.size() * 3 );
|
||||
|
||||
for ( const auto& polygon : polygons ) {
|
||||
for (size_t i = 1; i < polygon.points.size() - 1; ++i) {
|
||||
vertices.emplace_back(polygon.points[0]);
|
||||
vertices.emplace_back(polygon.points[i]);
|
||||
vertices.emplace_back(polygon.points[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// rewind
|
||||
for ( auto i = 0; i < vertices.size(); i += 3 ) {
|
||||
for ( const auto& polygon : polygons ) {
|
||||
const auto& p0 = vertices[i+0];
|
||||
const auto& p1 = vertices[i+1];
|
||||
const auto& p2 = vertices[i+2];
|
||||
|
||||
auto edge1 = p1.position - p0.position;
|
||||
auto edge2 = p2.position - p0.position;
|
||||
auto polygonNormal = uf::vector::cross(edge1, edge2);
|
||||
|
||||
if ( uf::vector::dot( polygonNormal, p0.normal ) < 0 ) {
|
||||
std::swap( vertices[i+1], vertices[i+2] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vertices;
|
||||
}
|
||||
|
||||
template<typename T, typename U = uint32_t>
|
||||
uf::stl::vector<U> verticesIndex( const uf::stl::vector<T>& vertices ) {
|
||||
uf::stl::vector<U> indices;
|
||||
indices.reserve( vertices.size() );
|
||||
|
||||
for ( auto i = 0; i < vertices.size(); ++i ) {
|
||||
indices.emplace_back( i );
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
template<typename T, typename U = uint32_t>
|
||||
uf::stl::vector<T> indexedVertices( const uf::stl::vector<T>& vertices, const uf::stl::vector<U>& indices ) {
|
||||
uf::stl::vector<T> newVertices;
|
||||
|
||||
for ( auto idx : indices ) {
|
||||
newVertices.emplace_back( vertices[idx] );
|
||||
}
|
||||
|
||||
return newVertices;
|
||||
}
|
||||
|
||||
|
||||
// to-do: fix why these causes problems
|
||||
// the polygon approach works fine enough
|
||||
template<typename T>
|
||||
uf::stl::vector<T> verticesPlaneClip( const uf::stl::vector<T>& vertices, const pod::Plane& plane ) {
|
||||
uf::stl::vector<T> newVertices;
|
||||
|
||||
for ( auto i = 0; i < vertices.size(); i += 3 ) {
|
||||
const auto& v0 = vertices[i+0];
|
||||
const auto& v1 = vertices[i+1];
|
||||
const auto& v2 = vertices[i+2];
|
||||
|
||||
/*
|
||||
float s0 = uf::shapes::planePointSide( plane, v0.position ) > 0;
|
||||
float s1 = uf::shapes::planePointSide( plane, v1.position ) > 0;
|
||||
float s2 = uf::shapes::planePointSide( plane, v2.position ) > 0;
|
||||
|
||||
if ( s0 == s1 && s1 == s2 ) {
|
||||
newVertices.emplace_back( v0 );
|
||||
newVertices.emplace_back( v1 );
|
||||
newVertices.emplace_back( v2 );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( s0 != s1 ) {
|
||||
float t = planeEdgeIntersection( plane, v0.position, v1.position );
|
||||
T intersection = T::interpolate( v0, v1, t );
|
||||
|
||||
if ( s0 ) {
|
||||
newVertices.emplace_back( v0 );
|
||||
newVertices.emplace_back( intersection );
|
||||
newVertices.emplace_back( v1 );
|
||||
} else {
|
||||
newVertices.emplace_back( v1 );
|
||||
newVertices.emplace_back( intersection );
|
||||
newVertices.emplace_back( v0 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( s1 != s2 ) {
|
||||
float t = planeEdgeIntersection( plane, v1.position, v2.position );
|
||||
T intersection = T::interpolate( v1, v2, t );
|
||||
|
||||
if ( s1 ) {
|
||||
newVertices.emplace_back( v1 );
|
||||
newVertices.emplace_back( intersection );
|
||||
newVertices.emplace_back( v2 );
|
||||
} else {
|
||||
newVertices.emplace_back( v2 );
|
||||
newVertices.emplace_back( intersection );
|
||||
newVertices.emplace_back( v1 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( s2 != s0 ) {
|
||||
float t = planeEdgeIntersection( plane, v2.position, v0.position );
|
||||
T intersection = T::interpolate( v2, v0, t );
|
||||
|
||||
if ( s2 ) {
|
||||
newVertices.emplace_back( v2 );
|
||||
newVertices.emplace_back( intersection );
|
||||
newVertices.emplace_back( v0 );
|
||||
} else {
|
||||
newVertices.emplace_back( v0 );
|
||||
newVertices.emplace_back( intersection );
|
||||
newVertices.emplace_back( v2 );
|
||||
}
|
||||
}
|
||||
*/
|
||||
float d0 = uf::shapes::planePointSide( plane, v0.position );
|
||||
float d1 = uf::shapes::planePointSide( plane, v1.position );
|
||||
float d2 = uf::shapes::planePointSide( plane, v2.position );
|
||||
|
||||
if (d0 < 0 && d1 < 0 && d2 < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (d0 >= 0 && d1 >= 0 && d2 >= 0) {
|
||||
newVertices.emplace_back( v0 );
|
||||
newVertices.emplace_back( v1 );
|
||||
newVertices.emplace_back( v2 );
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
// Some vertices are in front and some are behind
|
||||
bool edge0Crosses = (d0 >= 0) != (d1 >= 0);
|
||||
bool edge1Crosses = (d1 >= 0) != (d2 >= 0);
|
||||
bool edge2Crosses = (d2 >= 0) != (d0 >= 0);
|
||||
|
||||
T intersection01;
|
||||
T intersection12;
|
||||
T intersection20;
|
||||
|
||||
if ( edge0Crosses ) {
|
||||
auto t = planeEdgeIntersection( plane, v0.position, v1.position );
|
||||
intersection01 = T::interpolate( v0, v1, t );
|
||||
}
|
||||
if ( edge1Crosses ) {
|
||||
auto t = planeEdgeIntersection( plane, v1.position, v2.position );
|
||||
intersection12 = T::interpolate( v1, v2, t );
|
||||
}
|
||||
if ( edge2Crosses ) {
|
||||
auto t = planeEdgeIntersection( plane, v2.position, v0.position );
|
||||
intersection20 = T::interpolate( v2, v0, t );
|
||||
}
|
||||
|
||||
if (edge0Crosses && edge1Crosses && edge2Crosses) {
|
||||
// All edges cross the plane: split into three triangles
|
||||
newVertices.emplace_back( intersection01 );
|
||||
newVertices.emplace_back( intersection12 );
|
||||
newVertices.emplace_back( intersection20 );
|
||||
} else if (edge0Crosses && edge1Crosses) {
|
||||
// Edges 0 and 1 cross: create a quad
|
||||
newVertices.emplace_back( intersection01 );
|
||||
newVertices.emplace_back( intersection12 );
|
||||
newVertices.emplace_back( v1 );
|
||||
|
||||
newVertices.emplace_back( intersection01 );
|
||||
newVertices.emplace_back( v1 );
|
||||
newVertices.emplace_back( v0 );
|
||||
} else if (edge1Crosses && edge2Crosses) {
|
||||
// Edges 1 and 2 cross: create a quad
|
||||
newVertices.emplace_back( intersection12 );
|
||||
newVertices.emplace_back( intersection20 );
|
||||
newVertices.emplace_back( v2 );
|
||||
|
||||
newVertices.emplace_back( intersection12 );
|
||||
newVertices.emplace_back( v2 );
|
||||
newVertices.emplace_back( v1 );
|
||||
} else if (edge2Crosses && edge0Crosses) {
|
||||
// Edges 2 and 0 cross: create a quad
|
||||
newVertices.emplace_back( intersection20 );
|
||||
newVertices.emplace_back( intersection01 );
|
||||
newVertices.emplace_back( v0 );
|
||||
|
||||
newVertices.emplace_back( intersection20 );
|
||||
newVertices.emplace_back( v0 );
|
||||
newVertices.emplace_back( v2 );
|
||||
} else if (edge0Crosses) {
|
||||
// Only edge 0 crosses: create a triangle
|
||||
newVertices.emplace_back( intersection01 );
|
||||
newVertices.emplace_back( v1 );
|
||||
newVertices.emplace_back( v0 );
|
||||
} else if (edge1Crosses) {
|
||||
// Only edge 1 crosses: create a triangle
|
||||
newVertices.emplace_back( intersection12 );
|
||||
newVertices.emplace_back( v2 );
|
||||
newVertices.emplace_back( v1 );
|
||||
} else if (edge2Crosses) {
|
||||
// Only edge 2 crosses: create a triangle
|
||||
newVertices.emplace_back( intersection20 );
|
||||
newVertices.emplace_back( v0 );
|
||||
newVertices.emplace_back( v2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newVertices;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
pod::Polygon<T> polygonPlaneClip( const pod::Polygon<T>& polygon, const pod::Plane& plane ) {
|
||||
pod::Polygon<T> clipped;
|
||||
|
||||
auto count = polygon.points.size();
|
||||
bool modified = false;
|
||||
for (auto i = 0; i < count; ++i) {
|
||||
const T& current = polygon.points[i];
|
||||
const T& previous = polygon.points[(i - 1 + count) % count];
|
||||
|
||||
float currentSide = uf::shapes::planePointSide( plane, current.position );
|
||||
float previousSide = uf::shapes::planePointSide( plane, previous.position );
|
||||
|
||||
if (currentSide >= 0) {
|
||||
if (previousSide < 0) {
|
||||
float t = uf::shapes::planeEdgeIntersection( plane, previous.position, current.position );
|
||||
T interpolated = T::interpolate( previous, current, t );
|
||||
clipped.points.emplace_back(interpolated);
|
||||
|
||||
modified = true;
|
||||
}
|
||||
clipped.points.emplace_back(current);
|
||||
} else if (previousSide >= 0) {
|
||||
float t = uf::shapes::planeEdgeIntersection( plane, previous.position, current.position );
|
||||
T interpolated = T::interpolate( previous, current, t );
|
||||
clipped.points.emplace_back(interpolated);
|
||||
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( modified ) {
|
||||
std::reverse( clipped.points.begin(), clipped.points.end() );
|
||||
}
|
||||
|
||||
return clipped;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
uf::stl::vector<pod::Polygon<T>>& clip(
|
||||
uf::stl::vector<pod::Polygon<T>>& polygons,
|
||||
|
||||
const pod::Vector3f& min,
|
||||
const pod::Vector3f& max
|
||||
) {
|
||||
// inverted because its needed
|
||||
uf::stl::vector<pod::Plane> planes = {
|
||||
// Right plane: x = max.x
|
||||
{ pod::Vector3f{-1, 0, 0}, -max.x },
|
||||
// Left plane: x = min.x
|
||||
{ pod::Vector3f{1, 0, 0}, min.x },
|
||||
// Top plane: y = max.y
|
||||
{ pod::Vector3f{0, -1, 0}, -max.y },
|
||||
// Bottom plane: y = min.y
|
||||
{ pod::Vector3f{0, 1, 0}, min.y },
|
||||
// Front plane: z = max.z
|
||||
{ pod::Vector3f{0, 0, -1}, -max.z },
|
||||
// Back plane: z = min.z
|
||||
{ pod::Vector3f{0, 0, 1}, min.z }
|
||||
};
|
||||
|
||||
// clip against planes
|
||||
for ( const auto& plane : planes ) {
|
||||
uf::stl::vector<pod::Polygon<T>> clippedPolygons;
|
||||
for ( const auto& polygon : polygons ) {
|
||||
auto clipped = uf::shapes::polygonPlaneClip<T>( polygon, plane );
|
||||
if ( clipped.points.size() >= 3 ) clippedPolygons.emplace_back( clipped );
|
||||
}
|
||||
polygons = std::move(clippedPolygons);
|
||||
}
|
||||
|
||||
return polygons;
|
||||
}
|
||||
|
||||
template<typename T, typename U = uint32_t>
|
||||
uf::stl::vector<T>& clip(
|
||||
uf::stl::vector<T>& vertices,
|
||||
|
||||
const pod::Vector3f& min,
|
||||
const pod::Vector3f& max,
|
||||
|
||||
bool polygons = true
|
||||
) {
|
||||
if ( polygons ) {
|
||||
// convert to polys
|
||||
auto polygons = uf::shapes::verticesPolygonate<T>( vertices );
|
||||
// clip against planes
|
||||
uf::shapes::clip<T>( polygons, min, max );
|
||||
// triangulate
|
||||
vertices = uf::shapes::polygonsTriangulate( polygons );
|
||||
|
||||
return vertices;
|
||||
}
|
||||
|
||||
// inverted because its needed
|
||||
uf::stl::vector<pod::Plane> planes = {
|
||||
// Right plane: x = max.x
|
||||
{ pod::Vector3f{-1, 0, 0}, -max.x },
|
||||
// Left plane: x = min.x
|
||||
{ pod::Vector3f{1, 0, 0}, min.x },
|
||||
// Top plane: y = max.y
|
||||
{ pod::Vector3f{0, -1, 0}, -max.y },
|
||||
// Bottom plane: y = min.y
|
||||
{ pod::Vector3f{0, 1, 0}, min.y },
|
||||
// Front plane: z = max.z
|
||||
{ pod::Vector3f{0, 0, -1}, -max.z },
|
||||
// Back plane: z = min.z
|
||||
{ pod::Vector3f{0, 0, 1}, min.z }
|
||||
};
|
||||
|
||||
// clip against planes
|
||||
for ( const auto& plane : planes ) {
|
||||
vertices = uf::shapes::verticesPlaneClip( vertices, plane );
|
||||
}
|
||||
|
||||
return vertices;
|
||||
}
|
||||
|
||||
template<typename T, typename U = uint32_t>
|
||||
void clip(
|
||||
uf::stl::vector<T>& vertices,
|
||||
uf::stl::vector<U>& indices,
|
||||
|
||||
const pod::Vector3f& min,
|
||||
const pod::Vector3f& max,
|
||||
|
||||
bool polygons = true
|
||||
) {
|
||||
if ( polygons ) {
|
||||
// convert to polys
|
||||
auto polygons = uf::shapes::verticesPolygonate<T, U>( vertices, indices );
|
||||
// clip against planes
|
||||
uf::shapes::clip<T>( polygons, min, max );
|
||||
// triangulate
|
||||
vertices = uf::shapes::polygonsTriangulate( polygons );
|
||||
indices = uf::shapes::verticesIndex( vertices );
|
||||
return;
|
||||
}
|
||||
|
||||
// deindex vertices
|
||||
vertices = uf::shapes::indexedVertices( vertices, indices );
|
||||
// clip vertices
|
||||
uf::shapes::clip( vertices, min, max );
|
||||
// reindex
|
||||
indices = uf::shapes::verticesIndex( vertices );
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,7 +53,11 @@ namespace uf {
|
||||
template<typename T> pod::Transform<T> /*UF_API*/ fromMatrix( const pod::Matrix4t<T>& matrix );
|
||||
template<typename T> pod::Transform<T>& /*UF_API*/ reference( pod::Transform<T>& transform, const pod::Transform<T>& parent, bool reorient = true );
|
||||
template<typename T> pod::Transform<T> /*UF_API*/ interpolate( const pod::Transform<T>& from, const pod::Transform<T>& to, float factor, bool reorient = true );
|
||||
|
||||
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> 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& = {} );
|
||||
template<typename T> pod::Transform<T>& /*UF_API*/ decode( const ext::json::Value&, pod::Transform<T>& );
|
||||
|
||||
@ -156,6 +156,50 @@ template<typename T> pod::Transform<T> /*UF_API*/ uf::transform::interpolate( co
|
||||
transform.orientation = uf::quaternion::slerp( from.orientation, to.orientation, factor );
|
||||
return reorient ? uf::transform::reorient( transform ) : transform;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
pod::Transform<T> uf::transform::inverse(const pod::Transform<T>& t) {
|
||||
pod::Transform<T> inv;
|
||||
|
||||
// Inverse orientation = conjugate of normalized quaternion
|
||||
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
|
||||
};
|
||||
|
||||
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 });
|
||||
|
||||
// Update basis vectors
|
||||
inv = uf::transform::reorient(inv);
|
||||
|
||||
return 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);
|
||||
}
|
||||
|
||||
template<typename T> // Normalizes a vector
|
||||
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;
|
||||
|
||||
@ -125,6 +125,8 @@ namespace uf {
|
||||
// Equality checking
|
||||
template<typename T> int /*UF_API*/ compareTo( const T& left, const T& right ); // Equality check between two vectors (less than)
|
||||
template<typename T> bool /*UF_API*/ equals( const T& left, const T& right ); // Equality check between two vectors (equals)
|
||||
|
||||
template<typename T> bool /*UF_API*/ isValid( const T& v ); // Checks if all components are valid (non NaN, inf, etc.)
|
||||
// Basic arithmetic
|
||||
template<typename T> T /*UF_API*/ add( const T& left, const T& right ); // Adds two vectors of same type and size together
|
||||
template<typename T> T /*UF_API*/ add( const T& left, /*const typename T::type_t&*/ typename T::type_t scalar ); // Adds two vectors of same type and size together
|
||||
@ -172,8 +174,9 @@ namespace uf {
|
||||
template<typename T> typename T::type_t /*UF_API*/ norm( const T& vector ); // Compute the norm of the vector
|
||||
template<typename T> typename T::type_t /*UF_API*/ magnitude( const T& vector ); // Gets the magnitude of the vector
|
||||
template<typename T> T /*UF_API*/ normalize( const T& vector ); // Normalizes a vector
|
||||
template<typename T> void /*UF_API*/ orthonormalize( T& x, T& y ); // Normalizes a vector
|
||||
template<typename T> T /*UF_API*/ orthonormalize( const T& x, const T& y ); // Normalizes a vector
|
||||
template<typename T> T /*UF_API*/ clampMagnitude( const T& vector ); // Clamps the magnitude of a vector
|
||||
template<typename T> void /*UF_API*/ orthonormalize( T& x, T& y ); // Orthonormalizes a vector against another vector
|
||||
template<typename T> T /*UF_API*/ orthonormalize( const T& x, const T& y ); // Orthonormalizes a vector against another vector
|
||||
|
||||
template<typename T> uf::stl::string /*UF_API*/ toString( const T& vector ); // Parses a vector as a string
|
||||
template<typename T, size_t N> ext::json::Value encode( const pod::Vector<T,N>& v, const ext::json::EncodingSettings& = {} ); // Parses a vector into a JSON value
|
||||
|
||||
@ -57,8 +57,7 @@ T& uf::Vector<T,N>::setComponent( std::size_t i, const T& value ) {
|
||||
// Validation
|
||||
template<typename T, std::size_t N> // Checks if all components are valid (non NaN, inf, etc.)
|
||||
bool uf::Vector<T,N>::isValid() const {
|
||||
for ( std::size_t i = 0; i < N; ++i ) if ( this->m_pod[i] != this->m_pod[i] ) return false;
|
||||
return true;
|
||||
return uf::vector::isValid( this->m_pod );
|
||||
}
|
||||
// Basic arithmetic
|
||||
template<typename T, std::size_t N> // Adds two vectors of same type and size together
|
||||
|
||||
@ -42,6 +42,10 @@ bool /*UF_API*/ uf::vector::equals( const T& left, const T& right ) {
|
||||
if ( left[i] != right[i] ) return false;
|
||||
return true;
|
||||
}
|
||||
template<typename T> //
|
||||
bool /*UF_API*/ uf::vector::isValid( const T& v ) {
|
||||
return uf::vector::equals( v, v );
|
||||
}
|
||||
// Basic arithmetic
|
||||
template<typename T> // Adds two vectors of same type and size together
|
||||
T /*UF_API*/ uf::vector::add( const T& left, const T& right ) {
|
||||
@ -494,6 +498,18 @@ T /*UF_API*/ uf::vector::normalize( const T& vector ) {
|
||||
#endif
|
||||
return uf::vector::divide(vector, norm);
|
||||
}
|
||||
|
||||
template<typename T> // Clamps the length of a vector
|
||||
T /*UF_API*/ uf::vector::clampMagnitude( const T& v, float maxMag ) {
|
||||
T res = v;
|
||||
float mag = uf::vector::magnitude( res );
|
||||
if ( mag > maxMag ) {
|
||||
res /= (maxMag / sqrt(mag));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T> // Normalizes a vector
|
||||
void /*UF_API*/ uf::vector::orthonormalize( T& normal, T& tangent ) {
|
||||
normal = uf::vector::normalize( normal );
|
||||
|
||||
406
engine/inc/uf/utils/mesh/clip.h
Normal file
406
engine/inc/uf/utils/mesh/clip.h
Normal file
@ -0,0 +1,406 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
#include <uf/utils/math/vector.h>
|
||||
#include <uf/utils/math/shapes.h>
|
||||
|
||||
namespace uf {
|
||||
namespace shapes {
|
||||
inline float planePointSide( const pod::Plane& plane, const pod::Vector3f& point ) {
|
||||
return uf::vector::dot( point, plane.normal ) - plane.offset;
|
||||
}
|
||||
|
||||
inline float planeEdgeIntersection( const pod::Plane& plane, const pod::Vector3f& p1, const pod::Vector3f& p2 ) {
|
||||
return (plane.offset - uf::vector::dot(p1, plane.normal)) / uf::vector::dot(uf::vector::subtract(p2, p1), plane.normal);
|
||||
}
|
||||
|
||||
template<typename T, typename U = uint32_t>
|
||||
uf::stl::vector<pod::Polygon<T>> verticesPolygonate( const uf::stl::vector<T>& vertices, const uf::stl::vector<U>& indices ) {
|
||||
uf::stl::vector<pod::Polygon<T>> polygons;
|
||||
polygons.reserve( indices.size() / 3 );
|
||||
|
||||
for ( auto i = 0; i < indices.size(); i += 3 ) {
|
||||
polygons.emplace_back( pod::Polygon<T>{ { vertices[indices[i+0]], vertices[indices[i+1]], vertices[indices[i+2]] } });
|
||||
}
|
||||
|
||||
return polygons;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
uf::stl::vector<pod::Polygon<T>> verticesPolygonate( const uf::stl::vector<T>& vertices ) {
|
||||
uf::stl::vector<pod::Polygon<T>> polygons;
|
||||
polygons.reserve( vertices.size() / 3 );
|
||||
|
||||
for ( auto i = 0; i < vertices.size(); i += 3 ) {
|
||||
polygons.emplace_back( pod::Polygon<T>{ { vertices[i+0], vertices[i+1], vertices[i+2] } });
|
||||
}
|
||||
|
||||
return polygons;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
uf::stl::vector<T> polygonsTriangulate( const uf::stl::vector<pod::Polygon<T>>& polygons ) {
|
||||
uf::stl::vector<T> vertices;
|
||||
vertices.reserve( polygons.size() * 3 );
|
||||
|
||||
for ( const auto& polygon : polygons ) {
|
||||
for (size_t i = 1; i < polygon.points.size() - 1; ++i) {
|
||||
vertices.emplace_back(polygon.points[0]);
|
||||
vertices.emplace_back(polygon.points[i]);
|
||||
vertices.emplace_back(polygon.points[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// rewind
|
||||
for ( auto i = 0; i < vertices.size(); i += 3 ) {
|
||||
for ( const auto& polygon : polygons ) {
|
||||
const auto& p0 = vertices[i+0];
|
||||
const auto& p1 = vertices[i+1];
|
||||
const auto& p2 = vertices[i+2];
|
||||
|
||||
auto edge1 = p1.position - p0.position;
|
||||
auto edge2 = p2.position - p0.position;
|
||||
auto polygonNormal = uf::vector::cross(edge1, edge2);
|
||||
|
||||
if ( uf::vector::dot( polygonNormal, p0.normal ) < 0 ) {
|
||||
std::swap( vertices[i+1], vertices[i+2] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vertices;
|
||||
}
|
||||
|
||||
template<typename T, typename U = uint32_t>
|
||||
uf::stl::vector<U> verticesIndex( const uf::stl::vector<T>& vertices ) {
|
||||
uf::stl::vector<U> indices;
|
||||
indices.reserve( vertices.size() );
|
||||
|
||||
for ( auto i = 0; i < vertices.size(); ++i ) {
|
||||
indices.emplace_back( i );
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
template<typename T, typename U = uint32_t>
|
||||
uf::stl::vector<T> indexedVertices( const uf::stl::vector<T>& vertices, const uf::stl::vector<U>& indices ) {
|
||||
uf::stl::vector<T> newVertices;
|
||||
|
||||
for ( auto idx : indices ) {
|
||||
newVertices.emplace_back( vertices[idx] );
|
||||
}
|
||||
|
||||
return newVertices;
|
||||
}
|
||||
|
||||
|
||||
// to-do: fix why these causes problems
|
||||
// the polygon approach works fine enough
|
||||
template<typename T>
|
||||
uf::stl::vector<T> verticesPlaneClip( const uf::stl::vector<T>& vertices, const pod::Plane& plane ) {
|
||||
uf::stl::vector<T> newVertices;
|
||||
|
||||
for ( auto i = 0; i < vertices.size(); i += 3 ) {
|
||||
const auto& v0 = vertices[i+0];
|
||||
const auto& v1 = vertices[i+1];
|
||||
const auto& v2 = vertices[i+2];
|
||||
|
||||
/*
|
||||
float s0 = uf::shapes::planePointSide( plane, v0.position ) > 0;
|
||||
float s1 = uf::shapes::planePointSide( plane, v1.position ) > 0;
|
||||
float s2 = uf::shapes::planePointSide( plane, v2.position ) > 0;
|
||||
|
||||
if ( s0 == s1 && s1 == s2 ) {
|
||||
newVertices.emplace_back( v0 );
|
||||
newVertices.emplace_back( v1 );
|
||||
newVertices.emplace_back( v2 );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( s0 != s1 ) {
|
||||
float t = planeEdgeIntersection( plane, v0.position, v1.position );
|
||||
T intersection = T::interpolate( v0, v1, t );
|
||||
|
||||
if ( s0 ) {
|
||||
newVertices.emplace_back( v0 );
|
||||
newVertices.emplace_back( intersection );
|
||||
newVertices.emplace_back( v1 );
|
||||
} else {
|
||||
newVertices.emplace_back( v1 );
|
||||
newVertices.emplace_back( intersection );
|
||||
newVertices.emplace_back( v0 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( s1 != s2 ) {
|
||||
float t = planeEdgeIntersection( plane, v1.position, v2.position );
|
||||
T intersection = T::interpolate( v1, v2, t );
|
||||
|
||||
if ( s1 ) {
|
||||
newVertices.emplace_back( v1 );
|
||||
newVertices.emplace_back( intersection );
|
||||
newVertices.emplace_back( v2 );
|
||||
} else {
|
||||
newVertices.emplace_back( v2 );
|
||||
newVertices.emplace_back( intersection );
|
||||
newVertices.emplace_back( v1 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( s2 != s0 ) {
|
||||
float t = planeEdgeIntersection( plane, v2.position, v0.position );
|
||||
T intersection = T::interpolate( v2, v0, t );
|
||||
|
||||
if ( s2 ) {
|
||||
newVertices.emplace_back( v2 );
|
||||
newVertices.emplace_back( intersection );
|
||||
newVertices.emplace_back( v0 );
|
||||
} else {
|
||||
newVertices.emplace_back( v0 );
|
||||
newVertices.emplace_back( intersection );
|
||||
newVertices.emplace_back( v2 );
|
||||
}
|
||||
}
|
||||
*/
|
||||
float d0 = uf::shapes::planePointSide( plane, v0.position );
|
||||
float d1 = uf::shapes::planePointSide( plane, v1.position );
|
||||
float d2 = uf::shapes::planePointSide( plane, v2.position );
|
||||
|
||||
if (d0 < 0 && d1 < 0 && d2 < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (d0 >= 0 && d1 >= 0 && d2 >= 0) {
|
||||
newVertices.emplace_back( v0 );
|
||||
newVertices.emplace_back( v1 );
|
||||
newVertices.emplace_back( v2 );
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
// Some vertices are in front and some are behind
|
||||
bool edge0Crosses = (d0 >= 0) != (d1 >= 0);
|
||||
bool edge1Crosses = (d1 >= 0) != (d2 >= 0);
|
||||
bool edge2Crosses = (d2 >= 0) != (d0 >= 0);
|
||||
|
||||
T intersection01;
|
||||
T intersection12;
|
||||
T intersection20;
|
||||
|
||||
if ( edge0Crosses ) {
|
||||
auto t = planeEdgeIntersection( plane, v0.position, v1.position );
|
||||
intersection01 = T::interpolate( v0, v1, t );
|
||||
}
|
||||
if ( edge1Crosses ) {
|
||||
auto t = planeEdgeIntersection( plane, v1.position, v2.position );
|
||||
intersection12 = T::interpolate( v1, v2, t );
|
||||
}
|
||||
if ( edge2Crosses ) {
|
||||
auto t = planeEdgeIntersection( plane, v2.position, v0.position );
|
||||
intersection20 = T::interpolate( v2, v0, t );
|
||||
}
|
||||
|
||||
if (edge0Crosses && edge1Crosses && edge2Crosses) {
|
||||
// All edges cross the plane: split into three triangles
|
||||
newVertices.emplace_back( intersection01 );
|
||||
newVertices.emplace_back( intersection12 );
|
||||
newVertices.emplace_back( intersection20 );
|
||||
} else if (edge0Crosses && edge1Crosses) {
|
||||
// Edges 0 and 1 cross: create a quad
|
||||
newVertices.emplace_back( intersection01 );
|
||||
newVertices.emplace_back( intersection12 );
|
||||
newVertices.emplace_back( v1 );
|
||||
|
||||
newVertices.emplace_back( intersection01 );
|
||||
newVertices.emplace_back( v1 );
|
||||
newVertices.emplace_back( v0 );
|
||||
} else if (edge1Crosses && edge2Crosses) {
|
||||
// Edges 1 and 2 cross: create a quad
|
||||
newVertices.emplace_back( intersection12 );
|
||||
newVertices.emplace_back( intersection20 );
|
||||
newVertices.emplace_back( v2 );
|
||||
|
||||
newVertices.emplace_back( intersection12 );
|
||||
newVertices.emplace_back( v2 );
|
||||
newVertices.emplace_back( v1 );
|
||||
} else if (edge2Crosses && edge0Crosses) {
|
||||
// Edges 2 and 0 cross: create a quad
|
||||
newVertices.emplace_back( intersection20 );
|
||||
newVertices.emplace_back( intersection01 );
|
||||
newVertices.emplace_back( v0 );
|
||||
|
||||
newVertices.emplace_back( intersection20 );
|
||||
newVertices.emplace_back( v0 );
|
||||
newVertices.emplace_back( v2 );
|
||||
} else if (edge0Crosses) {
|
||||
// Only edge 0 crosses: create a triangle
|
||||
newVertices.emplace_back( intersection01 );
|
||||
newVertices.emplace_back( v1 );
|
||||
newVertices.emplace_back( v0 );
|
||||
} else if (edge1Crosses) {
|
||||
// Only edge 1 crosses: create a triangle
|
||||
newVertices.emplace_back( intersection12 );
|
||||
newVertices.emplace_back( v2 );
|
||||
newVertices.emplace_back( v1 );
|
||||
} else if (edge2Crosses) {
|
||||
// Only edge 2 crosses: create a triangle
|
||||
newVertices.emplace_back( intersection20 );
|
||||
newVertices.emplace_back( v0 );
|
||||
newVertices.emplace_back( v2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newVertices;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
pod::Polygon<T> polygonPlaneClip( const pod::Polygon<T>& polygon, const pod::Plane& plane ) {
|
||||
pod::Polygon<T> clipped;
|
||||
|
||||
auto count = polygon.points.size();
|
||||
bool modified = false;
|
||||
for (auto i = 0; i < count; ++i) {
|
||||
const T& current = polygon.points[i];
|
||||
const T& previous = polygon.points[(i - 1 + count) % count];
|
||||
|
||||
float currentSide = uf::shapes::planePointSide( plane, current.position );
|
||||
float previousSide = uf::shapes::planePointSide( plane, previous.position );
|
||||
|
||||
if (currentSide >= 0) {
|
||||
if (previousSide < 0) {
|
||||
float t = uf::shapes::planeEdgeIntersection( plane, previous.position, current.position );
|
||||
T interpolated = T::interpolate( previous, current, t );
|
||||
clipped.points.emplace_back(interpolated);
|
||||
|
||||
modified = true;
|
||||
}
|
||||
clipped.points.emplace_back(current);
|
||||
} else if (previousSide >= 0) {
|
||||
float t = uf::shapes::planeEdgeIntersection( plane, previous.position, current.position );
|
||||
T interpolated = T::interpolate( previous, current, t );
|
||||
clipped.points.emplace_back(interpolated);
|
||||
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( modified ) {
|
||||
std::reverse( clipped.points.begin(), clipped.points.end() );
|
||||
}
|
||||
|
||||
return clipped;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
uf::stl::vector<pod::Polygon<T>>& clip(
|
||||
uf::stl::vector<pod::Polygon<T>>& polygons,
|
||||
|
||||
const pod::Vector3f& min,
|
||||
const pod::Vector3f& max
|
||||
) {
|
||||
// inverted because its needed
|
||||
uf::stl::vector<pod::Plane> planes = {
|
||||
// Right plane: x = max.x
|
||||
{ pod::Vector3f{-1, 0, 0}, -max.x },
|
||||
// Left plane: x = min.x
|
||||
{ pod::Vector3f{1, 0, 0}, min.x },
|
||||
// Top plane: y = max.y
|
||||
{ pod::Vector3f{0, -1, 0}, -max.y },
|
||||
// Bottom plane: y = min.y
|
||||
{ pod::Vector3f{0, 1, 0}, min.y },
|
||||
// Front plane: z = max.z
|
||||
{ pod::Vector3f{0, 0, -1}, -max.z },
|
||||
// Back plane: z = min.z
|
||||
{ pod::Vector3f{0, 0, 1}, min.z }
|
||||
};
|
||||
|
||||
// clip against planes
|
||||
for ( const auto& plane : planes ) {
|
||||
uf::stl::vector<pod::Polygon<T>> clippedPolygons;
|
||||
for ( const auto& polygon : polygons ) {
|
||||
auto clipped = uf::shapes::polygonPlaneClip<T>( polygon, plane );
|
||||
if ( clipped.points.size() >= 3 ) clippedPolygons.emplace_back( clipped );
|
||||
}
|
||||
polygons = std::move(clippedPolygons);
|
||||
}
|
||||
|
||||
return polygons;
|
||||
}
|
||||
|
||||
template<typename T, typename U = uint32_t>
|
||||
uf::stl::vector<T>& clip(
|
||||
uf::stl::vector<T>& vertices,
|
||||
|
||||
const pod::Vector3f& min,
|
||||
const pod::Vector3f& max,
|
||||
|
||||
bool polygons = true
|
||||
) {
|
||||
if ( polygons ) {
|
||||
// convert to polys
|
||||
auto polygons = uf::shapes::verticesPolygonate<T>( vertices );
|
||||
// clip against planes
|
||||
uf::shapes::clip<T>( polygons, min, max );
|
||||
// triangulate
|
||||
vertices = uf::shapes::polygonsTriangulate( polygons );
|
||||
|
||||
return vertices;
|
||||
}
|
||||
|
||||
// inverted because its needed
|
||||
uf::stl::vector<pod::Plane> planes = {
|
||||
// Right plane: x = max.x
|
||||
{ pod::Vector3f{-1, 0, 0}, -max.x },
|
||||
// Left plane: x = min.x
|
||||
{ pod::Vector3f{1, 0, 0}, min.x },
|
||||
// Top plane: y = max.y
|
||||
{ pod::Vector3f{0, -1, 0}, -max.y },
|
||||
// Bottom plane: y = min.y
|
||||
{ pod::Vector3f{0, 1, 0}, min.y },
|
||||
// Front plane: z = max.z
|
||||
{ pod::Vector3f{0, 0, -1}, -max.z },
|
||||
// Back plane: z = min.z
|
||||
{ pod::Vector3f{0, 0, 1}, min.z }
|
||||
};
|
||||
|
||||
// clip against planes
|
||||
for ( const auto& plane : planes ) {
|
||||
vertices = uf::shapes::verticesPlaneClip( vertices, plane );
|
||||
}
|
||||
|
||||
return vertices;
|
||||
}
|
||||
|
||||
template<typename T, typename U = uint32_t>
|
||||
void clip(
|
||||
uf::stl::vector<T>& vertices,
|
||||
uf::stl::vector<U>& indices,
|
||||
|
||||
const pod::Vector3f& min,
|
||||
const pod::Vector3f& max,
|
||||
|
||||
bool polygons = true
|
||||
) {
|
||||
if ( polygons ) {
|
||||
// convert to polys
|
||||
auto polygons = uf::shapes::verticesPolygonate<T, U>( vertices, indices );
|
||||
// clip against planes
|
||||
uf::shapes::clip<T>( polygons, min, max );
|
||||
// triangulate
|
||||
vertices = uf::shapes::polygonsTriangulate( polygons );
|
||||
indices = uf::shapes::verticesIndex( vertices );
|
||||
return;
|
||||
}
|
||||
|
||||
// deindex vertices
|
||||
vertices = uf::shapes::indexedVertices( vertices, indices );
|
||||
// clip vertices
|
||||
uf::shapes::clip( vertices, min, max );
|
||||
// reindex
|
||||
indices = uf::shapes::verticesIndex( vertices );
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
#include <uf/utils/math/shapes.h>
|
||||
#include <uf/utils/mesh/mesh.h>
|
||||
#include <uf/utils/mesh/clip.h>
|
||||
#include <uf/engine/graph/graph.h>
|
||||
#include <limits>
|
||||
|
||||
|
||||
namespace uf {
|
||||
namespace meshgrid {
|
||||
struct Node {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/utils/math/vector.h>
|
||||
#include <uf/utils/math/shapes.h>
|
||||
#include <uf/utils/math/matrix.h>
|
||||
#include <uf/utils/math/quant.h>
|
||||
|
||||
@ -160,6 +159,38 @@ namespace uf {
|
||||
size_t offset = 0; // bytes to offset from within the associated buffer
|
||||
int32_t interleaved = -1; // index to interleaved buffer if in bounds
|
||||
} vertex, index, instance, indirect;
|
||||
|
||||
struct AttributeView {
|
||||
Attribute attribute;
|
||||
|
||||
const void* data(size_t first = 0) const {
|
||||
if ( !valid() ) return NULL;
|
||||
return static_cast<const uint8_t*>(attribute.pointer) + attribute.stride * first;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T* get(size_t first = 0) const {
|
||||
return reinterpret_cast<const T*>(data(first));
|
||||
}
|
||||
|
||||
bool valid() const { return attribute.pointer != NULL; }
|
||||
size_t stride() const { return attribute.stride; }
|
||||
size_t components() const { return attribute.descriptor.components; }
|
||||
};
|
||||
|
||||
struct View {
|
||||
uf::Mesh::Input vertex;
|
||||
uf::Mesh::Input index;
|
||||
int32_t indirectIndex = -1;
|
||||
|
||||
uf::stl::unordered_map<uf::stl::string, uf::Mesh::AttributeView> attributes;
|
||||
|
||||
const AttributeView& operator[](const uf::stl::string& name) const {
|
||||
static AttributeView null{};
|
||||
if ( auto it = attributes.find(name); it != attributes.end() ) return it->second;
|
||||
return null;
|
||||
}
|
||||
};
|
||||
/*
|
||||
struct Bounds {
|
||||
pod::Vector3f min = { std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max() };
|
||||
@ -246,6 +277,10 @@ namespace uf {
|
||||
std::string printInstances( bool = true ) const;
|
||||
std::string printIndirects( bool = true ) const;
|
||||
|
||||
uf::Mesh::View makeView( const uf::stl::vector<uf::stl::string>& wanted = {} ) const;
|
||||
uf::Mesh::View makeView( size_t commandIndex, const uf::stl::vector<uf::stl::string>& wanted = {} ) const;
|
||||
uf::stl::vector<uf::Mesh::View> makeViews( const uf::stl::vector<uf::stl::string>& wanted = {} ) const;
|
||||
|
||||
inline bool hasVertex( const uf::stl::vector<ext::RENDERER::AttributeDescriptor>& descriptors ) const { return _hasV( vertex, descriptors ); }
|
||||
inline bool hasVertex( const uf::Mesh& mesh ) const { return _hasV( vertex, mesh.vertex ); }
|
||||
inline void bindVertex( const uf::stl::vector<ext::RENDERER::AttributeDescriptor>& descriptors ) { return _bindV( vertex, descriptors ); }
|
||||
|
||||
73
engine/inc/uf/utils/tests/tests.h
Normal file
73
engine/inc/uf/utils/tests/tests.h
Normal file
@ -0,0 +1,73 @@
|
||||
#include <uf/config.h>
|
||||
#include <uf/utils/memory/vector.h>
|
||||
#include <uf/utils/memory/string.h>
|
||||
#include <uf/utils/memory/unordered_map.h>
|
||||
#include <uf/utils/memory/key_map.h>
|
||||
|
||||
#if 1 || UF_DEBUG
|
||||
#define EXPECT_TRUE(expr) { \
|
||||
auto& test = uf::unitTests::tests[uf::unitTests::current];\
|
||||
test.passes = ((expr));\
|
||||
test.message = ::fmt::format("EXPECT_TRUE({})", #expr);\
|
||||
UF_MSG_DEBUG("[{}] {}", test.passes ? "PASS" : "FAIL", test.message);\
|
||||
}
|
||||
|
||||
#define EXPECT_FALSE(expr) { \
|
||||
auto& test = uf::unitTests::tests[uf::unitTests::current];\
|
||||
test.passes = (!(expr));\
|
||||
test.message = ::fmt::format("EXPECT_FALSE({})", #expr);\
|
||||
UF_MSG_DEBUG("[{}] {}", test.passes ? "PASS" : "FAIL", test.message);\
|
||||
}
|
||||
|
||||
#define EXPECT_EQ(a,b) { \
|
||||
auto& test = uf::unitTests::tests[uf::unitTests::current];\
|
||||
test.passes = (((a) == (b)));\
|
||||
test.message = ::fmt::format("EXPECT_EQ({}, {}), ({} == {})", #a, #b, a, b);\
|
||||
UF_MSG_DEBUG("[{}] {}", test.passes ? "PASS" : "FAIL", test.message);\
|
||||
}
|
||||
|
||||
#define EXPECT_NEAR(a,b,eps) { \
|
||||
auto& test = uf::unitTests::tests[uf::unitTests::current];\
|
||||
test.passes = (std::fabs((a)-(b)) <= (eps));\
|
||||
test.message = ::fmt::format("EXPECT_NEAR({}, {}), ({} == {})", #a, #b, a, b);\
|
||||
UF_MSG_DEBUG("[{}] {}", test.passes ? "PASS" : "FAIL", test.message);\
|
||||
}
|
||||
|
||||
#define EXPECT_FLOAT_EQ(a,b) { \
|
||||
auto& test = uf::unitTests::tests[uf::unitTests::current];\
|
||||
test.passes = (std::fabs((a)-(b)) <= (1.0e-4f));\
|
||||
test.message = ::fmt::format("EXPECT_FLOAT_EQ({}, {}), ({} == {})", #a, #b, a, b);\
|
||||
UF_MSG_DEBUG("[{}] {}", test.passes ? "PASS" : "FAIL", test.message);\
|
||||
}
|
||||
|
||||
#define TEST(name, ...) \
|
||||
void TOKEN_PASTE(UNIT_TEST_, name)(){\
|
||||
__VA_ARGS__;\
|
||||
};\
|
||||
static uf::StaticInitialization TOKEN_PASTE(STATIC_INITIALIZATION_, __LINE__)( []{\
|
||||
uf::unitTests::tests[#name] = { false, "", TOKEN_PASTE(UNIT_TEST_, name) };\
|
||||
});\
|
||||
|
||||
#else
|
||||
// no-op
|
||||
#define TEST(...)
|
||||
#endif
|
||||
|
||||
namespace pod {
|
||||
struct UnitTest {
|
||||
typedef std::function<void()> function_t;
|
||||
|
||||
bool passes;
|
||||
uf::stl::string message;
|
||||
pod::UnitTest::function_t function;
|
||||
};
|
||||
}
|
||||
|
||||
namespace uf {
|
||||
namespace unitTests {
|
||||
extern UF_API uf::stl::string current;
|
||||
extern UF_API uf::stl::KeyMap<pod::UnitTest> tests;
|
||||
|
||||
void UF_API execute();
|
||||
}
|
||||
}
|
||||
@ -48,7 +48,6 @@
|
||||
|
||||
bool uf::ready = false;
|
||||
uf::stl::vector<uf::stl::string> uf::arguments;
|
||||
|
||||
uf::Serializer uf::config;
|
||||
|
||||
namespace {
|
||||
|
||||
@ -25,7 +25,9 @@ UF_BEHAVIOR_TRAITS_CPP(ext::PlayerBehavior, ticks = true, renders = false, multi
|
||||
void ext::PlayerBehavior::initialize( uf::Object& self ) {
|
||||
auto& transform = this->getComponent<pod::Transform<>>();
|
||||
|
||||
#if UF_USE_REACTPHYSICS
|
||||
auto& collider = this->getComponent<pod::PhysicsState>();
|
||||
#endif
|
||||
|
||||
auto& metadata = this->getComponent<ext::PlayerBehavior::Metadata>();
|
||||
auto& metadataJson = this->getComponent<uf::Serializer>();
|
||||
@ -130,9 +132,13 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
auto& cameraTransform = camera.getTransform();
|
||||
|
||||
auto& transform = this->getComponent<pod::Transform<>>();
|
||||
auto& physics = this->getComponent<pod::Physics>();
|
||||
auto& scene = uf::scene::getCurrentScene();
|
||||
#if UF_USE_REACTPHYSICS
|
||||
auto& physics = this->getComponent<pod::Physics>();
|
||||
auto& collider = this->getComponent<pod::PhysicsState>();
|
||||
#else
|
||||
auto& physics = this->getComponent<pod::RigidBody>();
|
||||
#endif
|
||||
|
||||
auto& metadata = this->getComponent<ext::PlayerBehavior::Metadata>();
|
||||
auto& metadataJson = this->getComponent<uf::Serializer>();
|
||||
@ -269,11 +275,12 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
}
|
||||
#endif
|
||||
|
||||
bool wasFloored = stats.floored;
|
||||
stats.menu = metadata.system.menu;
|
||||
stats.noclipped = metadata.system.noclipped;
|
||||
stats.floored = stats.noclipped;
|
||||
|
||||
{
|
||||
if ( !stats.floored ) {
|
||||
#if UF_USE_REACTPHYSICS
|
||||
float t = -1;
|
||||
uf::Object* hit = NULL;
|
||||
pod::Vector3f center = transform.position + metadata.movement.floored.feet;
|
||||
@ -282,6 +289,17 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
if ( metadata.movement.floored.print ) UF_MSG_DEBUG("Floored: {} | {}", hit->getName(), t);
|
||||
stats.floored = true;
|
||||
}
|
||||
#else
|
||||
pod::Vector3f origin = transform.position + metadata.movement.floored.feet;
|
||||
pod::Vector3f direction = metadata.movement.floored.floor;
|
||||
pod::RayQuery query = uf::physics::impl::rayCast( pod::Ray{origin, direction}, physics, 1.0f );
|
||||
|
||||
if ( query.hit ) {
|
||||
UF_MSG_DEBUG("{}: {} | {}", query.contact.penetration, uf::string::toString(*query.body->object), uf::vector::toString(physics.velocity));
|
||||
stats.floored = true;
|
||||
// if ( physics.velocity.y < 0.0f ) physics.velocity.y = 0.0f;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if 0
|
||||
TIMER(0.25, keys.use ) {
|
||||
@ -328,7 +346,9 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if UF_USE_REACTPHYSICS
|
||||
if ( collider.stats.gravity == pod::Vector3f{0,0,0} ) stats.noclipped = true;
|
||||
#endif
|
||||
|
||||
{
|
||||
speed.rotate = metadata.movement.rotate * uf::physics::time::delta;
|
||||
@ -343,7 +363,7 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
speed.run *= 1.5;
|
||||
}
|
||||
if ( !stats.floored || stats.noclipped ) speed.friction = 1;
|
||||
if ( stats.noclipped ) physics.linear.velocity = {};
|
||||
if ( stats.noclipped ) physics.velocity = {};
|
||||
}
|
||||
if ( keys.running ) speed.move = speed.run;
|
||||
else if ( keys.walk ) speed.move = speed.walk;
|
||||
@ -403,10 +423,13 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
translator = cameraTransform;
|
||||
translator.forward.y = 0;
|
||||
translator.forward = uf::vector::normalize( translator.forward );
|
||||
} else if ( stats.noclipped || collider.stats.gravity == pod::Vector3f{0,0,0} ){
|
||||
}
|
||||
#if UF_USE_REACTPHYSICS
|
||||
else if ( stats.noclipped || collider.stats.gravity == pod::Vector3f{0,0,0} ){
|
||||
translator.forward.y += cameraTransform.forward.y;
|
||||
translator.forward = uf::vector::normalize( translator.forward );
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( metadata.system.control ) {
|
||||
// noclip handler
|
||||
@ -430,16 +453,16 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
if ( keys.left ^ keys.right ) target += translator.right * (keys.right ? 1 : -1);
|
||||
target = uf::vector::normalize( target );
|
||||
|
||||
physics.linear.velocity *= { speed.friction, 1, speed.friction };
|
||||
physics.velocity *= { speed.friction, 1, speed.friction };
|
||||
|
||||
stats.walking = (keys.forward ^ keys.backwards) || (keys.left ^ keys.right);
|
||||
|
||||
if ( stats.walking ) {
|
||||
float factor = stats.floored ? 1.0f : speed.air;
|
||||
if ( stats.noclipped ) {
|
||||
physics.linear.velocity += target * speed.move;
|
||||
physics.velocity += target * speed.move;
|
||||
} else {
|
||||
physics.linear.velocity += target * std::clamp( speed.move * factor - uf::vector::dot( physics.linear.velocity, target ), 0.0f, speed.move * 10 * uf::physics::time::delta );
|
||||
physics.velocity += target * std::clamp( speed.move * factor - uf::vector::dot( physics.velocity, target ), 0.0f, speed.move * 10 * uf::physics::time::delta );
|
||||
}
|
||||
|
||||
auto dot = uf::vector::dot( transform.forward, target );
|
||||
@ -449,14 +472,18 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
auto axis = transform.up;
|
||||
float angle = uf::vector::signedAngle( transform.forward, target, axis ) * uf::physics::time::delta * 4; // speed.rotate;
|
||||
|
||||
#if UF_USE_REACTPHYSICS
|
||||
if ( collider.body ) uf::physics::impl::applyRotation( collider, axis, angle ); else
|
||||
#else
|
||||
if ( physics.object ) uf::physics::impl::applyRotation( physics, axis, angle ); else
|
||||
#endif
|
||||
uf::transform::rotate( transform, axis, angle );
|
||||
}
|
||||
}
|
||||
if ( !stats.floored ) stats.walking = false;
|
||||
}
|
||||
TIMER(0.0625, stats.floored && keys.jump && !stats.noclipped ) {
|
||||
physics.linear.velocity += translator.up * metadata.movement.jump;
|
||||
physics.velocity += translator.up * metadata.movement.jump;
|
||||
}
|
||||
if ( stats.floored && keys.jump && stats.noclipped ) transform.position += translator.up * metadata.movement.jump * uf::physics::time::delta * 4.0f;
|
||||
if ( keys.crouch ) {
|
||||
@ -500,7 +527,11 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
if ( metadata.camera.invert.x ) lookDelta.x *= -1;
|
||||
metadata.camera.limit.current.x += lookDelta.x;
|
||||
if ( metadata.camera.limit.current.x != metadata.camera.limit.current.x || ( metadata.camera.limit.current.x < metadata.camera.limit.max.x && metadata.camera.limit.current.x > metadata.camera.limit.min.x ) ) {
|
||||
#if UF_USE_REACTPHYSICS
|
||||
if ( collider.body ) uf::physics::impl::applyRotation( collider, transform.up, lookDelta.x ); else
|
||||
#else
|
||||
if ( physics.object ) uf::physics::impl::applyRotation( physics, transform.up, lookDelta.x ); else
|
||||
#endif
|
||||
uf::transform::rotate( transform, transform.up, lookDelta.x );
|
||||
} else metadata.camera.limit.current.x -= lookDelta.x;
|
||||
}
|
||||
@ -508,13 +539,19 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
if ( metadata.camera.invert.y ) lookDelta.y *= -1;
|
||||
metadata.camera.limit.current.y += lookDelta.y;
|
||||
if ( metadata.camera.limit.current.y != metadata.camera.limit.current.y || ( metadata.camera.limit.current.y < metadata.camera.limit.max.y && metadata.camera.limit.current.y > metadata.camera.limit.min.y ) ) {
|
||||
#if UF_USE_REACTPHYSICS
|
||||
// if ( collider.body && !collider.shared ) uf::physics::impl::applyRotation( collider, cameraTransform.right, lookDelta.y ); else
|
||||
#endif
|
||||
uf::transform::rotate( cameraTransform, cameraTransform.right, lookDelta.y );
|
||||
} else metadata.camera.limit.current.y -= lookDelta.y;
|
||||
}
|
||||
} else if ( metadata.system.control ) {
|
||||
if ( keys.lookRight ^ keys.lookLeft ) {
|
||||
#if UF_USE_REACTPHYSICS
|
||||
if ( collider.body ) uf::physics::impl::applyRotation( collider, transform.up, speed.rotate * (keys.lookRight ? 1 : -1) ); else
|
||||
#else
|
||||
if ( physics.object ) uf::physics::impl::applyRotation( physics, transform.up, speed.rotate * (keys.lookRight ? 1 : -1) ); else
|
||||
#endif
|
||||
uf::transform::rotate( transform, transform.up, speed.rotate * (keys.lookRight ? 1 : -1) );
|
||||
}
|
||||
if ( keys.lookUp ^ keys.lookDown ) {
|
||||
@ -529,15 +566,22 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
// cameraTransform.position = uf::quaternion::rotate( rotation, cameraTransform.position - transform.position );
|
||||
}
|
||||
if ( keys.lookUp ^ keys.lookDown ) {
|
||||
#if 0
|
||||
// if ( collider.body && !collider.shared ) uf::physics::impl::applyRotation( collider, cameraTransform.right, lookDelta.y ); else
|
||||
#endif
|
||||
float direction = keys.lookUp ? 1 : -1;
|
||||
if ( metadata.camera.invert.y ) direction *= -1;
|
||||
uf::transform::rotate( cameraTransform, cameraTransform.right, speed.rotate * direction );
|
||||
}
|
||||
}
|
||||
{
|
||||
if ( collider.body ) uf::physics::impl::setVelocity( collider, physics.linear.velocity ); else
|
||||
transform.position += physics.linear.velocity * uf::physics::time::delta;
|
||||
#if UF_USE_REACTPHYSICS
|
||||
if ( collider.body ) uf::physics::impl::setVelocity( collider, physics.velocity ); else
|
||||
#else
|
||||
if ( physics.object ) uf::physics::impl::setVelocity( physics, physics.velocity ); else
|
||||
#endif
|
||||
transform.position += physics.velocity * uf::physics::time::delta;
|
||||
// if ( uf::vector::magnitude( physics.velocity ) > 1.0e-6 ) UF_MSG_DEBUG("Velocity: {}", uf::vector::toString( physics.velocity ));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -29,7 +29,9 @@ namespace {
|
||||
auto& metadataJson = this->getComponent<uf::Serializer>();
|
||||
|
||||
auto& mesh = this->getComponent<uf::Mesh>();
|
||||
#if 0
|
||||
auto& collider = this->getComponent<pod::PhysicsState>();
|
||||
#endif
|
||||
|
||||
auto& graphic = this->getComponent<uf::Graphic>();
|
||||
auto& texture = graphic.material.textures.emplace_back();
|
||||
@ -67,6 +69,7 @@ namespace {
|
||||
uf::graph::convert( self );
|
||||
}
|
||||
|
||||
#if 0
|
||||
{
|
||||
auto& phyziks = metadataJson["physics"];
|
||||
|
||||
@ -77,8 +80,8 @@ namespace {
|
||||
collider.stats.gravity = uf::vector::decode( phyziks["gravity"], collider.stats.gravity );
|
||||
|
||||
uf::physics::impl::create( self, mesh, !phyziks["static"].as<bool>(true) );
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
void ext::RegionChunkBehavior::initialize( uf::Object& self ) {
|
||||
|
||||
@ -24,7 +24,6 @@
|
||||
|
||||
#include <uf/ext/gltf/gltf.h>
|
||||
|
||||
#include <uf/utils/math/collision.h>
|
||||
#include <uf/utils/window/payloads.h>
|
||||
|
||||
#include <uf/ext/ext.h>
|
||||
|
||||
@ -1347,11 +1347,17 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
|
||||
pod::Vector3f center = (max + min) * 0.5f;
|
||||
pod::Vector3f corner = uf::vector::abs(max - min) * 0.5f;
|
||||
|
||||
UF_MSG_DEBUG("{}: Center: {} | Corner: {}", node.name, uf::vector::toString( center ), uf::vector::toString( corner ));
|
||||
UF_MSG_DEBUG("{}: Min: {} | Max: {}", node.name, uf::vector::toString( min ), uf::vector::toString( max ));
|
||||
|
||||
metadataJson["physics"]["center"] = uf::vector::encode( center );
|
||||
metadataJson["physics"]["corner"] = uf::vector::encode( corner );
|
||||
if ( ext::json::isNull( metadataJson["physics"]["center"] ) ) metadataJson["physics"]["center"] = uf::vector::encode( center );
|
||||
if ( ext::json::isNull( metadataJson["physics"]["corner"] ) ) metadataJson["physics"]["corner"] = uf::vector::encode( corner );
|
||||
if ( ext::json::isNull( metadataJson["physics"]["min"] ) ) metadataJson["physics"]["min"] = uf::vector::encode( min );
|
||||
if ( ext::json::isNull( metadataJson["physics"]["max"] ) ) metadataJson["physics"]["max"] = uf::vector::encode( max );
|
||||
}
|
||||
#if !UF_GRAPH_EXTENDED
|
||||
#if UF_USE_REACTPHYSICS
|
||||
if ( type == "mesh" ) {
|
||||
auto& collider = entity.getComponent<pod::PhysicsState>();
|
||||
collider.stats.mass = phyziks["mass"].as(collider.stats.mass);
|
||||
@ -1362,6 +1368,12 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
|
||||
uf::physics::impl::create( entity.as<uf::Object>(), mesh, !phyziks["static"].as<bool>(true) );
|
||||
}
|
||||
#else
|
||||
if ( type == "mesh" ) {
|
||||
auto phyziks["mass"].as(collider.stats.mass);
|
||||
uf::physics::impl::create( entity.as<uf::Object>(), mesh, mass );
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -1917,10 +1929,11 @@ 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)
|
||||
{
|
||||
#if UF_USE_REACTPHYSICS
|
||||
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" ) {
|
||||
@ -1939,6 +1952,24 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
|
||||
uf::physics::impl::create( entity, mesh, !phyziks["static"].as<bool>(true) );
|
||||
}
|
||||
}
|
||||
#else
|
||||
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" ) {
|
||||
bool exists = entity.hasComponent<pod::RigidBody>();
|
||||
if ( exists ) {
|
||||
uf::physics::impl::destroy( entity );
|
||||
}
|
||||
|
||||
auto mass = phyziks["mass"].as(0.0f);
|
||||
uf::physics::impl::create( entity, mesh, mass );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
void uf::graph::reload( pod::Graph& graph ) {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#include <uf/engine/object/object.h>
|
||||
#include <uf/engine/asset/asset.h>
|
||||
#include <uf/engine/scene/scene.h>
|
||||
#include <uf/utils/math/collision.h>
|
||||
#include <uf/utils/time/time.h>
|
||||
#include <uf/utils/audio/audio.h>
|
||||
#include <uf/utils/math/transform.h>
|
||||
@ -10,7 +9,6 @@
|
||||
#include <uf/utils/camera/camera.h>
|
||||
#include <uf/utils/mesh/mesh.h>
|
||||
#include <uf/utils/renderer/renderer.h>
|
||||
#include <uf/utils/math/physics.h>
|
||||
#include <uf/ext/gltf/gltf.h>
|
||||
#include <uf/engine/graph/graph.h>
|
||||
|
||||
@ -132,27 +130,64 @@ void uf::ObjectBehavior::initialize( uf::Object& self ) {
|
||||
});
|
||||
|
||||
if ( ext::json::isObject(metadataJson["physics"]) ) {
|
||||
auto& metadataJsonPhysics = metadataJson["physics"];
|
||||
#if UF_USE_REACTPHYSICS
|
||||
auto& collider = this->getComponent<pod::PhysicsState>();
|
||||
collider.stats.flags = metadataJson["physics"]["flags"].as(collider.stats.flags);
|
||||
collider.stats.mass = metadataJson["physics"]["mass"].as(collider.stats.mass);
|
||||
collider.stats.restitution = metadataJson["physics"]["restitution"].as(collider.stats.restitution);
|
||||
collider.stats.friction = metadataJson["physics"]["friction"].as(collider.stats.friction);
|
||||
collider.stats.inertia = uf::vector::decode( metadataJson["physics"]["inertia"], collider.stats.inertia );
|
||||
collider.stats.gravity = uf::vector::decode( metadataJson["physics"]["gravity"], collider.stats.gravity );
|
||||
collider.stats.flags = metadataJsonPhysics["flags"].as(collider.stats.flags);
|
||||
collider.stats.mass = metadataJsonPhysics["mass"].as(collider.stats.mass);
|
||||
collider.stats.restitution = metadataJsonPhysics["restitution"].as(collider.stats.restitution);
|
||||
collider.stats.friction = metadataJsonPhysics["friction"].as(collider.stats.friction);
|
||||
collider.stats.inertia = uf::vector::decode( metadataJsonPhysics["inertia"], collider.stats.inertia );
|
||||
collider.stats.gravity = uf::vector::decode( metadataJsonPhysics["gravity"], collider.stats.gravity );
|
||||
|
||||
if ( metadataJson["physics"]["type"].as<uf::stl::string>() == "bounding box" ) {
|
||||
pod::Vector3f center = uf::vector::decode( metadataJson["physics"]["center"], pod::Vector3f{} );
|
||||
pod::Vector3f corner = uf::vector::decode( metadataJson["physics"]["corner"], pod::Vector3f{0.5, 0.5, 0.5} );
|
||||
if ( metadataJsonPhysics["type"].as<uf::stl::string>() == "bounding box" ) {
|
||||
pod::Vector3f center = uf::vector::decode( metadataJsonPhysics["center"], pod::Vector3f{} );
|
||||
pod::Vector3f corner = uf::vector::decode( metadataJsonPhysics["corner"], pod::Vector3f{0.5, 0.5, 0.5} );
|
||||
|
||||
if ( metadataJson["physics"]["recenter"].as<bool>(true) ) collider.transform.position = (center - transform.position);
|
||||
if ( metadataJsonPhysics["recenter"].as<bool>(true) ) collider.transform.position = (center - transform.position);
|
||||
|
||||
uf::physics::impl::create( *this, corner );
|
||||
} else if ( metadataJson["physics"]["type"].as<uf::stl::string>() == "capsule" ) {
|
||||
float radius = metadataJson["physics"]["radius"].as<float>();
|
||||
float height = metadataJson["physics"]["height"].as<float>();
|
||||
} else if ( metadataJsonPhysics["type"].as<uf::stl::string>() == "capsule" ) {
|
||||
float radius = metadataJsonPhysics["radius"].as<float>();
|
||||
float height = metadataJsonPhysics["height"].as<float>();
|
||||
|
||||
uf::physics::impl::create( *this, radius, height );
|
||||
}
|
||||
#else
|
||||
auto type = metadataJsonPhysics["type"].as<uf::stl::string>();
|
||||
float mass = metadataJsonPhysics["mass"].as<float>();
|
||||
|
||||
if ( type == "bounding box" || type == "aabb" ) {
|
||||
/*
|
||||
pod::Vector3f center = uf::vector::decode( metadataJsonPhysics["center"], pod::Vector3f{} );
|
||||
pod::Vector3f corner = uf::vector::decode( metadataJsonPhysics["corner"], pod::Vector3f{0.5, 0.5, 0.5} );
|
||||
uf::physics::impl::create( self, pod::AABB{ center - corner, center + corner }, mass );
|
||||
*/
|
||||
pod::Vector3f min = uf::vector::decode( metadataJsonPhysics["min"], pod::Vector3f{-0.5f, -0.5f, -0.5f} );
|
||||
pod::Vector3f max = uf::vector::decode( metadataJsonPhysics["max"], pod::Vector3f{0.5f, 0.5f, 0.5f} );
|
||||
uf::physics::impl::create( self, pod::AABB{ .min = min, .max = max }, mass );
|
||||
} else if ( type == "plane" ) {
|
||||
pod::Vector3f direction = uf::vector::decode( metadataJsonPhysics["direction"], pod::Vector3f{} );
|
||||
float offset = metadataJsonPhysics["offset"].as<float>();
|
||||
|
||||
uf::physics::impl::create( self, pod::Plane{ direction, offset }, mass );
|
||||
} else if ( type == "sphere" ) {
|
||||
float radius = metadataJsonPhysics["radius"].as<float>();
|
||||
|
||||
uf::physics::impl::create( self, pod::Sphere{ radius }, mass );
|
||||
} else if ( type == "capsule" ) {
|
||||
float radius = metadataJsonPhysics["radius"].as<float>();
|
||||
float height = metadataJsonPhysics["height"].as<float>();
|
||||
|
||||
uf::physics::impl::create( self, pod::Capsule{ radius, height * 0.5f }, mass );
|
||||
}
|
||||
|
||||
if ( this->getName() == "Player" && this->hasComponent<pod::RigidBody>() ) {
|
||||
auto& body = this->getComponent<pod::RigidBody>();
|
||||
body.inertiaTensor = { FLT_MAX, FLT_MAX, FLT_MAX };
|
||||
body.inverseInertiaTensor = { 0.0f, 0.0f, 0.0f };
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
UF_BEHAVIOR_METADATA_BIND_SERIALIZER_HOOKS(metadata, metadataJson);
|
||||
@ -195,11 +230,13 @@ void uf::ObjectBehavior::destroy( uf::Object& self ) {
|
||||
atlas.clear();
|
||||
// this->deleteComponent<uf::Atlas>();
|
||||
}
|
||||
#if UF_USE_REACTPHYSICS
|
||||
if ( this->hasComponent<pod::PhysicsState>() ) {
|
||||
auto& collider = this->getComponent<pod::PhysicsState>();
|
||||
uf::physics::impl::detach( collider );
|
||||
// this->deleteComponent<pod::PhysicsState>();
|
||||
}
|
||||
#endif
|
||||
if ( this->hasComponent<uf::renderer::RenderTargetRenderMode>() ) {
|
||||
auto& renderMode = this->getComponent<uf::renderer::RenderTargetRenderMode>();
|
||||
if ( uf::renderer::settings::experimental::registerRenderMode ) {
|
||||
@ -218,14 +255,6 @@ void uf::ObjectBehavior::destroy( uf::Object& self ) {
|
||||
// this->deleteComponent<uf::renderer::DeferredRenderMode>();
|
||||
}
|
||||
}
|
||||
/*
|
||||
if ( this->hasComponent<pod::Graph::Storage>() ) {
|
||||
uf::graph::destroy( this->getComponent<pod::Graph::Storage>() );
|
||||
}
|
||||
if ( this->hasComponent<uf::physics::impl::WorldState>() ) {
|
||||
uf::physics::impl::destroy( self );
|
||||
}
|
||||
*/
|
||||
|
||||
#if UF_ENTITY_OBJECT_UNIFIED
|
||||
for ( uf::Entity* kv : this->getChildren() ) kv->destroy();
|
||||
|
||||
@ -346,13 +346,15 @@ bool uf::Object::load( const uf::Serializer& _json ) {
|
||||
}
|
||||
}
|
||||
// Set movement
|
||||
#if UF_USE_REACTPHYSICS
|
||||
if ( ext::json::isObject( json["physics"] ) && !this->hasComponent<pod::Physics>() ) {
|
||||
auto& physics = this->getComponent<pod::Physics>();
|
||||
physics.linear.velocity = uf::vector::decode( json["physics"]["linear"]["velocity"], physics.linear.velocity );
|
||||
physics.linear.acceleration = uf::vector::decode( json["physics"]["linear"]["acceleration"], physics.linear.acceleration );
|
||||
physics.rotational.velocity = uf::vector::decode( json["physics"]["rotational"]["velocity"], physics.rotational.velocity );
|
||||
physics.rotational.acceleration = uf::vector::decode( json["physics"]["rotational"]["acceleration"], physics.rotational.acceleration );
|
||||
physics.velocity = uf::vector::decode( json["physics"]["linear"]["velocity"], physics.velocity );
|
||||
physics.acceleration = uf::vector::decode( json["physics"]["linear"]["acceleration"], physics.acceleration );
|
||||
physics.angularVelocity = uf::vector::decode( json["physics"]["rotational"]["velocity"], physics.angularVelocity );
|
||||
physics.angularAcceleration = uf::vector::decode( json["physics"]["rotational"]["acceleration"], physics.angularAcceleration );
|
||||
}
|
||||
#endif
|
||||
|
||||
// Load assets
|
||||
this->loadAssets( json );
|
||||
|
||||
@ -211,9 +211,11 @@ void uf::scene::unloadScene() {
|
||||
if ( current->hasComponent<pod::Graph::Storage>() ) {
|
||||
uf::graph::destroy( current->getComponent<pod::Graph::Storage>() );
|
||||
}
|
||||
#if 0
|
||||
if ( current->hasComponent<uf::physics::impl::WorldState>() ) {
|
||||
uf::physics::impl::destroy( *current );
|
||||
}
|
||||
#endif
|
||||
|
||||
// mark rendermodes as disabled immediately
|
||||
auto graph = current->getGraph(true);
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
#include <uf/ext/lua/lua.h>
|
||||
#if UF_USE_LUA
|
||||
#if UF_USE_LUA && UF_USE_REACTPHYSICS
|
||||
#include <uf/utils/math/physics.h>
|
||||
|
||||
namespace binds {
|
||||
bool hasBody( pod::Physics& self ) { return self.body; }
|
||||
pod::Vector3f& linearVelocity( pod::Physics& self ) { return self.linear.velocity; }
|
||||
pod::Quaternion<>& rotationalVelocity( pod::Physics& self ) { return self.rotational.velocity; }
|
||||
pod::Vector3f& linearVelocity( pod::Physics& self ) { return self.velocity; }
|
||||
pod::Quaternion<>& rotationalVelocity( pod::Physics& self ) { return self.angularVelocity; }
|
||||
|
||||
void setLinearVelocity( pod::Physics& self, const pod::Vector3f& v ) { self.linear.velocity = v; }
|
||||
void setRotationalVelocity( pod::Physics& self, const pod::Quaternion<>& v ) { self.rotational.velocity = v; }
|
||||
void setLinearVelocity( pod::Physics& self, const pod::Vector3f& v ) { self.velocity = v; }
|
||||
void setRotationalVelocity( pod::Physics& self, const pod::Quaternion<>& v ) { self.angularVelocity = v; }
|
||||
|
||||
void enableGravity( pod::PhysicsState& state, bool s ) {
|
||||
if ( !state.body ) return;
|
||||
|
||||
@ -16,138 +16,97 @@ namespace {
|
||||
rp3d::PhysicsWorld* world;
|
||||
|
||||
// i was wrong to assume that RP3D handles deleting these per the documentation
|
||||
// these should be userdatas / allocated using our mempool instead to avoid fragmentation
|
||||
uf::stl::unordered_map<size_t, uf::stl::vector<rp3d::TriangleVertexArray*>> triangleParts;
|
||||
|
||||
reactphysics3d::TriangleMesh* createTriangleMesh( const uf::Mesh& mesh, const uf::Object& object ) {
|
||||
auto* rMesh = ::common.createTriangleMesh();
|
||||
auto& parts = ::triangleParts[object.getUid()];
|
||||
|
||||
uf::Mesh::Input vertexInput = mesh.vertex;
|
||||
uf::Mesh::Input indexInput = mesh.index;
|
||||
auto views = mesh.makeViews({"position", "normal"});
|
||||
|
||||
uf::Mesh::Attribute vertexAttribute = mesh.vertex.attributes.front();
|
||||
uf::Mesh::Attribute normalAttribute = mesh.vertex.attributes.front();
|
||||
uf::Mesh::Attribute indexAttribute = mesh.index.attributes.front();
|
||||
if ( views.empty() ) return rMesh;
|
||||
|
||||
for ( auto& attribute : mesh.vertex.attributes ) {
|
||||
if ( attribute.descriptor.name == "position" ) vertexAttribute = attribute;
|
||||
if ( attribute.descriptor.name == "normal" ) normalAttribute = attribute;
|
||||
}
|
||||
UF_ASSERT( vertexAttribute.descriptor.name == "position" );
|
||||
for ( auto& view : views ) {
|
||||
if ( !view.vertex.count || !view.index.count ) continue;
|
||||
|
||||
rp3d::TriangleVertexArray::IndexDataType indexType = rp3d::TriangleVertexArray::IndexDataType::INDEX_INTEGER_TYPE;
|
||||
rp3d::TriangleVertexArray::VertexDataType vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_FLOAT_TYPE;
|
||||
rp3d::TriangleVertexArray::NormalDataType normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_FLOAT_TYPE;
|
||||
switch ( mesh.index.size ) {
|
||||
case sizeof(uint16_t): indexType = rp3d::TriangleVertexArray::IndexDataType::INDEX_SHORT_TYPE; break;
|
||||
case sizeof(uint32_t): indexType = rp3d::TriangleVertexArray::IndexDataType::INDEX_INTEGER_TYPE; break;
|
||||
default: UF_EXCEPTION("unsupported index type: {}", mesh.index.size); break;
|
||||
}
|
||||
switch ( vertexAttribute.descriptor.type ) {
|
||||
case uf::renderer::enums::Type::USHORT:
|
||||
case uf::renderer::enums::Type::SHORT: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_SHORT_TYPE; break;
|
||||
case uf::renderer::enums::Type::FLOAT: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_FLOAT_TYPE; break;
|
||||
#if UF_USE_FLOAT16
|
||||
case uf::renderer::enums::Type::HALF: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_FLOAT16_TYPE; break;
|
||||
#endif
|
||||
#if UF_USE_BFLOAT16
|
||||
case uf::renderer::enums::Type::BFLOAT: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_BFLOAT16_TYPE; break;
|
||||
#endif
|
||||
default: UF_EXCEPTION("unsupported vertex type: {}", vertexAttribute.descriptor.type); break;
|
||||
}
|
||||
switch ( normalAttribute.descriptor.type ) {
|
||||
case uf::renderer::enums::Type::USHORT:
|
||||
case uf::renderer::enums::Type::SHORT: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_SHORT_TYPE; break;
|
||||
case uf::renderer::enums::Type::FLOAT: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_FLOAT_TYPE; break;
|
||||
#if UF_USE_FLOAT16
|
||||
case uf::renderer::enums::Type::HALF: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_FLOAT16_TYPE; break;
|
||||
#endif
|
||||
#if UF_USE_BFLOAT16
|
||||
case uf::renderer::enums::Type::BFLOAT: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_BFLOAT16_TYPE; break;
|
||||
#endif
|
||||
default: UF_EXCEPTION("unsupported normal type: {}", normalAttribute.descriptor.type); break;
|
||||
}
|
||||
rp3d::TriangleVertexArray* part = NULL;
|
||||
|
||||
if ( mesh.indirect.count ) {
|
||||
for ( auto i = 0; i < mesh.indirect.count; ++i ) {
|
||||
vertexInput = mesh.remapVertexInput( i );
|
||||
indexInput = mesh.remapIndexInput( i );
|
||||
|
||||
// skip if culled
|
||||
if ( vertexInput.count == 0 || indexInput.count == 0 ) continue;
|
||||
auto& indices = view["index"];
|
||||
auto& positions = view["position"];
|
||||
auto& normals = view["normal"];
|
||||
|
||||
if ( normalAttribute.descriptor.name == "normal" ) {
|
||||
auto* part = new rp3d::TriangleVertexArray(
|
||||
vertexInput.count,
|
||||
(const uint8_t*) (vertexAttribute.pointer) + vertexAttribute.stride * vertexInput.first,
|
||||
vertexAttribute.stride,
|
||||
|
||||
(const uint8_t*) (normalAttribute.pointer) + normalAttribute.stride * vertexInput.first,
|
||||
normalAttribute.stride,
|
||||
UF_ASSERT( positions.valid() );
|
||||
|
||||
indexInput.count / 3,
|
||||
(const uint8_t*) (indexAttribute.pointer) + indexAttribute.stride * indexInput.first,
|
||||
indexAttribute.stride * 3,
|
||||
|
||||
vertexType,
|
||||
normalType,
|
||||
indexType
|
||||
);
|
||||
parts.emplace_back(part);
|
||||
rMesh->addSubpart(part);
|
||||
} else {
|
||||
auto* part = new rp3d::TriangleVertexArray(
|
||||
vertexInput.count,
|
||||
(const uint8_t*) (vertexAttribute.pointer) + vertexAttribute.stride * vertexInput.first,
|
||||
vertexAttribute.stride,
|
||||
|
||||
indexInput.count / 3,
|
||||
(const uint8_t*) (indexAttribute.pointer) + indexAttribute.stride * indexInput.first,
|
||||
indexAttribute.stride * 3,
|
||||
|
||||
vertexType,
|
||||
indexType
|
||||
);
|
||||
parts.emplace_back(part);
|
||||
rMesh->addSubpart(part);
|
||||
}
|
||||
// deduce types
|
||||
rp3d::TriangleVertexArray::IndexDataType indexType = rp3d::TriangleVertexArray::IndexDataType::INDEX_INTEGER_TYPE;
|
||||
rp3d::TriangleVertexArray::VertexDataType vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_FLOAT_TYPE;
|
||||
rp3d::TriangleVertexArray::NormalDataType normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_FLOAT_TYPE;
|
||||
switch ( mesh.index.size ) {
|
||||
case sizeof(uint16_t): indexType = rp3d::TriangleVertexArray::IndexDataType::INDEX_SHORT_TYPE; break;
|
||||
case sizeof(uint32_t): indexType = rp3d::TriangleVertexArray::IndexDataType::INDEX_INTEGER_TYPE; break;
|
||||
default: UF_EXCEPTION("unsupported index type: {}", mesh.index.size); break;
|
||||
}
|
||||
switch ( positions.attribute.descriptor.type ) {
|
||||
case uf::renderer::enums::Type::USHORT:
|
||||
case uf::renderer::enums::Type::SHORT: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_SHORT_TYPE; break;
|
||||
case uf::renderer::enums::Type::FLOAT: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_FLOAT_TYPE; break;
|
||||
#if UF_USE_FLOAT16
|
||||
case uf::renderer::enums::Type::HALF: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_FLOAT16_TYPE; break;
|
||||
#endif
|
||||
#if UF_USE_BFLOAT16
|
||||
case uf::renderer::enums::Type::BFLOAT: vertexType = rp3d::TriangleVertexArray::VertexDataType::VERTEX_BFLOAT16_TYPE; break;
|
||||
#endif
|
||||
default: UF_EXCEPTION("unsupported vertex type: {}", positions.attribute.descriptor.type); break;
|
||||
}
|
||||
switch ( normals.attribute.descriptor.type ) {
|
||||
case uf::renderer::enums::Type::USHORT:
|
||||
case uf::renderer::enums::Type::SHORT: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_SHORT_TYPE; break;
|
||||
case uf::renderer::enums::Type::FLOAT: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_FLOAT_TYPE; break;
|
||||
#if UF_USE_FLOAT16
|
||||
case uf::renderer::enums::Type::HALF: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_FLOAT16_TYPE; break;
|
||||
#endif
|
||||
#if UF_USE_BFLOAT16
|
||||
case uf::renderer::enums::Type::BFLOAT: normalType = rp3d::TriangleVertexArray::NormalDataType::NORMAL_BFLOAT16_TYPE; break;
|
||||
#endif
|
||||
default: UF_EXCEPTION("unsupported normal type: {}", normals.attribute.descriptor.type); break;
|
||||
}
|
||||
} else if ( vertexInput.count > 0 && indexInput.count > 0 ) {
|
||||
if ( normalAttribute.descriptor.name == "normal" ) {
|
||||
auto* part = new rp3d::TriangleVertexArray(
|
||||
vertexInput.count,
|
||||
(const uint8_t*) (vertexAttribute.pointer) + vertexAttribute.stride * vertexInput.first,
|
||||
vertexAttribute.stride,
|
||||
|
||||
(const uint8_t*) (normalAttribute.pointer) + normalAttribute.stride * vertexInput.first,
|
||||
normalAttribute.stride,
|
||||
|
||||
indexInput.count / 3,
|
||||
(const uint8_t*) (indexAttribute.pointer) + indexAttribute.stride * indexInput.first,
|
||||
indexAttribute.stride * 3,
|
||||
// has normals
|
||||
if ( normals.valid() ) {
|
||||
part = new rp3d::TriangleVertexArray(
|
||||
view.vertex.count,
|
||||
positions.data(view.vertex.first),
|
||||
positions.stride(),
|
||||
|
||||
normals.data(view.vertex.first),
|
||||
normals.stride(),
|
||||
|
||||
view.index.count / 3,
|
||||
indices.data(view.index.first),
|
||||
indices.stride() * 3,
|
||||
|
||||
vertexType,
|
||||
normalType,
|
||||
indexType
|
||||
);
|
||||
parts.emplace_back(part);
|
||||
rMesh->addSubpart(part);
|
||||
} else {
|
||||
auto* part = new rp3d::TriangleVertexArray(
|
||||
vertexInput.count,
|
||||
(const uint8_t*) (vertexAttribute.pointer) + vertexAttribute.stride * vertexInput.first,
|
||||
vertexAttribute.stride,
|
||||
|
||||
indexInput.count / 3,
|
||||
(const uint8_t*) (indexAttribute.pointer) + indexAttribute.stride * indexInput.first,
|
||||
indexAttribute.stride * 3,
|
||||
part = new rp3d::TriangleVertexArray(
|
||||
view.vertex.count,
|
||||
positions.data(view.vertex.first),
|
||||
positions.stride(),
|
||||
|
||||
view.index.count / 3,
|
||||
indices.data(view.index.first),
|
||||
indices.stride() * 3,
|
||||
|
||||
vertexType,
|
||||
indexType
|
||||
);
|
||||
parts.emplace_back(part);
|
||||
rMesh->addSubpart(part);
|
||||
}
|
||||
|
||||
parts.emplace_back(part);
|
||||
rMesh->addSubpart(part);
|
||||
}
|
||||
|
||||
return rMesh;
|
||||
@ -426,6 +385,8 @@ pod::PhysicsState& ext::reactphysics::create( uf::Object& object ) {
|
||||
state.transform.reference = &object.getComponent<pod::Transform<>>();
|
||||
state.shared = ext::reactphysics::shared;
|
||||
|
||||
UF_MSG_DEBUG("Created physics state: {}", uf::string::toString( object ));
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
@ -507,6 +468,8 @@ pod::PhysicsState& ext::reactphysics::create( uf::Object& object, const uf::Mesh
|
||||
state.stats.mass = 0;
|
||||
ext::reactphysics::attach( state );
|
||||
|
||||
UF_MSG_DEBUG("Created physics state (mesh): {}", uf::string::toString( object ));
|
||||
|
||||
return state;
|
||||
}
|
||||
// collider for boundingbox
|
||||
@ -514,6 +477,8 @@ pod::PhysicsState& ext::reactphysics::create( uf::Object& object, const pod::Vec
|
||||
auto& state = ext::reactphysics::create( object );
|
||||
state.shape = ::common.createBoxShape( rp3d::Vector3( abs(extent.x), abs(extent.y), abs(extent.z) ) );
|
||||
ext::reactphysics::attach( state );
|
||||
|
||||
UF_MSG_DEBUG("Created physics state (box): {}", uf::string::toString( object ));
|
||||
|
||||
return state;
|
||||
}
|
||||
@ -522,6 +487,8 @@ pod::PhysicsState& ext::reactphysics::create( uf::Object& object, float radius,
|
||||
auto& state = ext::reactphysics::create( object );
|
||||
state.shape = ::common.createCapsuleShape( radius, height );
|
||||
ext::reactphysics::attach( state );
|
||||
|
||||
UF_MSG_DEBUG("Created physics state (capsule): {} | {}, {}", uf::string::toString( object ), radius, height);
|
||||
|
||||
return state;
|
||||
}
|
||||
@ -549,8 +516,8 @@ void ext::reactphysics::syncTo( ext::reactphysics::WorldState& world ) {
|
||||
|
||||
if ( state.shared ) {
|
||||
if ( !ext::reactphysics::interpolate ) body->setTransform(::convert(state.transform));
|
||||
body->setLinearVelocity( ::convert(state.linear.velocity) );
|
||||
body->setAngularVelocity( ::convertQ(state.rotational.velocity) );
|
||||
body->setLinearVelocity( ::convert(state.velocity) );
|
||||
body->setAngularVelocity( ::convertQ(state.angularVelocity) );
|
||||
}
|
||||
// apply per-object gravities
|
||||
float mass = body->getMass();
|
||||
@ -629,11 +596,11 @@ void ext::reactphysics::syncFrom( ext::reactphysics::WorldState& world, float in
|
||||
*/
|
||||
|
||||
state.internal.current.transform = ::convert( body->getTransform() );
|
||||
state.internal.current.linear.velocity = ::convert( body->getLinearVelocity() );
|
||||
state.internal.current.rotational.velocity = ::convertQ( body->getAngularVelocity() );
|
||||
state.internal.current.velocity = ::convert( body->getLinearVelocity() );
|
||||
state.internal.current.angularVelocity = ::convertQ( body->getAngularVelocity() );
|
||||
|
||||
physics.linear.velocity = state.internal.current.linear.velocity;
|
||||
physics.rotational.velocity = state.internal.current.rotational.velocity;
|
||||
physics.velocity = state.internal.current.velocity;
|
||||
physics.angularVelocity = state.internal.current.angularVelocity;
|
||||
|
||||
if ( !ext::reactphysics::interpolate ) {
|
||||
transform.position = state.internal.current.transform.position;
|
||||
@ -650,8 +617,8 @@ void ext::reactphysics::syncFrom( ext::reactphysics::WorldState& world, float in
|
||||
transform.orientation = uf::quaternion::slerp( state.internal.previous.transform.orientation, state.internal.current.transform.orientation, interp);
|
||||
if ( state.transform.reference ) transform.position -= state.transform.position;
|
||||
|
||||
// physics.linear.velocity = uf::vector::lerp( state.internal.previous.linear.velocity, state.internal.current.linear.velocity, interp );
|
||||
// physics.rotational.velocity = uf::quaternion::slerp( state.internal.previous.rotational.velocity, state.internal.current.rotational.velocity, interp );
|
||||
// physics.velocity = uf::vector::lerp( state.internal.previous.velocity, state.internal.current.velocity, interp );
|
||||
// physics.angularVelocity = uf::quaternion::slerp( state.internal.previous.angularVelocity, state.internal.current.angularVelocity, interp );
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -688,7 +655,7 @@ void ext::reactphysics::setVelocity( pod::PhysicsState& state, const pod::Vector
|
||||
if ( !state.body ) return;
|
||||
if ( state.shared ) {
|
||||
auto& physics = state.object->getComponent<pod::Physics>();
|
||||
physics.linear.velocity = v;
|
||||
physics.velocity = v;
|
||||
// return;
|
||||
}
|
||||
state.body->setLinearVelocity( ::convert(v) );
|
||||
@ -698,7 +665,7 @@ void ext::reactphysics::applyVelocity( pod::PhysicsState& state, const pod::Vect
|
||||
|
||||
if ( state.shared ) {
|
||||
auto& physics = state.object->getComponent<pod::Physics>();
|
||||
physics.linear.velocity += v;
|
||||
physics.velocity += v;
|
||||
// return;
|
||||
}
|
||||
state.body->setLinearVelocity( state.body->getLinearVelocity() + ::convert(v) );
|
||||
|
||||
@ -62,79 +62,53 @@ size_t ext::xatlas::unwrapExperimental( pod::Graph& graph ) {
|
||||
source = mesh;
|
||||
source.updateDescriptor();
|
||||
|
||||
uf::Mesh::Input vertexInput = mesh.vertex;
|
||||
for (auto& view : source.makeViews({"position", "uv", "st", "index"})) {
|
||||
auto pos = view["position"];
|
||||
auto uv = view["uv"];
|
||||
auto st = view["st"];
|
||||
auto idx = view["index"];
|
||||
|
||||
uf::Mesh::Attribute positionAttribute;
|
||||
uf::Mesh::Attribute uvAttribute;
|
||||
uf::Mesh::Attribute stAttribute;
|
||||
|
||||
for ( auto& attribute : mesh.vertex.attributes ) {
|
||||
if ( attribute.descriptor.name == "position" ) positionAttribute = attribute;
|
||||
else if ( attribute.descriptor.name == "uv" ) uvAttribute = attribute;
|
||||
else if ( attribute.descriptor.name == "st" ) stAttribute = attribute;
|
||||
}
|
||||
UF_ASSERT( positionAttribute.descriptor.name == "position" && uvAttribute.descriptor.name == "uv" && stAttribute.descriptor.name == "st" );
|
||||
|
||||
if ( mesh.index.count ) {
|
||||
uf::Mesh::Input indexInput = mesh.index;
|
||||
uf::Mesh::Attribute indexAttribute = mesh.index.attributes.front();
|
||||
// Must have positions + indices
|
||||
if (!pos.valid() || !idx.valid()) continue;
|
||||
if (view.vertex.count == 0 || view.index.count == 0) continue;
|
||||
|
||||
// Choose xatlas index format
|
||||
::xatlas::IndexFormat indexType = ::xatlas::IndexFormat::UInt32;
|
||||
switch ( mesh.index.size ) {
|
||||
switch (idx.attribute.descriptor.size) {
|
||||
case sizeof(uint16_t): indexType = ::xatlas::IndexFormat::UInt16; break;
|
||||
case sizeof(uint32_t): indexType = ::xatlas::IndexFormat::UInt32; break;
|
||||
default: UF_EXCEPTION("unsupported index type"); break;
|
||||
}
|
||||
|
||||
if ( mesh.indirect.count ) {
|
||||
auto& primitives = /*graph.storage*/storage.primitives[name];
|
||||
pod::DrawCommand* drawCommands = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data();
|
||||
|
||||
for ( auto i = 0; i < mesh.indirect.count; ++i ) {
|
||||
size_t atlasID = drawCommands[i].auxID;
|
||||
|
||||
vertexInput = mesh.remapVertexInput( i );
|
||||
indexInput = mesh.remapIndexInput( i );
|
||||
|
||||
auto& atlas = atlases[atlasID];
|
||||
auto& entry = atlas.entries.emplace_back();
|
||||
entry.index = index;
|
||||
entry.commandID = i;
|
||||
|
||||
auto& decl = entry.decl;
|
||||
|
||||
decl.vertexPositionData = static_cast<uint8_t*>(positionAttribute.pointer) + positionAttribute.stride * vertexInput.first;
|
||||
decl.vertexPositionStride = positionAttribute.stride;
|
||||
|
||||
decl.vertexUvData = static_cast<uint8_t*>(uvAttribute.pointer) + uvAttribute.stride * vertexInput.first;
|
||||
decl.vertexUvStride = uvAttribute.stride;
|
||||
|
||||
decl.vertexCount = vertexInput.count;
|
||||
|
||||
decl.indexCount = indexInput.count;
|
||||
decl.indexData = static_cast<uint8_t*>(indexAttribute.pointer) + indexAttribute.stride * indexInput.first;
|
||||
decl.indexFormat = indexType;
|
||||
}
|
||||
} else {
|
||||
size_t atlasID = 0;
|
||||
auto& atlas = atlases[atlasID];
|
||||
auto& entry = atlas.entries.emplace_back();
|
||||
entry.index = index;
|
||||
|
||||
auto& decl = entry.decl;
|
||||
decl.vertexPositionData = static_cast<uint8_t*>(positionAttribute.pointer) + positionAttribute.stride * vertexInput.first;
|
||||
decl.vertexPositionStride = positionAttribute.stride;
|
||||
|
||||
decl.vertexUvData = static_cast<uint8_t*>(uvAttribute.pointer) + uvAttribute.stride * vertexInput.first;
|
||||
decl.vertexUvStride = uvAttribute.stride;
|
||||
|
||||
decl.vertexCount = vertexInput.count;
|
||||
|
||||
decl.indexCount = indexInput.count;
|
||||
decl.indexData = static_cast<uint8_t*>(indexAttribute.pointer) + indexAttribute.stride * indexInput.first;
|
||||
decl.indexFormat = indexType;
|
||||
// Get atlas target from indirect auxID or default 0
|
||||
size_t atlasID = 0;
|
||||
if ( 0 <= view.indirectIndex ) {
|
||||
auto* drawCommand = (pod::DrawCommand*) mesh.getBuffer(mesh.indirect).data();
|
||||
atlasID = drawCommand[view.indirectIndex].auxID;
|
||||
}
|
||||
} else UF_EXCEPTION("to-do: not require indices for meshes");
|
||||
|
||||
auto& atlas = atlases[atlasID];
|
||||
auto& entry = atlas.entries.emplace_back();
|
||||
entry.index = index;
|
||||
|
||||
auto& decl = entry.decl;
|
||||
decl.vertexPositionData = pos.data(view.vertex.first);
|
||||
decl.vertexPositionStride = pos.stride();
|
||||
|
||||
if ( uv.valid() ) {
|
||||
decl.vertexUvData = uv.data(view.vertex.first);
|
||||
decl.vertexUvStride = uv.stride();
|
||||
} else if ( st.valid() ) {
|
||||
decl.vertexUvData = st.data(view.vertex.first);
|
||||
decl.vertexUvStride = st.stride();
|
||||
}
|
||||
|
||||
decl.vertexCount = view.vertex.count;
|
||||
|
||||
decl.indexCount = view.index.count;
|
||||
decl.indexData = idx.data(view.index.first);
|
||||
decl.indexFormat = indexType;
|
||||
}
|
||||
}
|
||||
|
||||
::xatlas::ChartOptions chartOptions{};
|
||||
|
||||
@ -1,159 +0,0 @@
|
||||
#if UF_USE_UNUSED
|
||||
#include <uf/utils/math/collision/boundingbox.h>
|
||||
|
||||
uf::BoundingBox::BoundingBox( const pod::Vector3& origin, const pod::Vector3& corner ) {
|
||||
this->m_origin = origin;
|
||||
this->m_corner = corner;
|
||||
}
|
||||
const pod::Vector3& uf::BoundingBox::getOrigin() const {
|
||||
return this->m_origin;
|
||||
}
|
||||
const pod::Vector3& uf::BoundingBox::getCorner() const {
|
||||
return this->m_corner;
|
||||
}
|
||||
void uf::BoundingBox::setOrigin( const pod::Vector3& origin ) {
|
||||
this->m_origin = origin;
|
||||
}
|
||||
void uf::BoundingBox::setCorner( const pod::Vector3& corner ) {
|
||||
this->m_corner = corner;
|
||||
}
|
||||
pod::Vector3 uf::BoundingBox::min() const {
|
||||
pod::Vector3f position = this->getPosition() + this->m_origin;
|
||||
// const pod::Vector3f& position = this->m_origin;
|
||||
return position - this->m_corner;
|
||||
}
|
||||
pod::Vector3 uf::BoundingBox::max() const {
|
||||
pod::Vector3f position = this->getPosition() + this->m_origin;
|
||||
// const pod::Vector3f& position = this->m_origin;
|
||||
return position + this->m_corner;
|
||||
}
|
||||
pod::Vector3 uf::BoundingBox::closest( const pod::Vector3f& point ) const {
|
||||
pod::Vector3f position = this->getPosition() + this->m_origin;
|
||||
// const pod::Vector3f& position = this->m_origin;
|
||||
|
||||
float distance = 9E9;
|
||||
pod::Vector3f vector = point;
|
||||
pod::Vector3f min = position - this->m_corner;
|
||||
pod::Vector3f max = position + this->m_corner;
|
||||
|
||||
float test = 0;
|
||||
if ( (test = fabs(min.x - point.x)) < distance ) {
|
||||
distance = test;
|
||||
vector.x = min.x;
|
||||
}
|
||||
if ( (test = fabs(max.x - point.x)) < distance ) {
|
||||
distance = test;
|
||||
vector.x = max.x;
|
||||
}
|
||||
|
||||
if ( (test = fabs(min.y - point.y)) < distance ) {
|
||||
distance = test;
|
||||
vector.y = min.y;
|
||||
}
|
||||
if ( (test = fabs(max.y - point.y)) < distance ) {
|
||||
distance = test;
|
||||
vector.y = max.y;
|
||||
}
|
||||
|
||||
if ( (test = fabs(min.z - point.z)) < distance ) {
|
||||
distance = test;
|
||||
vector.z = min.z;
|
||||
}
|
||||
if ( (test = fabs(max.z - point.z)) < distance ) {
|
||||
distance = test;
|
||||
vector.z = max.z;
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
uf::stl::string uf::BoundingBox::type() const { return "BoundingBox"; }
|
||||
pod::Vector3* uf::BoundingBox::expand() const {
|
||||
pod::Vector3* raw = new pod::Vector3[8];
|
||||
raw[0] = pod::Vector3{ this->m_corner.x, this->m_corner.y, this->m_corner.z};
|
||||
raw[1] = pod::Vector3{ this->m_corner.x, this->m_corner.y, -this->m_corner.z};
|
||||
raw[2] = pod::Vector3{ -this->m_corner.x, this->m_corner.y, -this->m_corner.z};
|
||||
raw[3] = pod::Vector3{ -this->m_corner.x, this->m_corner.y, this->m_corner.z};
|
||||
raw[4] = pod::Vector3{ -this->m_corner.x, -this->m_corner.y, this->m_corner.z};
|
||||
raw[5] = pod::Vector3{ this->m_corner.x, -this->m_corner.y, this->m_corner.z};
|
||||
raw[6] = pod::Vector3{ this->m_corner.x, -this->m_corner.y, -this->m_corner.z};
|
||||
raw[7] = pod::Vector3{ -this->m_corner.x, -this->m_corner.y, this->m_corner.z};
|
||||
|
||||
pod::Vector3f position = this->getPosition() + this->m_origin;
|
||||
// const pod::Vector3f& position = this->m_origin;
|
||||
for ( uint i = 0; i < 8; i++ ) raw[0] += position;
|
||||
|
||||
return raw;
|
||||
}
|
||||
pod::Vector3 uf::BoundingBox::support( const pod::Vector3& direction ) const {
|
||||
pod::Vector3f position = this->getPosition() + this->m_origin;
|
||||
// const pod::Vector3f& position = this->m_origin;
|
||||
pod::Vector3 res = {
|
||||
position.x + fabs(this->m_corner.x) * (direction.x > 0 ? 1 : -1),
|
||||
position.y + fabs(this->m_corner.y) * (direction.y > 0 ? 1 : -1),
|
||||
position.z + fabs(this->m_corner.z) * (direction.z > 0 ? 1 : -1),
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
pod::Collider::Manifold uf::BoundingBox::intersects( const uf::BoundingBox& b ) const {
|
||||
const uf::BoundingBox& a = *this;
|
||||
pod::Collider::Manifold manifold(a, b);
|
||||
|
||||
BoundingBox difference(a.min() - b.max() + a.m_corner + b.m_corner, (a.m_corner + b.m_corner) * 2.0f);
|
||||
|
||||
pod::Vector3f min = difference.m_origin - this->m_corner;
|
||||
pod::Vector3f max = difference.m_origin + this->m_corner;
|
||||
manifold.colliding = min.x <= 0 && max.x >= 0 && min.y <= 0 && max.y >= 0;
|
||||
|
||||
if ( !manifold.colliding ) return manifold;
|
||||
|
||||
pod::Vector3f penetration = difference.closest( {0, 0, 0} );
|
||||
manifold.depth = uf::vector::norm( penetration );
|
||||
manifold.normal = penetration / manifold.depth;
|
||||
|
||||
/*
|
||||
float a_left = position_a.x - a.m_corner.x;
|
||||
float a_right = position_a.x + a.m_corner.x;
|
||||
float a_bottom = position_a.y - a.m_corner.y;
|
||||
float a_top = position_a.y + a.m_corner.y;
|
||||
float a_back = position_a.z - a.m_corner.z;
|
||||
float a_front = position_a.z + a.m_corner.z;
|
||||
|
||||
|
||||
float b_left = position_b.x - b.m_corner.x;
|
||||
float b_right = position_b.x + b.m_corner.x;
|
||||
float b_bottom = position_b.y - b.m_corner.y;
|
||||
float b_top = position_b.y + b.m_corner.y;
|
||||
float b_back = position_b.z - b.m_corner.z;
|
||||
float b_front = position_b.z + b.m_corner.z;
|
||||
|
||||
manifold.depth = 9E9;
|
||||
auto test = [&]( const pod::Vector3& axis, float minA, float maxA, float minB, float maxB )->bool{
|
||||
float axisLSqr = uf::vector::dot(axis, axis);
|
||||
if ( axisLSqr < 1E-8f ) return true;
|
||||
|
||||
float d0 = maxB - minA;
|
||||
float d1 = maxA - minB;
|
||||
if ( d0 < 0 || d1 < 0 ) return false;
|
||||
float overlap = d0 < d1 ? d0 : -d1;
|
||||
pod::Vector3 sep = axis * ( overlap / axisLSqr );
|
||||
float sepLSqr = uf::vector::dot( sep, sep );
|
||||
if ( sepLSqr < manifold.depth ) {
|
||||
manifold.normal = sep;
|
||||
manifold.depth = sepLSqr;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if ( !test( {1, 0, 0}, a_left, a_right, b_left, b_right ) ) return manifold;
|
||||
if ( !test( {0, 1, 0}, a_bottom, a_top, b_bottom, b_top ) ) return manifold;
|
||||
if ( !test( {0, 0, 1}, a_back, a_front, b_back, b_front ) ) return manifold;
|
||||
|
||||
manifold.normal = uf::vector::normalize( manifold.normal );
|
||||
manifold.depth = sqrt( manifold.depth );
|
||||
// manifold.depth = manifold.depth;
|
||||
manifold.colliding = true;
|
||||
*/
|
||||
return manifold;
|
||||
}
|
||||
#endif
|
||||
@ -1,249 +0,0 @@
|
||||
#if UF_USE_UNUSED
|
||||
#include <uf/utils/math/collision/gjk.h>
|
||||
|
||||
/*
|
||||
pod::Simplex::Simplex( const pod::Vector3& b, const pod::Vector3& c, const pod::Vector3& d ) {
|
||||
this->set(b, c, d);
|
||||
}
|
||||
void pod::Simplex::add( const pod::Vector3& a ) {
|
||||
this->d = this->c;
|
||||
this->c = this->b;
|
||||
this->b = a;
|
||||
}
|
||||
void pod::Simplex::set( const pod::Vector3& b, const pod::Vector3& c, const pod::Vector3& d ) {
|
||||
this->b = b;
|
||||
this->c = c;
|
||||
this->d = d;
|
||||
if ( b != pod::Vector3{} ) this->size++;
|
||||
if ( c != pod::Vector3{} ) this->size++;
|
||||
if ( d != pod::Vector3{} ) this->size++;
|
||||
}
|
||||
*/
|
||||
pod::Collider::~Collider(){}
|
||||
uf::stl::string pod::Collider::type() const { return ""; }
|
||||
pod::Collider::Manifold pod::Collider::intersects( const pod::Collider& y ) const {
|
||||
const pod::Collider& x = *this;
|
||||
|
||||
pod::Simplex::SupportPoint a;
|
||||
pod::Simplex simplex;
|
||||
pod::Vector3 direction = pod::Vector3{ 1.0f, 0.0f, 0.0f };
|
||||
|
||||
pod::Collider::Manifold manifold(x, y);
|
||||
float dot = -0.1;
|
||||
uint16_t iterations = 0;
|
||||
uint16_t iterations_cap = 25;
|
||||
while ( iterations++ < iterations_cap ) {
|
||||
direction = uf::vector::normalize(direction);
|
||||
|
||||
a = pod::Simplex::SupportPoint{};
|
||||
a.a = x.support(direction);
|
||||
a.b = y.support(-direction);
|
||||
a.v = a.a - a.b;
|
||||
|
||||
if ( (dot = a.dot(direction)) < 0 ) {
|
||||
return manifold;
|
||||
}
|
||||
if( simplex.size == 0 ) {
|
||||
simplex.b = a;
|
||||
|
||||
direction = uf::vector::multiply(direction, -1);
|
||||
simplex.size = 1;
|
||||
continue;
|
||||
}
|
||||
if ( simplex.size == 1 ) {
|
||||
direction = uf::vector::cross(a.cross(simplex.b), a.v);
|
||||
simplex.c = simplex.b;
|
||||
simplex.b = a;
|
||||
simplex.size = 2;
|
||||
continue;
|
||||
}
|
||||
if ( simplex.size == 2 ) {
|
||||
pod::Vector3 ao = a * -1;
|
||||
pod::Vector3 ab = simplex.b - a;
|
||||
pod::Vector3 ac = simplex.c - a;
|
||||
pod::Vector3 abc = uf::vector::cross(ab, ac);
|
||||
pod::Vector3 abP = uf::vector::cross(ab, abc);
|
||||
if ( uf::vector::dot(abP, ao) > 0 ) {
|
||||
simplex.c = simplex.b;
|
||||
simplex.b = a;
|
||||
|
||||
direction = uf::vector::cross(ab, ao);
|
||||
continue;
|
||||
}
|
||||
pod::Vector3 acP = uf::vector::cross(abc, ac);
|
||||
if ( uf::vector::dot(acP, ao) > 0 ) {
|
||||
simplex.b = a;
|
||||
|
||||
direction = uf::vector::cross(ac, ao);
|
||||
continue;
|
||||
}
|
||||
if ( uf::vector::dot(abc, ao) > 0 ) {
|
||||
simplex.d = simplex.c;
|
||||
simplex.c = simplex.b;
|
||||
simplex.b = a;
|
||||
|
||||
direction = abc;
|
||||
continue;
|
||||
}
|
||||
simplex.d = simplex.b;
|
||||
simplex.b = a;
|
||||
|
||||
direction = uf::vector::multiply(abc, -1);
|
||||
simplex.size = 3;
|
||||
continue;
|
||||
}
|
||||
if ( simplex.size == 3 ) {
|
||||
pod::Vector3 ao = a * -1;
|
||||
pod::Vector3 ab = simplex.b - a;
|
||||
pod::Vector3 ac = simplex.c - a;
|
||||
pod::Vector3 abc = uf::vector::cross(ab, ac);
|
||||
pod::Vector3 ad, acd, adb;
|
||||
if ( uf::vector::dot(abc, ao) > 0 ) {
|
||||
goto check_face;
|
||||
}
|
||||
ad = simplex.d - a;
|
||||
acd = uf::vector::cross(ac, ad);
|
||||
if ( uf::vector::dot(acd, ao) > 0 ) {
|
||||
simplex.b = simplex.c;
|
||||
simplex.c = simplex.d;
|
||||
|
||||
ab = ac;
|
||||
ac = ad;
|
||||
abc = acd;
|
||||
|
||||
goto check_face;
|
||||
}
|
||||
goto EPA;
|
||||
|
||||
check_face:
|
||||
pod::Vector3 abP = uf::vector::cross(ab, abc);
|
||||
if ( uf::vector::dot(abP, ao) > 0 ) {
|
||||
simplex.c = simplex.b;
|
||||
simplex.b = a;
|
||||
|
||||
direction = uf::vector::cross(uf::vector::cross(ab, ao), ab);
|
||||
simplex.size = 2;
|
||||
continue;
|
||||
}
|
||||
pod::Vector3 acP = uf::vector::cross(abc, ac);
|
||||
if ( uf::vector::dot(acP, ao) > 0 ) {
|
||||
simplex.b = a;
|
||||
direction = uf::vector::cross(uf::vector::cross(ac, ao), ac);
|
||||
simplex.size = 2;
|
||||
continue;
|
||||
}
|
||||
simplex.d = simplex.c;
|
||||
simplex.c = simplex.b;
|
||||
simplex.b = a;
|
||||
direction = abc;
|
||||
simplex.size = 3;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return manifold;
|
||||
|
||||
EPA:
|
||||
struct Triangle {
|
||||
pod::Simplex::SupportPoint points[3];
|
||||
pod::Vector3f normal;
|
||||
Triangle( const pod::Simplex::SupportPoint& a, const pod::Simplex::SupportPoint& b, const pod::Simplex::SupportPoint& c ) {
|
||||
points[0] = a;
|
||||
points[1] = b;
|
||||
points[2] = c;
|
||||
normal = uf::vector::normalize( uf::vector::cross( b.v - a.v, c.v - a.v ) );
|
||||
}
|
||||
};
|
||||
struct Edge {
|
||||
pod::Simplex::SupportPoint points[2];
|
||||
Edge( const pod::Simplex::SupportPoint& a, const pod::Simplex::SupportPoint& b ) {
|
||||
points[0] = a;
|
||||
points[1] = b;
|
||||
}
|
||||
};
|
||||
|
||||
iterations = 0;
|
||||
manifold.colliding = true;
|
||||
manifold.depth = 0.0f;
|
||||
manifold.normal = { 0, 0, 0 };
|
||||
|
||||
uf::stl::vector<Triangle> triangles;
|
||||
uf::stl::vector<Edge> edges;
|
||||
|
||||
triangles.emplace_back( a, simplex.b, simplex.c );
|
||||
triangles.emplace_back( a, simplex.b, simplex.d );
|
||||
triangles.emplace_back( a, simplex.c, simplex.d );
|
||||
triangles.emplace_back( simplex.b, simplex.c, simplex.d );
|
||||
|
||||
auto addEdge = [&]( const pod::Simplex::SupportPoint& a, const pod::Simplex::SupportPoint& b ) {
|
||||
for ( auto it = edges.begin(); it != edges.end(); ++it ) {
|
||||
if( it->points[0]== b && it->points[1]== a ) {
|
||||
edges.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
edges.emplace_back( a, b );
|
||||
};
|
||||
|
||||
while ( iterations++ < iterations_cap ) {
|
||||
// find closest triangle to origin
|
||||
struct {
|
||||
uf::stl::vector<Triangle>::iterator it;
|
||||
float distance = 9E9;
|
||||
pod::Simplex::SupportPoint support;
|
||||
} closest = { triangles.begin() };
|
||||
|
||||
for(auto it = triangles.begin(); it != triangles.end(); it++) {
|
||||
float distance = fabs( uf::vector::dot(it->normal, it->points[0].v) );
|
||||
if( distance < closest.distance ) {
|
||||
closest.distance = distance;
|
||||
closest.it = it;
|
||||
}
|
||||
}
|
||||
{
|
||||
closest.support.a = x.support(closest.it->normal);
|
||||
closest.support.b = y.support(-closest.it->normal);
|
||||
closest.support.v = closest.support.a - closest.support.b;
|
||||
}
|
||||
|
||||
manifold.normal = -closest.it->normal;
|
||||
manifold.depth = uf::vector::dot( closest.it->normal, closest.support.v );
|
||||
if( manifold.depth - closest.distance < 0.00001f ) {
|
||||
manifold.depth = uf::vector::dot( closest.it->normal, closest.it->points[0].v );
|
||||
return manifold;
|
||||
}
|
||||
|
||||
for(auto it = triangles.begin(); it != triangles.end();) {
|
||||
// can this face be 'seen' by closest.support?
|
||||
if( uf::vector::dot( it->normal, (closest.support.v - it->points[0].v) ) > 0) {
|
||||
addEdge( it->points[0], it->points[1] );
|
||||
addEdge( it->points[1], it->points[2] );
|
||||
addEdge( it->points[2], it->points[0] );
|
||||
it = triangles.erase(it);
|
||||
continue;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
|
||||
// create new triangles from the edges in the edge list
|
||||
for(auto it = edges.begin(); it != edges.end(); it++)
|
||||
triangles.emplace_back( closest.support, it->points[0], it->points[1] );
|
||||
|
||||
edges.clear();
|
||||
}
|
||||
return manifold;
|
||||
}
|
||||
|
||||
pod::Vector3f pod::Collider::getPosition() const {
|
||||
return this->m_transform.reference ? this->m_transform.reference->position : this->m_transform.position;
|
||||
// return uf::transform::flatten( this->m_transform.reference ? *this->m_transform.reference : this->m_transform ).position;
|
||||
}
|
||||
pod::Transform<>& pod::Collider::getTransform() {
|
||||
return this->m_transform;
|
||||
}
|
||||
const pod::Transform<>& pod::Collider::getTransform() const {
|
||||
return this->m_transform;
|
||||
}
|
||||
void pod::Collider::setTransform( const pod::Transform<>& transform ) {
|
||||
this->m_transform = transform;
|
||||
}
|
||||
#endif
|
||||
@ -1,44 +0,0 @@
|
||||
#if UF_USE_UNUSED
|
||||
#include <uf/utils/math/collision/mesh.h>
|
||||
|
||||
uf::MeshCollider::MeshCollider( const pod::Transform<>& transform, const uf::stl::vector<pod::Vector3>& positions ) : m_positions(positions) {
|
||||
this->setTransform(transform);
|
||||
}
|
||||
uf::stl::string UF_API uf::MeshCollider::type() const { return "Mesh"; }
|
||||
|
||||
uf::stl::vector<pod::Vector3>& uf::MeshCollider::getPositions() {
|
||||
return this->m_positions;
|
||||
}
|
||||
|
||||
const uf::stl::vector<pod::Vector3>& uf::MeshCollider::getPositions() const {
|
||||
return this->m_positions;
|
||||
}
|
||||
|
||||
void uf::MeshCollider::setPositions( const uf::stl::vector<pod::Vector3>& positions ) {
|
||||
this->m_positions = positions;
|
||||
}
|
||||
|
||||
pod::Vector3* uf::MeshCollider::expand() const {
|
||||
return (pod::Vector3*) &this->m_positions[0];
|
||||
}
|
||||
pod::Vector3 uf::MeshCollider::support( const pod::Vector3& direction ) const {
|
||||
size_t len = this->m_positions.size();
|
||||
pod::Vector3* points = this->expand();
|
||||
pod::Matrix4f model = uf::transform::model( this->m_transform );
|
||||
|
||||
struct {
|
||||
size_t i = 0;
|
||||
float dot = 0;
|
||||
} best;
|
||||
|
||||
for ( size_t i = 0; i < len; ++i ) {
|
||||
float dot = uf::vector::dot( uf::matrix::multiply<float>( model, points[i] ), direction);
|
||||
// float dot = uf::vector::dot( points[i], direction);
|
||||
if ( i == 0 || dot > best.dot ) {
|
||||
best.i = i;
|
||||
best.dot = dot;
|
||||
}
|
||||
}
|
||||
return points[best.i];
|
||||
}
|
||||
#endif
|
||||
@ -1,57 +0,0 @@
|
||||
#if UF_USE_UNUSED
|
||||
#include <uf/utils/math/collision/modular.h>
|
||||
|
||||
uf::ModularCollider::ModularCollider( uint len, pod::Vector3* container, bool should_free, const uf::ModularCollider::function_expand_t& expand, const uf::ModularCollider::function_support_t& support ) {
|
||||
this->m_len = len;
|
||||
this->m_container = container;
|
||||
this->m_should_free = should_free;
|
||||
this->m_function_expand = expand;
|
||||
this->m_function_support = support;
|
||||
}
|
||||
uf::ModularCollider::~ModularCollider() {
|
||||
if ( this->m_container != NULL && this->m_should_free ) delete[] this->m_container;
|
||||
}
|
||||
uf::stl::string uf::ModularCollider::type() const { return "Modular"; }
|
||||
void uf::ModularCollider::setExpand( const uf::ModularCollider::function_expand_t& expand ) {
|
||||
this->m_function_expand = expand;
|
||||
}
|
||||
void uf::ModularCollider::setSupport( const uf::ModularCollider::function_support_t& support ) {
|
||||
this->m_function_support = support;
|
||||
}
|
||||
|
||||
pod::Vector3* uf::ModularCollider::getContainer() {
|
||||
return this->m_container;
|
||||
}
|
||||
uint uf::ModularCollider::getSize() const {
|
||||
return this->m_len;
|
||||
}
|
||||
void uf::ModularCollider::setContainer( pod::Vector3* container, uint len ) {
|
||||
this->m_container = container;
|
||||
this->m_len = len;
|
||||
}
|
||||
|
||||
pod::Vector3* uf::ModularCollider::expand() const {
|
||||
return this->m_function_expand ? this->m_function_expand() : this->m_container;
|
||||
}
|
||||
pod::Vector3 uf::ModularCollider::support( const pod::Vector3& direction ) const {
|
||||
if ( this->m_function_support ) return this->m_function_support(direction);
|
||||
|
||||
pod::Vector3* points = this->expand();
|
||||
uint len = this->m_len;
|
||||
|
||||
struct {
|
||||
size_t i = 0;
|
||||
float dot = 0;
|
||||
} best;
|
||||
|
||||
for ( size_t i = 0; i < len; ++i ) {
|
||||
// float dot = uf::vector::dot( uf::matrix::multiply<float>( model, points[i] ), direction);
|
||||
float dot = uf::vector::dot( points[i], direction);
|
||||
if ( i == 0 || dot > best.dot ) {
|
||||
best.i = i;
|
||||
best.dot = dot;
|
||||
}
|
||||
}
|
||||
return points[best.i];
|
||||
}
|
||||
#endif
|
||||
@ -1,51 +0,0 @@
|
||||
#if UF_USE_UNUSED
|
||||
#include <uf/utils/math/collision/sphere.h>
|
||||
|
||||
uf::SphereCollider::SphereCollider( float r, const pod::Vector3& origin ) {
|
||||
this->m_radius = r;
|
||||
this->m_origin = origin;
|
||||
}
|
||||
uf::stl::string uf::SphereCollider::type() const { return "Sphere"; }
|
||||
float uf::SphereCollider::getRadius() const {
|
||||
return this->m_radius;
|
||||
}
|
||||
const pod::Vector3& uf::SphereCollider::getOrigin() const {
|
||||
return this->m_origin;
|
||||
}
|
||||
|
||||
void uf::SphereCollider::setRadius( float r ) {
|
||||
this->m_radius = r;
|
||||
}
|
||||
void uf::SphereCollider::setOrigin( const pod::Vector3& origin ) {
|
||||
this->m_origin = origin;
|
||||
}
|
||||
|
||||
pod::Vector3* uf::SphereCollider::expand() const {
|
||||
return NULL;
|
||||
}
|
||||
pod::Vector3 uf::SphereCollider::support( const pod::Vector3& direction ) const {
|
||||
pod::Vector3f position = this->getPosition() + this->m_origin;
|
||||
// const pod::Vector3f& position = this->m_origin;
|
||||
return position + direction * (this->m_radius/uf::vector::magnitude(direction));
|
||||
}
|
||||
pod::Collider::Manifold uf::SphereCollider::intersects( const uf::SphereCollider& b ) const {
|
||||
const uf::SphereCollider& a = *this;
|
||||
pod::Collider::Manifold manifold(a, b);
|
||||
|
||||
pod::Vector3f position_a = a.getPosition() + a.m_origin;
|
||||
pod::Vector3f position_b = b.getPosition() + b.m_origin;
|
||||
// const pod::Vector3f& position_a = a.m_origin;
|
||||
// const pod::Vector3f& position_b = b.m_origin;
|
||||
|
||||
float distanceSquared = uf::vector::distanceSquared(position_a, position_b);
|
||||
float sum = a.m_radius + b.m_radius;
|
||||
float sumSquared = sum * sum;
|
||||
|
||||
if ( distanceSquared >= sumSquared ) return manifold;
|
||||
|
||||
manifold.depth = fabs(b.m_radius - a.m_radius);
|
||||
manifold.normal = position_b - position_a;
|
||||
manifold.colliding = true;
|
||||
return manifold;
|
||||
}
|
||||
#endif
|
||||
@ -31,7 +31,7 @@ void uf::physics::tick( uf::Object& scene ) {
|
||||
if ( uf::physics::time::delta > uf::physics::time::clamp ) {
|
||||
uf::physics::time::delta = uf::physics::time::clamp;
|
||||
}
|
||||
uf::physics::impl::tick( scene );
|
||||
uf::physics::impl::tick( scene, uf::physics::time::delta );
|
||||
}
|
||||
void uf::physics::terminate( ) {
|
||||
return uf::physics::terminate( uf::scene::getCurrentScene() );
|
||||
|
||||
264
engine/src/utils/math/physics/aabb.inl
Normal file
264
engine/src/utils/math/physics/aabb.inl
Normal file
@ -0,0 +1,264 @@
|
||||
namespace {
|
||||
bool aabbOverlap( const pod::AABB& a, const pod::AABB& b, float eps ) {
|
||||
return (a.min.x < b.max.x - eps && a.max.x > b.min.x + eps) &&
|
||||
(a.min.y < b.max.y - eps && a.max.y > b.min.y + eps) &&
|
||||
(a.min.z < b.max.z - eps && a.max.z > b.min.z + eps);
|
||||
}
|
||||
|
||||
pod::AABB computeSegmentAABB( const pod::Vector3f& p1, const pod::Vector3f p2, float r ) {
|
||||
return {
|
||||
uf::vector::min( p1, p2 ) - r,
|
||||
uf::vector::max( p1, p2 ) + r,
|
||||
};
|
||||
}
|
||||
|
||||
pod::Vector3f closestPointOnAABB(const pod::Vector3f& p, const pod::AABB& box) {
|
||||
return {
|
||||
std::max(box.min.x, std::min(p.x, box.max.x)),
|
||||
std::max(box.min.y, std::min(p.y, box.max.y)),
|
||||
std::max(box.min.z, std::min(p.z, box.max.z))
|
||||
};
|
||||
}
|
||||
|
||||
std::pair<pod::Vector3f, pod::Vector3f> getCapsuleSegment( const pod::RigidBody& body ) {
|
||||
const auto& capsule = body.collider.u.capsule;
|
||||
const pod::Vector3f up = uf::quaternion::rotate( body.transform->orientation, pod::Vector3f{0,1,0} );
|
||||
|
||||
// segment defines the cylinder axis only (ignore spherical ends)
|
||||
auto p1 = body.transform->position + up * capsule.halfHeight;
|
||||
auto p2 = body.transform->position - up * capsule.halfHeight;
|
||||
return { p1, p2 };
|
||||
}
|
||||
|
||||
pod::AABB computeAABB( const pod::RigidBody& body ) {
|
||||
switch ( body.collider.type ) {
|
||||
case pod::ShapeType::AABB: {
|
||||
return {
|
||||
body.transform->position + body.collider.u.aabb.min,
|
||||
body.transform->position + body.collider.u.aabb.max,
|
||||
};
|
||||
} break;
|
||||
case pod::ShapeType::SPHERE: {
|
||||
return {
|
||||
body.transform->position - body.collider.u.sphere.radius,
|
||||
body.transform->position + body.collider.u.sphere.radius,
|
||||
};
|
||||
} break;
|
||||
case pod::ShapeType::CAPSULE: {
|
||||
auto [ p1, p2 ] = ::getCapsuleSegment( body );
|
||||
return ::computeSegmentAABB( p1, p2, body.collider.u.capsule.radius );
|
||||
} break;
|
||||
case pod::ShapeType::MESH: {
|
||||
if ( body.collider.u.mesh.bvh && !body.collider.u.mesh.bvh->nodes.empty() )
|
||||
return {
|
||||
body.transform->position + body.collider.u.mesh.bvh->nodes[0].bounds.min,
|
||||
body.transform->position + body.collider.u.mesh.bvh->nodes[0].bounds.max,
|
||||
};
|
||||
} break;
|
||||
default: {
|
||||
} break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
pod::AABB computeTriangleAABB( const pod::Triangle& tri ) {
|
||||
return {
|
||||
{
|
||||
std::min({tri.points[0].x, tri.points[1].x, tri.points[2].x}),
|
||||
std::min({tri.points[0].y, tri.points[1].y, tri.points[2].y}),
|
||||
std::min({tri.points[0].z, tri.points[1].z, tri.points[2].z}),
|
||||
},
|
||||
{
|
||||
std::max({tri.points[0].x, tri.points[1].x, tri.points[2].x}),
|
||||
std::max({tri.points[0].y, tri.points[1].y, tri.points[2].y}),
|
||||
std::max({tri.points[0].z, tri.points[1].z, tri.points[2].z}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
float triAabbDistanceSq( const pod::Triangle& tri, const pod::AABB& box ) {
|
||||
float minDistSq = FLT_MAX;
|
||||
for ( auto i = 0; i < 3; ++i ) {
|
||||
auto cp = ::closestPointOnAABB( tri.points[i], box );
|
||||
auto d = tri.points[i] - cp;
|
||||
minDistSq = std::min( minDistSq, uf::vector::dot( d, d ) );
|
||||
}
|
||||
return minDistSq;
|
||||
}
|
||||
|
||||
bool triAabbOverlap( const pod::Triangle& tri, const pod::AABB& box, float eps ) {
|
||||
// compute box center and half extents
|
||||
auto c = ( box.min + box.max ) * 0.5f;
|
||||
auto e = ( box.max - box.min ) * 0.5f;
|
||||
|
||||
// move triangle into box's local space
|
||||
auto v0 = tri.points[0] - c;
|
||||
auto v1 = tri.points[1] - c;
|
||||
auto v2 = tri.points[2] - c;
|
||||
|
||||
// triangle edges
|
||||
auto f0 = v1 - v0;
|
||||
auto f1 = v2 - v1;
|
||||
auto f2 = v0 - v2;
|
||||
|
||||
// sAT: test the 9 edge cross axes
|
||||
auto axisTest = [&]( const pod::Vector3f& axis ) {
|
||||
if ( uf::vector::magnitude(axis) < eps ) return true;
|
||||
|
||||
auto a = uf::vector::normalize( axis );
|
||||
|
||||
float p0 = uf::vector::dot( v0, a );
|
||||
float p1 = uf::vector::dot( v1, a );
|
||||
float p2 = uf::vector::dot( v2, a );
|
||||
|
||||
float r = e.x * fabs(a.x) + e.y * fabs(a.y) + e.z * fabs(a.z);
|
||||
|
||||
float minP = std::min({p0, p1, p2});
|
||||
float maxP = std::max({p0, p1, p2});
|
||||
|
||||
return !(minP > r || maxP < -r);
|
||||
};
|
||||
|
||||
if ( !axisTest( {0, -f0.z, f0.y} ) ) return false;
|
||||
if ( !axisTest( {0, -f1.z, f1.y} ) ) return false;
|
||||
if ( !axisTest( {0, -f2.z, f2.y} ) ) return false;
|
||||
if ( !axisTest( {f0.z, 0, -f0.x} ) ) return false;
|
||||
if ( !axisTest( {f1.z, 0, -f1.x} ) ) return false;
|
||||
if ( !axisTest( {f2.z, 0, -f2.x} ) ) return false;
|
||||
if ( !axisTest( {-f0.y, f0.x, 0} ) ) return false;
|
||||
if ( !axisTest( {-f1.y, f1.x, 0} ) ) return false;
|
||||
if ( !axisTest( {-f2.y, f2.x, 0} ) ) return false;
|
||||
|
||||
// test AABB face axes
|
||||
for ( auto i = 0; i < 3; ++i ) {
|
||||
float minVal = std::min({v0[i], v1[i], v2[i]});
|
||||
float maxVal = std::max({v0[i], v1[i], v2[i]});
|
||||
if ( minVal > e[i] || maxVal < -e[i] ) return false;
|
||||
}
|
||||
|
||||
// test triangle normal axis
|
||||
auto n = uf::vector::cross( f0, f1 );
|
||||
float d0 = uf::vector::dot( v0, n );
|
||||
float r = e.x * fabs(n.x) + e.y * fabs(n.y) + e.z * fabs(n.z);
|
||||
if ( fabs(d0) > r ) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pod::AABB mergeAabb( const pod::AABB& a, const pod::AABB& b ) {
|
||||
return {
|
||||
uf::vector::min( a.min, b.min ),
|
||||
uf::vector::max( a.max, b.max ),
|
||||
};
|
||||
}
|
||||
|
||||
pod::AABB transformAabbToLocal( const pod::AABB& box, const pod::Transform<>& transform ) {
|
||||
auto inv = uf::transform::inverse( transform );
|
||||
|
||||
// transform all 8 corners
|
||||
pod::Vector3f corners[8] = {
|
||||
{box.min.x, box.min.y, box.min.z},
|
||||
{box.max.x, box.min.y, box.min.z},
|
||||
{box.min.x, box.max.y, box.min.z},
|
||||
{box.max.x, box.max.y, box.min.z},
|
||||
{box.min.x, box.min.y, box.max.z},
|
||||
{box.max.x, box.min.y, box.max.z},
|
||||
{box.min.x, box.max.y, box.max.z},
|
||||
{box.max.x, box.max.y, box.max.z},
|
||||
};
|
||||
|
||||
pod::AABB out = {
|
||||
{ FLT_MAX, FLT_MAX, FLT_MAX },
|
||||
{ -FLT_MAX, -FLT_MAX, -FLT_MAX },
|
||||
};
|
||||
|
||||
for ( auto i = 0; i < 8; ++i ) {
|
||||
auto local = uf::transform::apply( inv, corners[i] );
|
||||
out.min = uf::vector::min( out.min, local );
|
||||
out.max = uf::vector::max( out.max, local );
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
pod::Vector3f aabbCenter( const pod::AABB& aabb ) {
|
||||
return ( aabb.min + aabb.max ) * 0.5f;
|
||||
}
|
||||
|
||||
bool aabbAabb( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES(AABB, AABB);
|
||||
|
||||
const pod::AABB& A = a.bounds;
|
||||
const pod::AABB& B = b.bounds;
|
||||
|
||||
if (!aabbOverlap(A, B)) return false;
|
||||
|
||||
// Calculate overlap extents
|
||||
float overlaps[3] = {
|
||||
std::min(A.max.x, B.max.x) - std::max(A.min.x, B.min.x),
|
||||
std::min(A.max.y, B.max.y) - std::max(A.min.y, B.min.y),
|
||||
std::min(A.max.z, B.max.z) - std::max(A.min.z, B.min.z)
|
||||
};
|
||||
|
||||
// Determine collision axis = smallest overlap
|
||||
int axis = -1;
|
||||
float minOverlap = FLT_MAX;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (overlaps[i] < minOverlap) {
|
||||
minOverlap = overlaps[i];
|
||||
axis = i;
|
||||
}
|
||||
}
|
||||
|
||||
pod::Vector3f delta = b.transform->position - a.transform->position;
|
||||
pod::Vector3f normal{0,0,0};
|
||||
normal[axis] = (delta[axis] < 0 ? -1.0f : 1.0f);
|
||||
|
||||
// Build manifold contacts: overlap region corners on the separating axis
|
||||
float xMin = std::max(A.min.x, B.min.x);
|
||||
float xMax = std::min(A.max.x, B.max.x);
|
||||
float yMin = std::max(A.min.y, B.min.y);
|
||||
float yMax = std::min(A.max.y, B.max.y);
|
||||
float zMin = std::max(A.min.z, B.min.z);
|
||||
float zMax = std::min(A.max.z, B.max.z);
|
||||
|
||||
// On chosen axis, clamp to overlapped rectangle -> 4 potential points
|
||||
if (axis == 0) { // X-axis separation, so face-on overlap in YZ plane
|
||||
manifold.points.emplace_back(pod::Contact{ { (normal.x > 0 ? A.max.x : A.min.x), yMin, zMin }, normal, minOverlap });
|
||||
manifold.points.emplace_back(pod::Contact{ { (normal.x > 0 ? A.max.x : A.min.x), yMin, zMax }, normal, minOverlap });
|
||||
manifold.points.emplace_back(pod::Contact{ { (normal.x > 0 ? A.max.x : A.min.x), yMax, zMin }, normal, minOverlap });
|
||||
manifold.points.emplace_back(pod::Contact{ { (normal.x > 0 ? A.max.x : A.min.x), yMax, zMax }, normal, minOverlap });
|
||||
}
|
||||
else if (axis == 1) { // Y-axis separation, overlap in XZ plane
|
||||
manifold.points.emplace_back(pod::Contact{ { xMin, (normal.y > 0 ? A.max.y : A.min.y), zMin }, normal, minOverlap });
|
||||
manifold.points.emplace_back(pod::Contact{ { xMin, (normal.y > 0 ? A.max.y : A.min.y), zMax }, normal, minOverlap });
|
||||
manifold.points.emplace_back(pod::Contact{ { xMax, (normal.y > 0 ? A.max.y : A.min.y), zMin }, normal, minOverlap });
|
||||
manifold.points.emplace_back(pod::Contact{ { xMax, (normal.y > 0 ? A.max.y : A.min.y), zMax }, normal, minOverlap });
|
||||
}
|
||||
else if (axis == 2) { // Z-axis separation, overlap in XY plane
|
||||
manifold.points.emplace_back(pod::Contact{ { xMin, yMin, (normal.z > 0 ? A.max.z : A.min.z) }, normal, minOverlap });
|
||||
manifold.points.emplace_back(pod::Contact{ { xMin, yMax, (normal.z > 0 ? A.max.z : A.min.z) }, normal, minOverlap });
|
||||
manifold.points.emplace_back(pod::Contact{ { xMax, yMin, (normal.z > 0 ? A.max.z : A.min.z) }, normal, minOverlap });
|
||||
manifold.points.emplace_back(pod::Contact{ { xMax, yMax, (normal.z > 0 ? A.max.z : A.min.z) }, normal, minOverlap });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool aabbSphere( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( AABB, SPHERE );
|
||||
return ::sphereAabb( b, a, manifold, eps );
|
||||
}
|
||||
bool aabbPlane( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( AABB, PLANE );
|
||||
return ::planeAabb( b, a, manifold, eps );
|
||||
}
|
||||
bool aabbCapsule( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( AABB, CAPSULE );
|
||||
return ::capsuleAabb( b, a, manifold, eps );
|
||||
}
|
||||
bool aabbMesh( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( AABB, MESH );
|
||||
return ::meshAabb( b, a, manifold, eps );
|
||||
}
|
||||
}
|
||||
249
engine/src/utils/math/physics/bvh.inl
Normal file
249
engine/src/utils/math/physics/bvh.inl
Normal file
@ -0,0 +1,249 @@
|
||||
// BVH
|
||||
namespace {
|
||||
// collects a list of nodes that are overlapping with each other
|
||||
void traverseNodePair( const pod::BVH& bvh, int leftID, int rightID, pod::BVH::pair_t& pairs ) {
|
||||
const auto& left = bvh.nodes[leftID];
|
||||
const auto& right = bvh.nodes[rightID];
|
||||
|
||||
if ( !::aabbOverlap( left.bounds, right.bounds ) ) return;
|
||||
|
||||
if ( left.count > 0 && right.count > 0 ) {
|
||||
for ( auto i = 0; i < left.count; ++i )
|
||||
for ( auto j = 0; j < right.count; ++j ) {
|
||||
int a = bvh.indices[left.start + i];
|
||||
int b = bvh.indices[right.start + j];
|
||||
pairs.emplace_back(std::pair{a, b});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( left.count == 0 ) {
|
||||
::traverseNodePair( bvh, left.left, rightID, pairs );
|
||||
::traverseNodePair( bvh, left.right, rightID, pairs );
|
||||
} else if ( right.count == 0 ) {
|
||||
::traverseNodePair( bvh, leftID, right.left, pairs );
|
||||
::traverseNodePair( bvh, leftID, right.right, pairs );
|
||||
}
|
||||
}
|
||||
// collects a list of nodes from each BVH that are overlapping with each other (for mesh v mesh)
|
||||
void traverseNodePair( const pod::BVH& a, int nodeA, const pod::BVH& b, int nodeB, pod::BVH::pair_t& pairs ) {
|
||||
const auto& nA = a.nodes[nodeA];
|
||||
const auto& nB = b.nodes[nodeB];
|
||||
|
||||
if ( !::aabbOverlap( nA.bounds, nB.bounds ) ) return;
|
||||
|
||||
if ( nA.count > 0 && nB.count > 0 ) {
|
||||
for ( auto i = 0; i < nA.count; i++ )
|
||||
for ( auto j = 0; j < nB.count; j++ )
|
||||
pairs.emplace_back(a.indices[nA.start+i], b.indices[nB.start+j]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( nA.count == 0 ) {
|
||||
::traverseNodePair( a, nA.left, b , nodeB, pairs );
|
||||
::traverseNodePair( a, nA.right, b , nodeB, pairs );
|
||||
} else {
|
||||
::traverseNodePair( a, nodeA, b, nB.left, pairs );
|
||||
::traverseNodePair( a, nodeA, b, nB.right, pairs );
|
||||
}
|
||||
}
|
||||
|
||||
void traverseBVH( const pod::BVH& bvh, int nodeIdx, pod::BVH::pair_t& pairs ) {
|
||||
const auto& node = bvh.nodes[nodeIdx];
|
||||
|
||||
if ( node.count > 0 ) {
|
||||
for ( auto i = 0; i < node.count; ++i ) {
|
||||
for ( auto j = i + 1; j < node.count; ++j ) {
|
||||
int a = bvh.indices[node.start + i];
|
||||
int b = bvh.indices[node.start + j];
|
||||
pairs.emplace_back(std::pair{a, b});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// recurse children
|
||||
::traverseNodePair( bvh, node.left, node.right, pairs );
|
||||
}
|
||||
|
||||
void queryOverlaps( const pod::BVH& bvh, pod::BVH::pair_t& outPairs ) {
|
||||
if ( bvh.nodes.empty() ) return;
|
||||
::traverseBVH( bvh, 0, outPairs );
|
||||
}
|
||||
|
||||
int buildBVHNode( pod::BVH& bvh, const uf::stl::vector<pod::AABB>& bounds, int start, int end, int capacity = 2 ) {
|
||||
pod::BVH::Node node{};
|
||||
node.left = -1;
|
||||
node.right = -1;
|
||||
node.start = start;
|
||||
node.count = 0;
|
||||
node.bounds = bounds[bvh.indices[start]];
|
||||
|
||||
// compute bounds of this node
|
||||
for ( auto i = start + 1; i < end; ++i) node.bounds = ::mergeAabb( node.bounds, bounds[bvh.indices[i]] );
|
||||
|
||||
int count = end - start;
|
||||
if ( count <= capacity ) {
|
||||
// leaf
|
||||
node.start = start;
|
||||
node.count = count;
|
||||
int index = (int) bvh.nodes.size();
|
||||
bvh.nodes.emplace_back(node);
|
||||
return index;
|
||||
}
|
||||
|
||||
// choose split axis by largest extent
|
||||
auto extent = node.bounds.max - node.bounds.min;
|
||||
int axis = (extent.x > extent.y && extent.x > extent.z) ? 0 : (extent.y > extent.z ? 1 : 2);
|
||||
|
||||
// sort indices by centroid along axis
|
||||
std::sort( bvh.indices.begin() + start, bvh.indices.begin() + end, [&](size_t a, size_t b) {
|
||||
float ca = ::aabbCenter( bounds[a] )[axis];
|
||||
float cb = ::aabbCenter( bounds[b] )[axis];
|
||||
return ca < cb;
|
||||
});
|
||||
|
||||
int mid = ( start + end ) / 2;
|
||||
int index = (int) bvh.nodes.size();
|
||||
bvh.nodes.emplace_back( node ); // insert now, gets filled later
|
||||
|
||||
node.left = ::buildBVHNode( bvh, bounds, start, mid, capacity );
|
||||
node.right = ::buildBVHNode( bvh, bounds, mid, end, capacity );
|
||||
bvh.nodes[index] = node;
|
||||
return index;
|
||||
}
|
||||
|
||||
void buildBroadphaseBVH( pod::BVH& bvh, const uf::stl::vector<pod::RigidBody*>& bodies, int capacity = 2 ) {
|
||||
bvh.indices.clear();
|
||||
bvh.nodes.clear();
|
||||
bvh.indices.reserve(bodies.size());
|
||||
|
||||
// stores bounds
|
||||
uf::stl::vector<pod::AABB> bounds;
|
||||
bounds.reserve(bodies.size());
|
||||
|
||||
// populate initial indices and bounds
|
||||
for ( auto i = 0; i < bodies.size(); ++i ) {
|
||||
bounds.emplace_back(::computeAABB( *bodies[i] ));
|
||||
bvh.indices.emplace_back(i); // i => bodies[i]
|
||||
}
|
||||
|
||||
// recursively build BVH from indices
|
||||
::buildBVHNode( bvh, bounds, 0, bodies.size(), capacity );
|
||||
}
|
||||
|
||||
void buildMeshBVH( pod::BVH& bvh, const uf::Mesh& mesh, int capacity = 4 ) {
|
||||
int triangles = mesh.index.count / 3;
|
||||
|
||||
bvh.indices.clear();
|
||||
bvh.nodes.clear();
|
||||
bvh.indices.reserve( triangles );
|
||||
|
||||
// stores bounds
|
||||
uf::stl::vector<pod::AABB> bounds;
|
||||
bounds.reserve( triangles );
|
||||
|
||||
auto views = mesh.makeViews({"position"});
|
||||
UF_ASSERT( !views.empty() );
|
||||
|
||||
// populate initial indices and bounds
|
||||
for ( auto& view : views ) {
|
||||
auto& indices = view["index"];
|
||||
auto& positions = view["position"];
|
||||
|
||||
auto tris = view.index.count / 3;
|
||||
for ( auto triIndexID = 0; triIndexID < tris; ++triIndexID ) {
|
||||
auto aabb = ::computeTriangleAABB( positions.data(view.vertex.first), positions.stride(), indices.data(view.index.first), mesh.index.size, triIndexID );
|
||||
auto triID = triIndexID + (view.index.first / 3);
|
||||
|
||||
bounds.emplace_back(aabb);
|
||||
bvh.indices.emplace_back(triID); // triID => mesh.index.buffer[triID * 3];
|
||||
}
|
||||
}
|
||||
UF_MSG_DEBUG("Built BVH with {} triangles from mesh with {} triangles ({} draw commands)", bounds.size(), triangles, views.size());
|
||||
|
||||
// recursively build BVH from indices
|
||||
::buildBVHNode( bvh, bounds, 0, triangles, capacity );
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
// query a BVH with an AABB via a stack
|
||||
void queryBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector<int>& indices ) {
|
||||
if ( bvh.nodes.empty() ) return;
|
||||
|
||||
uf::stl::stack<int> stack;
|
||||
stack.push(0);
|
||||
|
||||
while ( !stack.empty() ) {
|
||||
int idx = stack.top(); stack.pop();
|
||||
auto& node = bvh.nodes[idx];
|
||||
if ( !::aabbOverlap( bounds, node.bounds ) ) continue;
|
||||
|
||||
if ( node.count > 0 ) {
|
||||
for ( auto i = 0; i < node.count; ++i) indices.emplace_back(bvh.indices[node.start + i]);
|
||||
} else {
|
||||
stack.push(node.left);
|
||||
stack.push(node.right);
|
||||
}
|
||||
}
|
||||
}
|
||||
void queryBVH( const pod::BVH& bvh, const pod::RigidBody& body, uf::stl::vector<int>& indices ) {
|
||||
return ::queryBVH( bvh, body.bounds, indices );
|
||||
}
|
||||
|
||||
// query a BVH with an AABB via recursion
|
||||
void queryBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector<int>& indices, int nodeID ) {
|
||||
const auto& node = bvh.nodes[nodeID];
|
||||
if ( !::aabbOverlap( node.bounds, bounds ) ) return;
|
||||
|
||||
if ( node.count > 0 ) {
|
||||
for ( auto i = 0; i < node.count; ++i ) indices.emplace_back(bvh.indices[node.start + i]);
|
||||
return;
|
||||
}
|
||||
|
||||
// recurse
|
||||
::queryBVH( bvh, bounds, indices, node.left );
|
||||
::queryBVH( bvh, bounds, indices, node.right );
|
||||
}
|
||||
|
||||
// query a BVH with a ray via a stack
|
||||
void queryBVH( const pod::BVH& bvh, const pod::Ray& ray, uf::stl::vector<int>& indices, float maxDist ) {
|
||||
if ( bvh.nodes.empty() ) return;
|
||||
|
||||
uf::stl::stack<int> stack;
|
||||
stack.push(0);
|
||||
|
||||
while ( !stack.empty() ) {
|
||||
int idx = stack.top(); stack.pop();
|
||||
const auto& node = bvh.nodes[idx];
|
||||
|
||||
float tMin, tMax;
|
||||
if ( !::rayAabbIntersect( ray, node.bounds, tMin, tMax ) ) continue;
|
||||
if ( tMin > maxDist ) continue;
|
||||
|
||||
if ( node.count > 0 ) {
|
||||
for ( auto i = 0; i < node.count; ++i) indices.emplace_back(bvh.indices[node.start + i]);
|
||||
} else {
|
||||
stack.push(node.left);
|
||||
stack.push(node.right);
|
||||
}
|
||||
}
|
||||
}
|
||||
// query a BVH with a ray via recursion
|
||||
void queryBVH( const pod::BVH& bvh, const pod::Ray& ray, uf::stl::vector<int>& indices, int nodeID, float maxDist ) {
|
||||
const auto& node = bvh.nodes[nodeID];
|
||||
float tMin, tMax;
|
||||
if ( !::rayAabbIntersect( ray, node.bounds, tMin, tMax ) ) return;
|
||||
if ( tMin > maxDist ) return;
|
||||
|
||||
if ( node.count > 0 ) {
|
||||
for ( auto i = 0; i < node.count; ++i ) indices.emplace_back(bvh.indices[node.start + i]);
|
||||
return;
|
||||
}
|
||||
|
||||
// recurse
|
||||
::queryBVH( bvh, ray, indices, node.left, maxDist );
|
||||
::queryBVH( bvh, ray, indices, node.right, maxDist );
|
||||
}
|
||||
}
|
||||
109
engine/src/utils/math/physics/capsule.inl
Normal file
109
engine/src/utils/math/physics/capsule.inl
Normal file
@ -0,0 +1,109 @@
|
||||
namespace {
|
||||
bool capsuleCapsule( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( CAPSULE, CAPSULE );
|
||||
|
||||
auto [ A1, A2 ] = ::getCapsuleSegment( a );
|
||||
auto [ B1, B2 ] = ::getCapsuleSegment( b );
|
||||
|
||||
auto [ pA, pB ] = ::closestSegmentSegment( A1, A2, B1, B2 );
|
||||
float r = a.collider.u.capsule.radius + b.collider.u.capsule.radius;
|
||||
|
||||
auto delta = pB - pA;
|
||||
float dist2 = uf::vector::dot(delta, delta);
|
||||
if ( dist2 > r * r ) return false;
|
||||
float dist = std::sqrt( dist2 );
|
||||
|
||||
auto contact = ( pA + pB ) * 0.5f;
|
||||
auto normal = ::normalizeDelta( delta, dist, eps );
|
||||
float penetration = r - dist;
|
||||
|
||||
manifold.points.emplace_back(pod::Contact{ contact, normal, penetration });
|
||||
return true;
|
||||
}
|
||||
bool capsuleAabb( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( CAPSULE, AABB );
|
||||
const auto& capsule = a;
|
||||
const auto& box = b;
|
||||
|
||||
auto [ p1, p2 ] = ::getCapsuleSegment( capsule );
|
||||
float r = capsule.collider.u.capsule.radius;
|
||||
|
||||
auto closestPoint = ::closestPointSegmentAabb( p1, p2, box.bounds );
|
||||
auto closestSegment = ::closestPointOnSegment( closestPoint, p1, p2 );
|
||||
|
||||
auto delta = closestPoint - closestSegment;
|
||||
float dist2 = uf::vector::magnitude( delta );
|
||||
if ( dist2 > r * r ) return false;
|
||||
float dist = std::sqrt( dist2 );
|
||||
|
||||
float penetration = r - dist;
|
||||
auto normal = ( dist > eps ) ? ( delta / dist ) : pod::Vector3f{0,1,0};
|
||||
auto contact = closestSegment + normal * ( r - penetration * 0.5f );
|
||||
|
||||
manifold.points.emplace_back(pod::Contact{ contact, normal, penetration });
|
||||
return true;
|
||||
}
|
||||
bool capsulePlane( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( CAPSULE, PLANE );
|
||||
const auto& capsule = a;
|
||||
const auto& plane = b;
|
||||
|
||||
const auto& normal = plane.collider.u.plane.normal;
|
||||
float o = plane.collider.u.plane.offset;
|
||||
|
||||
auto [ p1, p2 ] = ::getCapsuleSegment( capsule );
|
||||
float r = capsule.collider.u.capsule.radius;
|
||||
|
||||
// the "foot" is just whichever end of the capsule is closest to the normal
|
||||
auto foot = ( uf::vector::dot( normal, p1 ) < uf::vector::dot( normal, p2 ) ) ? p1 : p2;
|
||||
float dist = uf::vector::dot( normal, foot ) - o;
|
||||
if ( dist > r ) return false;
|
||||
|
||||
auto contact = foot - (normal * r);
|
||||
float penetration = r - dist;
|
||||
manifold.points.emplace_back(pod::Contact{ contact, normal, penetration });
|
||||
|
||||
// build up to 4 contact points sampled around foot circle
|
||||
#if 0
|
||||
auto tangent1 = uf::vector::normalize( uf::vector::cross( normal, pod::Vector3f{1,0,0} ) );
|
||||
if ( uf::vector::magnitude( tangent1 ) < EPS(1e-6f) ) tangent1 = uf::vector::normalize( uf::vector::cross( normal, pod::Vector3f{0,1,0} ) );
|
||||
auto tangent2 = uf::vector::cross( normal, tangent1 );
|
||||
|
||||
// four directions around circle
|
||||
manifold.points.emplace_back(pod::Contact{ foot + tangent1 * r - normal * d, normal, penetration });
|
||||
manifold.points.emplace_back(pod::Contact{ foot - tangent1 * r - normal * d, normal, penetration });
|
||||
manifold.points.emplace_back(pod::Contact{ foot + tangent2 * r - normal * d, normal, penetration });
|
||||
manifold.points.emplace_back(pod::Contact{ foot - tangent2 * r - normal * d, normal, penetration });
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
bool capsuleSphere( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( CAPSULE, SPHERE );
|
||||
const auto& capsule = a;
|
||||
const auto& sphere = b;
|
||||
|
||||
auto [ p1, p2 ] = ::getCapsuleSegment( capsule );
|
||||
|
||||
const pod::Vector3f sphereCenter = sphere.transform->position;
|
||||
float r = capsule.collider.u.capsule.radius + sphere.collider.u.sphere.radius;
|
||||
|
||||
// closest point on capsule segment to sphere center
|
||||
auto closest = ::closestPointOnSegment( sphereCenter, p1, p2 );
|
||||
|
||||
auto delta = sphereCenter - closest;
|
||||
float dist2 = uf::vector::magnitude( delta );
|
||||
if ( dist2 > r * r ) return false;
|
||||
float dist = std::sqrt( dist2 );
|
||||
|
||||
float penetration = r - dist;
|
||||
auto normal = ::normalizeDelta( delta, dist, eps );
|
||||
auto contact = closest + normal * (capsule.collider.u.capsule.radius - penetration * 0.5f );
|
||||
|
||||
manifold.points.emplace_back(pod::Contact{ contact, normal, penetration });
|
||||
return true;
|
||||
}
|
||||
bool capsuleMesh( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( CAPSULE, MESH );
|
||||
return meshCapsule( b, a, manifold, eps );
|
||||
}
|
||||
}
|
||||
135
engine/src/utils/math/physics/epa.inl
Normal file
135
engine/src/utils/math/physics/epa.inl
Normal file
@ -0,0 +1,135 @@
|
||||
namespace {
|
||||
void addOrRemoveBorder( uf::stl::vector<std::pair<pod::SupportPoint, pod::SupportPoint>>& edges, std::pair<pod::SupportPoint, pod::SupportPoint> e) {
|
||||
// look for reversed edge
|
||||
auto it = std::find_if( edges.begin(), edges.end(), [&]( auto& ex ) {
|
||||
return ( ex.first.p == e.second.p && ex.second.p == e.first.p );
|
||||
});
|
||||
|
||||
if ( it != edges.end() ) edges.erase(it); // internal edge cancels out
|
||||
else edges.emplace_back(e); // keep border edge
|
||||
}
|
||||
|
||||
bool isValidSimplex( const pod::Simplex& s ) {
|
||||
if ( s.pts.size() < 4 ) return false;
|
||||
|
||||
const auto& A = s.pts[0].p;
|
||||
const auto& B = s.pts[1].p;
|
||||
const auto& C = s.pts[2].p;
|
||||
const auto& D = s.pts[3].p;
|
||||
|
||||
return fabs( uf::vector::dot( (B - A), uf::vector::cross(C - A, D - A) ) ) > EPS(1e-6f);
|
||||
}
|
||||
|
||||
pod::Face makeFace( const pod::SupportPoint& a, const pod::SupportPoint& b, const pod::SupportPoint& c ) {
|
||||
pod::Face face{ a, b, c };
|
||||
|
||||
face.normal = uf::vector::normalize( uf::vector::cross( b.p - a.p, c.p - a.p ) );
|
||||
face.distance = uf::vector::dot( face.normal, a.p );
|
||||
|
||||
if ( face.distance < 0 ) {
|
||||
std::swap( face.b, face.c );
|
||||
face.normal = -face.normal;
|
||||
face.distance = -face.distance;
|
||||
}
|
||||
return face;
|
||||
};
|
||||
|
||||
uf::stl::vector<pod::Face> initialPolytope( const pod::Simplex& s ) {
|
||||
UF_ASSERT( s.pts.size() == 4 );
|
||||
|
||||
const auto& A = s.pts[0];
|
||||
const auto& B = s.pts[1];
|
||||
const auto& C = s.pts[2];
|
||||
const auto& D = s.pts[3];
|
||||
|
||||
return {
|
||||
::makeFace( A, B, C ),
|
||||
::makeFace( A, C, D ),
|
||||
::makeFace( A, D, B ),
|
||||
::makeFace( B, D, C )
|
||||
};
|
||||
}
|
||||
|
||||
void expandPolytope( uf::stl::vector<pod::Face>& faces, const pod::SupportPoint& p ) {
|
||||
uf::stl::vector<int> remove;
|
||||
uf::stl::vector<std::pair<pod::SupportPoint, pod::SupportPoint>> borders;
|
||||
|
||||
// find faces visible to point
|
||||
for ( auto i = 0; i < faces.size(); ++i ) {
|
||||
if ( uf::vector::dot( faces[i].normal, p.p - faces[i].a.p ) > 0) {
|
||||
remove.emplace_back(i);
|
||||
::addOrRemoveBorder( borders, { faces[i].a, faces[i].b } );
|
||||
::addOrRemoveBorder( borders, { faces[i].b, faces[i].c } );
|
||||
::addOrRemoveBorder( borders, { faces[i].c, faces[i].a } );
|
||||
}
|
||||
}
|
||||
|
||||
// remove visible faces
|
||||
for ( auto i = (int) remove.size() - 1; i >= 0; --i ) {
|
||||
int idx = remove[i];
|
||||
faces[idx] = faces.back();
|
||||
faces.pop_back();
|
||||
}
|
||||
|
||||
// stitch new faces from border edges to new point
|
||||
for ( auto& e : borders ) {
|
||||
faces.emplace_back(::makeFace( e.first, e.second, p ));
|
||||
}
|
||||
}
|
||||
|
||||
pod::Contact epa( const pod::RigidBody& A, const pod::RigidBody& B, const pod::Simplex& simplex, int maxIterations = 64, float eps = EPS(1.0e-4f) ) {
|
||||
UF_ASSERT( ::isValidSimplex(simplex) );
|
||||
|
||||
auto faces = ::initialPolytope(simplex);
|
||||
if ( faces.empty() ) return {};
|
||||
|
||||
for ( auto it = 0; it < maxIterations; ++it ) {
|
||||
// find closest face
|
||||
int idx = -1;
|
||||
float minDist = FLT_MAX;
|
||||
for ( int i = 0; i < faces.size(); ++i ) {
|
||||
if ( faces[i].distance < minDist ) {
|
||||
minDist = faces[i].distance;
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
UF_ASSERT( idx != -1 );
|
||||
auto& f = faces[idx];
|
||||
|
||||
// new support
|
||||
auto sp = ::supportMinkowskiDetailed( A, B, f.normal );
|
||||
float d = uf::vector::dot( sp.p, f.normal );
|
||||
|
||||
// convergence check
|
||||
if ( fabs(d - f.distance) < eps || it == maxIterations - 1 ) {
|
||||
// project origin onto triangle (in Minkowski space)
|
||||
auto bary = ::computeBarycentric({}, f.a.p, f.b.p, f.c.p, true);
|
||||
|
||||
// use barycentrics to blend real-body points
|
||||
auto pA = ( f.a.pA * bary.x ) + ( f.b.pA * bary.y ) + ( f.c.pA * bary.z );
|
||||
auto pB = ( f.a.pB * bary.x ) + ( f.b.pB * bary.y ) + ( f.c.pB * bary.z );
|
||||
|
||||
auto contact = ( pA + pB ) * 0.5f;
|
||||
auto normal = f.normal;
|
||||
float penetration = uf::vector::dot( (pB - pA), normal );
|
||||
|
||||
// flip normal
|
||||
if ( penetration < 0.0f ) {
|
||||
f.normal = -f.normal;
|
||||
penetration = -penetration;
|
||||
}
|
||||
|
||||
// discard invalid contact
|
||||
if ( !std::isfinite(contact.x) || !std::isfinite(contact.y) || !std::isfinite(contact.z) || penetration <= 0.0f ) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return { contact, normal, penetration };
|
||||
}
|
||||
|
||||
::expandPolytope(faces, sp);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
198
engine/src/utils/math/physics/gjk.inl
Normal file
198
engine/src/utils/math/physics/gjk.inl
Normal file
@ -0,0 +1,198 @@
|
||||
namespace {
|
||||
pod::Vector3f support( const pod::RigidBody& body, const pod::Vector3f& dir ) {
|
||||
switch ( body.collider.type ) {
|
||||
case pod::ShapeType::SPHERE: {
|
||||
return body.transform->position + uf::vector::normalize( dir ) * body.collider.u.sphere.radius;
|
||||
} break;
|
||||
case pod::ShapeType::AABB: {
|
||||
return {
|
||||
( dir.x >= 0.0f ) ? body.bounds.max.x : body.bounds.min.x,
|
||||
( dir.y >= 0.0f ) ? body.bounds.max.y : body.bounds.min.y,
|
||||
( dir.z >= 0.0f ) ? body.bounds.max.z : body.bounds.min.z
|
||||
};
|
||||
} break;
|
||||
case pod::ShapeType::CAPSULE: {
|
||||
auto up = uf::quaternion::rotate( body.transform->orientation, pod::Vector3f{0,1,0} );
|
||||
auto p1 = body.transform->position + up * body.collider.u.capsule.halfHeight;
|
||||
auto p2 = body.transform->position - up * body.collider.u.capsule.halfHeight;
|
||||
// get closest end
|
||||
auto end = ( uf::vector::dot( dir, p1 ) > uf::vector::dot( dir, p2 ) ) ? p1 : p2;
|
||||
|
||||
return end + uf::vector::normalize( dir ) * body.collider.u.capsule.radius;
|
||||
}
|
||||
// to-do: mesh
|
||||
default: {
|
||||
|
||||
} break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
pod::Vector3f supportMinkowski( const pod::RigidBody& A, const pod::RigidBody& B, const pod::Vector3f& dir ) {
|
||||
return ::support( A, dir ) - ::support( B, -dir );
|
||||
}
|
||||
|
||||
pod::SupportPoint supportMinkowskiDetailed( const pod::RigidBody& A, const pod::RigidBody& B, const pod::Vector3f& dir ) {
|
||||
auto pA = ::support( A, dir );
|
||||
auto pB = ::support( B, -dir );
|
||||
return { pA - pB, pA, pB };
|
||||
}
|
||||
|
||||
bool updateSimplex( pod::Simplex& s, pod::Vector3f& dir ) {
|
||||
switch (s.pts.size()) {
|
||||
case 2: {
|
||||
// points
|
||||
auto& A = s.pts.back(); // newest
|
||||
auto& B = s.pts.front();
|
||||
// edges
|
||||
auto AB = B.p - A.p;
|
||||
auto AO = -A.p;
|
||||
|
||||
if (uf::vector::dot(AB, AO) > 0) {
|
||||
dir = uf::vector::cross( uf::vector::cross( AB, AO ), AB );
|
||||
} else {
|
||||
s.pts = {A};
|
||||
dir = AO;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case 3: {
|
||||
// points
|
||||
auto& A = s.pts[2]; // newest
|
||||
auto& B = s.pts[1];
|
||||
auto& C = s.pts[0];
|
||||
|
||||
// edges
|
||||
auto AB = B.p - A.p;
|
||||
auto AC = C.p - A.p;
|
||||
auto AO = -A.p;
|
||||
|
||||
auto ABC = uf::vector::cross( AB, AC );
|
||||
if ( uf::vector::dot( uf::vector::cross( AB, ABC ), AO ) > 0 ) {
|
||||
s.pts = { A, B };
|
||||
return ::updateSimplex( s, dir );
|
||||
}
|
||||
if ( uf::vector::dot( uf::vector::cross( ABC, AC ), AO ) > 0 ) {
|
||||
s.pts = { A, C };
|
||||
return ::updateSimplex( s, dir );
|
||||
}
|
||||
|
||||
if ( uf::vector::dot( ABC, AO ) > 0 ) dir = ABC;
|
||||
else { s.pts = { A, C, B }; dir = -ABC; }
|
||||
return false;
|
||||
}
|
||||
case 4: {
|
||||
// points
|
||||
auto& A = s.pts[3]; // newest
|
||||
auto& B = s.pts[2];
|
||||
auto& C = s.pts[1];
|
||||
auto& D = s.pts[0];
|
||||
// line to origin
|
||||
auto AO = -A.p;
|
||||
// faces
|
||||
auto ABC = uf::vector::cross( B.p - A.p, C.p - A.p );
|
||||
if ( uf::vector::dot( ABC, AO ) > 0 ) {
|
||||
s.pts = { A, B, C };
|
||||
return ::updateSimplex( s, dir );
|
||||
}
|
||||
|
||||
auto ACD = uf::vector::cross( C.p - A.p, D.p - A.p );
|
||||
if ( uf::vector::dot( ACD, AO ) > 0 ) {
|
||||
s.pts = { A, C, D };
|
||||
return ::updateSimplex( s, dir );
|
||||
}
|
||||
|
||||
auto ADB = uf::vector::cross( D.p - A.p, B.p - A.p );
|
||||
if ( uf::vector::dot( ADB, AO ) > 0 ) {
|
||||
s.pts = { A, D, B };
|
||||
return ::updateSimplex( s, dir );
|
||||
}
|
||||
|
||||
// origin inside tetrahedron
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isDegenerate( const pod::Simplex& s, const pod::SupportPoint& newPt, float eps = EPS(1.0e-6f) ) {
|
||||
// compare to existing
|
||||
for ( auto& sp : s.pts ) {
|
||||
if ( uf::vector::magnitude( sp.p - newPt.p ) < eps ) return true; // duplicate point
|
||||
}
|
||||
// if simplex already has 3 points, check area
|
||||
if ( s.pts.size() >= 2 ) {
|
||||
auto& A = s.pts[0].p;
|
||||
auto& B = s.pts[1].p;
|
||||
auto& C = newPt.p;
|
||||
|
||||
float area = uf::vector::magnitude( uf::vector::cross( B - A, C - A ) );
|
||||
if ( area < eps ) return true; // collinear with previous
|
||||
}
|
||||
if ( s.pts.size() >= 3 ) {
|
||||
auto& A = s.pts[0].p;
|
||||
auto& B = s.pts[1].p;
|
||||
auto& C = s.pts[2].p;
|
||||
auto& D = newPt.p;
|
||||
|
||||
float volume = fabs( uf::vector::dot( D - A, uf::vector::cross( B - A, C - A ) ) );
|
||||
if ( volume < eps ) return true; // coplanar with triangle
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool gjk( const pod::RigidBody& a, const pod::RigidBody& b, pod::Simplex& simplex, int maxIterations = 20, float eps = EPS(1e-6f) ) {
|
||||
auto dir = b.transform->position - a.transform->position;
|
||||
if ( uf::vector::magnitude(dir) < eps ) dir = {1,0,0}; // fallback direction
|
||||
|
||||
// initial condition
|
||||
simplex.pts.clear();
|
||||
simplex.pts.emplace_back(::supportMinkowskiDetailed( a, b, dir ));
|
||||
dir = -simplex.pts[0].p;
|
||||
|
||||
for ( auto it = 0; it < maxIterations; ++it ) {
|
||||
auto newPt = ::supportMinkowskiDetailed( a, b, dir );
|
||||
if ( uf::vector::dot( newPt.p, dir ) < 0 ) return false; // didn't pass origin, no collision
|
||||
// would invalidate the simplex
|
||||
if ( ::isDegenerate( simplex, newPt ) ) {
|
||||
#if 1
|
||||
// nudge direction with a small orthogonal component
|
||||
if ( fabs(dir.x) < fabs(dir.y) && fabs(dir.x) < fabs(dir.z) ) dir = uf::vector::normalize( pod::Vector3f{1,0,0} + dir * 0.01f );
|
||||
else if ( fabs(dir.y) < fabs(dir.z) ) dir = uf::vector::normalize( pod::Vector3f{0,1,0} + dir * 0.01f );
|
||||
else dir = uf::vector::normalize( pod::Vector3f{0,0,1} + dir * 0.01f );
|
||||
#else
|
||||
// choose an alternate probe
|
||||
static pod::Vector3f fallbackDirs[3] = { {1,0,0}, {0,1,0}, {0,0,1} };
|
||||
dir = fallbackDirs[ (it + simplex.pts.size()) % 3 ];
|
||||
#endif
|
||||
continue; // try again
|
||||
}
|
||||
// add new point to simplex
|
||||
simplex.pts.emplace_back( newPt );
|
||||
// update
|
||||
if ( ::updateSimplex(simplex, dir) ) return true; // simplex contains origin, finished
|
||||
}
|
||||
|
||||
#if 1
|
||||
return false;
|
||||
#else
|
||||
// if overlap detected but simplex ended at triangle, fix it, as EPA requires a tetrahedron:
|
||||
if ( simplex.pts.size() == 3 ) {
|
||||
// points
|
||||
auto& A0 = simplex.pts[0].p;
|
||||
auto& B0 = simplex.pts[1].p;
|
||||
auto& C0 = simplex.pts[2].p;
|
||||
// triangle normal
|
||||
auto normal = uf::vector::normalize( uf::vector::cross( B0 - A0, C0 - A0 ) );
|
||||
|
||||
// try support in +normal
|
||||
auto extra = ::supportMinkowskiDetailed( a, b, normal );
|
||||
float vol = fabs( uf::vector::dot( extra.p - A0, uf::vector::cross( B0 - A0, C0 - A0 ) ) );
|
||||
if ( vol < eps ) extra = ::supportMinkowskiDetailed( a, b, -normal ); // if still coplanar, try -normal
|
||||
simplex.pts.emplace_back(extra); // force tetrahedron
|
||||
}
|
||||
|
||||
return !simplex.pts.empty();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
260
engine/src/utils/math/physics/helpers.inl
Normal file
260
engine/src/utils/math/physics/helpers.inl
Normal file
@ -0,0 +1,260 @@
|
||||
// forward declare
|
||||
namespace {
|
||||
bool aabbAabb( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool aabbSphere( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool aabbPlane( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool aabbCapsule( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool aabbMesh( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
|
||||
bool sphereSphere( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool sphereAabb( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool spherePlane( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool sphereCapsule( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool sphereMesh( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
|
||||
bool planeAabb( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool planeSphere( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool planePlane( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool planeCapsule( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool planeMesh( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
|
||||
bool capsuleCapsule( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool capsuleAabb( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool capsulePlane( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool capsuleSphere( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool capsuleMesh( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
|
||||
bool meshAabb( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool meshSphere( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool meshPlane( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool meshCapsule( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
bool meshMesh( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps = EPS(1.0e-6f) );
|
||||
|
||||
bool aabbOverlap( const pod::AABB& a, const pod::AABB& b, float eps = EPS(1.0e-6f) );
|
||||
pod::AABB computeTriangleAABB( const pod::Triangle& tri );
|
||||
|
||||
void reduceContacts( pod::Manifold& manifold );
|
||||
|
||||
void traverseNodePair( const pod::BVH& bvh, int leftID, int rightID, pod::BVH::pair_t& pairs );
|
||||
void traverseNodePair( const pod::BVH& a, int nodeA, const pod::BVH& b, int nodeB, pod::BVH::pair_t& out );
|
||||
|
||||
void queryBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector<int>& indices );
|
||||
void queryBVH( const pod::BVH& bvh, const pod::AABB& bounds, uf::stl::vector<int>& indices, int nodeID );
|
||||
void queryBVH( const pod::BVH& bvh, const pod::Ray& ray, uf::stl::vector<int>& indices, int nodeID, float maxDist = FLT_MAX );
|
||||
void queryBVH( const pod::BVH& bvh, const pod::Ray& ray, uf::stl::vector<int>& indices, float maxDist = FLT_MAX );
|
||||
}
|
||||
|
||||
namespace {
|
||||
// create ID from pointers
|
||||
uint64_t makePairKey( const pod::RigidBody& a, const pod::RigidBody& b ) {
|
||||
auto idA = reinterpret_cast<uint64_t>(&a);
|
||||
auto idB = reinterpret_cast<uint64_t>(&b);
|
||||
if ( idA > idB ) std::swap(idA, idB); // ensure consistent order
|
||||
return (idA << 32) ^ idB;
|
||||
}
|
||||
|
||||
// normalizes the delta between two bodies / contacts by the distance (as it was already computed) if non-zero
|
||||
// a lot of collider v colliders use this semantic
|
||||
pod::Vector3f normalizeDelta( const pod::Vector3f& delta, float dist, float eps = EPS(1.0e-6), const pod::Vector3f& fallback = pod::Vector3f{0,1,0} ) {
|
||||
return ( dist > eps ) ? delta / dist : fallback;
|
||||
}
|
||||
|
||||
pod::Vector3f computeTangent( const pod::Vector3f& normal ) {
|
||||
pod::Vector3f up = ( std::fabs(normal.y) < 0.999f ) ? pod::Vector3f{0,1,0} : pod::Vector3f{1,0,0}; // pick a vector not parallel to normal
|
||||
pod::Vector3f tangent = uf::vector::normalize( uf::vector::cross( up, normal ) );
|
||||
return tangent;
|
||||
}
|
||||
pod::Vector3f closestPointOnSegment( const pod::Vector3f& p, const pod::Vector3f& a, const pod::Vector3f& b ) {
|
||||
pod::Vector3f ab = b - a;
|
||||
float t = uf::vector::dot(p - a, ab) / uf::vector::dot(ab, ab);
|
||||
t = std::max(0.0f, std::min(1.0f, t)); // clamp( t, 0, 1 )
|
||||
return a + ab * t;
|
||||
}
|
||||
|
||||
std::pair<pod::Vector3f, pod::Vector3f> closestSegmentSegment( const pod::Vector3f& A, const pod::Vector3f& B, const pod::Vector3f& C, const pod::Vector3f& D, float eps = EPS(1e-6f) ) {
|
||||
auto u = B - A;
|
||||
auto v = D - C;
|
||||
auto w = A - C;
|
||||
|
||||
float a = uf::vector::dot( u, u );
|
||||
float b = uf::vector::dot( u, v );
|
||||
float c = uf::vector::dot( v, v );
|
||||
float d = uf::vector::dot( u, w );
|
||||
float e = uf::vector::dot( v, w );
|
||||
|
||||
float Dd = a*c - b*b;
|
||||
|
||||
float sc, sN, sD = Dd;
|
||||
float tc, tN, tD = Dd;
|
||||
|
||||
if ( Dd < eps ) {
|
||||
sN = 0.0f;
|
||||
sD = 1.0f;
|
||||
tN = e;
|
||||
tD = c;
|
||||
} else {
|
||||
sN = (b*e - c*d);
|
||||
tN = (a*e - b*d);
|
||||
if (sN < 0) { sN = 0; tN = e; tD = c; }
|
||||
else if (sN > sD) { sN = sD; tN = e + b; tD = c; }
|
||||
}
|
||||
|
||||
if ( tN < 0 ) {
|
||||
tN = 0;
|
||||
if (-d < 0) sN = 0;
|
||||
else if (-d > a) sN = sD;
|
||||
else { sN = -d; sD = a; }
|
||||
} else if ( tN > tD ) {
|
||||
tN = tD;
|
||||
if ((-d + b) < 0) sN = 0;
|
||||
else if ((-d + b) > a) sN = sD;
|
||||
else { sN = (-d + b); sD = a; }
|
||||
}
|
||||
|
||||
sc = ( fabs(sN) < eps ) ? 0 : ( sN / sD );
|
||||
tc = ( fabs(tN) < eps ) ? 0 : ( tN / tD );
|
||||
|
||||
return { A + u * sc, C + v * tc };
|
||||
}
|
||||
|
||||
pod::Vector3f closestPointSegmentAabb( const pod::Vector3f& p1, const pod::Vector3f& p2, const pod::AABB& box, float eps = EPS(1e-6f) ) {
|
||||
// AABB center and half extents
|
||||
auto c = ( box.min + box.max ) * 0.5f;
|
||||
auto e = ( box.max - box.min ) * 0.5f;
|
||||
|
||||
// direction of line segment
|
||||
auto d = p2 - p1;
|
||||
float len2 = uf::vector::dot( d, d );
|
||||
float t = 0.0f;
|
||||
|
||||
if ( len2 > eps ) {
|
||||
// parametric closest t from box center
|
||||
t = uf::vector::dot( c - p1, d ) / len2;
|
||||
t = std::max(0.0f, std::min(1.0f, t));
|
||||
}
|
||||
|
||||
// closest point on segment to box center
|
||||
auto segClosest = p1 + d * t;
|
||||
|
||||
// clamp this point into AABB
|
||||
return {
|
||||
std::max(box.min.x, std::min(segClosest.x, box.max.x)),
|
||||
std::max(box.min.y, std::min(segClosest.y, box.max.y)),
|
||||
std::max(box.min.z, std::min(segClosest.z, box.max.z)),
|
||||
};
|
||||
}
|
||||
|
||||
pod::Vector3f computeBarycentric( const pod::Vector3f& p, const pod::Vector3f& a, const pod::Vector3f& b, const pod::Vector3f& c, bool clamps = false ) {
|
||||
// edges
|
||||
auto ab = b - a;
|
||||
auto ac = c - a;
|
||||
auto ao = p - a;
|
||||
|
||||
// compute barycentric coords wrt triangle
|
||||
float d1 = uf::vector::dot(ab, ao);
|
||||
float d2 = uf::vector::dot(ac, ao);
|
||||
float d00 = uf::vector::dot(ab, ab);
|
||||
float d01 = uf::vector::dot(ab, ac);
|
||||
float d11 = uf::vector::dot(ac, ac);
|
||||
|
||||
float denom = d00 * d11 - d01 * d01;
|
||||
|
||||
float v = (d11*d1 - d01*d2) / denom;
|
||||
float w = (d00*d2 - d01*d1) / denom;
|
||||
float u = 1.0f - v - w;
|
||||
|
||||
// clamp to triangle
|
||||
if ( clamps && (u < 0 || v < 0 || w < 0) ) {
|
||||
// if projected point is outside, snap to edges or vertices
|
||||
// check against AB
|
||||
float t = std::clamp(uf::vector::dot( ao, ab ) / d00, 0.0f, 1.0f);
|
||||
auto pAB = a + (ab * t);
|
||||
float distAB = uf::vector::dot( pAB, pAB );
|
||||
|
||||
// check against AC
|
||||
float t2 = std::clamp(uf::vector::dot( ao, ac ) / d11, 0.0f, 1.0f);
|
||||
auto pAC = a + (ac * t2);
|
||||
float distAC = uf::vector::dot( pAC, pAC );
|
||||
|
||||
// check against BC
|
||||
auto bc = c - b;
|
||||
float d22 = uf::vector::dot( bc, bc );
|
||||
float t3 = std::clamp(uf::vector::dot( -b, bc ) / d22, 0.0f, 1.0f);
|
||||
auto pBC = b + ( bc * t3 );
|
||||
float distBC = uf::vector::dot( pBC, pBC );
|
||||
|
||||
// pick closest edge/vertex
|
||||
if ( distAB <= distAC && distAB <= distBC ) return { 1.0f - t, t, 0.0f };
|
||||
if ( distAC <= distBC ) return { 1.0f - t2, 0.0f, t2 };
|
||||
return { 0.0f, 1.0f - t3, t3 };
|
||||
}
|
||||
|
||||
return { u, v, w };
|
||||
}
|
||||
// made a huge mess of probably mixing up arguments................
|
||||
pod::Vector3f computeBarycentric(const pod::Vector3f& p, const pod::Triangle& tri, bool clamps = false ) {
|
||||
return ::computeBarycentric( p, tri.points[0], tri.points[1], tri.points[2], clamps );
|
||||
}
|
||||
pod::Vector3f interpolateWithBarycentric( const pod::Vector3f& bary, const pod::Vector3f a, const pod::Vector3f b, const pod::Vector3f c ) {
|
||||
return a * bary.x + b * bary.y + c * bary.z;
|
||||
}
|
||||
pod::Vector3f interpolateWithBarycentric( const pod::Vector3f& bary, const pod::Vector3f points[3] ) {
|
||||
return ::interpolateWithBarycentric( bary, points[0], points[1], points[2] );
|
||||
}
|
||||
|
||||
pod::Vector3f closestPointOnTriangle( const pod::Vector3f& p, const pod::Vector3f& a, const pod::Vector3f& b, const pod::Vector3f& c ) {
|
||||
auto bary = ::computeBarycentric( p, a, b, c );
|
||||
return ::interpolateWithBarycentric( bary, a, b, c );
|
||||
}
|
||||
pod::Vector3f closestPointOnTriangle( const pod::Vector3f& p, const pod::Triangle& tri ) {
|
||||
return ::closestPointOnTriangle( p, tri.points[0], tri.points[1], tri.points[2] );
|
||||
}
|
||||
|
||||
|
||||
pod::Vector3f orientNormalToAB( const pod::RigidBody& a, const pod::RigidBody& b, pod::Vector3f n ) {
|
||||
return uf::vector::normalize( uf::vector::dot( n, b.transform->position - a.transform->position ) < 0.0f ? -n : n );
|
||||
}
|
||||
|
||||
float segmentTriangleDistanceSq( const pod::Vector3f& p0, const pod::Vector3f& p1, const pod::Triangle& tri, pod::Vector3f& outSeg, pod::Vector3f& outTri ) {
|
||||
// segment vs plane
|
||||
auto n = uf::vector::normalize( uf::vector::cross( tri.points[1] - tri.points[0], tri.points[2]-tri.points[0] ) );
|
||||
float d0 = uf::vector::dot( p0 - tri.points[0], n );
|
||||
float d1 = uf::vector::dot( p1 - tri.points[0], n );
|
||||
|
||||
// intersects plane
|
||||
if ( ( d0 * d1 ) <= 0 ) {
|
||||
float t = d0 / ( d0-d1 );
|
||||
auto p = p0 + ( p1 - p0 ) * t;
|
||||
auto q = ::closestPointOnTriangle( p, tri );
|
||||
outSeg = p; outTri = q;
|
||||
return uf::vector::magnitude( p - q );
|
||||
}
|
||||
|
||||
// otherwise check endpoints against triangle
|
||||
auto q0 = ::closestPointOnTriangle( p0, tri );
|
||||
auto q1 = ::closestPointOnTriangle( p1, tri );
|
||||
float d0sq = uf::vector::magnitude( p0 - q0 );
|
||||
float d1sq = uf::vector::magnitude( p1 - q1 );
|
||||
|
||||
if ( d0sq < d1sq ) { outSeg = p0; outTri = q0; return d0sq; }
|
||||
else { outSeg = p1; outTri = q1; return d1sq; }
|
||||
}
|
||||
|
||||
bool triangleTriangleIntersect( const pod::Triangle& a, const pod::Triangle& b, float eps = EPS(1e-6f) ) {
|
||||
auto boxA = ::computeTriangleAABB( a );
|
||||
auto boxB = ::computeTriangleAABB( b );
|
||||
|
||||
if ( !aabbOverlap( boxA, boxB ) ) return false;
|
||||
|
||||
// check vertices of a inside b or vice versa
|
||||
for ( auto i = 0; i < 3; i++ ) {
|
||||
auto q = ::closestPointOnTriangle( a.points[i], b );
|
||||
if ( uf::vector::magnitude( q - a.points[i] ) < eps ) return true;
|
||||
}
|
||||
for ( auto i = 0; i < 3; i++ ) {
|
||||
auto q = ::closestPointOnTriangle( b.points[i], a );
|
||||
if ( uf::vector::magnitude( q - b.points[i] ) < eps ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
410
engine/src/utils/math/physics/impl.cpp
Normal file
410
engine/src/utils/math/physics/impl.cpp
Normal file
@ -0,0 +1,410 @@
|
||||
#if !UF_USE_REACTPHYSICS
|
||||
#include <uf/utils/math/physics/impl.h>
|
||||
#include <uf/engine/scene/scene.h>
|
||||
#include <uf/utils/mesh/mesh.h>
|
||||
#include <uf/utils/memory/stack.h>
|
||||
|
||||
namespace {
|
||||
bool warmupSolver = false;
|
||||
bool blockContactSolver = false;
|
||||
bool useGjk = false;
|
||||
bool fixedStep = true;
|
||||
int substeps = 0;
|
||||
|
||||
int solverIterations = 20;
|
||||
float baumgarteCorrectionPercent = 0.4f;
|
||||
float baumgarteCorrectionSlop = 0.01f;
|
||||
uf::stl::unordered_map<size_t, pod::Manifold> manifoldsCache;
|
||||
}
|
||||
|
||||
#define EPS(x) x // 1.0e-6f
|
||||
#define ASSERT_COLLIDER_TYPES( A, B ) UF_ASSERT( a.collider.type == pod::ShapeType::A && b.collider.type == pod::ShapeType::B );
|
||||
#define UF_PHYSICS_TEST 0
|
||||
|
||||
#include "helpers.inl"
|
||||
#include "aabb.inl"
|
||||
#include "sphere.inl"
|
||||
#include "plane.inl"
|
||||
#include "capsule.inl"
|
||||
#include "mesh.inl"
|
||||
#include "ray.inl"
|
||||
#include "bvh.inl"
|
||||
#include "gjk.inl"
|
||||
#include "epa.inl"
|
||||
#include "integration.inl"
|
||||
|
||||
// unused
|
||||
float uf::physics::impl::timescale = 1.0f / 60.0f;
|
||||
bool uf::physics::impl::interpolate = false;
|
||||
bool uf::physics::impl::shared = false;
|
||||
bool uf::physics::impl::globalStorage = false;
|
||||
|
||||
// Bindings
|
||||
void uf::physics::impl::initialize() {
|
||||
uf::physics::impl::initialize( uf::scene::getCurrentScene() );
|
||||
}
|
||||
void uf::physics::impl::initialize( uf::Object& object ) {
|
||||
uf::physics::impl::initialize( object.getComponent<pod::World>() );
|
||||
}
|
||||
void uf::physics::impl::initialize( pod::World& world ) {
|
||||
// to-do: any additional initialization (since it's not needed right now)
|
||||
}
|
||||
void uf::physics::impl::tick( float dt ) {
|
||||
uf::physics::impl::tick( uf::scene::getCurrentScene(), dt );
|
||||
}
|
||||
void uf::physics::impl::tick( uf::Object& object, float dt ) {
|
||||
uf::physics::impl::tick( object.getComponent<pod::World>(), dt );
|
||||
}
|
||||
void uf::physics::impl::tick( pod::World& world, float dt ) {
|
||||
if ( !::fixedStep ) {
|
||||
|
||||
if ( ::substeps > 0 ) uf::physics::impl::substep( world, dt, ::substeps );
|
||||
else uf::physics::impl::step( world, dt );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static float accumulator = 0;
|
||||
accumulator += dt;
|
||||
while ( accumulator >= uf::physics::impl::timescale ) {
|
||||
if ( ::substeps > 0 ) uf::physics::impl::substep( world, uf::physics::impl::timescale, ::substeps );
|
||||
else uf::physics::impl::step( world, uf::physics::impl::timescale );
|
||||
accumulator -= uf::physics::impl::timescale;
|
||||
}
|
||||
}
|
||||
void uf::physics::impl::terminate() {
|
||||
uf::physics::impl::terminate( uf::scene::getCurrentScene() );
|
||||
}
|
||||
void uf::physics::impl::terminate( uf::Object& object ) {
|
||||
uf::physics::impl::terminate( object.getComponent<pod::World>() );
|
||||
}
|
||||
void uf::physics::impl::terminate( pod::World& world ) {
|
||||
world.bodies.clear();
|
||||
}
|
||||
|
||||
// Implementation
|
||||
void uf::physics::impl::substep( pod::World& world, float dt, int substeps ) {
|
||||
float h = dt / substeps;
|
||||
for ( auto i=0; i < substeps; ++i) {
|
||||
uf::physics::impl::step( world, h );
|
||||
}
|
||||
}
|
||||
void uf::physics::impl::step( pod::World& world, float dt ) {
|
||||
auto& bodies = world.bodies;
|
||||
auto& bvh = world.bvh;
|
||||
|
||||
if ( bodies.empty() ) return;
|
||||
|
||||
for ( auto* body : bodies ) {
|
||||
::integrate( *body, dt );
|
||||
}
|
||||
|
||||
uf::stl::vector<pod::Manifold> manifolds;
|
||||
uf::stl::vector<std::pair<int,int>> pairs;
|
||||
|
||||
// create BVH
|
||||
::buildBroadphaseBVH(bvh, bodies);
|
||||
// query for overlaps
|
||||
::queryOverlaps( bvh, pairs );
|
||||
// iterate overlaps
|
||||
for ( auto& [ia, ib] : pairs ) {
|
||||
auto& a = *bodies[ia];
|
||||
auto& b = *bodies[ib];
|
||||
|
||||
pod::Manifold manifold;
|
||||
if ( ::generateContacts( a, b, manifold, dt ) ) {
|
||||
// bodies with meshes already reorient the normal to the triangle's center
|
||||
// do not do it for meshes because it'll reorient to the mesh's origin
|
||||
if ( a.collider.type != pod::ShapeType::MESH && b.collider.type != pod::ShapeType::MESH ) {
|
||||
for ( auto& c : manifold.points ) c.normal = ::orientNormalToAB( a, b, c.normal );
|
||||
}
|
||||
// retrieve accumulated impulses
|
||||
if ( ::warmupSolver ) ::retrieveContacts( manifold, ::manifoldsCache[::makePairKey( a, b )] );
|
||||
// keep four most important contacts
|
||||
::reduceContacts( manifold );
|
||||
#if 0
|
||||
UF_MSG_DEBUG("body a={}, body b={}, manifold size={}", uf::vector::toString(a.transform->position), uf::vector::toString(b.transform->position), manifold.points.size());
|
||||
for ( auto& contact : manifold.points ) {
|
||||
UF_MSG_DEBUG("contact={}, normal={}, depth={}", uf::vector::toString( contact.point ), uf::vector::toString( contact.normal ), contact.penetration );
|
||||
}
|
||||
#endif
|
||||
|
||||
// store manifold
|
||||
manifolds.emplace_back(manifold);
|
||||
}
|
||||
}
|
||||
|
||||
// pass manifolds to solver
|
||||
::solveContacts( manifolds, dt );
|
||||
|
||||
if ( ::warmupSolver ) {
|
||||
// update cache
|
||||
for ( auto& manifold : manifolds ) {
|
||||
::manifoldsCache[::makePairKey( *manifold.a, *manifold.b )] = manifold;
|
||||
}
|
||||
|
||||
// prune if too old / empty
|
||||
for ( auto& [ key, manifold ] : manifoldsCache ) {
|
||||
// prune manifolds that are X frames old
|
||||
for ( auto it = manifold.points.begin(); it != manifold.points.end(); ) {
|
||||
if ( it->lifetime > 3 ) manifold.points.erase(it);
|
||||
else ++it;
|
||||
}
|
||||
// empty manifold, kill it
|
||||
if ( manifold.points.empty() ) manifoldsCache.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
// recompute bounds for further queries
|
||||
for ( auto* body : bodies ) {
|
||||
body->bounds = ::computeAABB( *body );
|
||||
}
|
||||
}
|
||||
|
||||
void uf::physics::impl::setMass( pod::RigidBody& body, float mass ) {
|
||||
body.mass = mass;
|
||||
body.inverseMass = 1.0f / mass;
|
||||
setInertia( body );
|
||||
}
|
||||
void uf::physics::impl::setInertia( pod::RigidBody& body ) {
|
||||
if ( body.isStatic || body.mass <= 0 ) {
|
||||
body.inverseInertiaTensor = {};
|
||||
return;
|
||||
}
|
||||
|
||||
// to-do: make this a flag or something
|
||||
if ( body.collider.type == pod::ShapeType::CAPSULE ) {
|
||||
body.inertiaTensor = { FLT_MAX, FLT_MAX, FLT_MAX };
|
||||
body.inverseInertiaTensor = { 0.0f, 0.0f, 0.0f };
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( body.collider.type ) {
|
||||
case pod::ShapeType::AABB: {
|
||||
pod::Vector3f extents = (body.collider.u.aabb.max - body.collider.u.aabb.min);
|
||||
extents *= extents; // square it;
|
||||
|
||||
body.inertiaTensor = extents * (body.mass / 12.0f);
|
||||
// to-do: add overloaded inverse order arithmetic operators
|
||||
//body.inverseInertiaTensor = 1.0f / body.inertiaTensor;
|
||||
body.inverseInertiaTensor = { 1.0f / body.inertiaTensor.x, 1.0f / body.inertiaTensor.y, 1.0f / body.inertiaTensor.z };
|
||||
} break;
|
||||
case pod::ShapeType::SPHERE: {
|
||||
float I = 0.4f * body.mass * body.collider.u.sphere.radius * body.collider.u.sphere.radius;
|
||||
float invI = 1.0f / I;
|
||||
body.inertiaTensor = { I, I, I };
|
||||
body.inverseInertiaTensor = { invI, invI, invI };
|
||||
} break;
|
||||
case pod::ShapeType::CAPSULE: {
|
||||
float r = body.collider.u.capsule.radius;
|
||||
float h = body.collider.u.capsule.halfHeight * 2.0f; // full cyl height
|
||||
float m = body.mass;
|
||||
|
||||
float Ixx = 0.25f * m * r * r + (1.0f/12.0f) * m * h * h;
|
||||
float Iyy = 0.5f * m * r * r;
|
||||
float Izz = Ixx;
|
||||
|
||||
body.inertiaTensor = { Ixx, Iyy, Izz };
|
||||
body.inverseInertiaTensor = { 1.0f/Ixx, 1.0f/Iyy, 1.0f/Izz };
|
||||
} break;
|
||||
|
||||
// to-do: add others
|
||||
default: {
|
||||
} break;
|
||||
}
|
||||
}
|
||||
void uf::physics::impl::applyForce( pod::RigidBody& body, const pod::Vector3f& force ) {
|
||||
if ( body.isStatic ) return;
|
||||
body.forceAccumulator += force;
|
||||
}
|
||||
void uf::physics::impl::applyForceAtPoint( pod::RigidBody body, const pod::Vector3f& force, const pod::Vector3f& point ) {
|
||||
if ( body.isStatic ) return;
|
||||
// linear force
|
||||
body.forceAccumulator += force;
|
||||
// angular force
|
||||
pod::Vector3f r = point - body.transform->position;
|
||||
body.torqueAccumulator += uf::vector::cross( r, force );
|
||||
}
|
||||
void uf::physics::impl::applyImpulse( pod::RigidBody& body, const pod::Vector3f& impulse ) {
|
||||
if ( body.isStatic ) return;
|
||||
body.velocity += impulse * body.inverseMass;
|
||||
}
|
||||
void uf::physics::impl::applyTorque( pod::RigidBody& body, const pod::Vector3f& torque ) {
|
||||
if ( body.isStatic ) return;
|
||||
body.torqueAccumulator += torque;
|
||||
}
|
||||
void uf::physics::impl::setVelocity( pod::RigidBody& body, const pod::Vector3f& v ) {
|
||||
body.velocity = v;
|
||||
}
|
||||
void uf::physics::impl::applyRotation( pod::RigidBody& body, const pod::Quaternion<>& q ) {
|
||||
uf::transform::rotate( *body.transform, q );
|
||||
}
|
||||
void uf::physics::impl::applyRotation( pod::RigidBody& body, const pod::Vector3f& axis, float angle ) {
|
||||
applyRotation( body, uf::quaternion::axisAngle( axis, angle ) );
|
||||
}
|
||||
|
||||
// body creation
|
||||
pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object, float mass ) {
|
||||
// bind to component
|
||||
pod::RigidBody& body = object.getComponent<pod::RigidBody>();
|
||||
// initial initialization
|
||||
body.world = &world;
|
||||
body.object = &object;
|
||||
body.transform = &object.getComponent<pod::Transform<>>();
|
||||
body.mass = mass;
|
||||
body.inverseMass = 1.0f / mass;
|
||||
body.isStatic = mass == 0.0f;
|
||||
|
||||
// insert into world
|
||||
world.bodies.emplace_back(&body);
|
||||
|
||||
return body;
|
||||
}
|
||||
pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object, const pod::AABB& aabb, float mass ) {
|
||||
auto& body = create( world, object, mass );
|
||||
body.collider.type = pod::ShapeType::AABB;
|
||||
body.collider.u.aabb = aabb;
|
||||
body.bounds = ::computeAABB( body );
|
||||
setInertia( body );
|
||||
UF_MSG_DEBUG("Creating body of type: {} | mass: {} | min: {} | max: {}", "AABB", mass, uf::vector::toString(aabb.min), uf::vector::toString(aabb.max) );
|
||||
return body;
|
||||
}
|
||||
pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object, const pod::Sphere& sphere, float mass ) {
|
||||
auto& body = create( world, object, mass );
|
||||
body.collider.type = pod::ShapeType::SPHERE;
|
||||
body.collider.u.sphere = sphere;
|
||||
body.bounds = ::computeAABB( body );
|
||||
setInertia( body );
|
||||
UF_MSG_DEBUG("Creating body of type: {} | mass: {} | radius: {}", "SPHERE", mass, sphere.radius );
|
||||
return body;
|
||||
}
|
||||
pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object, const pod::Plane& plane, float mass ) {
|
||||
auto& body = create( world, object, mass );
|
||||
body.collider.type = pod::ShapeType::PLANE;
|
||||
body.collider.u.plane = plane;
|
||||
body.bounds = ::computeAABB( body );
|
||||
setInertia( body );
|
||||
UF_MSG_DEBUG("Creating body of type: {} | mass: {} | normal: {} | offset: {}", "PLANE", mass, uf::vector::toString( plane.normal ), plane.offset );
|
||||
return body;
|
||||
}
|
||||
pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object, const pod::Capsule& capsule, float mass ) {
|
||||
auto& body = create( world, object, mass );
|
||||
body.collider.type = pod::ShapeType::CAPSULE;
|
||||
body.collider.u.capsule = capsule;
|
||||
body.material.restitution = 0.001f;
|
||||
body.bounds = ::computeAABB( body );
|
||||
setInertia( body );
|
||||
UF_MSG_DEBUG("Creating body of type: {} | mass: {} | radius: {} | height: {}", "CAPSULE", mass, capsule.radius, capsule.halfHeight * 2.0f );
|
||||
return body;
|
||||
}
|
||||
pod::RigidBody& uf::physics::impl::create( pod::World& world, uf::Object& object, const uf::Mesh& mesh, float mass ) {
|
||||
auto& body = create( world, object, mass );
|
||||
body.collider.type = pod::ShapeType::MESH;
|
||||
body.collider.u.mesh.mesh = &mesh;
|
||||
body.collider.u.mesh.bvh = new pod::BVH;
|
||||
|
||||
::buildMeshBVH( *body.collider.u.mesh.bvh, mesh );
|
||||
|
||||
UF_MSG_DEBUG("Built mesh BVH: nodes={} indices={}", body.collider.u.mesh.bvh->nodes.size(), body.collider.u.mesh.bvh->indices.size());
|
||||
|
||||
body.bounds = ::computeAABB( body );
|
||||
setInertia( body );
|
||||
UF_MSG_DEBUG("Creating body of type: {} | mass: {} ", "MESH", mass );
|
||||
return body;
|
||||
}
|
||||
|
||||
pod::RigidBody& uf::physics::impl::create( uf::Object& object, float mass ) {
|
||||
// bind to scene
|
||||
// auto& root = object.getRootParent<>(); // in the event a scene is being initialized that is not the root scene, use the root parent instead
|
||||
// auto& world = root.getComponent<pod::World>();
|
||||
auto& scene = uf::scene::getCurrentScene();
|
||||
auto& world = scene.getComponent<pod::World>();
|
||||
return create( world, object, mass );
|
||||
|
||||
}
|
||||
pod::RigidBody& uf::physics::impl::create( uf::Object& object, const pod::AABB& aabb, float mass ) {
|
||||
auto& scene = uf::scene::getCurrentScene();
|
||||
auto& world = scene.getComponent<pod::World>();
|
||||
return create( world, object, aabb, mass );
|
||||
}
|
||||
pod::RigidBody& uf::physics::impl::create( uf::Object& object, const pod::Sphere& sphere, float mass ) {
|
||||
auto& scene = uf::scene::getCurrentScene();
|
||||
auto& world = scene.getComponent<pod::World>();
|
||||
return create( world, object, sphere, mass );
|
||||
}
|
||||
pod::RigidBody& uf::physics::impl::create( uf::Object& object, const pod::Plane& plane, float mass ) {
|
||||
auto& scene = uf::scene::getCurrentScene();
|
||||
auto& world = scene.getComponent<pod::World>();
|
||||
return create( world, object, plane, mass );
|
||||
}
|
||||
pod::RigidBody& uf::physics::impl::create( uf::Object& object, const pod::Capsule& capsule, float mass ) {
|
||||
auto& scene = uf::scene::getCurrentScene();
|
||||
auto& world = scene.getComponent<pod::World>();
|
||||
return create( world, object, capsule, mass );
|
||||
}
|
||||
pod::RigidBody& uf::physics::impl::create( uf::Object& object, const uf::Mesh& mesh, float mass ) {
|
||||
auto& scene = uf::scene::getCurrentScene();
|
||||
auto& world = scene.getComponent<pod::World>();
|
||||
return create( world, object, mesh, mass );
|
||||
}
|
||||
|
||||
void uf::physics::impl::destroy( uf::Object& object ) {
|
||||
if ( !object.hasComponent<pod::RigidBody>() ) return;
|
||||
|
||||
return destroy( object.getComponent<pod::RigidBody>() );
|
||||
}
|
||||
void uf::physics::impl::destroy( pod::RigidBody& body ) {
|
||||
auto& world = *body.world;
|
||||
// remove from world
|
||||
for ( auto it = world.bodies.begin(); it != world.bodies.end(); ++it ) {
|
||||
if ( (*it)->object != body.object ) continue;
|
||||
world.bodies.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
// remove any pointered collider data
|
||||
if ( body.collider.type == pod::ShapeType::MESH ) {
|
||||
if ( body.collider.u.mesh.bvh ) delete body.collider.u.mesh.bvh;
|
||||
}
|
||||
}
|
||||
|
||||
pod::RayQuery uf::physics::impl::rayCast( const pod::Ray& ray, const pod::RigidBody& body, float maxDistance ) {
|
||||
return rayCast( ray, *body.world, &body, maxDistance );
|
||||
}
|
||||
pod::RayQuery uf::physics::impl::rayCast( const pod::Ray& ray, const pod::World& world, float maxDistance ) {
|
||||
return rayCast( ray, world, NULL, maxDistance );
|
||||
}
|
||||
pod::RayQuery uf::physics::impl::rayCast( const pod::Ray& ray, const pod::World& world, const pod::RigidBody* body, float maxDistance ) {
|
||||
pod::RayQuery rayHit;
|
||||
rayHit.contact.penetration = maxDistance;
|
||||
|
||||
auto& bvh = world.bvh;
|
||||
auto& bodies = world.bodies;
|
||||
|
||||
#if 1
|
||||
uf::stl::vector<int> candidates;
|
||||
::queryBVH( bvh, ray, candidates );
|
||||
|
||||
for ( auto i : candidates ) {
|
||||
#else
|
||||
for ( auto i = 0; i < bodies.size(); ++i ) {
|
||||
#endif
|
||||
auto* b = bodies[i];
|
||||
if ( body == b ) continue;
|
||||
switch ( b->collider.type ) {
|
||||
case pod::ShapeType::AABB: rayAabb( ray, *b, rayHit ); break;
|
||||
case pod::ShapeType::SPHERE: raySphere( ray, *b, rayHit ); break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return rayHit;
|
||||
}
|
||||
|
||||
#if UF_PHYSICS_TEST
|
||||
#include "tests.inl"
|
||||
#endif
|
||||
#endif
|
||||
308
engine/src/utils/math/physics/integration.inl
Normal file
308
engine/src/utils/math/physics/integration.inl
Normal file
@ -0,0 +1,308 @@
|
||||
namespace {
|
||||
float computeEffectiveMass( pod::RigidBody& a, pod::RigidBody& b, const pod::Vector3f& rA, const pod::Vector3f& rB, const pod::Vector3f& n ) {
|
||||
float inverseMass = 0.0f;
|
||||
if ( !a.isStatic ) inverseMass += a.inverseMass;
|
||||
if ( !b.isStatic ) inverseMass += b.inverseMass;
|
||||
|
||||
float angularTermA = 0.0f;
|
||||
float angularTermB = 0.0f;
|
||||
|
||||
if ( !a.isStatic ) {
|
||||
pod::Vector3f crossA = uf::vector::cross(rA, n);
|
||||
angularTermA = uf::vector::dot(uf::vector::cross(crossA * a.inverseInertiaTensor, rA), n);
|
||||
}
|
||||
if ( !b.isStatic ) {
|
||||
pod::Vector3f crossB = uf::vector::cross(rB, n);
|
||||
angularTermB = uf::vector::dot(uf::vector::cross(crossB * b.inverseInertiaTensor, rB), n);
|
||||
}
|
||||
|
||||
float result = inverseMass + angularTermA + angularTermB;
|
||||
if (result < EPS(1e-8f)) result = 1.0f; // prevent divide by zero
|
||||
return result;
|
||||
}
|
||||
|
||||
void applyImpulseTo( pod::RigidBody& a, pod::RigidBody& b, const pod::Vector3f& rA, const pod::Vector3f& rB, const pod::Vector3f& impulse ) {
|
||||
if ( !a.isStatic ) {
|
||||
a.velocity -= impulse * a.inverseMass;
|
||||
a.angularVelocity -= (uf::vector::cross(rA, impulse)) * a.inverseInertiaTensor;
|
||||
}
|
||||
if ( !b.isStatic ) {
|
||||
b.velocity += impulse * b.inverseMass;
|
||||
b.angularVelocity += (uf::vector::cross(rB, impulse)) * b.inverseInertiaTensor;
|
||||
}
|
||||
}
|
||||
|
||||
void applyRollingResistance( pod::RigidBody& body, float dt ) {
|
||||
if ( body.isStatic ) return;
|
||||
|
||||
float rollingFriction = 0.02f; // to-do: derive from material
|
||||
float angularSpeed = uf::vector::magnitude( body.angularVelocity );
|
||||
if ( angularSpeed < EPS(1.0e-6f) ) return;
|
||||
|
||||
body.angularVelocity += body.angularVelocity * body.mass * -rollingFriction * dt;
|
||||
// body.angularVelocity *= -rollingFriction * dt;
|
||||
}
|
||||
|
||||
void bindManifold( pod::RigidBody& a, pod::RigidBody& b, pod::Manifold& manifold, float dt = 0 ) {
|
||||
manifold.a = &a;
|
||||
manifold.b = &b;
|
||||
manifold.dt = dt;
|
||||
manifold.points.clear();
|
||||
}
|
||||
|
||||
bool generateContactsGjk( pod::RigidBody& a, pod::RigidBody& b, pod::Manifold& manifold, float dt ) {
|
||||
::bindManifold( a, b, manifold, dt );
|
||||
|
||||
pod::Simplex simplex;
|
||||
|
||||
if ( !::gjk(a,b,simplex) ) return false;
|
||||
|
||||
auto result = ::epa( a, b, simplex );
|
||||
|
||||
manifold.points.clear();
|
||||
manifold.points.emplace_back(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool generateContacts( pod::RigidBody& a, pod::RigidBody& b, pod::Manifold& manifold, float dt ) {
|
||||
if ( ::useGjk ) return generateContactsGjk( a, b, manifold, dt );
|
||||
::bindManifold( a, b, manifold, dt );
|
||||
|
||||
#define CHECK_CONTACT( A, B, fun )\
|
||||
if ( a.collider.type == pod::ShapeType::A && b.collider.type == pod::ShapeType::B ) return fun( a, b, manifold );
|
||||
|
||||
CHECK_CONTACT( AABB, AABB, aabbAabb );
|
||||
CHECK_CONTACT( AABB, SPHERE, aabbSphere );
|
||||
CHECK_CONTACT( AABB, PLANE, aabbPlane );
|
||||
CHECK_CONTACT( AABB, CAPSULE, aabbCapsule );
|
||||
CHECK_CONTACT( AABB, MESH, aabbMesh );
|
||||
|
||||
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( 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( 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( MESH, AABB, meshAabb );
|
||||
CHECK_CONTACT( MESH, SPHERE, meshSphere );
|
||||
CHECK_CONTACT( MESH, PLANE, meshPlane );
|
||||
CHECK_CONTACT( MESH, CAPSULE, meshCapsule );
|
||||
CHECK_CONTACT( MESH, MESH, meshMesh );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool similarContact( const pod::Contact& a, const pod::Contact& b, float eps = 1.0e-3f, float distSq = 0.95f ) {
|
||||
return uf::vector::distanceSquared(a.point, b.point) < eps && uf::vector::dot(a.normal, b.normal) > distSq;
|
||||
}
|
||||
|
||||
void reduceContacts( pod::Manifold& manifold ) {
|
||||
if ( manifold.points.size() <= 4 ) return;
|
||||
|
||||
uf::stl::vector<pod::Contact> reduced;
|
||||
for ( auto& c : manifold.points ) {
|
||||
bool merged = false;
|
||||
for ( auto& r : reduced ) {
|
||||
if ( !::similarContact( c, r ) ) continue;
|
||||
// merge, pick deeper penetration
|
||||
if (c.penetration > r.penetration) r = c;
|
||||
merged = true;
|
||||
break;
|
||||
}
|
||||
if ( !merged ) reduced.emplace_back(c);
|
||||
}
|
||||
|
||||
// keep only deepest + farthest up to 4
|
||||
std::sort(reduced.begin(), reduced.end(), [](auto& a, auto& b){ return a.penetration > b.penetration; });
|
||||
if (reduced.size() > 4) reduced.resize(4);
|
||||
|
||||
manifold.points = reduced;
|
||||
}
|
||||
|
||||
void retrieveContacts( pod::Manifold& current, const pod::Manifold& previous, float decay = 0.35f ) {
|
||||
for ( auto& c : current.points ) {
|
||||
for ( auto& p : previous.points ) {
|
||||
if ( !::similarContact( c, p ) ) continue;
|
||||
c.accumulatedNormalImpulse = p.accumulatedNormalImpulse * decay;
|
||||
c.accumulatedTangentImpulse = p.accumulatedTangentImpulse * decay;
|
||||
c.lifetime = p.lifetime + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void warmupContacts( pod::RigidBody& a, pod::RigidBody& b, const pod::Contact& c, float dt ) {
|
||||
if ( !c.lifetime ) return; // too new
|
||||
|
||||
// build relative offsets
|
||||
pod::Vector3f rA = c.point - a.transform->position;
|
||||
pod::Vector3f rB = c.point - b.transform->position;
|
||||
|
||||
// normal impulse
|
||||
pod::Vector3f Pn = c.normal * c.accumulatedNormalImpulse;
|
||||
::applyImpulseTo( a, b, rA, rB, Pn );
|
||||
|
||||
// tangent basis
|
||||
pod::Vector3f Pt = c.tangent * c.accumulatedTangentImpulse;
|
||||
::applyImpulseTo( a, b, rA, rB, Pt );
|
||||
|
||||
UF_MSG_DEBUG("Warming, Pn={}, Pt={}, lifetime={}", uf::vector::toString(Pn), uf::vector::toString(Pt), c.lifetime );
|
||||
}
|
||||
|
||||
// baumgarte position correction
|
||||
void positionCorrection( pod::RigidBody& a, pod::RigidBody& b, const pod::Contact& contact ) {
|
||||
float correctionMagnitude = std::max(contact.penetration - ::baumgarteCorrectionSlop, 0.0f) / (a.inverseMass + b.inverseMass) * ::baumgarteCorrectionPercent;
|
||||
pod::Vector3f correction = contact.normal * correctionMagnitude;
|
||||
|
||||
if ( !a.isStatic ) a.transform->position -= correction * a.inverseMass;
|
||||
if ( !b.isStatic ) b.transform->position += correction * b.inverseMass;
|
||||
}
|
||||
|
||||
void resolveContact( pod::RigidBody& a, pod::RigidBody& b, pod::Contact& contact, float dt ) {
|
||||
// relative positions from centers to contact point
|
||||
pod::Vector3f rA = contact.point - a.transform->position;
|
||||
pod::Vector3f rB = contact.point - b.transform->position;
|
||||
|
||||
// relative velocity at contact
|
||||
pod::Vector3f vA = a.velocity + uf::vector::cross(a.angularVelocity, rA);
|
||||
pod::Vector3f vB = b.velocity + uf::vector::cross(b.angularVelocity, rB);
|
||||
pod::Vector3f rv = vB - vA;
|
||||
|
||||
// normal contact velocity
|
||||
float velAlongNormal = uf::vector::dot(rv, contact.normal);
|
||||
float velTolerance = 0; // -1.0e3f;
|
||||
if ( velAlongNormal > velTolerance ) return; // if separating, no impulse
|
||||
|
||||
// compute restitution (bounce)
|
||||
float e = std::min(a.material.restitution, b.material.restitution);
|
||||
|
||||
// nullify restitution if velocity is small enough
|
||||
if ( fabs(velAlongNormal) < 1.0f) e = 0.0f;
|
||||
|
||||
// effective inverse mass along normal
|
||||
float invMassN = ::computeEffectiveMass(a, b, rA, rB, contact.normal);
|
||||
|
||||
// normal impulse scalar
|
||||
float jn = -(1.0f + e) * velAlongNormal;
|
||||
jn /= invMassN;
|
||||
if ( ::warmupSolver ) {
|
||||
float jnOld = contact.accumulatedNormalImpulse;
|
||||
float jnNew = std::max(0.0f, jnOld + jn);
|
||||
float jnDelta = jnNew - jnOld;
|
||||
contact.accumulatedNormalImpulse = jnNew;
|
||||
jn = jnDelta;
|
||||
}
|
||||
|
||||
pod::Vector3f normalImpulse = contact.normal * jn;
|
||||
::applyImpulseTo(a, b, rA, rB, normalImpulse);
|
||||
|
||||
// tangent direction
|
||||
pod::Vector3f tangent = rv - contact.normal * uf::vector::dot(rv, contact.normal);
|
||||
float tangentMag = uf::vector::magnitude(tangent);
|
||||
if (tangentMag > EPS(1e-6f)) {
|
||||
tangent /= tangentMag;
|
||||
|
||||
// effective mass along tangent
|
||||
float invMassT = ::computeEffectiveMass(a, b, rA, rB, tangent);
|
||||
|
||||
// tangential relative velocity
|
||||
float vt = uf::vector::dot(rv, tangent);
|
||||
|
||||
// required tangential impulse to cancel tangent velocity
|
||||
float jt = -vt / invMassT;
|
||||
|
||||
// friction coefficients
|
||||
float mu_s = std::sqrt(a.material.staticFriction * b.material.staticFriction);
|
||||
float mu_d = std::sqrt(a.material.dynamicFriction * b.material.dynamicFriction);
|
||||
|
||||
if ( std::fabs(jt) > jn * mu_s) jt = -jn * mu_d; // dynamic friction: resist sliding proportionally
|
||||
|
||||
if ( ::warmupSolver ) {
|
||||
float maxFriction = mu_s * contact.accumulatedNormalImpulse;
|
||||
float jtOld = contact.accumulatedTangentImpulse;
|
||||
float jtNew = std::max(-maxFriction, std::min(jtOld + jt, maxFriction));
|
||||
float jtDelta = jtNew - jtOld;
|
||||
contact.accumulatedTangentImpulse = jtNew;
|
||||
contact.tangent = tangent;
|
||||
jt = jtDelta;
|
||||
}
|
||||
|
||||
pod::Vector3f frictionImpulse = tangent * jt;
|
||||
::applyImpulseTo(a, b, rA, rB, frictionImpulse);
|
||||
}
|
||||
|
||||
::positionCorrection(a, b, contact);
|
||||
}
|
||||
|
||||
void solveContacts( uf::stl::vector<pod::Manifold>& manifolds, float dt ) {
|
||||
if ( ::warmupSolver ) {
|
||||
for ( auto& m : manifolds ) {
|
||||
for ( auto& c : m.points ) {
|
||||
::warmupContacts(*m.a, *m.b, c, dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ( auto i = 0; i < ::solverIterations; ++i ) {
|
||||
for ( auto& m : manifolds ) {
|
||||
#if 0
|
||||
if ( ::blockContactSolver ) {
|
||||
//::solveManifoldBlockN( m, dt );
|
||||
::solveManifoldBlock2x2( m, dt );
|
||||
} else {
|
||||
for ( auto& c : m.points ) {
|
||||
::resolveContact(*m.a, *m.b, c, dt);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
for ( auto& c : m.points ) ::resolveContact(*m.a, *m.b, c, dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void integrate( pod::RigidBody& body, float dt ) {
|
||||
// only integrate dynamic bodies
|
||||
if ( body.isStatic || body.mass == 0 ) return;
|
||||
|
||||
auto& world = *body.world;
|
||||
|
||||
// linear integration
|
||||
pod::Vector3f acceleration = body.forceAccumulator * body.inverseMass;
|
||||
acceleration += world.gravity; // apply gravity
|
||||
body.velocity += acceleration * dt;
|
||||
|
||||
auto previous = body.transform->position;
|
||||
body.transform->position += body.velocity * dt;
|
||||
|
||||
// angular integration
|
||||
body.angularVelocity += body.torqueAccumulator * body.inverseInertiaTensor * dt;
|
||||
|
||||
// update orientation
|
||||
if ( uf::vector::magnitude( body.angularVelocity ) > EPS(1.0e-8f) ) {
|
||||
pod::Quaternion<> dq = uf::quaternion::axisAngle(uf::vector::normalize(body.angularVelocity), uf::vector::magnitude(body.angularVelocity)*dt);
|
||||
uf::transform::rotate( *body.transform, dq );
|
||||
}
|
||||
|
||||
// reset accumulators
|
||||
body.forceAccumulator = {};
|
||||
body.torqueAccumulator = {};
|
||||
|
||||
// update AABB
|
||||
body.bounds = ::computeAABB( body );
|
||||
|
||||
// apply rolling resistance
|
||||
::applyRollingResistance(body, dt);
|
||||
}
|
||||
}
|
||||
303
engine/src/utils/math/physics/mesh.inl
Normal file
303
engine/src/utils/math/physics/mesh.inl
Normal file
@ -0,0 +1,303 @@
|
||||
#define REORIENT_NORMALS_ON_FETCH 0
|
||||
#define REORIENT_NORMALS_ON_CONTACT 0
|
||||
|
||||
// mesh BVH
|
||||
namespace {
|
||||
// to-do: clean this up
|
||||
uint32_t getIndex( const void* indexData, size_t indexSize, size_t idx ) {
|
||||
if ( indexSize == sizeof(uint8_t) ) {
|
||||
auto* ptr = reinterpret_cast<const uint8_t*>(indexData);
|
||||
return static_cast<uint32_t>(ptr[idx]);
|
||||
} else if ( indexSize == sizeof(uint16_t) ) {
|
||||
auto* ptr = reinterpret_cast<const uint16_t*>(indexData);
|
||||
return static_cast<uint32_t>(ptr[idx]);
|
||||
} else if ( indexSize == sizeof(uint32_t) ) {
|
||||
auto* ptr = reinterpret_cast<const uint32_t*>(indexData);
|
||||
return ptr[idx];
|
||||
}
|
||||
UF_EXCEPTION("Unsupported index type of size {}", indexSize);
|
||||
}
|
||||
pod::AABB computeTriangleAABB( const void* vertices, size_t vertexStride, const void* indexData, size_t indexSize, size_t triID ) {
|
||||
auto triIndexID = triID * 3;
|
||||
|
||||
uint32_t i0 = ::getIndex( indexData, indexSize, triIndexID + 0 );
|
||||
uint32_t i1 = ::getIndex( indexData, indexSize, triIndexID + 1 );
|
||||
uint32_t i2 = ::getIndex( indexData, indexSize, triIndexID + 2 );
|
||||
|
||||
auto& v0 = *reinterpret_cast<const pod::Vector3f*>(reinterpret_cast<const uint8_t*>(vertices) + i0 * vertexStride);
|
||||
auto& v1 = *reinterpret_cast<const pod::Vector3f*>(reinterpret_cast<const uint8_t*>(vertices) + i1 * vertexStride);
|
||||
auto& v2 = *reinterpret_cast<const pod::Vector3f*>(reinterpret_cast<const uint8_t*>(vertices) + i2 * vertexStride);
|
||||
|
||||
return {
|
||||
{
|
||||
std::min({v0.x, v1.x, v2.x}),
|
||||
std::min({v0.y, v1.y, v2.y}),
|
||||
std::min({v0.z, v1.z, v2.z}),
|
||||
},
|
||||
{
|
||||
std::max({v0.x, v1.x, v2.x}),
|
||||
std::max({v0.y, v1.y, v2.y}),
|
||||
std::max({v0.z, v1.z, v2.z}),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pod::TriangleWithNormal fetchTriangle( const uf::Mesh& mesh, size_t triID ) {
|
||||
auto views = mesh.makeViews({"position", "normal"});
|
||||
UF_ASSERT(!views.empty());
|
||||
|
||||
uint32_t triIndexID = triID * 3; // remap triangle ID to index ID
|
||||
// find which view contains this triangle index.
|
||||
const uf::Mesh::View* found = nullptr;
|
||||
for ( auto& v : views ) {
|
||||
if ( v.index.first <= triIndexID && triIndexID < v.index.first + v.index.count ) {
|
||||
found = &v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
UF_ASSERT( found );
|
||||
|
||||
pod::TriangleWithNormal tri;
|
||||
|
||||
auto& positions = (*found)["position"];
|
||||
auto& normals = (*found)["normal"];
|
||||
auto& indices = (*found)["index"];
|
||||
|
||||
const void* indexBase = indices.data(found->index.first);
|
||||
size_t indexSize = mesh.index.size;
|
||||
|
||||
// reset back to local indices range
|
||||
triIndexID -= found->index.first;
|
||||
|
||||
uint32_t idxs[3];
|
||||
// to-do: just make this a macro that could have a parallel hint
|
||||
for ( auto i = 0; i < 3; ++i ) idxs[i] = getIndex(indexBase, indexSize, triIndexID + i);
|
||||
|
||||
{
|
||||
auto* base = reinterpret_cast<const uint8_t*>(positions.data(found->vertex.first));
|
||||
size_t stride = positions.stride();
|
||||
|
||||
for ( auto i = 0; i < 3; ++i ) tri.points[i] = *reinterpret_cast<const pod::Vector3f*>(base + idxs[i] * stride);
|
||||
}
|
||||
|
||||
if ( normals.valid() && false ) {
|
||||
auto* base = reinterpret_cast<const uint8_t*>(normals.data(found->vertex.first));
|
||||
size_t stride = normals.stride();
|
||||
for ( auto i = 0; i < 3; ++i ) tri.normals[i] = *reinterpret_cast<const pod::Vector3f*>(base + idxs[i] * stride);
|
||||
} else {
|
||||
auto normal = uf::vector::normalize(uf::vector::cross(tri.points[1]-tri.points[0], tri.points[2]-tri.points[0]));
|
||||
for ( auto i = 0; i < 3; ++i ) tri.normals[i] = normal;
|
||||
}
|
||||
|
||||
return tri;
|
||||
}
|
||||
|
||||
// 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::RigidBody& body ) {
|
||||
auto tri = ::fetchTriangle( mesh, triID );
|
||||
if ( body.collider.type == pod::ShapeType::MESH ) {
|
||||
if ( body.transform ) {
|
||||
for ( auto i = 0; i < 3; ++i ) {
|
||||
tri.points[i] = uf::transform::apply(*body.transform, tri.points[i]);
|
||||
tri.normals[i] = uf::quaternion::rotate(body.transform->orientation, tri.normals[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
#if REORIENT_NORMALS_ON_FETCH
|
||||
auto triCenter = (tri.points[0] + tri.points[1] + tri.points[2]) / 3.0f;
|
||||
auto delta = body.transform->position - triCenter;
|
||||
for ( auto i = 0; i < 3; ++i ) {
|
||||
if ( uf::vector::dot(tri.normals[i], delta) < 0.0f ) tri.normals[i] = -tri.normals[i];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return tri;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
namespace {
|
||||
bool meshAabb( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( MESH, AABB );
|
||||
|
||||
const auto& mesh = a;
|
||||
const auto& aabb = b;
|
||||
|
||||
const auto& meshData = *mesh.collider.u.mesh.mesh;
|
||||
const auto& bvh = *mesh.collider.u.mesh.bvh;
|
||||
|
||||
auto bounds = ::transformAabbToLocal( aabb.bounds, *mesh.transform );
|
||||
|
||||
uf::stl::vector<int> candidates;
|
||||
::queryBVH(bvh, bounds, candidates);
|
||||
|
||||
bool hit = false;
|
||||
for ( auto triID : candidates ) {
|
||||
auto tri = ::fetchTriangle( meshData, triID, aabb );
|
||||
auto closestTri = ::closestPointOnTriangle( ::aabbCenter( bounds ), tri );
|
||||
auto closestAabb = ::closestPointOnAABB( closestTri, bounds );
|
||||
|
||||
if ( !uf::vector::isValid( closestTri ) ) {
|
||||
//UF_MSG_DEBUG("tri #{}={}, {}, {}", triID, uf::vector::toString( tri.points[0] ), uf::vector::toString( tri.points[1] ), uf::vector::toString( tri.points[2] ));
|
||||
//UF_MSG_DEBUG("closestTri={}, closestAabb={}", uf::vector::toString( closestTri ), uf::vector::toString( closestAabb ));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto delta = closestAabb - closestTri; // direction between points
|
||||
float dist2 = uf::vector::magnitude( delta );
|
||||
float tolerance = 1.0e-3; // std::max( 1.0e-3f, uf::vector::magnitude(aabb.velocity) * manifold.dt * 0.5f );
|
||||
if ( dist2 >= tolerance ) continue;
|
||||
float dist = std::sqrt( dist2 );
|
||||
|
||||
auto contact = closestTri; // ( closestAabb + closestTri ) * 0.5f; // center of points
|
||||
auto normal = ::normalizeDelta( delta, dist, eps ); // uf::vector::normalize( ::interpolateWithBarycentric( tri.normals, ::computeBarycentric( contact, tri ) ));
|
||||
float penetration = tolerance - dist;
|
||||
|
||||
#if REORIENT_NORMALS_ON_CONTACT
|
||||
if ( uf::vector::dot(normal, delta) < 0.0f ) normal = -normal;
|
||||
#endif
|
||||
|
||||
contact = uf::transform::apply( *mesh.transform, contact );
|
||||
normal = uf::quaternion::rotate( mesh.transform->orientation, normal );
|
||||
|
||||
manifold.points.emplace_back(pod::Contact{ contact, normal, penetration });
|
||||
hit = true;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
bool meshSphere( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES(MESH, SPHERE);
|
||||
|
||||
const auto& mesh = a;
|
||||
const auto& sphere = b;
|
||||
|
||||
const auto& meshData = *mesh.collider.u.mesh.mesh;
|
||||
const auto& bvh = *mesh.collider.u.mesh.bvh;
|
||||
|
||||
auto bounds = ::transformAabbToLocal( ::computeAABB( sphere ), *mesh.transform );
|
||||
|
||||
auto center = ::aabbCenter( bounds );
|
||||
float r = sphere.collider.u.sphere.radius;
|
||||
|
||||
uf::stl::vector<int> candidates;
|
||||
::queryBVH(bvh, bounds, candidates);
|
||||
|
||||
bool hit = false;
|
||||
for ( auto triID : candidates ) {
|
||||
auto tri = ::fetchTriangle( meshData, triID, sphere );
|
||||
auto closest = ::closestPointOnTriangle( ::aabbCenter(bounds), tri );
|
||||
|
||||
if ( !uf::vector::isValid( closest ) ) {
|
||||
//UF_MSG_DEBUG("tri #{}={}, {}, {}", triID, uf::vector::toString( tri.points[0] ), uf::vector::toString( tri.points[1] ), uf::vector::toString( tri.points[2] ));
|
||||
//UF_MSG_DEBUG("closest={}", uf::vector::toString( closest ));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto delta = center - closest;
|
||||
float dist2 = uf::vector::magnitude(delta);
|
||||
if ( dist2 > r * r ) continue;
|
||||
float dist = std::sqrt( dist2 );
|
||||
|
||||
auto contact = closest;
|
||||
auto normal = ::normalizeDelta( delta, dist, eps );
|
||||
float penetration = r - dist;
|
||||
|
||||
#if REORIENT_NORMALS_ON_CONTACT
|
||||
if ( uf::vector::dot(normal, delta) < 0.0f ) normal = -normal;
|
||||
#endif
|
||||
|
||||
contact = uf::transform::apply(*mesh.transform, contact);
|
||||
normal = uf::quaternion::rotate(mesh.transform->orientation, normal);
|
||||
|
||||
manifold.points.emplace_back(pod::Contact{ contact, normal, penetration });
|
||||
hit = true;
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
||||
// to-do
|
||||
bool meshPlane( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES(MESH, PLANE);
|
||||
return false;
|
||||
}
|
||||
bool meshCapsule( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES(MESH, CAPSULE);
|
||||
|
||||
const auto& mesh = a;
|
||||
const auto& capsule = b;
|
||||
|
||||
const auto& meshData = *mesh.collider.u.mesh.mesh;
|
||||
const auto& bvh = *mesh.collider.u.mesh.bvh;
|
||||
|
||||
// capsule line segment in world space
|
||||
auto [ p1, p2 ] = ::getCapsuleSegment(b);
|
||||
float r = b.collider.u.capsule.radius;
|
||||
|
||||
auto bounds = ::transformAabbToLocal( ::computeSegmentAABB(p1, p2, r), *a.transform );
|
||||
|
||||
uf::stl::vector<int> candidates;
|
||||
::queryBVH(bvh, bounds, candidates);
|
||||
|
||||
// for some reason (the segment points), it's just easier to transform the triangle to world space, than compare in local then transform the contact to world
|
||||
bool hit = false;
|
||||
for ( auto triID : candidates ) {
|
||||
auto tri = ::fetchTriangle( meshData, triID, mesh );
|
||||
|
||||
pod::Vector3f closestSeg, closestTri;
|
||||
float dist2 = ::segmentTriangleDistanceSq( p1, p2, tri, closestSeg, closestTri );
|
||||
|
||||
if ( !uf::vector::isValid( closestTri ) ) {
|
||||
//UF_MSG_DEBUG("tri #{}={}, {}, {}", triID, uf::vector::toString( tri.points[0] ), uf::vector::toString( tri.points[1] ), uf::vector::toString( tri.points[2] ));
|
||||
//UF_MSG_DEBUG("closestTri={}", uf::vector::toString( closestTri ));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( dist2 > r * r ) continue;
|
||||
float dist = std::sqrt( dist2 );
|
||||
auto delta = ( closestSeg - closestTri );
|
||||
|
||||
auto contact = closestTri; // ( closestSeg + closestTri ) * 0.5f; // center of points
|
||||
auto normal = ::normalizeDelta( delta, dist, eps ); // uf::vector::normalize( ::interpolateWithBarycentric( tri.normals, ::computeBarycentric( contact, tri ) ));
|
||||
float penetration = r - dist;
|
||||
|
||||
#if REORIENT_NORMALS_ON_CONTACT
|
||||
if ( uf::vector::dot(normal, delta) < 0.0f ) normal = -normal;
|
||||
#endif
|
||||
|
||||
manifold.points.emplace_back(pod::Contact{ contact, normal, penetration });
|
||||
hit = true;
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
||||
// to-do
|
||||
bool meshMesh( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( MESH, MESH );
|
||||
|
||||
auto& bodyA = a;
|
||||
auto& bvhA = *a.collider.u.mesh.bvh;
|
||||
auto& meshA = *a.collider.u.mesh.mesh;
|
||||
|
||||
auto& bodyB = b;
|
||||
auto& meshB = *b.collider.u.mesh.mesh;
|
||||
auto& bvhB = *b.collider.u.mesh.bvh;
|
||||
|
||||
pod::BVH::pair_t pairs;
|
||||
::traverseNodePair(bvhA, 0, bvhB, 0, pairs);
|
||||
|
||||
for (auto [idA, idB] : pairs) {
|
||||
auto tA = ::fetchTriangle( meshA, idA, bodyA );
|
||||
auto tB = ::fetchTriangle( meshB, idB, bodyB );
|
||||
|
||||
// narrowphase tri-tri overlap: SAT
|
||||
if ( ::triangleTriangleIntersect( tA, tB ) ) {
|
||||
pod::Vector3f contact = ( tA.points[0] + tB.points[0] )*0.5f;
|
||||
pod::Vector3f n = uf::vector::normalize( uf::vector::cross( tA.points[1] - tA.points[0], tA.points[2] - tA.points[0] ) );
|
||||
manifold.points.emplace_back(pod::Contact{ contact, n, 0.001f });
|
||||
}
|
||||
}
|
||||
return !manifold.points.empty();
|
||||
}
|
||||
}
|
||||
57
engine/src/utils/math/physics/plane.inl
Normal file
57
engine/src/utils/math/physics/plane.inl
Normal file
@ -0,0 +1,57 @@
|
||||
namespace {
|
||||
bool planeAabb( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( PLANE, AABB );
|
||||
|
||||
const auto& plane = a;
|
||||
const auto& aabb = b;
|
||||
|
||||
auto normal = uf::vector::normalize( plane.collider.u.plane.normal );
|
||||
float offset = plane.collider.u.plane.offset;
|
||||
|
||||
auto center = (aabb.bounds.min + aabb.bounds.max) * 0.5f; // center
|
||||
auto extent = (aabb.bounds.max - aabb.bounds.min) * 0.5f; // half extents
|
||||
float r = fabs(extent.x * normal.x) + fabs(extent.y * normal.y) + fabs(extent.z * normal.z); // effective projection radius of box onto plane normal
|
||||
|
||||
float dist = uf::vector::dot( normal, center ) - offset;
|
||||
if ( dist > r ) return false;
|
||||
|
||||
pod::Vector3f contact = center - normal * dist;
|
||||
float penetration = r - dist;
|
||||
|
||||
manifold.points.emplace_back(pod::Contact{ contact, normal, penetration });
|
||||
return true;
|
||||
}
|
||||
bool planeSphere(const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps) {
|
||||
ASSERT_COLLIDER_TYPES(PLANE, SPHERE);
|
||||
|
||||
const auto& plane = a;
|
||||
const auto& sphere = b;
|
||||
|
||||
auto& normal = plane.collider.u.plane.normal;
|
||||
float offset = plane.collider.u.plane.offset;
|
||||
|
||||
auto center = sphere.transform->position;
|
||||
float r = sphere.collider.u.sphere.radius;
|
||||
|
||||
float dist = uf::vector::dot( normal, center ) - offset;
|
||||
if ( dist > r ) return false;
|
||||
|
||||
auto contact = center - normal * dist - normal * r;
|
||||
float penetration = r - dist;
|
||||
|
||||
manifold.points.emplace_back(pod::Contact{ contact, normal, penetration });
|
||||
return true;
|
||||
}
|
||||
bool planePlane( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( PLANE, PLANE );
|
||||
return false;
|
||||
}
|
||||
bool planeCapsule( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( PLANE, CAPSULE );
|
||||
return capsulePlane( b, a, manifold, eps );
|
||||
}
|
||||
bool planeMesh( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( PLANE, MESH );
|
||||
return meshPlane( b, a, manifold, eps );
|
||||
}
|
||||
}
|
||||
237
engine/src/utils/math/physics/ray.inl
Normal file
237
engine/src/utils/math/physics/ray.inl
Normal file
@ -0,0 +1,237 @@
|
||||
namespace {
|
||||
bool rayTriangleIntersect( const pod::Ray& ray, const pod::Vector3f tri[3], float& t, float& u, float& v, float eps = EPS(1.0e-6f) ) {
|
||||
auto edge1 = tri[1] - tri[0];
|
||||
auto edge2 = tri[2] - tri[0];
|
||||
auto h = uf::vector::cross( ray.direction, edge2 );
|
||||
float a = uf::vector::dot( edge1, h );
|
||||
|
||||
if ( fabs(a) < eps ) return false;
|
||||
|
||||
float f = 1.0f / a;
|
||||
auto s = ray.origin - tri[0];
|
||||
u = f * uf::vector::dot( s, h );
|
||||
if ( u < 0.0f || u > 1.0f ) return false;
|
||||
|
||||
auto q = uf::vector::cross( s, edge1 );
|
||||
v = f * uf::vector::dot( ray.direction, q );
|
||||
if (v < 0.0f || u + v > 1.0f) return false;
|
||||
|
||||
t = f * uf::vector::dot( edge2, q );
|
||||
return ( t > eps );
|
||||
}
|
||||
|
||||
bool rayAabbIntersect( const pod::Ray& ray, const pod::AABB& box, float& tMin, float& tMax, float eps = EPS(1.0e-6f) ) {
|
||||
tMin = 0.0f;
|
||||
tMax = FLT_MAX;
|
||||
|
||||
for ( auto i = 0; i < 3; ++i ) {
|
||||
float minB = box.min[i];
|
||||
float maxB = box.max[i];
|
||||
|
||||
// inflate degenerate slab
|
||||
if ( fabs(maxB - minB) < 1e-6f) {
|
||||
maxB += 1e-6f;
|
||||
minB -= 1e-6f;
|
||||
}
|
||||
|
||||
float invD = 1.0f / ray.direction[i];
|
||||
float t0 = ( minB - ray.origin[i] ) * invD;
|
||||
float t1 = ( maxB - ray.origin[i] ) * invD;
|
||||
|
||||
if ( invD < 0.0f ) std::swap(t0, t1);
|
||||
|
||||
tMin = std::max(tMin, t0);
|
||||
tMax = std::min(tMax, t1);
|
||||
|
||||
if ( tMax <= tMin ) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rayAabb( const pod::Ray& ray, const pod::RigidBody& body, pod::RayQuery& rayHit ) {
|
||||
float tMin = 0.0f;
|
||||
float tMax = FLT_MAX;
|
||||
|
||||
if ( !::rayAabbIntersect( ray, body.bounds, tMin, tMax ) ) return false;
|
||||
|
||||
if ( tMin < rayHit.contact.penetration ) {
|
||||
rayHit.hit = true;
|
||||
rayHit.body = &body;
|
||||
rayHit.contact.point = ray.origin + ray.direction * tMin;
|
||||
rayHit.contact.penetration = tMin;
|
||||
|
||||
auto local = rayHit.contact.point - (body.bounds.min + body.bounds.max) * 0.5f;
|
||||
auto extents = (body.bounds.max - body.bounds.min) * 0.5f;
|
||||
auto absLocal = pod::Vector3f{fabs(local.x), fabs(local.y), fabs(local.z)};
|
||||
|
||||
if ( absLocal.x > absLocal.y && absLocal.x > absLocal.z ) {
|
||||
rayHit.contact.normal = { (local.x > 0 ? 1.0f : -1.0f), 0, 0 };
|
||||
} else if ( absLocal.y > absLocal.z ) {
|
||||
rayHit.contact.normal = { 0, (local.y > 0 ? 1.0f : -1.0f), 0 };
|
||||
} else {
|
||||
rayHit.contact.normal = { 0, 0, (local.z > 0 ? 1.0f : -1.0f) };
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool raySphere( const pod::Ray& ray, const pod::RigidBody& body, pod::RayQuery& rayHit ) {
|
||||
auto center = body.transform->position;
|
||||
float r = body.collider.u.sphere.radius;
|
||||
|
||||
auto oc = ray.origin - center;
|
||||
// ray starts inside
|
||||
if ( uf::vector::magnitude( oc ) < r * r ) {
|
||||
rayHit.hit = true;
|
||||
rayHit.body = &body;
|
||||
rayHit.contact.point = ray.origin;
|
||||
rayHit.contact.normal = uf::vector::normalize(ray.origin - center);
|
||||
rayHit.contact.penetration = 0.0f;
|
||||
return true;
|
||||
}
|
||||
|
||||
float a = uf::vector::dot( ray.direction, ray.direction );
|
||||
float b = 2.0f * uf::vector::dot( oc, ray.direction );
|
||||
float c = uf::vector::dot( oc, oc ) - r * r;
|
||||
|
||||
float discriminant = b*b - 4*a*c;
|
||||
if ( discriminant < 0.0f ) return false;
|
||||
|
||||
float sqrtDisc = std::sqrt(discriminant);
|
||||
float t0 = (-b - sqrtDisc) / (2*a);
|
||||
float t1 = (-b + sqrtDisc) / (2*a);
|
||||
|
||||
// take nearest valid hit
|
||||
float t = t0;
|
||||
if ( t < 0.0f ) t = t1;
|
||||
if ( t < 0.0f ) return false;
|
||||
|
||||
if ( t >= rayHit.contact.penetration ) return false; // further than current closest
|
||||
|
||||
// record hit
|
||||
rayHit.hit = true;
|
||||
rayHit.body = &body;
|
||||
rayHit.contact.point = ray.origin + ray.direction * t;
|
||||
rayHit.contact.normal = uf::vector::normalize( rayHit.contact.point - center );
|
||||
rayHit.contact.penetration = t;
|
||||
return true;
|
||||
}
|
||||
bool rayPlane( const pod::Ray& ray, const pod::RigidBody& body, pod::RayQuery& rayHit, float eps = EPS(1e-6f) ) {
|
||||
auto& normal = body.collider.u.plane.normal;
|
||||
float offset = body.collider.u.plane.offset;
|
||||
|
||||
float denom = uf::vector::dot( normal, ray.direction );
|
||||
if ( fabs(denom) < eps ) return false; // parallel
|
||||
|
||||
float t = (offset - uf::vector::dot( normal, ray.origin )) / denom;
|
||||
if ( t < 0.0f ) return false; // behind ray start
|
||||
if ( t >= rayHit.contact.penetration ) return false; // existing hit is closer
|
||||
|
||||
// record hit
|
||||
rayHit.hit = true;
|
||||
rayHit.body = &body;
|
||||
rayHit.contact.point = ray.origin + ray.direction * t;
|
||||
rayHit.contact.normal = normal * ( denom < 0.0f ? 1.0f : -1.0f ); // face ray
|
||||
rayHit.contact.penetration = t;
|
||||
return true;
|
||||
}
|
||||
bool rayCapsule( const pod::Ray& ray, const pod::RigidBody& body, pod::RayQuery& rayHit ) {
|
||||
auto [ p1, p2 ] = ::getCapsuleSegment( body );
|
||||
float r = body.collider.u.capsule.radius;
|
||||
|
||||
auto d = p2 - p1; // segment direction
|
||||
auto m = ray.origin - p1;
|
||||
auto n = ray.direction;
|
||||
|
||||
float md = uf::vector::dot(m, d);
|
||||
float nd = uf::vector::dot(n, d);
|
||||
float dd = uf::vector::dot(d, d);
|
||||
|
||||
// coeffs for intersection with infinite cylinder
|
||||
float a = dd - nd*nd;
|
||||
float k = uf::vector::dot(m, m) - r*r;
|
||||
float c = dd - md*md;
|
||||
|
||||
float b = dd * uf::vector::dot(m,n) - nd*md;
|
||||
float discr = b*b - a*c;
|
||||
if ( discr < 0.0f ) return false;
|
||||
|
||||
float t = (-b - std::sqrt(discr)) / a; // nearer hit
|
||||
|
||||
if ( t < 0 || t > rayHit.contact.penetration ) return false; // clipped by range
|
||||
|
||||
// check hit actually lies within segment caps
|
||||
float y = md + t*nd;
|
||||
if ( y < 0.0f || y > dd ) {
|
||||
// check against spherical caps instead
|
||||
float tSphere = FLT_MAX;
|
||||
bool hit = false;
|
||||
|
||||
for ( auto center : {p1, p2} ) {
|
||||
auto oc = ray.origin - center;
|
||||
float b = uf::vector::dot(oc, n);
|
||||
float c = uf::vector::dot(oc, oc) - r*r;
|
||||
float disc = b*b - c;
|
||||
if ( disc >= 0 ) {
|
||||
float tTmp = -b - std::sqrt(disc);
|
||||
if ( tTmp >= 0 && tTmp < tSphere ) {
|
||||
tSphere = tTmp;
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !hit || tSphere > rayHit.contact.penetration ) return false;
|
||||
|
||||
t = tSphere;
|
||||
}
|
||||
|
||||
// register hit
|
||||
if ( t < rayHit.contact.penetration ) {
|
||||
rayHit.hit = true;
|
||||
rayHit.body = &body;
|
||||
rayHit.contact.point = ray.origin + n * t;
|
||||
rayHit.contact.penetration = t;
|
||||
|
||||
// normal: from capsule axis to hit point
|
||||
auto closest = ::closestPointOnSegment( rayHit.contact.point, p1, p2 );
|
||||
auto normal = uf::vector::normalize( rayHit.contact.point - closest );
|
||||
rayHit.contact.normal = normal;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rayMesh( const pod::Ray& ray, const pod::RigidBody& body, pod::RayQuery& rayHit ) {
|
||||
const uf::Mesh& meshData = *body.collider.u.mesh.mesh;
|
||||
const pod::BVH& bvh = *body.collider.u.mesh.bvh;
|
||||
|
||||
pod::Ray localRay;
|
||||
localRay.origin = uf::transform::applyInverse( *body.transform, ray.origin );
|
||||
localRay.direction = uf::quaternion::rotate( uf::quaternion::inverse(body.transform->orientation), ray.direction );
|
||||
|
||||
uf::stl::vector<int> indices;
|
||||
::queryBVH( bvh, localRay, indices );
|
||||
|
||||
for ( auto triID : indices ) {
|
||||
auto tri = ::fetchTriangle( meshData, triID );
|
||||
|
||||
float t, u, v;
|
||||
if ( !::rayTriangleIntersect( localRay, tri.points, t, u, v ) ) continue;
|
||||
if ( t >= rayHit.contact.penetration ) continue;
|
||||
|
||||
auto l = localRay.origin + localRay.direction * t;
|
||||
auto bary = ::computeBarycentric(l, tri.points[0], tri.points[1], tri.points[2]);
|
||||
auto n = uf::vector::normalize(::interpolateWithBarycentric(bary, tri.normals));
|
||||
|
||||
// push back to world
|
||||
auto p = uf::transform::apply(*body.transform, l);
|
||||
n = uf::quaternion::rotate(body.transform->orientation, n);
|
||||
|
||||
rayHit.hit = true;
|
||||
rayHit.body = &body;
|
||||
rayHit.contact.point = p;
|
||||
rayHit.contact.normal = n;
|
||||
rayHit.contact.penetration = t;
|
||||
}
|
||||
|
||||
return rayHit.hit;
|
||||
}
|
||||
}
|
||||
66
engine/src/utils/math/physics/sphere.inl
Normal file
66
engine/src/utils/math/physics/sphere.inl
Normal file
@ -0,0 +1,66 @@
|
||||
namespace {
|
||||
bool sphereSphere( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( SPHERE, SPHERE );
|
||||
|
||||
auto delta = b.transform->position - a.transform->position;
|
||||
float r = a.collider.u.sphere.radius + b.collider.u.sphere.radius;
|
||||
|
||||
float dist2 = uf::vector::dot(delta, delta);
|
||||
if ( dist2 > r * r ) return false;
|
||||
float dist = std::sqrt( dist2 );
|
||||
|
||||
float penetration = r - dist;
|
||||
auto normal = ::normalizeDelta( delta, dist, eps );
|
||||
auto contact = a.transform->position + (normal * a.collider.u.sphere.radius);
|
||||
|
||||
manifold.points.emplace_back(pod::Contact{ contact, normal, penetration });
|
||||
return true;
|
||||
}
|
||||
bool sphereAabb( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( SPHERE, AABB );
|
||||
|
||||
const auto& sphere = a;
|
||||
const auto& aabb = b;
|
||||
|
||||
auto& center = sphere.transform->position;
|
||||
float r = sphere.collider.u.sphere.radius;
|
||||
|
||||
auto& bounds = aabb.bounds;
|
||||
auto closest = pod::Vector3f{
|
||||
std::max(bounds.min.x, std::min(center.x, bounds.max.x)),
|
||||
std::max(bounds.min.y, std::min(center.y, bounds.max.y)),
|
||||
std::max(bounds.min.z, std::min(center.z, bounds.max.z)),
|
||||
};
|
||||
|
||||
auto delta = center - closest;
|
||||
float dist2 = uf::vector::dot(delta, delta);
|
||||
if ( dist2 > r * r ) return false;
|
||||
float dist = std::sqrt( dist2 );
|
||||
|
||||
float penetration = r - dist;
|
||||
auto normal = ::normalizeDelta( delta, dist, eps );
|
||||
auto contact = center - normal * r;
|
||||
|
||||
#if 0
|
||||
pod::Vector3f dir = center - ::aabbCenter( bounds );
|
||||
if (std::fabs(dir.x) > std::fabs(dir.y) && std::fabs(dir.x) > std::fabs(dir.z)) normal = { (dir.x > 0 ? 1 : -1), 0, 0 };
|
||||
else if (std::fabs(dir.y) > std::fabs(dir.z)) normal = { 0, (dir.y > 0 ? 1 : -1), 0 };
|
||||
else normal = { 0, 0, (dir.z > 0 ? 1 : -1) };
|
||||
#endif
|
||||
|
||||
manifold.points.emplace_back(pod::Contact{ contact, normal, penetration });
|
||||
return true;
|
||||
}
|
||||
bool spherePlane( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( SPHERE, PLANE );
|
||||
return planeSphere( b, a, manifold, eps );
|
||||
}
|
||||
bool sphereCapsule( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( SPHERE, CAPSULE );
|
||||
return capsuleSphere( b, a, manifold, eps );
|
||||
}
|
||||
bool sphereMesh( const pod::RigidBody& a, const pod::RigidBody& b, pod::Manifold& manifold, float eps ) {
|
||||
ASSERT_COLLIDER_TYPES( SPHERE, MESH );
|
||||
return meshSphere( b, a, manifold, eps );
|
||||
}
|
||||
}
|
||||
815
engine/src/utils/math/physics/tests.inl
Normal file
815
engine/src/utils/math/physics/tests.inl
Normal file
@ -0,0 +1,815 @@
|
||||
#include <uf/utils/tests/tests.h>
|
||||
|
||||
#define FRAMERATE 60
|
||||
#define INV_FRAMERATE 1.0f / FRAMERATE
|
||||
|
||||
#define PHYSICS_STEP(time) for ( int i = 0; i < time * FRAMERATE; i++ ) uf::physics::impl::step(world, INV_FRAMERATE);
|
||||
|
||||
namespace {
|
||||
uf::Mesh generateMesh( float size = 1 ) {
|
||||
uf::Mesh mesh;
|
||||
mesh.bind<pod::Vertex_3F2F, uint16_t>();
|
||||
mesh.insertVertices<pod::Vertex_3F2F>({
|
||||
{ pod::Vector3f{-1.0f * size, 0.0f, 1.0f * size}, pod::Vector2f{0.0f, 0.0f} },
|
||||
{ pod::Vector3f{-1.0f * size, 0.0f, -1.0f * size}, pod::Vector2f{0.0f, 1.0f} },
|
||||
{ pod::Vector3f{ 1.0f * size, 0.0f, -1.0f * size}, pod::Vector2f{1.0f, 1.0f} },
|
||||
|
||||
{ pod::Vector3f{ 1.0f * size, 0.0f, -1.0f * size}, pod::Vector2f{1.0f, 1.0f} },
|
||||
{ pod::Vector3f{ 1.0f * size, 0.0f, 1.0f * size}, pod::Vector2f{1.0f, 0.0f} },
|
||||
{ pod::Vector3f{-1.0f * size, 0.0f, 1.0f * size}, pod::Vector2f{0.0f, 0.0f} },
|
||||
});
|
||||
mesh.insertIndices<uint16_t>({
|
||||
//0, 1, 2, 3, 4, 5
|
||||
0, 2, 1, 3, 5, 4
|
||||
});
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
|
||||
// list of unit tests to "standardly" verify the system works, but honestly this is a mess
|
||||
|
||||
TEST(SphereSphere_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& bodyA = uf::physics::impl::create(world, objA, pod::Sphere{1.0f}, 1.0f);
|
||||
auto& bodyB = uf::physics::impl::create(world, objB, pod::Sphere{1.0f}, 1.0f);
|
||||
|
||||
bodyA.transform->position = {0,0,0};
|
||||
bodyB.transform->position = {1.5f,0,0}; // closer than sum of radii (2.0)
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = sphereSphere(bodyA, bodyB, m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
EXPECT_NEAR(m.points[0].penetration, 0.5f, EPS(1e-4f));
|
||||
})
|
||||
|
||||
TEST(AabbAabb_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
pod::AABB box = { {-1,-1,-1}, {1,1,1} };
|
||||
|
||||
auto& bodyA = uf::physics::impl::create(world, objA, box, 1.0f);
|
||||
auto& bodyB = uf::physics::impl::create(world, objB, box, 1.0f);
|
||||
|
||||
bodyA.transform->position = {0,0,0};
|
||||
bodyB.transform->position = {1.5f,0,0}; // overlap in x-axis
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = aabbAabb(bodyA, bodyB, m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
})
|
||||
|
||||
TEST(RaySphere_Hit, {
|
||||
pod::World world;
|
||||
uf::Object obj;
|
||||
auto& body = uf::physics::impl::create(world, obj, pod::Sphere{1.0f}, 1.0f);
|
||||
body.transform->position = {0,0,0};
|
||||
|
||||
pod::Ray ray{ {0,0,-5}, uf::vector::normalize(pod::Vector3f{0,0,1}) };
|
||||
pod::RayQuery hit = uf::physics::impl::rayCast(ray, world, 100.0f);
|
||||
|
||||
EXPECT_TRUE(hit.hit);
|
||||
EXPECT_NEAR(hit.contact.penetration, 4.0f, EPS(1e-4f));
|
||||
})
|
||||
|
||||
TEST(SphereSphere_NoCollision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& bodyA = uf::physics::impl::create(world, objA, pod::Sphere{1.0f}, 1.0f);
|
||||
auto& bodyB = uf::physics::impl::create(world, objB, pod::Sphere{1.0f}, 1.0f);
|
||||
|
||||
bodyA.transform->position = {0,0,0};
|
||||
bodyB.transform->position = {5.0f,0,0}; // too far
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = sphereSphere(bodyA, bodyB, m);
|
||||
EXPECT_TRUE(!collided);
|
||||
})
|
||||
|
||||
TEST(SphereAabb_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objSphere, objBox;
|
||||
|
||||
auto& sphere = uf::physics::impl::create(world, objSphere, pod::Sphere{1.0f}, 1.0f);
|
||||
auto& box = uf::physics::impl::create(world, objBox, pod::AABB{{-1,-1,-1}, {1,1,1}}, 1.0f);
|
||||
|
||||
sphere.transform->position = {0.5f, 0.0f, 0.0f}; // overlapping inside box
|
||||
box.transform->position = {0,0,0};
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = sphereAabb(sphere, box, m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
EXPECT_TRUE(m.points[0].penetration > 0.0f);
|
||||
})
|
||||
|
||||
TEST(SpherePlane_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objSphere, objPlane;
|
||||
|
||||
auto& sphere = uf::physics::impl::create(world, objSphere, pod::Sphere{1.0f}, 1.0f);
|
||||
auto& plane = uf::physics::impl::create(world, objPlane, pod::Plane{{0,1,0},0.0f}, 0.0f);
|
||||
|
||||
// Place sphere so it's intersecting the plane
|
||||
sphere.transform->position = {0,0.5f,0};
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = planeSphere(plane, sphere, m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
EXPECT_NEAR(m.points[0].penetration, 0.5f, EPS(1e-4f));
|
||||
})
|
||||
|
||||
TEST(SpherePlane_NoCollision, {
|
||||
pod::World world;
|
||||
uf::Object objSphere, objPlane;
|
||||
|
||||
auto& sphere = uf::physics::impl::create(world, objSphere, pod::Sphere{1.0f}, 1.0f);
|
||||
auto& plane = uf::physics::impl::create(world, objPlane, pod::Plane{{0,1,0},0.0f}, 0.0f);
|
||||
|
||||
sphere.transform->position = {0, 5.0f, 0}; // clearly above
|
||||
pod::Manifold m;
|
||||
bool collided = planeSphere(plane, sphere, m);
|
||||
EXPECT_TRUE(!collided);
|
||||
})
|
||||
|
||||
TEST(CapsuleCapsule_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& capA = uf::physics::impl::create(world, objA, pod::Capsule{0.5f, 1.0f}, 1.0f);
|
||||
auto& capB = uf::physics::impl::create(world, objB, pod::Capsule{0.5f, 1.0f}, 1.0f);
|
||||
|
||||
capA.transform->position = {0,0,0};
|
||||
capB.transform->position = {0.8f,0,0}; // slight overlap
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = capsuleCapsule(capA, capB, m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
EXPECT_TRUE(m.points[0].penetration > 0.0f);
|
||||
})
|
||||
|
||||
TEST(RayAabb_Miss, {
|
||||
pod::World world;
|
||||
uf::Object obj;
|
||||
auto& box = uf::physics::impl::create(world, obj, pod::AABB{{-1,-1,-1},{1,1,1}}, 1.0f);
|
||||
box.transform->position = {0,0,0};
|
||||
|
||||
pod::Ray ray{{5,5,5}, uf::vector::normalize(pod::Vector3f{1,0,0})};
|
||||
auto hit = uf::physics::impl::rayCast(ray, world, 100.0f);
|
||||
EXPECT_TRUE(!hit.hit);
|
||||
})
|
||||
|
||||
// GJK shouldn't be used for sphere sphere
|
||||
#if 0
|
||||
TEST(Gjk_SphereSphereOverlap, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& a = uf::physics::impl::create(world, objA, pod::Sphere{1.0f}, 1.0f);
|
||||
auto& b = uf::physics::impl::create(world, objB, pod::Sphere{1.0f}, 1.0f);
|
||||
|
||||
a.transform->position = {0,0,0};
|
||||
b.transform->position = {1.5f,0,0};
|
||||
|
||||
pod::Simplex simplex;
|
||||
bool inside = gjk(a, b, simplex);
|
||||
EXPECT_TRUE(inside);
|
||||
auto contact = epa(a, b, simplex);
|
||||
EXPECT_TRUE(contact.penetration > 0.0f);
|
||||
})
|
||||
#endif
|
||||
|
||||
TEST(PhysicsStep_Gravity, {
|
||||
pod::World world;
|
||||
uf::Object obj;
|
||||
auto& body = uf::physics::impl::create(world, obj, pod::Sphere{1.0f}, 1.0f);
|
||||
body.transform->position = {0, 10, 0};
|
||||
body.velocity = {0,0,0};
|
||||
|
||||
PHYSICS_STEP(1)
|
||||
|
||||
EXPECT_NEAR(body.transform->position.y, 10.0f - world.gravity.y, 0.05f);
|
||||
})
|
||||
|
||||
TEST(PhysicsStep_SpherePlane_Bounce, {
|
||||
pod::World world;
|
||||
uf::Object objSphere, objPlane;
|
||||
|
||||
auto& sphere = uf::physics::impl::create(world, objSphere, pod::Sphere{1.0f}, 1.0f);
|
||||
auto& plane = uf::physics::impl::create(world, objPlane, pod::Plane{{0,1,0}, 0.0f}, 0.0f);
|
||||
|
||||
sphere.transform->position = {0, 2, 0};
|
||||
sphere.material.restitution = 1.0f;
|
||||
|
||||
PHYSICS_STEP(1)
|
||||
|
||||
// After bouncing, sphere should be near plane surface, not sinking below
|
||||
EXPECT_TRUE(sphere.transform->position.y >= 0.9f);
|
||||
EXPECT_TRUE(fabs(sphere.velocity.y) < 10.0f); // should have reversed sign at least once
|
||||
})
|
||||
|
||||
TEST(PhysicsStep_AabbStacking, {
|
||||
pod::World world;
|
||||
uf::Object bottomObj, fallingObj;
|
||||
|
||||
auto& bottom = uf::physics::impl::create(world, bottomObj, pod::AABB{{-1,-1,-1},{1,1,1}}, 0.0f);
|
||||
bottom.transform->position = {0,0,0};
|
||||
|
||||
auto& falling = uf::physics::impl::create(world, fallingObj, pod::AABB{{-1,-1,-1},{1,1,1}}, 1.0f);
|
||||
falling.transform->position = {0,5,0};
|
||||
|
||||
PHYSICS_STEP(5);
|
||||
|
||||
// After time, falling cube should rest on top of static one
|
||||
EXPECT_TRUE(falling.transform->position.y > 1.9f);
|
||||
EXPECT_TRUE(fabs(falling.velocity.y) < 0.1f);
|
||||
})
|
||||
|
||||
TEST(PhysicsStep_SphereSphere_HeadOn, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
|
||||
auto& A = uf::physics::impl::create(world, objA, pod::Sphere{1.0f}, 1.0f);
|
||||
auto& B = uf::physics::impl::create(world, objB, pod::Sphere{1.0f}, 1.0f);
|
||||
|
||||
A.transform->position = {-5,0,0};
|
||||
B.transform->position = { 5,0,0};
|
||||
A.velocity = { 5,0,0};
|
||||
B.velocity = {-5,0,0};
|
||||
|
||||
PHYSICS_STEP(5);
|
||||
|
||||
// Expect velocities swapped (perfect elastic bounce with equal masses)
|
||||
EXPECT_TRUE(A.velocity.x < 0.0f);
|
||||
EXPECT_TRUE(B.velocity.x > 0.0f);
|
||||
})
|
||||
|
||||
TEST(PhysicsStep_RaycastDynamic, {
|
||||
pod::World world;
|
||||
uf::Object obj;
|
||||
world.gravity = {};
|
||||
auto& body = uf::physics::impl::create(world, obj, pod::Sphere{1.0f}, 1.0f);
|
||||
body.transform->position = {0,0,0};
|
||||
body.velocity = {0,0,10};
|
||||
|
||||
PHYSICS_STEP(1);
|
||||
|
||||
pod::Ray ray{ {0,0,-5}, {0,0,1} };
|
||||
pod::RayQuery q = uf::physics::impl::rayCast(ray, world, 100.0f);
|
||||
EXPECT_TRUE(q.hit);
|
||||
EXPECT_TRUE(fabs(q.contact.point.z - 10.0f) <= 1.0f); // near where it moved
|
||||
})
|
||||
|
||||
TEST(SphereSphere_TouchingButNotOverlapping, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& a = uf::physics::impl::create(world, objA, pod::Sphere{1.0f}, 1.0f);
|
||||
auto& b = uf::physics::impl::create(world, objB, pod::Sphere{1.0f}, 1.0f);
|
||||
a.transform->position = {0,0,0};
|
||||
b.transform->position = {2.0f,0,0}; // exactly touching
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = sphereSphere(a, b, m);
|
||||
|
||||
EXPECT_TRUE(collided); // should count as a collision…
|
||||
EXPECT_NEAR(m.points[0].penetration, 0.0f, EPS(1e-6f));
|
||||
})
|
||||
|
||||
TEST(RaySphere_OriginInside, {
|
||||
pod::World world;
|
||||
uf::Object obj;
|
||||
auto& body = uf::physics::impl::create(world, obj, pod::Sphere{2.0f}, 1.0f);
|
||||
body.transform->position = {0,0,0};
|
||||
|
||||
pod::Ray ray{ {0,0,0}, {1,0,0} }; // starts inside
|
||||
auto q = uf::physics::impl::rayCast(ray, world, 100.0f);
|
||||
|
||||
EXPECT_TRUE(q.hit);
|
||||
EXPECT_NEAR(q.contact.penetration, 0.0f, EPS(1e-6f));
|
||||
})
|
||||
|
||||
TEST(PhysicsStep_StaticFriction_Holds, {
|
||||
pod::World world;
|
||||
uf::Object objSphere, objPlane;
|
||||
|
||||
auto& sphere = uf::physics::impl::create(world, objSphere, pod::Sphere{1.0f}, 1.0f);
|
||||
auto& plane = uf::physics::impl::create(world, objPlane, pod::Plane{{0,1,0}, 0.0f}, 0.0f);
|
||||
|
||||
world.gravity = {0,-9.81f,0};
|
||||
sphere.transform->position = {0,1.0f,0};
|
||||
sphere.material.staticFriction = 2.0f; // stronger grip to cover solver slop
|
||||
|
||||
// Apply smaller force (well below μ_s * N)
|
||||
uf::physics::impl::applyForce(sphere, {2,0,0});
|
||||
|
||||
PHYSICS_STEP(1);
|
||||
|
||||
EXPECT_NEAR(sphere.transform->position.x, 0.0f, 0.05f); // allow tiny error
|
||||
})
|
||||
|
||||
TEST(PhysicsStep_StaticFriction_Slips, {
|
||||
pod::World world;
|
||||
uf::Object objSphere, objPlane;
|
||||
|
||||
auto& sphere = uf::physics::impl::create(world, objSphere, pod::Sphere{1.0f}, 1.0f);
|
||||
auto& plane = uf::physics::impl::create(world, objPlane, pod::Plane{{0,1,0}, 0.0f}, 0.0f);
|
||||
|
||||
world.gravity = {0,-9.81f,0};
|
||||
sphere.transform->position = {0,1.0f,0};
|
||||
sphere.material.staticFriction = 1.0f;
|
||||
|
||||
uf::physics::impl::applyForce(sphere, {15,0,0}); // Above limit
|
||||
|
||||
PHYSICS_STEP(1);
|
||||
|
||||
EXPECT_TRUE(fabs(sphere.transform->position.x) > 0.1f); // It should slide
|
||||
})
|
||||
|
||||
TEST(CapsulePlane_Slope_StaticHold, {
|
||||
pod::World world;
|
||||
uf::Object objCap, objPlane;
|
||||
auto& cap = uf::physics::impl::create(world, objCap, pod::Capsule{0.5f,1.0f}, 1.0f);
|
||||
auto& plane = uf::physics::impl::create(world, objPlane, pod::Plane{{0,1,1},0.0f}, 0.0f);
|
||||
|
||||
// Place capsule on slope
|
||||
cap.transform->position = {0,2,0};
|
||||
cap.material.staticFriction = 2.0f; // More than tan(45) = 1
|
||||
cap.material.dynamicFriction = 1.0f;
|
||||
|
||||
PHYSICS_STEP(5);
|
||||
|
||||
EXPECT_NEAR(cap.transform->position.z, 0.0f, 0.2f); // Held in place
|
||||
EXPECT_NEAR(cap.velocity.z, 0.0f, 0.05f);
|
||||
})
|
||||
|
||||
TEST(CapsulePlane_Slope_Slip, {
|
||||
pod::World world;
|
||||
uf::Object objCap, objPlane;
|
||||
auto& cap = uf::physics::impl::create(world, objCap, pod::Capsule{0.5f,1.0f}, 1.0f);
|
||||
auto& plane = uf::physics::impl::create(world, objPlane, pod::Plane{{0,1,1},0.0f}, 0.0f);
|
||||
|
||||
cap.transform->position = {0,2,0};
|
||||
cap.material.staticFriction = 0.5f; // Less than tan(45) => must slip
|
||||
cap.material.dynamicFriction = 0.5f;
|
||||
|
||||
PHYSICS_STEP(5);
|
||||
|
||||
EXPECT_TRUE(fabs(cap.transform->position.z) > 1.0f); // Should have slid downhill
|
||||
})
|
||||
|
||||
TEST(CapsulePlane_RestingContact, {
|
||||
pod::World world;
|
||||
uf::Object objCap, objPlane;
|
||||
auto& cap = uf::physics::impl::create(world, objCap, pod::Capsule{0.5f, 1.0f}, 1.0f);
|
||||
auto& plane = uf::physics::impl::create(world, objPlane, pod::Plane{{0,1,0}, 0.0f}, 0.0f);
|
||||
|
||||
cap.transform->position = {0, 1.5f, 0}; // halfHeight=1, radius=0.5, so "foot" at y=0
|
||||
cap.velocity = {0,0,0};
|
||||
|
||||
PHYSICS_STEP(1);
|
||||
|
||||
// Capsule should rest on the floor at y=1.5
|
||||
EXPECT_NEAR(cap.transform->position.y, 1.5f, 0.05f);
|
||||
EXPECT_NEAR(cap.velocity.y, 0.0f, 0.05f); // no jitter
|
||||
})
|
||||
|
||||
TEST(CapsuleAabb_RestingContact, {
|
||||
pod::World world;
|
||||
uf::Object objCap, objBox;
|
||||
|
||||
auto& cap = uf::physics::impl::create(world, objCap, pod::Capsule{0.5f, 1.0f}, 1.0f);
|
||||
auto& floor = uf::physics::impl::create(world, objBox, pod::AABB{{-5, -1, -5},{5, 0, 5}}, 0.0f);
|
||||
|
||||
cap.transform->position = {0, 1.5f, 0};
|
||||
cap.velocity = {0,0,0};
|
||||
|
||||
PHYSICS_STEP(2);
|
||||
|
||||
EXPECT_NEAR(cap.transform->position.y, 1.5f, 0.05f);
|
||||
EXPECT_NEAR(cap.velocity.y, 0.0f, 0.05f);
|
||||
})
|
||||
|
||||
TEST(CapsulePlane_Settling, {
|
||||
pod::World world;
|
||||
uf::Object objCap, objPlane;
|
||||
|
||||
auto& cap = uf::physics::impl::create(world, objCap, pod::Capsule{0.5f, 1.0f}, 1.0f);
|
||||
auto& plane = uf::physics::impl::create(world, objPlane, pod::Plane{{0,1,0},0.0f}, 0.0f);
|
||||
|
||||
cap.transform->position = {0, 2.0f, 0}; // slightly above
|
||||
cap.velocity = {0,0,0};
|
||||
|
||||
PHYSICS_STEP(3);
|
||||
|
||||
EXPECT_NEAR(cap.transform->position.y, 1.5f, 0.05f);
|
||||
})
|
||||
|
||||
TEST(CapsulePlane_SlopeStaticFriction, {
|
||||
pod::World world;
|
||||
uf::Object objCap, objPlane;
|
||||
|
||||
auto& cap = uf::physics::impl::create(world, objCap, pod::Capsule{0.5f, 1.0f}, 1.0f);
|
||||
auto& slope = uf::physics::impl::create(world, objPlane, pod::Plane{{0,1,1},0.0f}, 0.0f); // 45° slope
|
||||
|
||||
cap.transform->position = {0, 3.0f, 0};
|
||||
cap.material.staticFriction = 2.0f;
|
||||
cap.material.dynamicFriction = 1.0f;
|
||||
|
||||
PHYSICS_STEP(4);
|
||||
|
||||
// Should not slide much if static friction is strong
|
||||
EXPECT_NEAR(cap.transform->position.z, 0.0f, 0.1f);
|
||||
})
|
||||
|
||||
TEST(CapsuleAabb_StepEdge, {
|
||||
pod::World world;
|
||||
uf::Object objCap, objBox;
|
||||
|
||||
auto& cap = uf::physics::impl::create(world, objCap, pod::Capsule{0.5f, 1.0f}, 1.0f);
|
||||
auto& floor = uf::physics::impl::create(world, objBox, pod::AABB{{0,-1,-5},{5,0,5}}, 0.0f);
|
||||
|
||||
cap.transform->position = {0.25f, 1.5f, 0}; // Capsule foot half on, half off
|
||||
|
||||
PHYSICS_STEP(4);
|
||||
|
||||
// Should not spaz out or fall through
|
||||
EXPECT_NEAR(cap.transform->position.y, 1.5f, 0.1f);
|
||||
})
|
||||
|
||||
TEST(Diagnostic_CapsuleGrounding, {
|
||||
pod::World world;
|
||||
uf::Object objCap, objFloor;
|
||||
|
||||
// Capsule: radius 0.5, half-height 1.0 (total height 2.0 + end caps)
|
||||
auto& cap = uf::physics::impl::create(world, objCap, pod::Capsule{0.5f, 1.0f}, 1.0f);
|
||||
|
||||
// Test toggle: try both AABB floors and Plane floors.
|
||||
bool usePlane = true;
|
||||
auto& floor = usePlane ? uf::physics::impl::create(world, objFloor, pod::Plane{{0,1,0}, 0.0f}, 0.0f) : uf::physics::impl::create(world, objFloor, pod::AABB{{-5,-1,-5},{5,0,5}}, 0.0f);
|
||||
|
||||
cap.transform->position = {0, 3, 0}; // start a little above floor
|
||||
cap.velocity = {0,0,0};
|
||||
|
||||
PHYSICS_STEP(2);
|
||||
|
||||
// Final resting state: should be near Y=1.5 (halfHeight + radius)
|
||||
EXPECT_NEAR(cap.transform->position.y, 1.5f, 0.1f);
|
||||
})
|
||||
|
||||
TEST(CapsulePlane_ContactNormal, {
|
||||
pod::World world;
|
||||
uf::Object objCap, objPlane;
|
||||
|
||||
auto& cap = uf::physics::impl::create(world, objCap, pod::Capsule{0.5f, 1.0f}, 1.0f);
|
||||
auto& plane = uf::physics::impl::create(world, objPlane, pod::Plane{{0,1,0},0.0f}, 0.0f);
|
||||
|
||||
cap.transform->position = {0,1.5f,0};
|
||||
pod::Manifold m;
|
||||
bool collided = capsulePlane(cap, plane, m);
|
||||
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
if ( !m.points.empty() ) {
|
||||
EXPECT_NEAR(m.points[0].normal.y, 1.0f, 1e-3f); // Normal should point UP
|
||||
} else {
|
||||
EXPECT_NEAR(1.0f, 0.0f, 0.0f);
|
||||
}
|
||||
})
|
||||
|
||||
TEST(AabbPlane_RestingNoSink, {
|
||||
pod::World world;
|
||||
uf::Object playerObj, groundObj;
|
||||
|
||||
auto& player = uf::physics::impl::create(world, playerObj, pod::AABB{{-0.5f,-1.0f,-0.5f},{0.5f,0.0f,0.5f}}, 1.0f);
|
||||
auto& ground = uf::physics::impl::create(world, groundObj, pod::Plane{{0,1,0},0}, 0.0f);
|
||||
|
||||
player.transform->position = {0, 2.0f, 0};
|
||||
|
||||
PHYSICS_STEP(5);
|
||||
|
||||
// Expect the player to remain on ground at y=0.0 without sinking further
|
||||
EXPECT_NEAR(player.transform->position.y, 0.0f, 0.05f);
|
||||
EXPECT_NEAR(player.velocity.y, 0.0f, 0.05f);
|
||||
})
|
||||
|
||||
TEST(CapsuleSphere_Collision, {
|
||||
pod::World world;
|
||||
uf::Object capObj, sphObj;
|
||||
auto& cap = uf::physics::impl::create(world, capObj, pod::Capsule{0.5f,1.0f}, 1.0f);
|
||||
auto& sph = uf::physics::impl::create(world, sphObj, pod::Sphere{0.5f}, 1.0f);
|
||||
|
||||
cap.transform->position = {0,0,0};
|
||||
sph.transform->position = {0,0.5f,0}; // overlap
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = capsuleSphere(cap, sph, m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
})
|
||||
|
||||
TEST(CapsuleSphere_NoCollision, {
|
||||
pod::World world;
|
||||
uf::Object capObj, sphObj;
|
||||
auto& cap = uf::physics::impl::create(world, capObj, pod::Capsule{0.5f,1.0f}, 1.0f);
|
||||
auto& sph = uf::physics::impl::create(world, sphObj, pod::Sphere{0.5f}, 1.0f);
|
||||
|
||||
cap.transform->position = {0,0,0};
|
||||
sph.transform->position = {0,5,0}; // too far
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = capsuleSphere(cap, sph, m);
|
||||
EXPECT_TRUE(!collided);
|
||||
})
|
||||
|
||||
|
||||
TEST(AabbSphere_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& box = uf::physics::impl::create(world, objA, pod::AABB{{-1,-1,-1},{1,1,1}}, 1.0f);
|
||||
auto& sphere= uf::physics::impl::create(world, objB, pod::Sphere{0.5f}, 1.0f);
|
||||
|
||||
box.transform->position = {0,0,0};
|
||||
sphere.transform->position = {0.75f,0,0}; // Intersecting into box
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = aabbSphere(box,sphere,m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
})
|
||||
|
||||
TEST(AabbSphere_NoCollision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& box = uf::physics::impl::create(world, objA, pod::AABB{{-1,-1,-1},{1,1,1}}, 1.0f);
|
||||
auto& sphere= uf::physics::impl::create(world, objB, pod::Sphere{0.5f}, 1.0f);
|
||||
|
||||
box.transform->position = {0,0,0};
|
||||
sphere.transform->position = {5,0,0}; // too far away
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = aabbSphere(box,sphere,m);
|
||||
EXPECT_TRUE(!collided);
|
||||
})
|
||||
|
||||
|
||||
TEST(AabbPlane_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& box = uf::physics::impl::create(world, objA, pod::AABB{{-1,-1,-1},{1,1,1}}, 1.0f);
|
||||
auto& plane = uf::physics::impl::create(world, objB, pod::Plane{{0,1,0},0.0f}, 0.0f);
|
||||
|
||||
box.transform->position = {0,0.5f,0}; // half interpenetrating plane
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = aabbPlane(box,plane,m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
})
|
||||
|
||||
TEST(AabbPlane_NoCollision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& box = uf::physics::impl::create(world, objA, pod::AABB{{-1,-1,-1},{1,1,1}}, 1.0f);
|
||||
auto& plane = uf::physics::impl::create(world, objB, pod::Plane{{0,1,0},0.0f}, 0.0f);
|
||||
|
||||
box.transform->position = {0,5,0}; // clearly above
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = aabbPlane(box,plane,m);
|
||||
EXPECT_TRUE(!collided);
|
||||
})
|
||||
|
||||
|
||||
TEST(AabbCapsule_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& box = uf::physics::impl::create(world, objA, pod::AABB{{-1,-1,-1},{1,1,1}}, 1.0f);
|
||||
auto& cap = uf::physics::impl::create(world, objB, pod::Capsule{0.5f,1.0f}, 1.0f);
|
||||
|
||||
box.transform->position = {0,0,0};
|
||||
cap.transform->position = {0,0.5f,0}; // partially overlapping
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = aabbCapsule(box,cap,m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
})
|
||||
|
||||
TEST(AabbCapsule_NoCollision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& box = uf::physics::impl::create(world, objA, pod::AABB{{-1,-1,-1},{1,1,1}}, 1.0f);
|
||||
auto& cap = uf::physics::impl::create(world, objB, pod::Capsule{0.5f,1.0f}, 1.0f);
|
||||
|
||||
box.transform->position = {0,0,0};
|
||||
cap.transform->position = {0,5,0};
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = aabbCapsule(box,cap,m);
|
||||
EXPECT_TRUE(!collided);
|
||||
})
|
||||
|
||||
|
||||
TEST(SphereCapsule_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& sphere = uf::physics::impl::create(world, objA, pod::Sphere{0.5f}, 1.0f);
|
||||
auto& cap = uf::physics::impl::create(world, objB, pod::Capsule{0.5f,1.0f}, 1.0f);
|
||||
|
||||
sphere.transform->position = {0,0.0f,0};
|
||||
cap.transform->position = {0,0.25f,0};
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = sphereCapsule(sphere,cap,m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
})
|
||||
|
||||
TEST(SphereCapsule_NoCollision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& sphere = uf::physics::impl::create(world, objA, pod::Sphere{0.5f}, 1.0f);
|
||||
auto& cap = uf::physics::impl::create(world, objB, pod::Capsule{0.5f,1.0f}, 1.0f);
|
||||
|
||||
sphere.transform->position = {0,5,0};
|
||||
cap.transform->position = {0,0,0};
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = sphereCapsule(sphere,cap,m);
|
||||
EXPECT_TRUE(!collided);
|
||||
})
|
||||
|
||||
TEST(PlanePlane_NoCollision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& planeA= uf::physics::impl::create(world, objA, pod::Plane{{0,1,0},0.0f}, 0.0f);
|
||||
auto& planeB= uf::physics::impl::create(world, objB, pod::Plane{{0,0,1},0.0f}, 0.0f);
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = planePlane(planeA,planeB,m);
|
||||
EXPECT_TRUE(!collided); // always false in your engine
|
||||
})
|
||||
|
||||
|
||||
TEST(PlaneCapsule_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& plane = uf::physics::impl::create(world, objA, pod::Plane{{0,1,0},0.0f}, 0.0f);
|
||||
auto& cap = uf::physics::impl::create(world, objB, pod::Capsule{0.5f,1.0f}, 1.0f);
|
||||
|
||||
cap.transform->position = {0,0.25f,0}; // foot intersecting
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = planeCapsule(plane,cap,m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
})
|
||||
|
||||
TEST(PlaneCapsule_NoCollision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
auto& plane = uf::physics::impl::create(world, objA, pod::Plane{{0,1,0},0.0f}, 0.0f);
|
||||
auto& cap = uf::physics::impl::create(world, objB, pod::Capsule{0.5f,1.0f}, 1.0f);
|
||||
|
||||
cap.transform->position = {0,5,0}; // far above
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = planeCapsule(plane,cap,m);
|
||||
EXPECT_TRUE(!collided);
|
||||
})
|
||||
TEST(MeshSphere_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objMesh, objSphere;
|
||||
|
||||
// Create mesh body (a plane on Y=0, size=1)
|
||||
auto mesh = ::generateMesh(1.0f);
|
||||
auto& meshBody = uf::physics::impl::create(world, objMesh, mesh, 0.0f); // static mesh
|
||||
meshBody.transform->position = {0,0,0};
|
||||
|
||||
// Sphere just above plane, radius 1, intersects
|
||||
auto& sphereBody = uf::physics::impl::create(world, objSphere, pod::Sphere{2.0f}, 1.0f);
|
||||
sphereBody.transform->position = {0,0.5f,0}; // half below plane (since plane is at y=0)
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = meshSphere(meshBody, sphereBody, m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
if ( !m.points.empty() ) EXPECT_TRUE(m.points[0].penetration > 0.0f);
|
||||
})
|
||||
|
||||
TEST(MeshSphere_NoCollision, {
|
||||
pod::World world;
|
||||
uf::Object objMesh, objSphere;
|
||||
|
||||
auto mesh = ::generateMesh(1.0f);
|
||||
auto& meshBody = uf::physics::impl::create(world, objMesh, mesh, 0.0f);
|
||||
meshBody.transform->position = {0,0,0};
|
||||
|
||||
auto& sphereBody = uf::physics::impl::create(world, objSphere, pod::Sphere{0.5f}, 1.0f);
|
||||
sphereBody.transform->position = {0,5.0f,0}; // far above plane
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = meshSphere(meshBody, sphereBody, m);
|
||||
EXPECT_FALSE(collided);
|
||||
})
|
||||
|
||||
TEST(MeshAabb_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objMesh, objBox;
|
||||
|
||||
auto mesh = ::generateMesh(2.0f);
|
||||
auto& meshBody = uf::physics::impl::create(world, objMesh, mesh, 0.0f);
|
||||
meshBody.transform->position = {0,0,0};
|
||||
|
||||
pod::AABB box = { {-0.5f,-0.5f,-0.5f}, {0.5f,0.5f,0.5f} };
|
||||
auto& boxBody = uf::physics::impl::create(world, objBox, box, 1.0f);
|
||||
boxBody.transform->position = {0,0.25f,0}; // overlaps plane
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = meshAabb(meshBody, boxBody, m);
|
||||
EXPECT_TRUE(collided);
|
||||
EXPECT_TRUE(!m.points.empty());
|
||||
})
|
||||
|
||||
TEST(MeshAabb_NoCollision, {
|
||||
pod::World world;
|
||||
uf::Object objMesh, objBox;
|
||||
|
||||
auto mesh = ::generateMesh(2.0f);
|
||||
auto& meshBody = uf::physics::impl::create(world, objMesh, mesh, 0.0f);
|
||||
meshBody.transform->position = {0,0,0};
|
||||
|
||||
pod::AABB box = { {-0.5f,-0.5f,-0.5f}, {0.5f,0.5f,0.5f} };
|
||||
auto& boxBody = uf::physics::impl::create(world, objBox, box, 1.0f);
|
||||
boxBody.transform->position = {0,5.0f,0}; // above plane, no overlap
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = meshAabb(meshBody, boxBody, m);
|
||||
EXPECT_FALSE(collided);
|
||||
})
|
||||
|
||||
TEST(RayMesh_Hit, {
|
||||
pod::World world;
|
||||
uf::Object objMesh;
|
||||
|
||||
auto mesh = ::generateMesh(1.0f);
|
||||
auto& meshBody = uf::physics::impl::create(world, objMesh, mesh, 0.0f);
|
||||
meshBody.transform->position = {0,0,0};
|
||||
|
||||
pod::Ray ray{ {0,1,0}, {0,-1,0} }; // from above, pointing down
|
||||
pod::RayQuery hit = uf::physics::impl::rayCast(ray, world, 100.0f);
|
||||
|
||||
EXPECT_TRUE(hit.hit);
|
||||
EXPECT_TRUE(hit.contact.penetration > 0.0f);
|
||||
})
|
||||
|
||||
TEST(RayMesh_Miss, {
|
||||
pod::World world;
|
||||
uf::Object objMesh;
|
||||
|
||||
auto mesh = ::generateMesh(1.0f);
|
||||
auto& meshBody = uf::physics::impl::create(world, objMesh, mesh, 0.0f);
|
||||
meshBody.transform->position = {0,0,0};
|
||||
|
||||
pod::Ray ray{ {0,2,0}, {1,0,0} }; // parallel, goes sideways
|
||||
pod::RayQuery hit = uf::physics::impl::rayCast(ray, world, 100.0f);
|
||||
|
||||
EXPECT_FALSE(hit.hit);
|
||||
})
|
||||
|
||||
TEST(MeshMesh_Collision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
|
||||
auto mesh = ::generateMesh(1.0f);
|
||||
|
||||
auto& meshA = uf::physics::impl::create(world, objA, mesh, 0.0f);
|
||||
auto& meshB = uf::physics::impl::create(world, objB, mesh, 0.0f);
|
||||
|
||||
meshA.transform->position = {0,0,0};
|
||||
meshB.transform->position = {0,0,0}; // same location
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = meshMesh(meshA, meshB, m);
|
||||
EXPECT_TRUE(collided);
|
||||
})
|
||||
|
||||
TEST(MeshMesh_NoCollision, {
|
||||
pod::World world;
|
||||
uf::Object objA, objB;
|
||||
|
||||
auto mesh = ::generateMesh(1.0f);
|
||||
|
||||
auto& meshA = uf::physics::impl::create(world, objA, mesh, 0.0f);
|
||||
auto& meshB = uf::physics::impl::create(world, objB, mesh, 0.0f);
|
||||
|
||||
meshA.transform->position = {0,0,0};
|
||||
meshB.transform->position = {0,10.0f,0}; // too far apart
|
||||
|
||||
pod::Manifold m;
|
||||
bool collided = meshMesh(meshA, meshB, m);
|
||||
EXPECT_FALSE(collided);
|
||||
})
|
||||
@ -362,6 +362,57 @@ std::string uf::Mesh::printIndirects( bool full ) const {
|
||||
return str.str();
|
||||
}
|
||||
|
||||
uf::Mesh::View uf::Mesh::makeView( const uf::stl::vector<uf::stl::string>& wanted ) const {
|
||||
uf::Mesh::View view;
|
||||
view.vertex = vertex;
|
||||
view.index = index;
|
||||
|
||||
if ( wanted.size() ) {
|
||||
for ( auto& attr : vertex.attributes ) {
|
||||
if ( std::find(wanted.begin(), wanted.end(), attr.descriptor.name ) == wanted.end() ) continue;
|
||||
view.attributes[attr.descriptor.name] = { attr };
|
||||
}
|
||||
} else {
|
||||
for ( auto& attr : vertex.attributes ) view.attributes[attr.descriptor.name] = { attr };
|
||||
}
|
||||
|
||||
if ( !index.attributes.empty() ) {
|
||||
view.attributes["index"] = { index.attributes.front() };
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
uf::Mesh::View uf::Mesh::makeView( size_t i, const uf::stl::vector<uf::stl::string>& wanted ) const {
|
||||
uf::Mesh::View view;
|
||||
view.vertex = remapVertexInput(i);
|
||||
view.index = remapIndexInput(i);
|
||||
view.indirectIndex = i;
|
||||
|
||||
if ( wanted.size() ) {
|
||||
for (auto& attr : vertex.attributes) {
|
||||
if ( std::find(wanted.begin(), wanted.end(), attr.descriptor.name ) == wanted.end() ) continue;
|
||||
view.attributes[attr.descriptor.name] = { attr };
|
||||
}
|
||||
} else {
|
||||
for ( auto& attr : vertex.attributes ) view.attributes[attr.descriptor.name] = { attr };
|
||||
}
|
||||
|
||||
if ( !index.attributes.empty() ) {
|
||||
view.attributes["index"] = { index.attributes.front() };
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
uf::stl::vector<uf::Mesh::View> uf::Mesh::makeViews( const uf::stl::vector<uf::stl::string>& wanted ) const {
|
||||
uf::stl::vector<uf::Mesh::View> views;
|
||||
if ( indirect.count > 0 ) {
|
||||
for ( auto i = 0; i < indirect.count; i++ ) views.emplace_back(makeView(i, wanted));
|
||||
} else {
|
||||
views.emplace_back( makeView(wanted) );
|
||||
}
|
||||
return views;
|
||||
}
|
||||
|
||||
uf::Mesh::Input uf::Mesh::remapInput( const uf::Mesh::Input& input, size_t i ) const {
|
||||
uf::Mesh::Input res = input;
|
||||
UF_ASSERT( &input == &vertex || &input == &index );
|
||||
|
||||
28
engine/src/utils/tests/tests.cpp
Normal file
28
engine/src/utils/tests/tests.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include <uf/utils/tests/tests.h>
|
||||
|
||||
|
||||
uf::stl::string uf::unitTests::current;
|
||||
uf::stl::KeyMap<pod::UnitTest> uf::unitTests::tests;
|
||||
|
||||
void uf::unitTests::execute() {
|
||||
auto testCount = uf::unitTests::tests.keys.size();
|
||||
|
||||
if ( !testCount ) return;
|
||||
|
||||
UF_MSG_DEBUG("Running {} tests...", testCount);
|
||||
for ( auto& name : uf::unitTests::tests.keys ) {
|
||||
uf::unitTests::current = name;
|
||||
uf::unitTests::tests[name].function();
|
||||
}
|
||||
UF_MSG_DEBUG("");
|
||||
int pass = 0;
|
||||
int fail = 0;
|
||||
for ( auto& name : uf::unitTests::tests.keys ) {
|
||||
auto& test = uf::unitTests::tests[name];
|
||||
auto& value = test.passes ? pass : fail;
|
||||
++value;
|
||||
UF_MSG_DEBUG("[{}]: [{}]: {}", test.passes ? "PASS" : "FAIL", name, test.message);
|
||||
}
|
||||
UF_MSG_DEBUG("{} / {} tests passed.", pass, testCount );
|
||||
UF_MSG_DEBUG("{} / {} tests failed.", fail, testCount );
|
||||
}
|
||||
@ -1,8 +1,11 @@
|
||||
#include <uf/config.h>
|
||||
#include <uf/ext/ext.h>
|
||||
|
||||
void EXT_API ext::initialize() {
|
||||
// perform unit tests
|
||||
#include <uf/utils/tests/tests.h>
|
||||
|
||||
void EXT_API ext::initialize() {
|
||||
uf::unitTests::execute();
|
||||
}
|
||||
void EXT_API ext::tick() {
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user