hush
This commit is contained in:
239
src/collision.hh
Normal file
239
src/collision.hh
Normal file
@@ -0,0 +1,239 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* collision.hh - PS1 Collision System
|
||||
*
|
||||
* Provides spatial hashing broadphase and AABB narrowphase collision detection.
|
||||
* Designed for PS1's limited CPU - uses fixed-point math and spatial partitioning.
|
||||
*
|
||||
* Architecture:
|
||||
* - Broadphase: Spatial grid (cells of fixed size)
|
||||
* - Narrowphase: AABB intersection tests
|
||||
* - Trigger system: Enter/Stay/Exit events
|
||||
*/
|
||||
|
||||
#include <psyqo/fixed-point.hh>
|
||||
#include <psyqo/vector.hh>
|
||||
#include <EASTL/vector.h>
|
||||
|
||||
#include "gameobject.hh"
|
||||
|
||||
namespace psxsplash {
|
||||
|
||||
// Forward declarations
|
||||
class SceneManager;
|
||||
|
||||
/**
|
||||
* Collision type flags - matches Unity PSXCollisionType enum
|
||||
*/
|
||||
enum class CollisionType : uint8_t {
|
||||
None = 0,
|
||||
Solid = 1, // Blocks movement
|
||||
Trigger = 2, // Fires events, doesn't block
|
||||
Platform = 3 // Solid from above only
|
||||
};
|
||||
|
||||
/**
|
||||
* Collision layer mask - 8 layers available
|
||||
* Objects only collide with matching layers
|
||||
*/
|
||||
using CollisionMask = uint8_t;
|
||||
|
||||
/**
|
||||
* Axis-Aligned Bounding Box in fixed-point
|
||||
* Used for broadphase and narrowphase collision
|
||||
*/
|
||||
struct AABB {
|
||||
psyqo::Vec3 min;
|
||||
psyqo::Vec3 max;
|
||||
|
||||
// Check if this AABB intersects another
|
||||
bool intersects(const AABB& other) const {
|
||||
return (min.x <= other.max.x && max.x >= other.min.x) &&
|
||||
(min.y <= other.max.y && max.y >= other.min.y) &&
|
||||
(min.z <= other.max.z && max.z >= other.min.z);
|
||||
}
|
||||
|
||||
// Check if a point is inside this AABB
|
||||
bool contains(const psyqo::Vec3& point) const {
|
||||
return (point.x >= min.x && point.x <= max.x) &&
|
||||
(point.y >= min.y && point.y <= max.y) &&
|
||||
(point.z >= min.z && point.z <= max.z);
|
||||
}
|
||||
|
||||
// Get center of AABB
|
||||
psyqo::Vec3 center() const {
|
||||
return psyqo::Vec3{
|
||||
(min.x + max.x) / 2,
|
||||
(min.y + max.y) / 2,
|
||||
(min.z + max.z) / 2
|
||||
};
|
||||
}
|
||||
|
||||
// Get half-extents
|
||||
psyqo::Vec3 halfExtents() const {
|
||||
return psyqo::Vec3{
|
||||
(max.x - min.x) / 2,
|
||||
(max.y - min.y) / 2,
|
||||
(max.z - min.z) / 2
|
||||
};
|
||||
}
|
||||
|
||||
// Expand AABB by a vector (for swept tests)
|
||||
void expand(const psyqo::Vec3& delta);
|
||||
};
|
||||
static_assert(sizeof(AABB) == 24, "AABB must be 24 bytes (2x Vec3)");
|
||||
|
||||
/**
|
||||
* Collision data for a single object
|
||||
* Stored separately from GameObject for cache efficiency
|
||||
*/
|
||||
struct CollisionData {
|
||||
AABB bounds; // World-space AABB (24 bytes)
|
||||
CollisionType type; // Collision behavior (1 byte)
|
||||
CollisionMask layerMask; // Which layers this collides with (1 byte)
|
||||
uint8_t flags; // Additional flags (1 byte)
|
||||
uint8_t gridCell; // Current spatial grid cell (1 byte)
|
||||
uint16_t gameObjectIndex; // Index into GameObject array (2 bytes)
|
||||
uint16_t padding; // Alignment padding (2 bytes)
|
||||
};
|
||||
static_assert(sizeof(CollisionData) == 32, "CollisionData must be 32 bytes");
|
||||
|
||||
/**
|
||||
* Collision result - returned when collision is detected
|
||||
*/
|
||||
struct CollisionResult {
|
||||
uint16_t objectA; // First object index
|
||||
uint16_t objectB; // Second object index
|
||||
psyqo::Vec3 normal; // Collision normal (from A to B)
|
||||
psyqo::FixedPoint<12> penetration; // Penetration depth
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger state for tracking enter/stay/exit
|
||||
*/
|
||||
struct TriggerPair {
|
||||
uint16_t triggerIndex; // Index of trigger object
|
||||
uint16_t otherIndex; // Index of other object
|
||||
uint8_t framesSinceContact; // Counter for exit detection
|
||||
uint8_t state; // 0=new, 1=staying, 2=exiting
|
||||
uint16_t padding;
|
||||
};
|
||||
|
||||
/**
|
||||
* Spatial Grid for broadphase collision
|
||||
* Divides world into fixed-size cells for fast overlap queries
|
||||
*/
|
||||
class SpatialGrid {
|
||||
public:
|
||||
// Grid configuration
|
||||
static constexpr int GRID_SIZE = 8; // 8x8x8 grid
|
||||
static constexpr int CELL_COUNT = GRID_SIZE * GRID_SIZE * GRID_SIZE;
|
||||
static constexpr int MAX_OBJECTS_PER_CELL = 16;
|
||||
|
||||
// World bounds (fixed for simplicity) - values set in collision.cpp
|
||||
static psyqo::FixedPoint<12> WORLD_MIN;
|
||||
static psyqo::FixedPoint<12> WORLD_MAX;
|
||||
static psyqo::FixedPoint<12> CELL_SIZE;
|
||||
|
||||
struct Cell {
|
||||
uint16_t objectIndices[MAX_OBJECTS_PER_CELL];
|
||||
uint8_t count;
|
||||
uint8_t padding[3];
|
||||
};
|
||||
|
||||
// Clear all cells
|
||||
void clear();
|
||||
|
||||
// Insert an object into the grid
|
||||
void insert(uint16_t objectIndex, const AABB& bounds);
|
||||
|
||||
// Get all potential colliders for an AABB
|
||||
// Returns number of results written to output
|
||||
int queryAABB(const AABB& bounds, uint16_t* output, int maxResults) const;
|
||||
|
||||
// Get cell index for a position
|
||||
int getCellIndex(const psyqo::Vec3& pos) const;
|
||||
|
||||
private:
|
||||
Cell m_cells[CELL_COUNT];
|
||||
|
||||
// Convert world position to grid coordinates
|
||||
void worldToGrid(const psyqo::Vec3& pos, int& gx, int& gy, int& gz) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Main Collision System
|
||||
* Manages all collision detection and trigger events
|
||||
*/
|
||||
class CollisionSystem {
|
||||
public:
|
||||
static constexpr int MAX_COLLIDERS = 64;
|
||||
static constexpr int MAX_TRIGGERS = 32;
|
||||
static constexpr int MAX_COLLISION_RESULTS = 32;
|
||||
|
||||
CollisionSystem() = default;
|
||||
|
||||
// Initialize the system
|
||||
void init();
|
||||
|
||||
// Reset for new scene
|
||||
void reset();
|
||||
|
||||
// Register a collider (called during scene load)
|
||||
void registerCollider(uint16_t gameObjectIndex, const AABB& localBounds,
|
||||
CollisionType type, CollisionMask mask);
|
||||
|
||||
// Update collision data for an object (call when object moves)
|
||||
void updateCollider(uint16_t gameObjectIndex, const psyqo::Vec3& position,
|
||||
const psyqo::Matrix33& rotation);
|
||||
|
||||
// Run collision detection for one frame
|
||||
// Returns number of collisions detected
|
||||
int detectCollisions();
|
||||
|
||||
// Get collision results (valid until next detectCollisions call)
|
||||
const CollisionResult* getResults() const { return m_results; }
|
||||
int getResultCount() const { return m_resultCount; }
|
||||
|
||||
// Check if two specific objects are colliding
|
||||
bool areColliding(uint16_t indexA, uint16_t indexB) const;
|
||||
|
||||
// Raycast against all colliders
|
||||
// Returns true if hit, fills hitPoint and hitNormal
|
||||
bool raycast(const psyqo::Vec3& origin, const psyqo::Vec3& direction,
|
||||
psyqo::FixedPoint<12> maxDistance,
|
||||
psyqo::Vec3& hitPoint, psyqo::Vec3& hitNormal,
|
||||
uint16_t& hitObjectIndex) const;
|
||||
|
||||
// Get trigger events for current frame (call from SceneManager)
|
||||
void processTriggerEvents(class SceneManager& scene);
|
||||
|
||||
// Debug: Get collider count
|
||||
int getColliderCount() const { return m_colliderCount; }
|
||||
|
||||
private:
|
||||
// Collision data for all registered colliders
|
||||
CollisionData m_colliders[MAX_COLLIDERS];
|
||||
int m_colliderCount = 0;
|
||||
|
||||
// Spatial partitioning grid
|
||||
SpatialGrid m_grid;
|
||||
|
||||
// Collision results for current frame
|
||||
CollisionResult m_results[MAX_COLLISION_RESULTS];
|
||||
int m_resultCount = 0;
|
||||
|
||||
// Trigger tracking
|
||||
TriggerPair m_triggerPairs[MAX_TRIGGERS];
|
||||
int m_triggerPairCount = 0;
|
||||
|
||||
// Narrowphase AABB test
|
||||
bool testAABB(const AABB& a, const AABB& b,
|
||||
psyqo::Vec3& normal, psyqo::FixedPoint<12>& penetration) const;
|
||||
|
||||
// Update trigger state machine
|
||||
void updateTriggerState(uint16_t triggerIndex, uint16_t otherIndex, bool isOverlapping);
|
||||
};
|
||||
|
||||
} // namespace psxsplash
|
||||
Reference in New Issue
Block a user