Fix collision bugs setting now allows for non-axis-aligned walls to be correct

This commit is contained in:
MysterD 2022-06-01 01:00:00 -07:00
parent 3e46cc1161
commit 7b7e2245aa
8 changed files with 267 additions and 80 deletions

View file

@ -134,8 +134,15 @@ void *vec3f_cross(Vec3f dest, Vec3f a, Vec3f b) {
/// Scale vector 'dest' so it has length 1
void *vec3f_normalize(Vec3f dest) {
//! Possible division by zero
f32 invsqrt = 1.0f / sqrtf(dest[0] * dest[0] + dest[1] * dest[1] + dest[2] * dest[2]);
f32 div = sqrtf(dest[0] * dest[0] + dest[1] * dest[1] + dest[2] * dest[2]);
if (div == 0) {
dest[0] = 0;
dest[1] = 0;
dest[2] = 0;
return dest;
}
f32 invsqrt = 1.0f / div;
dest[0] *= invsqrt;
dest[1] *= invsqrt;

View file

@ -11,6 +11,89 @@
#include "game/game_init.h"
#include "pc/utils/misc.h"
#include "pc/network/network.h"
#include "src/game/debug.h" // DO NOT COMMIT
Vec3f gFindWallDirection = { 0 };
u8 gFindWallDirectionActive = false;
#define CLAMP(_val, _min, _max) MAX(MIN((_val), _max), _min)
static void closest_point_to_triangle(struct Surface* surf, Vec3f src, Vec3f out) {
Vec3f v1; vec3s_to_vec3f(v1, surf->vertex1);
Vec3f v2; vec3s_to_vec3f(v2, surf->vertex2);
Vec3f v3; vec3s_to_vec3f(v3, surf->vertex3);
Vec3f edge0; vec3f_dif(edge0, v2, v1);
Vec3f edge1; vec3f_dif(edge1, v3, v1);
Vec3f v0; vec3f_dif(v0, v1, src);
f32 a = vec3f_dot(edge0, edge0);
f32 b = vec3f_dot(edge0, edge1);
f32 c = vec3f_dot(edge1, edge1);
f32 d = vec3f_dot(edge0, v0);
f32 e = vec3f_dot(edge1, v0);
f32 det = (a * c) - (b * b);
f32 s = (b * e) - (c * d);
f32 t = (b * d) - (a * e);
if ((s + t) < det) {
if (s < 0) {
if (t < 0) {
if (d < 0) {
s = CLAMP(-d/a, 0, 1);
t = 0;
} else {
s = 0;
t = CLAMP(-e/c, 0, 1);
}
} else {
s = 0;
t = CLAMP(-e/c, 0, 1);
}
} else if (t < 0) {
s = CLAMP(-d/a, 0, 1);
t = 0;
} else {
f32 invDet = 1 / det;
s *= invDet;
t *= invDet;
}
} else {
if (s < 0) {
f32 tmp0 = (b + d);
f32 tmp1 = (c + e);
if (tmp1 > tmp0) {
f32 numer = tmp1 - tmp0;
f32 denom = a-2*b+c;
s = CLAMP(numer/denom, 0, 1);
t = (1 - s);
} else {
t = CLAMP(-e/c, 0, 1);
s = 0;
}
} else if (t < 0.f) {
if ((a + d) > (b + e)) {
f32 numer = c+e-b-d;
f32 denom = a-2*b+c;
s = CLAMP(numer/denom, 0, 1);
t = (1 - s);
} else {
s = CLAMP(-e/c, 0, 1);
t = 0;
}
} else {
f32 numer = c+e-b-d;
f32 denom = a-2*b+c;
s = CLAMP(numer/denom, 0, 1);
t = 1 - s;
}
}
out[0] = v1[0] + s * edge0[0] + t * edge1[0];
out[1] = v1[1] + s * edge0[1] + t * edge1[1];
out[2] = v1[2] + s * edge0[2] + t * edge1[2];
}
/**************************************************
* WALLS *
@ -33,6 +116,9 @@ static s32 find_wall_collisions_from_list(struct SurfaceNode *surfaceNode,
register f32 y1, y2, y3;
s32 numCols = 0;
Vec3f cPos = { 0 };
Vec3f cNorm = { 0 };
// Max collision radius = 200
if (radius > 200.0f) {
radius = 200.0f;
@ -48,66 +134,106 @@ static s32 find_wall_collisions_from_list(struct SurfaceNode *surfaceNode,
continue;
}
offset = surf->normal.x * x + surf->normal.y * y + surf->normal.z * z + surf->originOffset;
if (gServerSettings.fixCollisionBugs) {
// Check AABB to exclude walls before doing expensive triangle check
f32 minX = MIN(MIN(surf->vertex1[0], surf->vertex2[0]), surf->vertex3[0]) - radius;
f32 minZ = MIN(MIN(surf->vertex1[2], surf->vertex2[2]), surf->vertex3[2]) - radius;
f32 maxX = MAX(MAX(surf->vertex1[0], surf->vertex2[0]), surf->vertex3[0]) + radius;
f32 maxZ = MAX(MAX(surf->vertex1[2], surf->vertex2[2]), surf->vertex3[2]) + radius;
if (x < minX || x > maxX) { continue; }
if (z < minZ || z > maxZ) { continue; }
if (offset < -radius || offset > radius) {
continue;
}
px = x;
pz = z;
//! (Quantum Tunneling) Due to issues with the vertices walls choose and
// the fact they are floating point, certain floating point positions
// along the seam of two walls may collide with neither wall or both walls.
if (surf->flags & SURFACE_FLAG_X_PROJECTION) {
w1 = -surf->vertex1[2]; w2 = -surf->vertex2[2]; w3 = -surf->vertex3[2];
y1 = surf->vertex1[1]; y2 = surf->vertex2[1]; y3 = surf->vertex3[1];
if (surf->normal.x > 0.0f) {
if ((y1 - y) * (w2 - w1) - (w1 - -pz) * (y2 - y1) > 0.0f) {
continue;
}
if ((y2 - y) * (w3 - w2) - (w2 - -pz) * (y3 - y2) > 0.0f) {
continue;
}
if ((y3 - y) * (w1 - w3) - (w3 - -pz) * (y1 - y3) > 0.0f) {
continue;
}
} else {
if ((y1 - y) * (w2 - w1) - (w1 - -pz) * (y2 - y1) < 0.0f) {
continue;
}
if ((y2 - y) * (w3 - w2) - (w2 - -pz) * (y3 - y2) < 0.0f) {
continue;
}
if ((y3 - y) * (w1 - w3) - (w3 - -pz) * (y1 - y3) < 0.0f) {
// Exclude triangles from wrong movement side
Vec3f norm = { surf->normal.x, surf->normal.y, surf->normal.z };
if (gFindWallDirectionActive) {
if (vec3f_dot(norm, gFindWallDirection) > 0) {
continue;
}
}
} else {
w1 = surf->vertex1[0]; w2 = surf->vertex2[0]; w3 = surf->vertex3[0];
y1 = surf->vertex1[1]; y2 = surf->vertex2[1]; y3 = surf->vertex3[1];
if (surf->normal.z > 0.0f) {
if ((y1 - y) * (w2 - w1) - (w1 - px) * (y2 - y1) > 0.0f) {
continue;
}
if ((y2 - y) * (w3 - w2) - (w2 - px) * (y3 - y2) > 0.0f) {
continue;
}
if ((y3 - y) * (w1 - w3) - (w3 - px) * (y1 - y3) > 0.0f) {
continue;
// Find closest point to triangle
Vec3f src = { x, y, z };
closest_point_to_triangle(surf, src, cPos);
// Exclude triangles where y isn't inside of it
if (fabs(cPos[1] - y) > 1) { continue; }
// Figure out normal
f32 dX = src[0] - cPos[0];
f32 dZ = src[2] - cPos[2];
f32 dist = sqrtf(dX * dX + dZ * dZ);
if (dist > radius) { continue; }
cNorm[0] = dX / dist;
cNorm[1] = 0;
cNorm[2] = dZ / dist;
// Exclude triangles that are colliding from the wrong side
if (!gFindWallDirectionActive && vec3f_dot(norm, cNorm) < 0) { continue; }
} else {
offset = surf->normal.x * x + surf->normal.y * y + surf->normal.z * z + surf->originOffset;
if (offset < -radius || offset > radius) {
continue;
}
px = x;
pz = z;
//! (Quantum Tunneling) Due to issues with the vertices walls choose and
// the fact they are floating point, certain floating point positions
// along the seam of two walls may collide with neither wall or both walls.
if (surf->flags & SURFACE_FLAG_X_PROJECTION) {
w1 = -surf->vertex1[2]; w2 = -surf->vertex2[2]; w3 = -surf->vertex3[2];
y1 = surf->vertex1[1]; y2 = surf->vertex2[1]; y3 = surf->vertex3[1];
if (surf->normal.x > 0.0f) {
if ((y1 - y) * (w2 - w1) - (w1 - -pz) * (y2 - y1) > 0.0f) {
continue;
}
if ((y2 - y) * (w3 - w2) - (w2 - -pz) * (y3 - y2) > 0.0f) {
continue;
}
if ((y3 - y) * (w1 - w3) - (w3 - -pz) * (y1 - y3) > 0.0f) {
continue;
}
} else {
if ((y1 - y) * (w2 - w1) - (w1 - -pz) * (y2 - y1) < 0.0f) {
continue;
}
if ((y2 - y) * (w3 - w2) - (w2 - -pz) * (y3 - y2) < 0.0f) {
continue;
}
if ((y3 - y) * (w1 - w3) - (w3 - -pz) * (y1 - y3) < 0.0f) {
continue;
}
}
} else {
if ((y1 - y) * (w2 - w1) - (w1 - px) * (y2 - y1) < 0.0f) {
continue;
}
if ((y2 - y) * (w3 - w2) - (w2 - px) * (y3 - y2) < 0.0f) {
continue;
}
if ((y3 - y) * (w1 - w3) - (w3 - px) * (y1 - y3) < 0.0f) {
continue;
w1 = surf->vertex1[0]; w2 = surf->vertex2[0]; w3 = surf->vertex3[0];
y1 = surf->vertex1[1]; y2 = surf->vertex2[1]; y3 = surf->vertex3[1];
if (surf->normal.z > 0.0f) {
if ((y1 - y) * (w2 - w1) - (w1 - px) * (y2 - y1) > 0.0f) {
continue;
}
if ((y2 - y) * (w3 - w2) - (w2 - px) * (y3 - y2) > 0.0f) {
continue;
}
if ((y3 - y) * (w1 - w3) - (w3 - px) * (y1 - y3) > 0.0f) {
continue;
}
} else {
if ((y1 - y) * (w2 - w1) - (w1 - px) * (y2 - y1) < 0.0f) {
continue;
}
if ((y2 - y) * (w3 - w2) - (w2 - px) * (y3 - y2) < 0.0f) {
continue;
}
if ((y3 - y) * (w1 - w3) - (w3 - px) * (y1 - y3) < 0.0f) {
continue;
}
}
}
}
@ -147,11 +273,17 @@ static s32 find_wall_collisions_from_list(struct SurfaceNode *surfaceNode,
//! (Wall Overlaps) Because this doesn't update the x and z local variables,
// multiple walls can push mario more than is required.
// <Fixed when gServerSettings.fixCollisionBugs != 0>
data->x += surf->normal.x * (radius - offset);
data->z += surf->normal.z * (radius - offset);
if (gServerSettings.fixCollisionBugs) {
data->x = cPos[0] + cNorm[0] * radius;
data->z = cPos[2] + cNorm[2] * radius;
x = data->x;
z = data->z;
data->normalAddition[0] += cNorm[0];
data->normalAddition[2] += cNorm[2];
data->normalCount++;
} else {
data->x += surf->normal.x * (radius - offset);
data->z += surf->normal.z * (radius - offset);
}
//! (Unreferenced Walls) Since this only returns the first four walls,

View file

@ -23,6 +23,8 @@ struct WallCollisionData
/*0x14*/ s16 unused;
/*0x16*/ s16 numWalls;
/*0x18*/ struct Surface *walls[4];
/*????*/ Vec3f normalAddition;
/*????*/ u8 normalCount;
};
struct FloorGeometry
@ -34,6 +36,9 @@ struct FloorGeometry
f32 originOffset;
};
extern Vec3f gFindWallDirection;
extern u8 gFindWallDirectionActive;
s32 f32_find_wall_collision(f32 *xPtr, f32 *yPtr, f32 *zPtr, f32 offsetY, f32 radius);
s32 find_wall_collisions(struct WallCollisionData *colData);
f32 find_ceil(f32 posX, f32 posY, f32 posZ, struct Surface **pceil);

