Revamped collision system
This commit is contained in:
@@ -3,17 +3,14 @@
|
|||||||
|
|
||||||
#include <psyqo/fixed-point.hh>
|
#include <psyqo/fixed-point.hh>
|
||||||
|
|
||||||
// Helper type alias for brevity
|
|
||||||
using FP = psyqo::FixedPoint<12>;
|
using FP = psyqo::FixedPoint<12>;
|
||||||
|
|
||||||
namespace psxsplash {
|
namespace psxsplash {
|
||||||
|
|
||||||
// Static member initialization
|
|
||||||
psyqo::FixedPoint<12> SpatialGrid::WORLD_MIN = FP(-16);
|
psyqo::FixedPoint<12> SpatialGrid::WORLD_MIN = FP(-16);
|
||||||
psyqo::FixedPoint<12> SpatialGrid::WORLD_MAX = FP(16);
|
psyqo::FixedPoint<12> SpatialGrid::WORLD_MAX = FP(16);
|
||||||
psyqo::FixedPoint<12> SpatialGrid::CELL_SIZE = FP(4); // (32 / 8) = 4
|
psyqo::FixedPoint<12> SpatialGrid::CELL_SIZE = FP(4);
|
||||||
|
|
||||||
// AABB expand implementation
|
|
||||||
void AABB::expand(const psyqo::Vec3& delta) {
|
void AABB::expand(const psyqo::Vec3& delta) {
|
||||||
psyqo::FixedPoint<12> zero;
|
psyqo::FixedPoint<12> zero;
|
||||||
if (delta.x > zero) max.x = max.x + delta.x;
|
if (delta.x > zero) max.x = max.x + delta.x;
|
||||||
@@ -35,7 +32,6 @@ void SpatialGrid::clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SpatialGrid::worldToGrid(const psyqo::Vec3& pos, int& gx, int& gy, int& gz) const {
|
void SpatialGrid::worldToGrid(const psyqo::Vec3& pos, int& gx, int& gy, int& gz) const {
|
||||||
// Clamp position to world bounds
|
|
||||||
auto px = pos.x;
|
auto px = pos.x;
|
||||||
auto py = pos.y;
|
auto py = pos.y;
|
||||||
auto pz = pos.z;
|
auto pz = pos.z;
|
||||||
@@ -47,13 +43,10 @@ void SpatialGrid::worldToGrid(const psyqo::Vec3& pos, int& gx, int& gy, int& gz)
|
|||||||
if (pz < WORLD_MIN) pz = WORLD_MIN;
|
if (pz < WORLD_MIN) pz = WORLD_MIN;
|
||||||
if (pz > WORLD_MAX) pz = WORLD_MAX;
|
if (pz > WORLD_MAX) pz = WORLD_MAX;
|
||||||
|
|
||||||
// Convert to grid coordinates (0 to GRID_SIZE-1)
|
|
||||||
// Using integer division after scaling
|
|
||||||
gx = ((px - WORLD_MIN) / CELL_SIZE).integer();
|
gx = ((px - WORLD_MIN) / CELL_SIZE).integer();
|
||||||
gy = ((py - WORLD_MIN) / CELL_SIZE).integer();
|
gy = ((py - WORLD_MIN) / CELL_SIZE).integer();
|
||||||
gz = ((pz - WORLD_MIN) / CELL_SIZE).integer();
|
gz = ((pz - WORLD_MIN) / CELL_SIZE).integer();
|
||||||
|
|
||||||
// Clamp to valid range
|
|
||||||
if (gx < 0) gx = 0;
|
if (gx < 0) gx = 0;
|
||||||
if (gx >= GRID_SIZE) gx = GRID_SIZE - 1;
|
if (gx >= GRID_SIZE) gx = GRID_SIZE - 1;
|
||||||
if (gy < 0) gy = 0;
|
if (gy < 0) gy = 0;
|
||||||
@@ -69,14 +62,12 @@ int SpatialGrid::getCellIndex(const psyqo::Vec3& pos) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SpatialGrid::insert(uint16_t objectIndex, const AABB& bounds) {
|
void SpatialGrid::insert(uint16_t objectIndex, const AABB& bounds) {
|
||||||
// Get grid range for this AABB
|
|
||||||
int minGx, minGy, minGz;
|
int minGx, minGy, minGz;
|
||||||
int maxGx, maxGy, maxGz;
|
int maxGx, maxGy, maxGz;
|
||||||
|
|
||||||
worldToGrid(bounds.min, minGx, minGy, minGz);
|
worldToGrid(bounds.min, minGx, minGy, minGz);
|
||||||
worldToGrid(bounds.max, maxGx, maxGy, maxGz);
|
worldToGrid(bounds.max, maxGx, maxGy, maxGz);
|
||||||
|
|
||||||
// Insert into all overlapping cells
|
|
||||||
for (int gz = minGz; gz <= maxGz; gz++) {
|
for (int gz = minGz; gz <= maxGz; gz++) {
|
||||||
for (int gy = minGy; gy <= maxGy; gy++) {
|
for (int gy = minGy; gy <= maxGy; gy++) {
|
||||||
for (int gx = minGx; gx <= maxGx; gx++) {
|
for (int gx = minGx; gx <= maxGx; gx++) {
|
||||||
@@ -86,8 +77,6 @@ void SpatialGrid::insert(uint16_t objectIndex, const AABB& bounds) {
|
|||||||
if (cell.count < MAX_OBJECTS_PER_CELL) {
|
if (cell.count < MAX_OBJECTS_PER_CELL) {
|
||||||
cell.objectIndices[cell.count++] = objectIndex;
|
cell.objectIndices[cell.count++] = objectIndex;
|
||||||
}
|
}
|
||||||
// If cell is full, object won't be in this cell (may miss collisions)
|
|
||||||
// This is a tradeoff for memory/performance
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,18 +85,15 @@ void SpatialGrid::insert(uint16_t objectIndex, const AABB& bounds) {
|
|||||||
int SpatialGrid::queryAABB(const AABB& bounds, uint16_t* output, int maxResults) const {
|
int SpatialGrid::queryAABB(const AABB& bounds, uint16_t* output, int maxResults) const {
|
||||||
int resultCount = 0;
|
int resultCount = 0;
|
||||||
|
|
||||||
// Get grid range for query AABB
|
|
||||||
int minGx, minGy, minGz;
|
int minGx, minGy, minGz;
|
||||||
int maxGx, maxGy, maxGz;
|
int maxGx, maxGy, maxGz;
|
||||||
|
|
||||||
worldToGrid(bounds.min, minGx, minGy, minGz);
|
worldToGrid(bounds.min, minGx, minGy, minGz);
|
||||||
worldToGrid(bounds.max, maxGx, maxGy, maxGz);
|
worldToGrid(bounds.max, maxGx, maxGy, maxGz);
|
||||||
|
|
||||||
// Track which objects we've already added (two 32-bit masks for objects 0-63)
|
uint32_t addedMaskLow = 0;
|
||||||
uint32_t addedMaskLow = 0; // Objects 0-31
|
uint32_t addedMaskHigh = 0;
|
||||||
uint32_t addedMaskHigh = 0; // Objects 32-63
|
|
||||||
|
|
||||||
// Query all overlapping cells
|
|
||||||
for (int gz = minGz; gz <= maxGz; gz++) {
|
for (int gz = minGz; gz <= maxGz; gz++) {
|
||||||
for (int gy = minGy; gy <= maxGy; gy++) {
|
for (int gy = minGy; gy <= maxGy; gy++) {
|
||||||
for (int gx = minGx; gx <= maxGx; gx++) {
|
for (int gx = minGx; gx <= maxGx; gx++) {
|
||||||
@@ -117,7 +103,6 @@ int SpatialGrid::queryAABB(const AABB& bounds, uint16_t* output, int maxResults)
|
|||||||
for (int i = 0; i < cell.count; i++) {
|
for (int i = 0; i < cell.count; i++) {
|
||||||
uint16_t objIndex = cell.objectIndices[i];
|
uint16_t objIndex = cell.objectIndices[i];
|
||||||
|
|
||||||
// Skip if already added (using bitmask for objects 0-63)
|
|
||||||
if (objIndex < 32) {
|
if (objIndex < 32) {
|
||||||
uint32_t bit = 1U << objIndex;
|
uint32_t bit = 1U << objIndex;
|
||||||
if (addedMaskLow & bit) continue;
|
if (addedMaskLow & bit) continue;
|
||||||
@@ -149,6 +134,7 @@ void CollisionSystem::init() {
|
|||||||
|
|
||||||
void CollisionSystem::reset() {
|
void CollisionSystem::reset() {
|
||||||
m_colliderCount = 0;
|
m_colliderCount = 0;
|
||||||
|
m_triggerBoxCount = 0;
|
||||||
m_resultCount = 0;
|
m_resultCount = 0;
|
||||||
m_triggerPairCount = 0;
|
m_triggerPairCount = 0;
|
||||||
m_grid.clear();
|
m_grid.clear();
|
||||||
@@ -166,6 +152,14 @@ void CollisionSystem::registerCollider(uint16_t gameObjectIndex, const AABB& loc
|
|||||||
data.gameObjectIndex = gameObjectIndex;
|
data.gameObjectIndex = gameObjectIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CollisionSystem::registerTriggerBox(const AABB& bounds, int16_t luaFileIndex) {
|
||||||
|
if (m_triggerBoxCount >= MAX_TRIGGER_BOXES) return;
|
||||||
|
|
||||||
|
TriggerBoxData& tb = m_triggerBoxes[m_triggerBoxCount++];
|
||||||
|
tb.bounds = bounds;
|
||||||
|
tb.luaFileIndex = luaFileIndex;
|
||||||
|
}
|
||||||
|
|
||||||
void CollisionSystem::updateCollider(uint16_t gameObjectIndex, const psyqo::Vec3& position,
|
void CollisionSystem::updateCollider(uint16_t gameObjectIndex, const psyqo::Vec3& position,
|
||||||
const psyqo::Matrix33& rotation) {
|
const psyqo::Matrix33& rotation) {
|
||||||
for (int i = 0; i < m_colliderCount; i++) {
|
for (int i = 0; i < m_colliderCount; i++) {
|
||||||
@@ -177,90 +171,114 @@ void CollisionSystem::updateCollider(uint16_t gameObjectIndex, const psyqo::Vec3
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int CollisionSystem::detectCollisions() {
|
int CollisionSystem::detectCollisions(const AABB& playerAABB, psyqo::Vec3& pushBack) {
|
||||||
m_resultCount = 0;
|
m_resultCount = 0;
|
||||||
|
const FP zero(0);
|
||||||
|
pushBack = psyqo::Vec3{zero, zero, zero};
|
||||||
|
|
||||||
// Clear and rebuild spatial grid
|
// Rebuild spatial grid with all colliders
|
||||||
m_grid.clear();
|
m_grid.clear();
|
||||||
for (int i = 0; i < m_colliderCount; i++) {
|
for (int i = 0; i < m_colliderCount; i++) {
|
||||||
m_grid.insert(i, m_colliders[i].bounds);
|
m_grid.insert(i, m_colliders[i].bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check each collider against potential colliders from grid
|
// Test player AABB against all colliders for push-back
|
||||||
for (int i = 0; i < m_colliderCount; i++) {
|
|
||||||
const CollisionData& colliderA = m_colliders[i];
|
|
||||||
|
|
||||||
// Skip if no collision type
|
|
||||||
if (colliderA.type == CollisionType::None) continue;
|
|
||||||
|
|
||||||
// Query spatial grid for nearby objects
|
|
||||||
uint16_t nearby[32];
|
uint16_t nearby[32];
|
||||||
int nearbyCount = m_grid.queryAABB(colliderA.bounds, nearby, 32);
|
int nearbyCount = m_grid.queryAABB(playerAABB, nearby, 32);
|
||||||
|
|
||||||
for (int j = 0; j < nearbyCount; j++) {
|
for (int j = 0; j < nearbyCount; j++) {
|
||||||
int otherIndex = nearby[j];
|
int idx = nearby[j];
|
||||||
|
const CollisionData& collider = m_colliders[idx];
|
||||||
|
if (collider.type == CollisionType::None) continue;
|
||||||
|
|
||||||
// Skip self
|
|
||||||
if (otherIndex == i) continue;
|
|
||||||
|
|
||||||
// Skip if already processed (only process pairs once)
|
|
||||||
if (otherIndex < i) continue;
|
|
||||||
|
|
||||||
const CollisionData& colliderB = m_colliders[otherIndex];
|
|
||||||
|
|
||||||
// Skip if no collision type
|
|
||||||
if (colliderB.type == CollisionType::None) continue;
|
|
||||||
|
|
||||||
// Check layer masks
|
|
||||||
if ((colliderA.layerMask & colliderB.layerMask) == 0) continue;
|
|
||||||
|
|
||||||
// Narrowphase AABB test
|
|
||||||
psyqo::Vec3 normal;
|
psyqo::Vec3 normal;
|
||||||
psyqo::FixedPoint<12> penetration;
|
psyqo::FixedPoint<12> penetration;
|
||||||
|
|
||||||
if (testAABB(colliderA.bounds, colliderB.bounds, normal, penetration)) {
|
if (testAABB(playerAABB, collider.bounds, normal, penetration)) {
|
||||||
// Collision detected
|
// Accumulate push-back along the separation normal
|
||||||
|
pushBack.x = pushBack.x + normal.x * penetration;
|
||||||
|
pushBack.y = pushBack.y + normal.y * penetration;
|
||||||
|
pushBack.z = pushBack.z + normal.z * penetration;
|
||||||
|
|
||||||
if (m_resultCount < MAX_COLLISION_RESULTS) {
|
if (m_resultCount < MAX_COLLISION_RESULTS) {
|
||||||
CollisionResult& result = m_results[m_resultCount++];
|
CollisionResult& result = m_results[m_resultCount++];
|
||||||
result.objectA = colliderA.gameObjectIndex;
|
result.objectA = 0xFFFF; // player
|
||||||
result.objectB = colliderB.gameObjectIndex;
|
result.objectB = collider.gameObjectIndex;
|
||||||
result.normal = normal;
|
result.normal = normal;
|
||||||
result.penetration = penetration;
|
result.penetration = penetration;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle triggers
|
|
||||||
if (colliderA.type == CollisionType::Trigger) {
|
|
||||||
updateTriggerState(i, otherIndex, true);
|
|
||||||
}
|
|
||||||
if (colliderB.type == CollisionType::Trigger) {
|
|
||||||
updateTriggerState(otherIndex, i, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update trigger pairs that are no longer overlapping
|
|
||||||
for (int i = 0; i < m_triggerPairCount; i++) {
|
|
||||||
TriggerPair& pair = m_triggerPairs[i];
|
|
||||||
pair.framesSinceContact++;
|
|
||||||
|
|
||||||
// If no contact for several frames, trigger exit
|
|
||||||
if (pair.framesSinceContact > 2 && pair.state != 2) {
|
|
||||||
pair.state = 2; // Exiting
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_resultCount;
|
return m_resultCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CollisionSystem::detectTriggers(const AABB& playerAABB, SceneManager& scene) {
|
||||||
|
int writeIndex = 0;
|
||||||
|
|
||||||
|
// Mark all existing pairs as potentially stale
|
||||||
|
for (int i = 0; i < m_triggerPairCount; i++) {
|
||||||
|
m_triggerPairs[i].framesSinceContact++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test player against each trigger box
|
||||||
|
for (int ti = 0; ti < m_triggerBoxCount; ti++) {
|
||||||
|
const TriggerBoxData& tb = m_triggerBoxes[ti];
|
||||||
|
|
||||||
|
if (!playerAABB.intersects(tb.bounds)) continue;
|
||||||
|
|
||||||
|
// Find existing pair
|
||||||
|
bool found = false;
|
||||||
|
for (int pi = 0; pi < m_triggerPairCount; pi++) {
|
||||||
|
if (m_triggerPairs[pi].triggerIndex == ti) {
|
||||||
|
m_triggerPairs[pi].framesSinceContact = 0;
|
||||||
|
if (m_triggerPairs[pi].state == 0) {
|
||||||
|
m_triggerPairs[pi].state = 1; // enter -> active
|
||||||
|
}
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New pair: enter
|
||||||
|
if (!found && m_triggerPairCount < MAX_TRIGGER_PAIRS) {
|
||||||
|
TriggerPair& pair = m_triggerPairs[m_triggerPairCount++];
|
||||||
|
pair.triggerIndex = ti;
|
||||||
|
pair.padding = 0;
|
||||||
|
pair.framesSinceContact = 0;
|
||||||
|
pair.state = 0;
|
||||||
|
pair.padding2 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process pairs: fire events and clean up exited pairs
|
||||||
|
writeIndex = 0;
|
||||||
|
for (int i = 0; i < m_triggerPairCount; i++) {
|
||||||
|
TriggerPair& pair = m_triggerPairs[i];
|
||||||
|
int16_t luaIdx = m_triggerBoxes[pair.triggerIndex].luaFileIndex;
|
||||||
|
|
||||||
|
if (pair.state == 0) {
|
||||||
|
// Enter
|
||||||
|
scene.fireTriggerEnter(luaIdx, pair.triggerIndex);
|
||||||
|
pair.state = 1;
|
||||||
|
m_triggerPairs[writeIndex++] = pair;
|
||||||
|
} else if (pair.framesSinceContact > 2) {
|
||||||
|
// Exit
|
||||||
|
scene.fireTriggerExit(luaIdx, pair.triggerIndex);
|
||||||
|
} else {
|
||||||
|
// Still inside, keep alive
|
||||||
|
m_triggerPairs[writeIndex++] = pair;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_triggerPairCount = writeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
bool CollisionSystem::testAABB(const AABB& a, const AABB& b,
|
bool CollisionSystem::testAABB(const AABB& a, const AABB& b,
|
||||||
psyqo::Vec3& normal, psyqo::FixedPoint<12>& penetration) const {
|
psyqo::Vec3& normal, psyqo::FixedPoint<12>& penetration) const {
|
||||||
// Check for overlap on all axes
|
|
||||||
if (a.max.x < b.min.x || a.min.x > b.max.x) return false;
|
if (a.max.x < b.min.x || a.min.x > b.max.x) return false;
|
||||||
if (a.max.y < b.min.y || a.min.y > b.max.y) return false;
|
if (a.max.y < b.min.y || a.min.y > b.max.y) return false;
|
||||||
if (a.max.z < b.min.z || a.min.z > b.max.z) return false;
|
if (a.max.z < b.min.z || a.min.z > b.max.z) return false;
|
||||||
|
|
||||||
// Calculate penetration on each axis
|
|
||||||
auto overlapX1 = a.max.x - b.min.x;
|
auto overlapX1 = a.max.x - b.min.x;
|
||||||
auto overlapX2 = b.max.x - a.min.x;
|
auto overlapX2 = b.max.x - a.min.x;
|
||||||
auto overlapY1 = a.max.y - b.min.y;
|
auto overlapY1 = a.max.y - b.min.y;
|
||||||
@@ -268,17 +286,14 @@ bool CollisionSystem::testAABB(const AABB& a, const AABB& b,
|
|||||||
auto overlapZ1 = a.max.z - b.min.z;
|
auto overlapZ1 = a.max.z - b.min.z;
|
||||||
auto overlapZ2 = b.max.z - a.min.z;
|
auto overlapZ2 = b.max.z - a.min.z;
|
||||||
|
|
||||||
// Find minimum overlap axis
|
|
||||||
auto minOverlapX = (overlapX1 < overlapX2) ? overlapX1 : overlapX2;
|
auto minOverlapX = (overlapX1 < overlapX2) ? overlapX1 : overlapX2;
|
||||||
auto minOverlapY = (overlapY1 < overlapY2) ? overlapY1 : overlapY2;
|
auto minOverlapY = (overlapY1 < overlapY2) ? overlapY1 : overlapY2;
|
||||||
auto minOverlapZ = (overlapZ1 < overlapZ2) ? overlapZ1 : overlapZ2;
|
auto minOverlapZ = (overlapZ1 < overlapZ2) ? overlapZ1 : overlapZ2;
|
||||||
|
|
||||||
// Constants for normals
|
|
||||||
const FP zero(0);
|
const FP zero(0);
|
||||||
const FP one(1);
|
const FP one(1);
|
||||||
const FP negOne(-1);
|
const FP negOne(-1);
|
||||||
|
|
||||||
// Determine separation axis (axis with least penetration)
|
|
||||||
if (minOverlapX <= minOverlapY && minOverlapX <= minOverlapZ) {
|
if (minOverlapX <= minOverlapY && minOverlapX <= minOverlapZ) {
|
||||||
penetration = minOverlapX;
|
penetration = minOverlapX;
|
||||||
normal = psyqo::Vec3{(overlapX1 < overlapX2) ? negOne : one, zero, zero};
|
normal = psyqo::Vec3{(overlapX1 < overlapX2) ? negOne : one, zero, zero};
|
||||||
@@ -293,31 +308,6 @@ bool CollisionSystem::testAABB(const AABB& a, const AABB& b,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollisionSystem::updateTriggerState(uint16_t triggerIndex, uint16_t otherIndex, bool isOverlapping) {
|
|
||||||
// Look for existing pair
|
|
||||||
for (int i = 0; i < m_triggerPairCount; i++) {
|
|
||||||
TriggerPair& pair = m_triggerPairs[i];
|
|
||||||
if (pair.triggerIndex == triggerIndex && pair.otherIndex == otherIndex) {
|
|
||||||
if (isOverlapping) {
|
|
||||||
pair.framesSinceContact = 0;
|
|
||||||
if (pair.state == 0) {
|
|
||||||
pair.state = 1; // Now staying
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New pair - add it
|
|
||||||
if (isOverlapping && m_triggerPairCount < MAX_TRIGGERS) {
|
|
||||||
TriggerPair& pair = m_triggerPairs[m_triggerPairCount++];
|
|
||||||
pair.triggerIndex = triggerIndex;
|
|
||||||
pair.otherIndex = otherIndex;
|
|
||||||
pair.framesSinceContact = 0;
|
|
||||||
pair.state = 0; // New (enter event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CollisionSystem::areColliding(uint16_t indexA, uint16_t indexB) const {
|
bool CollisionSystem::areColliding(uint16_t indexA, uint16_t indexB) const {
|
||||||
for (int i = 0; i < m_resultCount; i++) {
|
for (int i = 0; i < m_resultCount; i++) {
|
||||||
if ((m_results[i].objectA == indexA && m_results[i].objectB == indexB) ||
|
if ((m_results[i].objectA == indexA && m_results[i].objectB == indexB) ||
|
||||||
@@ -332,32 +322,26 @@ bool CollisionSystem::raycast(const psyqo::Vec3& origin, const psyqo::Vec3& dire
|
|||||||
psyqo::FixedPoint<12> maxDistance,
|
psyqo::FixedPoint<12> maxDistance,
|
||||||
psyqo::Vec3& hitPoint, psyqo::Vec3& hitNormal,
|
psyqo::Vec3& hitPoint, psyqo::Vec3& hitNormal,
|
||||||
uint16_t& hitObjectIndex) const {
|
uint16_t& hitObjectIndex) const {
|
||||||
// Simple brute-force raycast against all colliders
|
|
||||||
// TODO: Use spatial grid for optimization
|
|
||||||
|
|
||||||
auto closestT = maxDistance;
|
auto closestT = maxDistance;
|
||||||
bool hit = false;
|
bool hit = false;
|
||||||
|
|
||||||
// Fixed-point constants
|
|
||||||
const FP zero(0);
|
const FP zero(0);
|
||||||
const FP one(1);
|
const FP one(1);
|
||||||
const FP negOne(-1);
|
const FP negOne(-1);
|
||||||
const FP largeVal(1000);
|
const FP largeVal(1000);
|
||||||
const FP negLargeVal(-1000);
|
const FP negLargeVal(-1000);
|
||||||
FP epsilon;
|
FP epsilon;
|
||||||
epsilon.value = 4; // ~0.001 in 20.12 fixed point
|
epsilon.value = 4;
|
||||||
|
|
||||||
for (int i = 0; i < m_colliderCount; i++) {
|
for (int i = 0; i < m_colliderCount; i++) {
|
||||||
const CollisionData& collider = m_colliders[i];
|
const CollisionData& collider = m_colliders[i];
|
||||||
if (collider.type == CollisionType::None) continue;
|
if (collider.type == CollisionType::None) continue;
|
||||||
|
|
||||||
// Ray-AABB intersection test (slab method)
|
|
||||||
const AABB& box = collider.bounds;
|
const AABB& box = collider.bounds;
|
||||||
|
|
||||||
auto tMin = negLargeVal;
|
auto tMin = negLargeVal;
|
||||||
auto tMax = largeVal;
|
auto tMax = largeVal;
|
||||||
|
|
||||||
// X slab
|
|
||||||
if (direction.x != zero) {
|
if (direction.x != zero) {
|
||||||
auto invD = one / direction.x;
|
auto invD = one / direction.x;
|
||||||
auto t1 = (box.min.x - origin.x) * invD;
|
auto t1 = (box.min.x - origin.x) * invD;
|
||||||
@@ -369,7 +353,6 @@ bool CollisionSystem::raycast(const psyqo::Vec3& origin, const psyqo::Vec3& dire
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Y slab
|
|
||||||
if (direction.y != zero) {
|
if (direction.y != zero) {
|
||||||
auto invD = one / direction.y;
|
auto invD = one / direction.y;
|
||||||
auto t1 = (box.min.y - origin.y) * invD;
|
auto t1 = (box.min.y - origin.y) * invD;
|
||||||
@@ -381,7 +364,6 @@ bool CollisionSystem::raycast(const psyqo::Vec3& origin, const psyqo::Vec3& dire
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Z slab
|
|
||||||
if (direction.z != zero) {
|
if (direction.z != zero) {
|
||||||
auto invD = one / direction.z;
|
auto invD = one / direction.z;
|
||||||
auto t1 = (box.min.z - origin.z) * invD;
|
auto t1 = (box.min.z - origin.z) * invD;
|
||||||
@@ -402,14 +384,12 @@ bool CollisionSystem::raycast(const psyqo::Vec3& origin, const psyqo::Vec3& dire
|
|||||||
hitObjectIndex = collider.gameObjectIndex;
|
hitObjectIndex = collider.gameObjectIndex;
|
||||||
hit = true;
|
hit = true;
|
||||||
|
|
||||||
// Calculate hit point
|
|
||||||
hitPoint = psyqo::Vec3{
|
hitPoint = psyqo::Vec3{
|
||||||
origin.x + direction.x * t,
|
origin.x + direction.x * t,
|
||||||
origin.y + direction.y * t,
|
origin.y + direction.y * t,
|
||||||
origin.z + direction.z * t
|
origin.z + direction.z * t
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate normal (which face was hit)
|
|
||||||
if ((hitPoint.x - box.min.x).abs() < epsilon) hitNormal = psyqo::Vec3{negOne, zero, zero};
|
if ((hitPoint.x - box.min.x).abs() < epsilon) hitNormal = psyqo::Vec3{negOne, zero, zero};
|
||||||
else if ((hitPoint.x - box.max.x).abs() < epsilon) hitNormal = psyqo::Vec3{one, zero, zero};
|
else if ((hitPoint.x - box.max.x).abs() < epsilon) hitNormal = psyqo::Vec3{one, zero, zero};
|
||||||
else if ((hitPoint.y - box.min.y).abs() < epsilon) hitNormal = psyqo::Vec3{zero, negOne, zero};
|
else if ((hitPoint.y - box.min.y).abs() < epsilon) hitNormal = psyqo::Vec3{zero, negOne, zero};
|
||||||
@@ -422,37 +402,4 @@ bool CollisionSystem::raycast(const psyqo::Vec3& origin, const psyqo::Vec3& dire
|
|||||||
return hit;
|
return hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollisionSystem::processTriggerEvents(SceneManager& scene) {
|
|
||||||
// Process trigger pairs and fire Lua events
|
|
||||||
int writeIndex = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < m_triggerPairCount; i++) {
|
|
||||||
TriggerPair& pair = m_triggerPairs[i];
|
|
||||||
|
|
||||||
// Get game object indices
|
|
||||||
uint16_t triggerObjIdx = m_colliders[pair.triggerIndex].gameObjectIndex;
|
|
||||||
uint16_t otherObjIdx = m_colliders[pair.otherIndex].gameObjectIndex;
|
|
||||||
|
|
||||||
switch (pair.state) {
|
|
||||||
case 0: // Enter
|
|
||||||
scene.fireTriggerEnter(triggerObjIdx, otherObjIdx);
|
|
||||||
pair.state = 1; // Move to staying
|
|
||||||
m_triggerPairs[writeIndex++] = pair;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1: // Staying
|
|
||||||
scene.fireTriggerStay(triggerObjIdx, otherObjIdx);
|
|
||||||
m_triggerPairs[writeIndex++] = pair;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2: // Exit
|
|
||||||
scene.fireTriggerExit(triggerObjIdx, otherObjIdx);
|
|
||||||
// Don't copy - remove from list
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_triggerPairCount = writeIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace psxsplash
|
} // namespace psxsplash
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ class SceneManager;
|
|||||||
enum class CollisionType : uint8_t {
|
enum class CollisionType : uint8_t {
|
||||||
None = 0,
|
None = 0,
|
||||||
Solid = 1,
|
Solid = 1,
|
||||||
Trigger = 2,
|
|
||||||
Platform = 3
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using CollisionMask = uint8_t;
|
using CollisionMask = uint8_t;
|
||||||
@@ -23,14 +21,12 @@ struct AABB {
|
|||||||
psyqo::Vec3 min;
|
psyqo::Vec3 min;
|
||||||
psyqo::Vec3 max;
|
psyqo::Vec3 max;
|
||||||
|
|
||||||
// Check if this AABB intersects another
|
|
||||||
bool intersects(const AABB& other) const {
|
bool intersects(const AABB& other) const {
|
||||||
return (min.x <= other.max.x && max.x >= other.min.x) &&
|
return (min.x <= other.max.x && max.x >= other.min.x) &&
|
||||||
(min.y <= other.max.y && max.y >= other.min.y) &&
|
(min.y <= other.max.y && max.y >= other.min.y) &&
|
||||||
(min.z <= other.max.z && max.z >= other.min.z);
|
(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 {
|
bool contains(const psyqo::Vec3& point) const {
|
||||||
return (point.x >= min.x && point.x <= max.x) &&
|
return (point.x >= min.x && point.x <= max.x) &&
|
||||||
(point.y >= min.y && point.y <= max.y) &&
|
(point.y >= min.y && point.y <= max.y) &&
|
||||||
@@ -72,12 +68,18 @@ struct CollisionResult {
|
|||||||
psyqo::FixedPoint<12> penetration;
|
psyqo::FixedPoint<12> penetration;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TriggerBoxData {
|
||||||
|
AABB bounds;
|
||||||
|
int16_t luaFileIndex;
|
||||||
|
uint16_t padding;
|
||||||
|
};
|
||||||
|
|
||||||
struct TriggerPair {
|
struct TriggerPair {
|
||||||
uint16_t triggerIndex;
|
uint16_t triggerIndex;
|
||||||
uint16_t otherIndex;
|
|
||||||
uint8_t framesSinceContact;
|
|
||||||
uint8_t state; // 0=new, 1=staying, 2=exiting
|
|
||||||
uint16_t padding;
|
uint16_t padding;
|
||||||
|
uint8_t framesSinceContact;
|
||||||
|
uint8_t state; // 0=new(enter), 1=active, 2=exiting
|
||||||
|
uint16_t padding2;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SpatialGrid {
|
class SpatialGrid {
|
||||||
@@ -109,7 +111,8 @@ private:
|
|||||||
class CollisionSystem {
|
class CollisionSystem {
|
||||||
public:
|
public:
|
||||||
static constexpr int MAX_COLLIDERS = 64;
|
static constexpr int MAX_COLLIDERS = 64;
|
||||||
static constexpr int MAX_TRIGGERS = 32;
|
static constexpr int MAX_TRIGGER_BOXES = 32;
|
||||||
|
static constexpr int MAX_TRIGGER_PAIRS = 32;
|
||||||
static constexpr int MAX_COLLISION_RESULTS = 32;
|
static constexpr int MAX_COLLISION_RESULTS = 32;
|
||||||
|
|
||||||
CollisionSystem() = default;
|
CollisionSystem() = default;
|
||||||
@@ -122,7 +125,11 @@ public:
|
|||||||
void updateCollider(uint16_t gameObjectIndex, const psyqo::Vec3& position,
|
void updateCollider(uint16_t gameObjectIndex, const psyqo::Vec3& position,
|
||||||
const psyqo::Matrix33& rotation);
|
const psyqo::Matrix33& rotation);
|
||||||
|
|
||||||
int detectCollisions();
|
void registerTriggerBox(const AABB& bounds, int16_t luaFileIndex);
|
||||||
|
|
||||||
|
int detectCollisions(const AABB& playerAABB, psyqo::Vec3& pushBack);
|
||||||
|
|
||||||
|
void detectTriggers(const AABB& playerAABB, class SceneManager& scene);
|
||||||
|
|
||||||
const CollisionResult* getResults() const { return m_results; }
|
const CollisionResult* getResults() const { return m_results; }
|
||||||
int getResultCount() const { return m_resultCount; }
|
int getResultCount() const { return m_resultCount; }
|
||||||
@@ -134,24 +141,25 @@ public:
|
|||||||
psyqo::Vec3& hitPoint, psyqo::Vec3& hitNormal,
|
psyqo::Vec3& hitPoint, psyqo::Vec3& hitNormal,
|
||||||
uint16_t& hitObjectIndex) const;
|
uint16_t& hitObjectIndex) const;
|
||||||
|
|
||||||
void processTriggerEvents(class SceneManager& scene);
|
|
||||||
int getColliderCount() const { return m_colliderCount; }
|
int getColliderCount() const { return m_colliderCount; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CollisionData m_colliders[MAX_COLLIDERS];
|
CollisionData m_colliders[MAX_COLLIDERS];
|
||||||
int m_colliderCount = 0;
|
int m_colliderCount = 0;
|
||||||
|
|
||||||
|
TriggerBoxData m_triggerBoxes[MAX_TRIGGER_BOXES];
|
||||||
|
int m_triggerBoxCount = 0;
|
||||||
|
|
||||||
SpatialGrid m_grid;
|
SpatialGrid m_grid;
|
||||||
|
|
||||||
CollisionResult m_results[MAX_COLLISION_RESULTS];
|
CollisionResult m_results[MAX_COLLISION_RESULTS];
|
||||||
int m_resultCount = 0;
|
int m_resultCount = 0;
|
||||||
|
|
||||||
TriggerPair m_triggerPairs[MAX_TRIGGERS];
|
TriggerPair m_triggerPairs[MAX_TRIGGER_PAIRS];
|
||||||
int m_triggerPairCount = 0;
|
int m_triggerPairCount = 0;
|
||||||
|
|
||||||
bool testAABB(const AABB& a, const AABB& b,
|
bool testAABB(const AABB& a, const AABB& b,
|
||||||
psyqo::Vec3& normal, psyqo::FixedPoint<12>& penetration) const;
|
psyqo::Vec3& normal, psyqo::FixedPoint<12>& penetration) const;
|
||||||
void updateTriggerState(uint16_t triggerIndex, uint16_t otherIndex, bool isOverlapping);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace psxsplash
|
} // namespace psxsplash
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "loadingscreen.hh"
|
#include "loadingscreen.hh"
|
||||||
#include "fileloader.hh"
|
#include "fileloader.hh"
|
||||||
|
#include <psyqo/kernel.hh>
|
||||||
|
extern "C" int ramsyscall_printf(const char*, ...);
|
||||||
#include "renderer.hh"
|
#include "renderer.hh"
|
||||||
|
|
||||||
#include <psyqo/primitives/rectangles.hh>
|
#include <psyqo/primitives/rectangles.hh>
|
||||||
@@ -35,16 +37,22 @@ bool LoadingScreen::load(psyqo::GPU& gpu, psyqo::Font<>& systemFont, int sceneIn
|
|||||||
// Build filename using the active backend's naming convention
|
// Build filename using the active backend's naming convention
|
||||||
char filename[32];
|
char filename[32];
|
||||||
FileLoader::BuildLoadingFilename(sceneIndex, filename, sizeof(filename));
|
FileLoader::BuildLoadingFilename(sceneIndex, filename, sizeof(filename));
|
||||||
|
ramsyscall_printf("LoadingScreen: loading '%s'\n", filename);
|
||||||
|
|
||||||
int fileSize = 0;
|
int fileSize = 0;
|
||||||
uint8_t* data = FileLoader::Get().LoadFileSync(filename, fileSize);
|
uint8_t* data = FileLoader::Get().LoadFileSync(filename, fileSize);
|
||||||
if (!data || fileSize < (int)sizeof(LoaderPackHeader)) {
|
if (!data || fileSize < (int)sizeof(LoaderPackHeader)) {
|
||||||
|
ramsyscall_printf("LoadingScreen: FAILED to load (data=%p size=%d)\n", data, fileSize);
|
||||||
if (data) FileLoader::Get().FreeFile(data);
|
if (data) FileLoader::Get().FreeFile(data);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* header = reinterpret_cast<const LoaderPackHeader*>(data);
|
auto* header = reinterpret_cast<const LoaderPackHeader*>(data);
|
||||||
|
ramsyscall_printf("LoadingScreen: magic='%c%c' ver=%d canvases=%d fonts=%d atlases=%d cluts=%d tableOff=%u\n",
|
||||||
|
header->magic[0], header->magic[1], header->version,
|
||||||
|
header->canvasCount, header->fontCount, header->atlasCount, header->clutCount, header->tableOffset);
|
||||||
if (header->magic[0] != 'L' || header->magic[1] != 'P') {
|
if (header->magic[0] != 'L' || header->magic[1] != 'P') {
|
||||||
|
ramsyscall_printf("LoadingScreen: bad magic, aborting\n");
|
||||||
FileLoader::Get().FreeFile(data);
|
FileLoader::Get().FreeFile(data);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -65,12 +73,17 @@ bool LoadingScreen::load(psyqo::GPU& gpu, psyqo::Font<>& systemFont, int sceneIn
|
|||||||
m_ui.uploadFonts(gpu);
|
m_ui.uploadFonts(gpu);
|
||||||
|
|
||||||
// Ensure canvas 0 is visible
|
// Ensure canvas 0 is visible
|
||||||
|
ramsyscall_printf("LoadingScreen: canvasCount=%d\n", m_ui.getCanvasCount());
|
||||||
if (m_ui.getCanvasCount() > 0) {
|
if (m_ui.getCanvasCount() > 0) {
|
||||||
m_ui.setCanvasVisible(0, true);
|
m_ui.setCanvasVisible(0, true);
|
||||||
|
int elemCount = m_ui.getCanvasElementCount(0);
|
||||||
|
ramsyscall_printf("LoadingScreen: canvas 0 has %d elements\n", elemCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the progress bar named "loading"
|
// Find the progress bar named "loading"
|
||||||
findProgressBar();
|
findProgressBar();
|
||||||
|
ramsyscall_printf("LoadingScreen: hasProgressBar=%d barXY=(%d,%d) barWH=(%d,%d)\n",
|
||||||
|
m_hasProgressBar, m_barX, m_barY, m_barW, m_barH);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
42
src/lua.cpp
42
src/lua.cpp
@@ -349,10 +349,9 @@ void psxsplash::Lua::RegisterGameObject(GameObject* go) {
|
|||||||
// Resolve each event and build the bitmask
|
// Resolve each event and build the bitmask
|
||||||
// Only events that exist in the script get their bit set
|
// Only events that exist in the script get their bit set
|
||||||
if (onCreateMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_CREATE;
|
if (onCreateMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_CREATE;
|
||||||
if (onCollisionMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_COLLISION;
|
if (onCollideWithPlayerMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_COLLISION;
|
||||||
if (onInteractMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_INTERACT;
|
if (onInteractMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_INTERACT;
|
||||||
if (onTriggerEnterMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_ENTER;
|
if (onTriggerEnterMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_ENTER;
|
||||||
if (onTriggerStayMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_STAY;
|
|
||||||
if (onTriggerExitMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_EXIT;
|
if (onTriggerExitMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_TRIGGER_EXIT;
|
||||||
if (onUpdateMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_UPDATE;
|
if (onUpdateMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_UPDATE;
|
||||||
if (onDestroyMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_DESTROY;
|
if (onDestroyMethodWrapper.resolveGlobal(L)) eventMask |= EVENT_ON_DESTROY;
|
||||||
@@ -379,9 +378,9 @@ void psxsplash::Lua::RegisterGameObject(GameObject* go) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void psxsplash::Lua::OnCollision(GameObject* self, GameObject* other) {
|
void psxsplash::Lua::OnCollideWithPlayer(GameObject* self) {
|
||||||
if (!hasEvent(self, EVENT_ON_COLLISION)) return;
|
if (!hasEvent(self, EVENT_ON_COLLISION)) return;
|
||||||
onCollisionMethodWrapper.callMethod(*this, self, other);
|
onCollideWithPlayerMethodWrapper.callMethod(*this, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
void psxsplash::Lua::OnInteract(GameObject* self) {
|
void psxsplash::Lua::OnInteract(GameObject* self) {
|
||||||
@@ -394,16 +393,41 @@ void psxsplash::Lua::OnTriggerEnter(GameObject* trigger, GameObject* other) {
|
|||||||
onTriggerEnterMethodWrapper.callMethod(*this, trigger, other);
|
onTriggerEnterMethodWrapper.callMethod(*this, trigger, other);
|
||||||
}
|
}
|
||||||
|
|
||||||
void psxsplash::Lua::OnTriggerStay(GameObject* trigger, GameObject* other) {
|
|
||||||
if (!hasEvent(trigger, EVENT_ON_TRIGGER_STAY)) return;
|
|
||||||
onTriggerStayMethodWrapper.callMethod(*this, trigger, other);
|
|
||||||
}
|
|
||||||
|
|
||||||
void psxsplash::Lua::OnTriggerExit(GameObject* trigger, GameObject* other) {
|
void psxsplash::Lua::OnTriggerExit(GameObject* trigger, GameObject* other) {
|
||||||
if (!hasEvent(trigger, EVENT_ON_TRIGGER_EXIT)) return;
|
if (!hasEvent(trigger, EVENT_ON_TRIGGER_EXIT)) return;
|
||||||
onTriggerExitMethodWrapper.callMethod(*this, trigger, other);
|
onTriggerExitMethodWrapper.callMethod(*this, trigger, other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void psxsplash::Lua::OnTriggerEnterScript(int luaFileIndex, int triggerIndex) {
|
||||||
|
auto L = m_state;
|
||||||
|
L.rawGetI(LUA_REGISTRYINDEX, m_luascriptsReference);
|
||||||
|
L.rawGetI(-1, luaFileIndex);
|
||||||
|
if (!L.isTable(-1)) { L.clearStack(); return; }
|
||||||
|
L.push("onTriggerEnter", 14);
|
||||||
|
L.getTable(-2);
|
||||||
|
if (!L.isFunction(-1)) { L.clearStack(); return; }
|
||||||
|
L.pushNumber(triggerIndex);
|
||||||
|
if (L.pcall(1, 0) != LUA_OK) {
|
||||||
|
printf("Lua error: %s\n", L.toString(-1));
|
||||||
|
}
|
||||||
|
L.clearStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
void psxsplash::Lua::OnTriggerExitScript(int luaFileIndex, int triggerIndex) {
|
||||||
|
auto L = m_state;
|
||||||
|
L.rawGetI(LUA_REGISTRYINDEX, m_luascriptsReference);
|
||||||
|
L.rawGetI(-1, luaFileIndex);
|
||||||
|
if (!L.isTable(-1)) { L.clearStack(); return; }
|
||||||
|
L.push("onTriggerExit", 13);
|
||||||
|
L.getTable(-2);
|
||||||
|
if (!L.isFunction(-1)) { L.clearStack(); return; }
|
||||||
|
L.pushNumber(triggerIndex);
|
||||||
|
if (L.pcall(1, 0) != LUA_OK) {
|
||||||
|
printf("Lua error: %s\n", L.toString(-1));
|
||||||
|
}
|
||||||
|
L.clearStack();
|
||||||
|
}
|
||||||
|
|
||||||
void psxsplash::Lua::OnDestroy(GameObject* go) {
|
void psxsplash::Lua::OnDestroy(GameObject* go) {
|
||||||
if (!hasEvent(go, EVENT_ON_DESTROY)) return;
|
if (!hasEvent(go, EVENT_ON_DESTROY)) return;
|
||||||
onDestroyMethodWrapper.callMethod(*this, go);
|
onDestroyMethodWrapper.callMethod(*this, go);
|
||||||
|
|||||||
39
src/lua.h
39
src/lua.h
@@ -31,14 +31,13 @@ enum EventMask : uint32_t {
|
|||||||
EVENT_ON_COLLISION = 1 << 1,
|
EVENT_ON_COLLISION = 1 << 1,
|
||||||
EVENT_ON_INTERACT = 1 << 2,
|
EVENT_ON_INTERACT = 1 << 2,
|
||||||
EVENT_ON_TRIGGER_ENTER = 1 << 3,
|
EVENT_ON_TRIGGER_ENTER = 1 << 3,
|
||||||
EVENT_ON_TRIGGER_STAY = 1 << 4,
|
EVENT_ON_TRIGGER_EXIT = 1 << 4,
|
||||||
EVENT_ON_TRIGGER_EXIT = 1 << 5,
|
EVENT_ON_UPDATE = 1 << 5,
|
||||||
EVENT_ON_UPDATE = 1 << 6,
|
EVENT_ON_DESTROY = 1 << 6,
|
||||||
EVENT_ON_DESTROY = 1 << 7,
|
EVENT_ON_ENABLE = 1 << 7,
|
||||||
EVENT_ON_ENABLE = 1 << 8,
|
EVENT_ON_DISABLE = 1 << 8,
|
||||||
EVENT_ON_DISABLE = 1 << 9,
|
EVENT_ON_BUTTON_PRESS = 1 << 9,
|
||||||
EVENT_ON_BUTTON_PRESS = 1 << 10,
|
EVENT_ON_BUTTON_RELEASE = 1 << 10,
|
||||||
EVENT_ON_BUTTON_RELEASE = 1 << 11,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Lua {
|
class Lua {
|
||||||
@@ -70,11 +69,12 @@ class Lua {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Event dispatchers - these check the bitmask before calling Lua
|
// Event dispatchers - these check the bitmask before calling Lua
|
||||||
void OnCollision(GameObject* self, GameObject* other);
|
void OnCollideWithPlayer(GameObject* self);
|
||||||
void OnInteract(GameObject* self);
|
void OnInteract(GameObject* self);
|
||||||
void OnTriggerEnter(GameObject* trigger, GameObject* other);
|
void OnTriggerEnter(GameObject* trigger, GameObject* other);
|
||||||
void OnTriggerStay(GameObject* trigger, GameObject* other);
|
|
||||||
void OnTriggerExit(GameObject* trigger, GameObject* other);
|
void OnTriggerExit(GameObject* trigger, GameObject* other);
|
||||||
|
void OnTriggerEnterScript(int luaFileIndex, int triggerIndex);
|
||||||
|
void OnTriggerExitScript(int luaFileIndex, int triggerIndex);
|
||||||
void OnUpdate(GameObject* go, int deltaFrames); // Per-object update
|
void OnUpdate(GameObject* go, int deltaFrames); // Per-object update
|
||||||
void OnDestroy(GameObject* go);
|
void OnDestroy(GameObject* go);
|
||||||
void OnEnable(GameObject* go);
|
void OnEnable(GameObject* go);
|
||||||
@@ -157,19 +157,18 @@ class Lua {
|
|||||||
[[no_unique_address]] FunctionWrapper<1, typestring_is("onSceneCreationStart")> onSceneCreationStartFunctionWrapper;
|
[[no_unique_address]] FunctionWrapper<1, typestring_is("onSceneCreationStart")> onSceneCreationStartFunctionWrapper;
|
||||||
[[no_unique_address]] FunctionWrapper<2, typestring_is("onSceneCreationEnd")> onSceneCreationEndFunctionWrapper;
|
[[no_unique_address]] FunctionWrapper<2, typestring_is("onSceneCreationEnd")> onSceneCreationEndFunctionWrapper;
|
||||||
|
|
||||||
// Object-level events (methodId 100-111, offset to avoid collision with scene events)
|
// Object-level events (methodId 100+, offset to avoid collision with scene events)
|
||||||
[[no_unique_address]] FunctionWrapper<100, typestring_is("onCreate")> onCreateMethodWrapper;
|
[[no_unique_address]] FunctionWrapper<100, typestring_is("onCreate")> onCreateMethodWrapper;
|
||||||
[[no_unique_address]] FunctionWrapper<101, typestring_is("onCollision")> onCollisionMethodWrapper;
|
[[no_unique_address]] FunctionWrapper<101, typestring_is("onCollideWithPlayer")> onCollideWithPlayerMethodWrapper;
|
||||||
[[no_unique_address]] FunctionWrapper<102, typestring_is("onInteract")> onInteractMethodWrapper;
|
[[no_unique_address]] FunctionWrapper<102, typestring_is("onInteract")> onInteractMethodWrapper;
|
||||||
[[no_unique_address]] FunctionWrapper<103, typestring_is("onTriggerEnter")> onTriggerEnterMethodWrapper;
|
[[no_unique_address]] FunctionWrapper<103, typestring_is("onTriggerEnter")> onTriggerEnterMethodWrapper;
|
||||||
[[no_unique_address]] FunctionWrapper<104, typestring_is("onTriggerStay")> onTriggerStayMethodWrapper;
|
[[no_unique_address]] FunctionWrapper<104, typestring_is("onTriggerExit")> onTriggerExitMethodWrapper;
|
||||||
[[no_unique_address]] FunctionWrapper<105, typestring_is("onTriggerExit")> onTriggerExitMethodWrapper;
|
[[no_unique_address]] FunctionWrapper<105, typestring_is("onUpdate")> onUpdateMethodWrapper;
|
||||||
[[no_unique_address]] FunctionWrapper<106, typestring_is("onUpdate")> onUpdateMethodWrapper;
|
[[no_unique_address]] FunctionWrapper<106, typestring_is("onDestroy")> onDestroyMethodWrapper;
|
||||||
[[no_unique_address]] FunctionWrapper<107, typestring_is("onDestroy")> onDestroyMethodWrapper;
|
[[no_unique_address]] FunctionWrapper<107, typestring_is("onEnable")> onEnableMethodWrapper;
|
||||||
[[no_unique_address]] FunctionWrapper<108, typestring_is("onEnable")> onEnableMethodWrapper;
|
[[no_unique_address]] FunctionWrapper<108, typestring_is("onDisable")> onDisableMethodWrapper;
|
||||||
[[no_unique_address]] FunctionWrapper<109, typestring_is("onDisable")> onDisableMethodWrapper;
|
[[no_unique_address]] FunctionWrapper<109, typestring_is("onButtonPress")> onButtonPressMethodWrapper;
|
||||||
[[no_unique_address]] FunctionWrapper<110, typestring_is("onButtonPress")> onButtonPressMethodWrapper;
|
[[no_unique_address]] FunctionWrapper<110, typestring_is("onButtonRelease")> onButtonReleaseMethodWrapper;
|
||||||
[[no_unique_address]] FunctionWrapper<111, typestring_is("onButtonRelease")> onButtonReleaseMethodWrapper;
|
|
||||||
|
|
||||||
void PushGameObject(GameObject* go);
|
void PushGameObject(GameObject* go);
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,6 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
|
|||||||
SPLASHPACKCollider* collider = sceneSetup.colliders[i];
|
SPLASHPACKCollider* collider = sceneSetup.colliders[i];
|
||||||
if (collider == nullptr) continue;
|
if (collider == nullptr) continue;
|
||||||
|
|
||||||
// Convert fixed-point values from binary format to AABB
|
|
||||||
AABB bounds;
|
AABB bounds;
|
||||||
bounds.min.x.value = collider->minX;
|
bounds.min.x.value = collider->minX;
|
||||||
bounds.min.y.value = collider->minY;
|
bounds.min.y.value = collider->minY;
|
||||||
@@ -187,10 +186,8 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
|
|||||||
bounds.max.y.value = collider->maxY;
|
bounds.max.y.value = collider->maxY;
|
||||||
bounds.max.z.value = collider->maxZ;
|
bounds.max.z.value = collider->maxZ;
|
||||||
|
|
||||||
// Convert collision type
|
|
||||||
CollisionType type = static_cast<CollisionType>(collider->collisionType);
|
CollisionType type = static_cast<CollisionType>(collider->collisionType);
|
||||||
|
|
||||||
// Register with collision system
|
|
||||||
m_collisionSystem.registerCollider(
|
m_collisionSystem.registerCollider(
|
||||||
collider->gameObjectIndex,
|
collider->gameObjectIndex,
|
||||||
bounds,
|
bounds,
|
||||||
@@ -199,6 +196,22 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData, LoadingSc
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register trigger boxes from splashpack data
|
||||||
|
for (size_t i = 0; i < sceneSetup.triggerBoxes.size(); i++) {
|
||||||
|
SPLASHPACKTriggerBox* tb = sceneSetup.triggerBoxes[i];
|
||||||
|
if (tb == nullptr) continue;
|
||||||
|
|
||||||
|
AABB bounds;
|
||||||
|
bounds.min.x.value = tb->minX;
|
||||||
|
bounds.min.y.value = tb->minY;
|
||||||
|
bounds.min.z.value = tb->minZ;
|
||||||
|
bounds.max.x.value = tb->maxX;
|
||||||
|
bounds.max.y.value = tb->maxY;
|
||||||
|
bounds.max.z.value = tb->maxZ;
|
||||||
|
|
||||||
|
m_collisionSystem.registerTriggerBox(bounds, tb->luaFileIndex);
|
||||||
|
}
|
||||||
|
|
||||||
// Load Lua files - order is important here. We need
|
// Load Lua files - order is important here. We need
|
||||||
// to load the Lua files before we register the game objects,
|
// to load the Lua files before we register the game objects,
|
||||||
// as the game objects may reference Lua files by index.
|
// as the game objects may reference Lua files by index.
|
||||||
@@ -289,21 +302,44 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
|
|||||||
|
|
||||||
// Collision detection
|
// Collision detection
|
||||||
uint32_t collisionStart = gpu.now();
|
uint32_t collisionStart = gpu.now();
|
||||||
int collisionCount = m_collisionSystem.detectCollisions();
|
|
||||||
|
|
||||||
// Process solid collisions - call OnCollision on BOTH objects
|
// Build player AABB from position + radius/height
|
||||||
|
AABB playerAABB;
|
||||||
|
{
|
||||||
|
psyqo::FixedPoint<12> r;
|
||||||
|
r.value = m_playerRadius;
|
||||||
|
psyqo::FixedPoint<12> px = static_cast<psyqo::FixedPoint<12>>(m_playerPosition.x);
|
||||||
|
psyqo::FixedPoint<12> py = static_cast<psyqo::FixedPoint<12>>(m_playerPosition.y);
|
||||||
|
psyqo::FixedPoint<12> pz = static_cast<psyqo::FixedPoint<12>>(m_playerPosition.z);
|
||||||
|
psyqo::FixedPoint<12> h = static_cast<psyqo::FixedPoint<12>>(m_playerHeight);
|
||||||
|
playerAABB.min = psyqo::Vec3{px - r, py - h, pz - r};
|
||||||
|
playerAABB.max = psyqo::Vec3{px + r, py, pz + r};
|
||||||
|
}
|
||||||
|
|
||||||
|
psyqo::Vec3 pushBack;
|
||||||
|
int collisionCount = m_collisionSystem.detectCollisions(playerAABB, pushBack);
|
||||||
|
|
||||||
|
// Apply push-back to player position
|
||||||
|
{
|
||||||
|
psyqo::FixedPoint<12> zero;
|
||||||
|
if (pushBack.x != zero || pushBack.z != zero) {
|
||||||
|
m_playerPosition.x = m_playerPosition.x + pushBack.x;
|
||||||
|
m_playerPosition.z = m_playerPosition.z + pushBack.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire onCollideWithPlayer Lua events on collided objects
|
||||||
const CollisionResult* results = m_collisionSystem.getResults();
|
const CollisionResult* results = m_collisionSystem.getResults();
|
||||||
for (int i = 0; i < collisionCount; i++) {
|
for (int i = 0; i < collisionCount; i++) {
|
||||||
auto* objA = getGameObject(results[i].objectA);
|
if (results[i].objectA != 0xFFFF) continue;
|
||||||
auto* objB = getGameObject(results[i].objectB);
|
auto* obj = getGameObject(results[i].objectB);
|
||||||
if (objA && objB) {
|
if (obj) {
|
||||||
L.OnCollision(objA, objB);
|
L.OnCollideWithPlayer(obj);
|
||||||
L.OnCollision(objB, objA); // Call on both objects
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process trigger events (enter/stay/exit)
|
// Process trigger boxes (enter/exit)
|
||||||
m_collisionSystem.processTriggerEvents(*this);
|
m_collisionSystem.detectTriggers(playerAABB, *this);
|
||||||
|
|
||||||
gpu.pumpCallbacks();
|
gpu.pumpCallbacks();
|
||||||
uint32_t collisionEnd = gpu.now();
|
uint32_t collisionEnd = gpu.now();
|
||||||
@@ -489,29 +525,14 @@ void psxsplash::SceneManager::GameTick(psyqo::GPU &gpu) {
|
|||||||
processPendingSceneLoad();
|
processPendingSceneLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger event callbacks
|
void psxsplash::SceneManager::fireTriggerEnter(int16_t luaFileIndex, uint16_t triggerIndex) {
|
||||||
void psxsplash::SceneManager::fireTriggerEnter(uint16_t triggerObjIdx, uint16_t otherObjIdx) {
|
if (luaFileIndex < 0) return;
|
||||||
auto* trigger = getGameObject(triggerObjIdx);
|
L.OnTriggerEnterScript(luaFileIndex, triggerIndex);
|
||||||
auto* other = getGameObject(otherObjIdx);
|
|
||||||
if (trigger && other) {
|
|
||||||
L.OnTriggerEnter(trigger, other);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void psxsplash::SceneManager::fireTriggerStay(uint16_t triggerObjIdx, uint16_t otherObjIdx) {
|
void psxsplash::SceneManager::fireTriggerExit(int16_t luaFileIndex, uint16_t triggerIndex) {
|
||||||
auto* trigger = getGameObject(triggerObjIdx);
|
if (luaFileIndex < 0) return;
|
||||||
auto* other = getGameObject(otherObjIdx);
|
L.OnTriggerExitScript(luaFileIndex, triggerIndex);
|
||||||
if (trigger && other) {
|
|
||||||
L.OnTriggerStay(trigger, other);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void psxsplash::SceneManager::fireTriggerExit(uint16_t triggerObjIdx, uint16_t otherObjIdx) {
|
|
||||||
auto* trigger = getGameObject(triggerObjIdx);
|
|
||||||
auto* other = getGameObject(otherObjIdx);
|
|
||||||
if (trigger && other) {
|
|
||||||
L.OnTriggerExit(trigger, other);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -746,8 +767,12 @@ void psxsplash::SceneManager::shrinkBuffer() {
|
|||||||
if (m_liveDataSize == 0 || m_currentSceneData == nullptr) return;
|
if (m_liveDataSize == 0 || m_currentSceneData == nullptr) return;
|
||||||
|
|
||||||
uint8_t* oldBase = m_currentSceneData;
|
uint8_t* oldBase = m_currentSceneData;
|
||||||
uint8_t* newBase = new uint8_t[m_liveDataSize];
|
// Allocate the shrunk buffer. The volatile cast prevents the compiler
|
||||||
if (!newBase) return;
|
// from assuming operator new never returns NULL (it does with
|
||||||
|
// -fno-exceptions), which would let it optimize away the null check.
|
||||||
|
uint8_t* volatile newBaseV = new uint8_t[m_liveDataSize];
|
||||||
|
uint8_t* newBase = newBaseV;
|
||||||
|
if (!newBase) return; // Heap exhausted — keep the full buffer
|
||||||
__builtin_memcpy(newBase, oldBase, m_liveDataSize);
|
__builtin_memcpy(newBase, oldBase, m_liveDataSize);
|
||||||
|
|
||||||
intptr_t delta = reinterpret_cast<intptr_t>(newBase) - reinterpret_cast<intptr_t>(oldBase);
|
intptr_t delta = reinterpret_cast<intptr_t>(newBase) - reinterpret_cast<intptr_t>(oldBase);
|
||||||
|
|||||||
@@ -36,10 +36,9 @@ class SceneManager {
|
|||||||
static void SetFont(psyqo::Font<>* font) { s_font = font; }
|
static void SetFont(psyqo::Font<>* font) { s_font = font; }
|
||||||
static psyqo::Font<>* GetFont() { return s_font; }
|
static psyqo::Font<>* GetFont() { return s_font; }
|
||||||
|
|
||||||
// Trigger event callbacks (called by CollisionSystem)
|
// Trigger event callbacks (called by CollisionSystem for trigger boxes)
|
||||||
void fireTriggerEnter(uint16_t triggerObjIdx, uint16_t otherObjIdx);
|
void fireTriggerEnter(int16_t luaFileIndex, uint16_t triggerIndex);
|
||||||
void fireTriggerStay(uint16_t triggerObjIdx, uint16_t otherObjIdx);
|
void fireTriggerExit(int16_t luaFileIndex, uint16_t triggerIndex);
|
||||||
void fireTriggerExit(uint16_t triggerObjIdx, uint16_t otherObjIdx);
|
|
||||||
|
|
||||||
// Get game object by index (for collision callbacks)
|
// Get game object by index (for collision callbacks)
|
||||||
GameObject* getGameObject(uint16_t index) {
|
GameObject* getGameObject(uint16_t index) {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ struct SPLASHPACKFileHeader {
|
|||||||
uint16_t bvhNodeCount;
|
uint16_t bvhNodeCount;
|
||||||
uint16_t bvhTriangleRefCount;
|
uint16_t bvhTriangleRefCount;
|
||||||
uint16_t sceneType;
|
uint16_t sceneType;
|
||||||
uint16_t pad0;
|
uint16_t triggerBoxCount;
|
||||||
uint16_t worldCollisionMeshCount;
|
uint16_t worldCollisionMeshCount;
|
||||||
uint16_t worldCollisionTriCount;
|
uint16_t worldCollisionTriCount;
|
||||||
uint16_t navRegionCount;
|
uint16_t navRegionCount;
|
||||||
@@ -93,7 +93,7 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
|||||||
psyqo::Kernel::assert(data != nullptr, "Splashpack loading data pointer is null");
|
psyqo::Kernel::assert(data != nullptr, "Splashpack loading data pointer is null");
|
||||||
psxsplash::SPLASHPACKFileHeader *header = reinterpret_cast<psxsplash::SPLASHPACKFileHeader *>(data);
|
psxsplash::SPLASHPACKFileHeader *header = reinterpret_cast<psxsplash::SPLASHPACKFileHeader *>(data);
|
||||||
psyqo::Kernel::assert(__builtin_memcmp(header->magic, "SP", 2) == 0, "Splashpack has incorrect magic");
|
psyqo::Kernel::assert(__builtin_memcmp(header->magic, "SP", 2) == 0, "Splashpack has incorrect magic");
|
||||||
psyqo::Kernel::assert(header->version >= 15, "Splashpack version too old (need v15+): re-export from SplashEdit");
|
psyqo::Kernel::assert(header->version >= 16, "Splashpack version too old (need v16+): re-export from SplashEdit");
|
||||||
|
|
||||||
setup.playerStartPosition = header->playerStartPos;
|
setup.playerStartPosition = header->playerStartPos;
|
||||||
setup.playerStartRotation = header->playerStartRot;
|
setup.playerStartRotation = header->playerStartRot;
|
||||||
@@ -137,6 +137,14 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
|||||||
cursor += sizeof(psxsplash::SPLASHPACKCollider);
|
cursor += sizeof(psxsplash::SPLASHPACKCollider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read trigger boxes (after colliders)
|
||||||
|
setup.triggerBoxes.reserve(header->triggerBoxCount);
|
||||||
|
for (uint16_t i = 0; i < header->triggerBoxCount; i++) {
|
||||||
|
psxsplash::SPLASHPACKTriggerBox *tb = reinterpret_cast<psxsplash::SPLASHPACKTriggerBox *>(cursor);
|
||||||
|
setup.triggerBoxes.push_back(tb);
|
||||||
|
cursor += sizeof(psxsplash::SPLASHPACKTriggerBox);
|
||||||
|
}
|
||||||
|
|
||||||
// BVH data
|
// BVH data
|
||||||
if (header->bvhNodeCount > 0) {
|
if (header->bvhNodeCount > 0) {
|
||||||
BVHNode* bvhNodes = reinterpret_cast<BVHNode*>(cursor);
|
BVHNode* bvhNodes = reinterpret_cast<BVHNode*>(cursor);
|
||||||
|
|||||||
@@ -19,25 +19,34 @@ namespace psxsplash {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Collision data as stored in the binary file (fixed layout for serialization)
|
* Collision data as stored in the binary file (fixed layout for serialization)
|
||||||
* This is the binary-compatible version of CollisionData
|
|
||||||
*/
|
*/
|
||||||
struct SPLASHPACKCollider {
|
struct SPLASHPACKCollider {
|
||||||
// AABB bounds in fixed-point (24 bytes)
|
// AABB bounds in fixed-point (24 bytes)
|
||||||
int32_t minX, minY, minZ;
|
int32_t minX, minY, minZ;
|
||||||
int32_t maxX, maxY, maxZ;
|
int32_t maxX, maxY, maxZ;
|
||||||
// Collision metadata (8 bytes)
|
// Collision metadata (8 bytes)
|
||||||
uint8_t collisionType; // CollisionType enum
|
uint8_t collisionType;
|
||||||
uint8_t layerMask; // Which layers this collides with
|
uint8_t layerMask;
|
||||||
uint16_t gameObjectIndex; // Which GameObject this belongs to
|
uint16_t gameObjectIndex;
|
||||||
uint32_t padding; // Alignment padding
|
uint32_t padding;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(SPLASHPACKCollider) == 32, "SPLASHPACKCollider must be 32 bytes");
|
static_assert(sizeof(SPLASHPACKCollider) == 32, "SPLASHPACKCollider must be 32 bytes");
|
||||||
|
|
||||||
|
struct SPLASHPACKTriggerBox {
|
||||||
|
int32_t minX, minY, minZ;
|
||||||
|
int32_t maxX, maxY, maxZ;
|
||||||
|
int16_t luaFileIndex;
|
||||||
|
uint16_t padding;
|
||||||
|
uint32_t padding2;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(SPLASHPACKTriggerBox) == 32, "SPLASHPACKTriggerBox must be 32 bytes");
|
||||||
|
|
||||||
struct SplashpackSceneSetup {
|
struct SplashpackSceneSetup {
|
||||||
int sceneLuaFileIndex;
|
int sceneLuaFileIndex;
|
||||||
eastl::vector<LuaFile *> luaFiles;
|
eastl::vector<LuaFile *> luaFiles;
|
||||||
eastl::vector<GameObject *> objects;
|
eastl::vector<GameObject *> objects;
|
||||||
eastl::vector<SPLASHPACKCollider *> colliders;
|
eastl::vector<SPLASHPACKCollider *> colliders;
|
||||||
|
eastl::vector<SPLASHPACKTriggerBox *> triggerBoxes;
|
||||||
|
|
||||||
// New component arrays
|
// New component arrays
|
||||||
eastl::vector<Interactable *> interactables;
|
eastl::vector<Interactable *> interactables;
|
||||||
|
|||||||
@@ -438,9 +438,6 @@ psyqo::Vec3 WorldCollision::moveAndSlide(const psyqo::Vec3& oldPos,
|
|||||||
const auto& tri = m_triangles[mesh.firstTriangle + ti];
|
const auto& tri = m_triangles[mesh.firstTriangle + ti];
|
||||||
triTests++;
|
triTests++;
|
||||||
|
|
||||||
// Skip trigger surfaces
|
|
||||||
if (tri.flags & SURFACE_TRIGGER) continue;
|
|
||||||
|
|
||||||
// Skip floor and ceiling triangles — Y is resolved by nav regions.
|
// Skip floor and ceiling triangles — Y is resolved by nav regions.
|
||||||
// In PS1 space (Y-down): floor normals have ny < 0, ceiling ny > 0.
|
// In PS1 space (Y-down): floor normals have ny < 0, ceiling ny > 0.
|
||||||
// If |ny| > walkable slope threshold, it's a floor/ceiling, not a wall.
|
// If |ny| > walkable slope threshold, it's a floor/ceiling, not a wall.
|
||||||
@@ -511,7 +508,6 @@ bool WorldCollision::groundTrace(const psyqo::Vec3& pos,
|
|||||||
|
|
||||||
for (int ti = 0; ti < mesh.triangleCount; ti++) {
|
for (int ti = 0; ti < mesh.triangleCount; ti++) {
|
||||||
const auto& tri = m_triangles[mesh.firstTriangle + ti];
|
const auto& tri = m_triangles[mesh.firstTriangle + ti];
|
||||||
if (tri.flags & SURFACE_TRIGGER) continue;
|
|
||||||
|
|
||||||
int32_t t = rayVsTriangle(ox, oy, oz, dx, dy, dz, tri);
|
int32_t t = rayVsTriangle(ox, oy, oz, dx, dy, dz, tri);
|
||||||
if (t >= 0 && t < bestDist) {
|
if (t >= 0 && t < bestDist) {
|
||||||
@@ -560,7 +556,6 @@ bool WorldCollision::ceilingTrace(const psyqo::Vec3& pos,
|
|||||||
|
|
||||||
for (int ti = 0; ti < mesh.triangleCount; ti++) {
|
for (int ti = 0; ti < mesh.triangleCount; ti++) {
|
||||||
const auto& tri = m_triangles[mesh.firstTriangle + ti];
|
const auto& tri = m_triangles[mesh.firstTriangle + ti];
|
||||||
if (tri.flags & SURFACE_TRIGGER) continue;
|
|
||||||
|
|
||||||
int32_t t = rayVsTriangle(ox, oy, oz, dx, dy, dz, tri);
|
int32_t t = rayVsTriangle(ox, oy, oz, dx, dy, dz, tri);
|
||||||
if (t >= 0 && t < bestDist) {
|
if (t >= 0 && t < bestDist) {
|
||||||
|
|||||||
@@ -22,11 +22,10 @@ namespace psxsplash {
|
|||||||
// Surface flags — packed per-triangle, exported from SplashEdit
|
// Surface flags — packed per-triangle, exported from SplashEdit
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
enum SurfaceFlag : uint8_t {
|
enum SurfaceFlag : uint8_t {
|
||||||
SURFACE_SOLID = 0x01, // Normal solid wall/floor
|
SURFACE_SOLID = 0x01,
|
||||||
SURFACE_SLOPE = 0x02, // Steep slope (treated as wall for movement)
|
SURFACE_SLOPE = 0x02,
|
||||||
SURFACE_STAIRS = 0x04, // Staircase (smooth Y interpolation)
|
SURFACE_STAIRS = 0x04,
|
||||||
SURFACE_TRIGGER = 0x08, // Non-solid trigger volume
|
SURFACE_NO_WALK = 0x10,
|
||||||
SURFACE_NO_WALK = 0x10, // Marks geometry as non-walkable floor
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user