Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions include/public/Core/GameObject.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <ECS/Component.hpp>
#include <Utility/Container/List.hpp>
#include <Memory/RefPtr.hpp>
#include <cstdint>
#include <type_traits>
#include <string>

Expand Down Expand Up @@ -76,6 +77,7 @@ namespace Sleak {
if (bIsInitialized) newComponent->Initialize();
if (m_isActive && bIsInitialized) newComponent->OnEnable();
Components.add(std::move(newComponent));
++m_componentVersion;
}

/// Destroys and detaches the first component of type T, if present.
Expand All @@ -87,6 +89,7 @@ namespace Sleak {
if (dynamic_cast<T*>(Components[i].get()) != nullptr) {
Components[i]->OnDestroy();
Components.erase(i);
++m_componentVersion;
break;
}
}
Expand All @@ -109,6 +112,15 @@ namespace Sleak {
return nullptr;
}

/// Every attached component, in the order they were added.
const List<RefPtr<Component>>& GetComponents() const {
return Components;
}

/// Changes whenever a component is added or removed, so callers can
/// cache GetComponent() results and know when to look again.
uint32_t GetComponentVersion() const { return m_componentVersion; }

/// True if a component of type T is attached.
template <typename T>
bool HasComponent() {
Expand Down Expand Up @@ -162,6 +174,7 @@ namespace Sleak {
std::string m_tag = "Untagged";

List<RefPtr<Component>> Components;
uint32_t m_componentVersion = 0;

// Hierarchy
GameObject* m_parent;
Expand Down
17 changes: 16 additions & 1 deletion include/public/ECS/Component.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Sleak {
class GameObject;
namespace Physics { struct CollisionEvent; }
/// Base for behavior attached to a GameObject. Owner deletes it via
/// GameObject::RemoveComponent(), never directly.
///
Expand All @@ -15,7 +16,9 @@ namespace Sleak {
/// parameter, because GameObject::AddComponent() supplies it and
/// forwards the rest. Initialize() and Update() are pure virtual;
/// FixedUpdate(), LateUpdate(), OnEnable(), OnDisable(), and
/// OnDestroy() are optional.
/// OnDestroy() are optional, as are the OnCollision and OnTrigger
/// callbacks the physics world sends to every component on an object
/// whose collider starts, keeps, or stops touching another.
///
/// The owning GameObject holds components in a RefPtr and destroys them
/// with itself, so never delete a component directly. Reach the owner
Expand Down Expand Up @@ -73,6 +76,18 @@ namespace Sleak {
virtual void OnEnable() {}
virtual void OnDisable() {}

/// Sent on the first physics step this object's collider touches
/// another solid collider.
virtual void OnCollisionEnter(const Physics::CollisionEvent&) {}
/// Sent on every later physics step the two keep touching.
virtual void OnCollisionStay(const Physics::CollisionEvent&) {}
/// Sent on the first physics step they no longer touch.
virtual void OnCollisionExit(const Physics::CollisionEvent&) {}
/// Same as OnCollisionEnter when either collider is a trigger.
virtual void OnTriggerEnter(const Physics::CollisionEvent&) {}
virtual void OnTriggerStay(const Physics::CollisionEvent&) {}
virtual void OnTriggerExit(const Physics::CollisionEvent&) {}

GameObject* GetOwner() {
return owner;
}
Expand Down
28 changes: 27 additions & 1 deletion include/public/Physics/ColliderComponent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
#include <ECS/Component.hpp>
#include <Core/OSDef.hpp>
#include <Physics/Colliders.hpp>
#include <cstdint>

namespace Sleak {

struct MeshData;
class Camera;
class RigidbodyComponent;
class TransformComponent;
namespace Physics { class PhysicsWorld; }

/// Attaches a collision shape to a GameObject and registers it with PhysicsWorld's broadphase.
///
Expand All @@ -27,7 +32,8 @@ namespace Sleak {
/// API, so put triggers, terrain, and characters on distinct layers and
/// a raycast can ignore the ones it does not care about. Marking a
/// collider as a trigger keeps it in the broadphase while skipping
/// physical response.
/// physical response; overlaps then arrive as OnTriggerEnter/Stay/Exit
/// on the components of both objects.
///
/// @code{.cpp}
/// // Static level geometry
Expand Down Expand Up @@ -81,6 +87,15 @@ namespace Sleak {
/// Local shape transformed into world space by the owner's current transform.
Physics::AABB GetWorldAABB() const;

/// Where the shape sits in the world: owner position plus offset,
/// and owner scale.
void GetWorldPose(Math::Vector3D& position,
Math::Vector3D& scale) const;

/// Rigidbody on the same object, or null. Cached until the owner's
/// components change.
RigidbodyComponent* GetRigidbody() const;

// Offset from owner's transform
void SetOffset(const Math::Vector3D& offset) { m_offset = offset; }
Math::Vector3D GetOffset() const { return m_offset; }
Expand All @@ -100,13 +115,24 @@ namespace Sleak {
int GetProxyId() const { return m_proxyId; }

private:
friend class Physics::PhysicsWorld;

void RefreshCache() const;
/// Moves the owner through its transform, or a camera's position.
void TranslateOwner(const Math::Vector3D& delta);

Physics::ColliderShape m_shape;
Physics::ColliderType m_type = Physics::ColliderType::AABB;
Math::Vector3D m_offset;
uint32_t m_layer = 0xFFFFFFFF;
uint32_t m_mask = 0xFFFFFFFF;
bool m_isTrigger = false;
int m_proxyId = -1;

mutable RigidbodyComponent* m_body = nullptr;
mutable TransformComponent* m_transform = nullptr;
mutable Camera* m_camera = nullptr;
mutable uint32_t m_cacheVersion = UINT32_MAX;
};

} // namespace Sleak
Expand Down
27 changes: 27 additions & 0 deletions include/public/Physics/CollisionDetection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,33 @@ namespace Physics {
CollisionManifold TestSphereVsTriangle(const BoundingSphere& sphere,
const Vector3D& v0, const Vector3D& v1, const Vector3D& v2);

/// Ray against a world-space box. dir must be unit length; a ray starting
/// inside the box reports no hit. On a hit, t is the distance along dir
/// and normal is the surface normal facing the ray.
bool RaycastAABB(const AABB& box, const Vector3D& origin,
const Vector3D& dir, float maxDist, float& t,
Vector3D& normal);
/// Ray against a world-space sphere, same contract as RaycastAABB().
bool RaycastSphere(const BoundingSphere& sphere, const Vector3D& origin,
const Vector3D& dir, float maxDist, float& t,
Vector3D& normal);
/// Ray against a world-space capsule, same contract as RaycastAABB().
bool RaycastCapsule(const BoundingCapsule& capsule, const Vector3D& origin,
const Vector3D& dir, float maxDist, float& t,
Vector3D& normal);
/// Two-sided ray against one triangle. dir need not be unit length; t is
/// in units of dir.
bool RaycastTriangle(const Vector3D& v0, const Vector3D& v1,
const Vector3D& v2, const Vector3D& origin,
const Vector3D& dir, float maxDist, float& t,
Vector3D& normal);
/// Ray against a collider shape placed at pos with scale, the same way
/// TestCollision() places it. dir must be unit length.
bool RaycastShape(const ColliderShape& shape, const Vector3D& pos,
const Vector3D& scale, const Vector3D& origin,
const Vector3D& dir, float maxDist, float& t,
Vector3D& normal);

} // namespace Physics
} // namespace Sleak

Expand Down
3 changes: 2 additions & 1 deletion include/public/Physics/DynamicAABBTree.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef _DYNAMIC_AABB_TREE_HPP_
#define _DYNAMIC_AABB_TREE_HPP_

#include <Core/OSDef.hpp>
#include <Physics/Colliders.hpp>
#include <functional>
#include <vector>
Expand All @@ -26,7 +27,7 @@ namespace Physics {

/// Broadphase AABB tree; PhysicsWorld inserts colliders as proxies and queries overlaps against it.
/// @ingroup physics
class DynamicAABBTree {
class ENGINE_API DynamicAABBTree {
public:
DynamicAABBTree();
~DynamicAABBTree() = default;
Expand Down
83 changes: 72 additions & 11 deletions include/public/Physics/PhysicsWorld.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef _PHYSICS_WORLD_HPP_
#define _PHYSICS_WORLD_HPP_

#include <Core/OSDef.hpp>
#include <Physics/Colliders.hpp>
#include <Physics/CollisionDetection.hpp>
#include <Physics/DynamicAABBTree.hpp>
Expand All @@ -21,6 +22,29 @@ namespace Sleak {
CollisionManifold manifold;
};

/// One collider found by OverlapSphere() or OverlapAABB().
/// @ingroup physics
struct OverlapHit {
ColliderComponent* collider = nullptr;
Vector3D point;
/// Points from the query shape toward the collider.
Vector3D normal;
float penetration = 0.0f;
};

/// What a component receives in OnCollision* and OnTrigger* callbacks.
/// Point, normal, and penetration are zero on Exit.
/// @ingroup physics
struct CollisionEvent {
/// The collider on the object receiving the callback.
ColliderComponent* self = nullptr;
ColliderComponent* other = nullptr;
Vector3D point;
/// Points from self toward other.
Vector3D normal;
float penetration = 0.0f;
};

/// Result of a single Raycast call.
/// @ingroup physics
struct RayHit {
Expand Down Expand Up @@ -53,7 +77,17 @@ namespace Sleak {
///
/// Step() integrates every dynamic RigidbodyComponent using that
/// body's own gravity setting, refreshes the DynamicAABBTree
/// broadphase, then finds and resolves overlapping pairs.
/// broadphase, then finds and resolves overlapping pairs. Only pairs
/// where at least one side has a non-static rigidbody are tested.
/// Two dynamic bodies split the correction by inverse mass, a
/// kinematic body pushes a dynamic one without being pushed, and a
/// trigger never pushes anything.
///
/// After resolving, the world compares this step's touching pairs
/// with the last step's and calls OnCollisionEnter/Stay/Exit, or
/// OnTriggerEnter/Stay/Exit when either side is a trigger, on every
/// component of both objects. Pairs involving a collider that gets
/// unregistered (its object removed or destroyed) end without an Exit.
///
/// The part you will use directly is the query API. Raycast(),
/// SphereSweep(), OverlapSphere(), and OverlapAABB() all take an
Expand All @@ -76,15 +110,15 @@ namespace Sleak {
/// // Everything inside a blast radius
/// auto caught = world->OverlapSphere(
/// Sleak::Math::Vector3D(0.0f, 1.0f, 0.0f), 4.0f);
/// for (const auto& pair : caught) {
/// ApplyDamage(pair.b->GetOwner());
/// for (const auto& hit : caught) {
/// ApplyDamage(hit.collider->GetOwner());
/// }
/// @endcode
///
/// @see ColliderComponent, RigidbodyComponent, DynamicAABBTree,
/// RayHit, SweepResult, CollisionPair
/// RayHit, SweepResult, OverlapHit, CollisionEvent
/// @ingroup physics
class PhysicsWorld {
class ENGINE_API PhysicsWorld {
public:
PhysicsWorld() = default;
~PhysicsWorld() = default;
Expand All @@ -98,23 +132,50 @@ namespace Sleak {
void RegisterCollider(ColliderComponent* collider);
void UnregisterCollider(ColliderComponent* collider);

/// Query API: colliders overlapping a sphere, filtered by layerMask.
std::vector<CollisionPair> OverlapSphere(const Vector3D& center, float radius, uint32_t layerMask = 0xFFFFFFFF) const;
/// Colliders overlapping an AABB, filtered by layerMask.
std::vector<CollisionPair> OverlapAABB(const AABB& aabb, uint32_t layerMask = 0xFFFFFFFF) const;
/// Query API: colliders whose shape overlaps a sphere, filtered
/// by layerMask.
std::vector<OverlapHit> OverlapSphere(
const Vector3D& center, float radius,
uint32_t layerMask = 0xFFFFFFFF) const;
/// Colliders whose shape overlaps an AABB, filtered by layerMask.
std::vector<OverlapHit> OverlapAABB(
const AABB& aabb, uint32_t layerMask = 0xFFFFFFFF) const;
/// Sweeps a sphere from start along direction and returns the first collider it hits within maxDist.
SweepResult SphereSweep(const Vector3D& start, const Vector3D& direction,
float radius, float maxDist, uint32_t layerMask = 0xFFFFFFFF) const;
/// Casts a ray and returns the closest collider hit within maxDist.
/// Casts a ray against the collider shapes and returns the closest
/// hit within maxDist. direction need not be normalized, distance
/// is in world units, and colliders containing origin are skipped.
RayHit Raycast(const Vector3D& origin, const Vector3D& direction,
float maxDist, uint32_t layerMask = 0xFFFFFFFF) const;

private:
struct TouchingPair {
ColliderComponent* a = nullptr;
ColliderComponent* b = nullptr;
uint64_t key = 0;
bool trigger = false;
ContactPoint contact;
};

std::vector<OverlapHit> OverlapShape(const ColliderShape& query,
const AABB& bounds,
uint32_t layerMask) const;
void Integrate(float dt);
void UpdateBroadphase();
void FindPairsAndResolve();
void CollectPairs();
void ResolvePairs();
void DispatchEvents();
bool RemovedDuringDispatch(const ColliderComponent* collider) const;

DynamicAABBTree m_tree;
std::vector<ColliderComponent*> m_colliders;

std::vector<TouchingPair> m_candidates;
std::vector<TouchingPair> m_touching;
std::vector<TouchingPair> m_prevTouching;
std::vector<const ColliderComponent*> m_removedDuringDispatch;
bool m_dispatching = false;
};

} // namespace Physics
Expand Down
3 changes: 3 additions & 0 deletions include/public/Physics/RigidbodyComponent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ namespace Sleak {

/// Pushes the body out along normal by penetration and updates ground/wall collision state.
void ResolveCollision(const Math::Vector3D& normal, float penetration);
/// Records a contact along normal and cancels velocity into it,
/// without moving the body. ResolveCollision() calls this after moving.
void ApplyContactNormal(const Math::Vector3D& normal);

BodyType GetBodyType() const { return m_bodyType; }
void SetBodyType(BodyType type) { m_bodyType = type; }
Expand Down
Loading
Loading