View file

@ -4,6 +4,7 @@
#include "debug.h"
#include "engine/behavior_script.h"
#include "engine/surface_collision.h"
#include "game/level_update.h"
#include "game_init.h"
#include "main.h"
#include "object_constants.h"
@ -568,3 +569,15 @@ void debug_enemy_unknown(s16 *enemyArr) {
enemyArr[6] = gDebugInfo[DEBUG_PAGE_ENEMYINFO][3];
enemyArr[7] = gDebugInfo[DEBUG_PAGE_ENEMYINFO][4];
}
void debug_position(f32 x, f32 y, f32 z, bool red) {
struct Object* player = gMarioStates[0].marioObj;
if (player == NULL) { return; }
struct Object* obj = spawn_object(player, red ? MODEL_RED_COIN : MODEL_YELLOW_COIN, bhvSparkle);
if (obj != NULL) {
obj_scale(obj, 0.25f);
obj->oPosX = x;
obj->oPosY = y;
obj->oPosZ = z;
}
}

View file

@ -25,4 +25,6 @@ void try_print_debug_mario_object_info(void);
void try_do_mario_debug_object_spawn(void);
void try_print_debug_mario_level_info(void);
void debug_position(f32 x, f32 y, f32 z, bool red);
#endif // DEBUG_H

View file

@ -623,9 +623,9 @@ f32 vec3f_find_ceil(Vec3f pos, f32 height, struct Surface **ceil) {
// Prevent exposed ceilings
f32 vec3f_mario_ceil(Vec3f pos, f32 height, struct Surface **ceil) {
if (gServerSettings.fixCollisionBugs) {
height = MAX(height, pos[1]);
height = MAX(height + 80.0f, pos[1] - 2);
}
return vec3f_find_ceil(pos, height, ceil);
return find_ceil(pos[0], height, pos[2], ceil);
}
/**
@ -2273,17 +2273,15 @@ void mario_update_wall(struct MarioState* m, struct WallCollisionData* wcd) {
? wcd->walls[wcd->numWalls - 1]
: NULL;
vec3f_set(m->wallNormal, 0, 0, 0);
for (u8 i = 0; i < wcd->numWalls; i++) {
if (!gServerSettings.fixCollisionBugs) {
i = (wcd->numWalls - 1);
}
struct Surface* wall = wcd->walls[i];
Vec3f normal = { wall->normal.x, wall->normal.y, wall->normal.z };
vec3f_add(m->wallNormal, normal);
}
if (m->wall) {
vec3f_normalize(m->wallNormal);
if (gServerSettings.fixCollisionBugs && wcd->normalCount > 0) {
vec3f_set(m->wallNormal,
wcd->normalAddition[0] / wcd->normalCount,
wcd->normalAddition[1] / wcd->normalCount,
wcd->normalAddition[2] / wcd->normalCount);
} else if (m->wall) {
vec3f_set(m->wallNormal,
m->wall->normal.x,
m->wall->normal.y,
m->wall->normal.z);
}
}

View file

@ -334,11 +334,24 @@ s32 perform_ground_step(struct MarioState *m) {
smlua_call_event_hooks_mario_param(HOOK_BEFORE_PHYS_STEP, m);
for (i = 0; i < 4; i++) {
intendedPos[0] = m->pos[0] + m->floor->normal.y * (m->vel[0] / 4.0f);
intendedPos[2] = m->pos[2] + m->floor->normal.y * (m->vel[2] / 4.0f);
intendedPos[1] = m->pos[1];
Vec3f step = {
m->floor->normal.y * (m->vel[0] / 4.0f),
0,
m->floor->normal.y * (m->vel[2] / 4.0f),
};
intendedPos[0] = m->pos[0] + step[0];
intendedPos[1] = m->pos[1];
intendedPos[2] = m->pos[2] + step[2];
vec3f_normalize(step);
vec3f_copy(gFindWallDirection, step);
gFindWallDirectionActive = true;
stepResult = perform_ground_quarter_step(m, intendedPos);
gFindWallDirectionActive = false;
if (stepResult == GROUND_STEP_LEFT_GROUND || stepResult == GROUND_STEP_HIT_WALL_STOP_QSTEPS) {
break;
}
@ -665,11 +678,22 @@ s32 perform_air_step(struct MarioState *m, u32 stepArg) {
m->wall = NULL;
for (i = 0; i < 4; i++) {
intendedPos[0] = m->pos[0] + m->vel[0] / 4.0f;
intendedPos[1] = m->pos[1] + m->vel[1] / 4.0f;
intendedPos[2] = m->pos[2] + m->vel[2] / 4.0f;
Vec3f step = {
m->vel[0] / 4.0f,
m->vel[1] / 4.0f,
m->vel[2] / 4.0f,
};
intendedPos[0] = m->pos[0] + step[0];
intendedPos[1] = m->pos[1] + step[1];
intendedPos[2] = m->pos[2] + step[2];
vec3f_normalize(step);
vec3f_copy(gFindWallDirection, step);
gFindWallDirectionActive = true;
quarterStepResult = perform_air_quarter_step(m, intendedPos, stepArg);
gFindWallDirectionActive = false;
//! On one qf, hit OOB/ceil/wall to store the 2 return value, and continue
// getting 0s until your last qf. Graze a wall on your last qf, and it will

View file

@ -170,9 +170,15 @@ s8 obj_find_wall(f32 objNewX, f32 objY, f32 objNewZ, f32 objVelX, f32 objVelZ) {
o->oPosY = hitbox.y;
o->oPosZ = hitbox.z;
wall_nX = hitbox.walls[0]->normal.x;
wall_nY = hitbox.walls[0]->normal.y;
wall_nZ = hitbox.walls[0]->normal.z;
if (gServerSettings.fixCollisionBugs && hitbox.normalCount > 0) {
wall_nX = hitbox.normalAddition[0] / hitbox.normalCount;
wall_nY = hitbox.normalAddition[1] / hitbox.normalCount;
wall_nZ = hitbox.normalAddition[2] / hitbox.normalCount;
} else {
wall_nX = hitbox.walls[0]->normal.x;
wall_nY = hitbox.walls[0]->normal.y;
wall_nZ = hitbox.walls[0]->normal.z;
}
objVelXCopy = objVelX;
objVelZCopy = objVelZ;