diff --git a/src/engine/math_util.c b/src/engine/math_util.c index a8fc7189..4670ffcc 100644 --- a/src/engine/math_util.c +++ b/src/engine/math_util.c @@ -1116,3 +1116,165 @@ s32 anim_spline_poll(Vec3f result) { return hasEnded; } + +/************************************************** + * RAYCASTING * + **************************************************/ + +#define RAY_OFFSET 30.0f /*How many units to extrapolate surfaces when testing for a raycast*/ +#define RAY_STEPS 4 /*How many steps to do when casting rays, default to quartersteps.*/ + +s32 ray_surface_intersect(Vec3f orig, Vec3f dir, f32 dir_length, struct Surface *surface, Vec3f hit_pos, f32 *length) { + Vec3f v0, v1, v2, e1, e2, h, s, q; + f32 a, f, u, v; + Vec3f add_dir; + Vec3f norm; + // Ignore certain surface types. + if (surface->type == SURFACE_INTANGIBLE || surface->flags & SURFACE_FLAG_NO_CAM_COLLISION) return FALSE; + // Get surface normal and some other stuff + vec3_set(norm, 0, surface->normal.y, 0); + vec3_mul_val(norm, RAY_OFFSET); + vec3_copy(v0, surface->vertex1); + vec3_copy(v1, surface->vertex2); + vec3_copy(v2, surface->vertex3); + vec3_add( v0, norm); + vec3_add( v1, norm); + vec3_add( v2, norm); + vec3_diff(e1, v1, v0); + vec3_diff(e2, v2, v0); + vec3_cross(h, dir, e2); + // Check if we're perpendicular from the surface + a = vec3_dot(e1, h); + if ((a > -NEAR_ZERO) && (a < NEAR_ZERO)) return FALSE; + // Check if we're making contact with the surface + f = 1.0f / a; + vec3_diff(s, orig, v0); + u = f * vec3_dot(s, h); + if ((u < 0.0f) || (u > 1.0f)) return FALSE; + vec3_cross(q, s, e1); + v = f * vec3_dot(dir, q); + if ((v < 0.0f) || ((u + v) > 1.0f)) return FALSE; + // Get the length between our origin and the surface contact point + *length = f * vec3_dot(e2, q); + if (*length <= 0.00001 || *length > dir_length) return FALSE; + // Successful contact + vec3f_copy(add_dir, dir); + vec3_mul_val(add_dir, *length); + vec3_sum(hit_pos, orig, add_dir); + return TRUE; +} + +void find_surface_on_ray_list(struct SurfaceNode *list, Vec3f orig, Vec3f dir, f32 dir_length, struct Surface **hit_surface, Vec3f hit_pos, f32 *max_length) { + s32 hit; + f32 length; + Vec3f chk_hit_pos; + f32 top, bottom; + #if PUPPYPRINT_DEBUG + OSTime first = osGetTime(); + #endif + + // Get upper and lower bounds of ray + if (dir[1] >= 0.0f) { + top = (orig[1] + (dir[1] * dir_length)); + bottom = orig[1]; + } else { + top = orig[1]; + bottom = (orig[1] + (dir[1] * dir_length)); + } + + // Iterate through every surface of the list + for (; list != NULL; list = list->next) { + // Reject surface if out of vertical bounds + if (list->surface->lowerY > top || list->surface->upperY < bottom) continue; + // Check intersection between the ray and this surface + if ((hit = ray_surface_intersect(orig, dir, dir_length, list->surface, chk_hit_pos, &length)) && (length <= *max_length)) { + *hit_surface = list->surface; + vec3f_copy(hit_pos, chk_hit_pos); + *max_length = length; + } + } +#if PUPPYPRINT_DEBUG + collisionTime[perfIteration] += (osGetTime() - first); +#endif +} + +void find_surface_on_ray_cell(s32 cellX, s32 cellZ, Vec3f orig, Vec3f normalized_dir, f32 dir_length, struct Surface **hit_surface, Vec3f hit_pos, f32 *max_length, s32 flags) { + // Skip if OOB + if (cellX >= 0 && cellX <= (NUM_CELLS - 1) && cellZ >= 0 && cellZ <= (NUM_CELLS - 1)) { + // Iterate through each surface in this partition + if ((normalized_dir[1] > -NEAR_ONE) && (flags & RAYCAST_FIND_CEIL)) { + find_surface_on_ray_list(gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_CEILS].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); + find_surface_on_ray_list(gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_CEILS].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); + } + if ((normalized_dir[1] < NEAR_ONE) && (flags & RAYCAST_FIND_FLOOR)) { + find_surface_on_ray_list(gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_FLOORS].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); + find_surface_on_ray_list(gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_FLOORS].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); + } + if (flags & RAYCAST_FIND_WALL) { + find_surface_on_ray_list(gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_WALLS].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); + find_surface_on_ray_list(gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_WALLS].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); + } + if (flags & RAYCAST_FIND_WATER) { + find_surface_on_ray_list(gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_WATER].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); + find_surface_on_ray_list(gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_WATER].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); + } + } +} + +void find_surface_on_ray(Vec3f orig, Vec3f dir, struct Surface **hit_surface, Vec3f hit_pos, s32 flags) { + s32 cellZ, cellX, cellPrevX, cellPrevZ; + f32 fCellZ, fCellX; + Vec3f normalized_dir; + f32 step; + s32 i; + + // Set that no surface has been hit + *hit_surface = NULL; + vec3_sum(hit_pos, orig, dir); + + // Get normalized direction + f32 dir_length = vec3_mag(dir); + f32 max_length = dir_length; + vec3f_copy(normalized_dir, dir); + vec3f_normalize(normalized_dir); + + // Get our cell coordinate + fCellX = (orig[0] + LEVEL_BOUNDARY_MAX) / CELL_SIZE; + fCellZ = (orig[2] + LEVEL_BOUNDARY_MAX) / CELL_SIZE; + cellX = fCellX; + cellZ = fCellZ; + cellPrevX = cellX; + cellPrevZ = cellZ; + + // Don't do DDA if straight down + if (normalized_dir[1] >= 0.99999f || normalized_dir[1] <= -0.99999f) { + find_surface_on_ray_cell(cellX, cellZ, orig, normalized_dir, dir_length, hit_surface, hit_pos, &max_length, flags); + return; + } + + // Get cells we cross using DDA + if (ABS(dir[0]) >= ABS(dir[2])) { + step = RAY_STEPS * ABS(dir[0]) / CELL_SIZE; + } else { + step = RAY_STEPS * ABS(dir[2]) / CELL_SIZE; + } + f32 dx = dir[0] / step / CELL_SIZE; + f32 dz = dir[2] / step / CELL_SIZE; + + for (i = 0; i < step && *hit_surface == NULL; i++) { + find_surface_on_ray_cell(cellX, cellZ, orig, normalized_dir, dir_length, hit_surface, hit_pos, &max_length, flags); + + // Move cell coordinate + fCellX += dx; + fCellZ += dz; + cellPrevX = cellX; + cellPrevZ = cellZ; + cellX = fCellX; + cellZ = fCellZ; + + if ((cellPrevX != cellX) && (cellPrevZ != cellZ)) { + find_surface_on_ray_cell(cellX, cellPrevZ, orig, normalized_dir, dir_length, hit_surface, hit_pos, &max_length, flags); + find_surface_on_ray_cell(cellPrevX, cellZ, orig, normalized_dir, dir_length, hit_surface, hit_pos, &max_length, flags); + } + } +} diff --git a/src/engine/math_util.h b/src/engine/math_util.h index c43ea5ce..5098ee3a 100644 --- a/src/engine/math_util.h +++ b/src/engine/math_util.h @@ -530,5 +530,6 @@ void spline_get_weights(Vec4f result, f32 t, UNUSED s32 c); void anim_spline_init(Vec4s *keyFrames); s32 anim_spline_poll(Vec3f result); void mtxf_rot_trans_mul(Vec3s rot, Vec3f trans, Mat4 dest, Mat4 src); +void find_surface_on_ray(Vec3f orig, Vec3f dir, struct Surface **hit_surface, Vec3f hit_pos, s32 flags); #endif // MATH_UTIL_H diff --git a/src/engine/surface_collision.c b/src/engine/surface_collision.c index c86a39cb..11524535 100644 --- a/src/engine/surface_collision.c +++ b/src/engine/surface_collision.c @@ -832,166 +832,3 @@ s32 unused_resolve_floor_or_ceil_collisions(s32 checkCeil, f32 *px, f32 *py, f32 return 0; } - -/************************************************** - * RAYCASTING * - **************************************************/ - -#define RAY_OFFSET 30.0f /*How many units to extrapolate surfaces when testing for a raycast*/ -#define RAY_STEPS 4 /*How many steps to do when casting rays, default to quartersteps.*/ - -s32 ray_surface_intersect(Vec3f orig, Vec3f dir, f32 dir_length, struct Surface *surface, Vec3f hit_pos, f32 *length) { - Vec3f v0, v1, v2, e1, e2, h, s, q; - f32 a, f, u, v; - Vec3f add_dir; - Vec3f norm; - // Ignore certain surface types. - if (surface->type == SURFACE_INTANGIBLE || surface->flags & SURFACE_FLAG_NO_CAM_COLLISION) return FALSE; - // Get surface normal and some other stuff - vec3_set(norm, 0, surface->normal.y, 0); - vec3_mul_val(norm, RAY_OFFSET); - vec3_copy(v0, surface->vertex1); - vec3_copy(v1, surface->vertex2); - vec3_copy(v2, surface->vertex3); - vec3_add( v0, norm); - vec3_add( v1, norm); - vec3_add( v2, norm); - vec3_diff(e1, v1, v0); - vec3_diff(e2, v2, v0); - vec3_cross(h, dir, e2); - // Check if we're perpendicular from the surface - a = vec3_dot(e1, h); - if ((a > -NEAR_ZERO) && (a < NEAR_ZERO)) return FALSE; - // Check if we're making contact with the surface - f = 1.0f / a; - vec3_diff(s, orig, v0); - u = f * vec3_dot(s, h); - if ((u < 0.0f) || (u > 1.0f)) return FALSE; - vec3_cross(q, s, e1); - v = f * vec3_dot(dir, q); - if ((v < 0.0f) || ((u + v) > 1.0f)) return FALSE; - // Get the length between our origin and the surface contact point - *length = f * vec3_dot(e2, q); - if (*length <= 0.00001 || *length > dir_length) return FALSE; - // Successful contact - vec3f_copy(add_dir, dir); - vec3_mul_val(add_dir, *length); - vec3_sum(hit_pos, orig, add_dir); - return TRUE; -} - -void find_surface_on_ray_list(struct SurfaceNode *list, Vec3f orig, Vec3f dir, f32 dir_length, struct Surface **hit_surface, Vec3f hit_pos, f32 *max_length) { - s32 hit; - f32 length; - Vec3f chk_hit_pos; - f32 top, bottom; - #if PUPPYPRINT_DEBUG - OSTime first = osGetTime(); - #endif - - // Get upper and lower bounds of ray - if (dir[1] >= 0.0f) { - top = (orig[1] + (dir[1] * dir_length)); - bottom = orig[1]; - } else { - top = orig[1]; - bottom = (orig[1] + (dir[1] * dir_length)); - } - - // Iterate through every surface of the list - for (; list != NULL; list = list->next) { - // Reject surface if out of vertical bounds - if (list->surface->lowerY > top || list->surface->upperY < bottom) continue; - // Check intersection between the ray and this surface - if ((hit = ray_surface_intersect(orig, dir, dir_length, list->surface, chk_hit_pos, &length)) && (length <= *max_length)) { - *hit_surface = list->surface; - vec3f_copy(hit_pos, chk_hit_pos); - *max_length = length; - } - } -#if PUPPYPRINT_DEBUG - collisionTime[perfIteration] += (osGetTime() - first); -#endif -} - -void find_surface_on_ray_cell(s32 cellX, s32 cellZ, Vec3f orig, Vec3f normalized_dir, f32 dir_length, struct Surface **hit_surface, Vec3f hit_pos, f32 *max_length, s32 flags) { - // Skip if OOB - if (cellX >= 0 && cellX <= (NUM_CELLS - 1) && cellZ >= 0 && cellZ <= (NUM_CELLS - 1)) { - // Iterate through each surface in this partition - if ((normalized_dir[1] > -NEAR_ONE) && (flags & RAYCAST_FIND_CEIL)) { - find_surface_on_ray_list(gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_CEILS].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); - find_surface_on_ray_list(gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_CEILS].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); - } - if ((normalized_dir[1] < NEAR_ONE) && (flags & RAYCAST_FIND_FLOOR)) { - find_surface_on_ray_list(gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_FLOORS].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); - find_surface_on_ray_list(gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_FLOORS].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); - } - if (flags & RAYCAST_FIND_WALL) { - find_surface_on_ray_list(gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_WALLS].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); - find_surface_on_ray_list(gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_WALLS].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); - } - if (flags & RAYCAST_FIND_WATER) { - find_surface_on_ray_list(gStaticSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_WATER].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); - find_surface_on_ray_list(gDynamicSurfacePartition[cellZ][cellX][SPATIAL_PARTITION_WATER].next, orig, normalized_dir, dir_length, hit_surface, hit_pos, max_length); - } - } -} - -void find_surface_on_ray(Vec3f orig, Vec3f dir, struct Surface **hit_surface, Vec3f hit_pos, s32 flags) { - s32 cellZ, cellX, cellPrevX, cellPrevZ; - f32 fCellZ, fCellX; - Vec3f normalized_dir; - f32 step; - s32 i; - - // Set that no surface has been hit - *hit_surface = NULL; - vec3_sum(hit_pos, orig, dir); - - // Get normalized direction - f32 dir_length = vec3_mag(dir); - f32 max_length = dir_length; - vec3f_copy(normalized_dir, dir); - vec3f_normalize(normalized_dir); - - // Get our cell coordinate - fCellX = (orig[0] + LEVEL_BOUNDARY_MAX) / CELL_SIZE; - fCellZ = (orig[2] + LEVEL_BOUNDARY_MAX) / CELL_SIZE; - cellX = fCellX; - cellZ = fCellZ; - cellPrevX = cellX; - cellPrevZ = cellZ; - - // Don't do DDA if straight down - if (normalized_dir[1] >= 0.99999f || normalized_dir[1] <= -0.99999f) { - find_surface_on_ray_cell(cellX, cellZ, orig, normalized_dir, dir_length, hit_surface, hit_pos, &max_length, flags); - return; - } - - // Get cells we cross using DDA - if (ABS(dir[0]) >= ABS(dir[2])) { - step = RAY_STEPS * ABS(dir[0]) / CELL_SIZE; - } else { - step = RAY_STEPS * ABS(dir[2]) / CELL_SIZE; - } - f32 dx = dir[0] / step / CELL_SIZE; - f32 dz = dir[2] / step / CELL_SIZE; - - for (i = 0; i < step && *hit_surface == NULL; i++) { - find_surface_on_ray_cell(cellX, cellZ, orig, normalized_dir, dir_length, hit_surface, hit_pos, &max_length, flags); - - // Move cell coordinate - fCellX += dx; - fCellZ += dz; - cellPrevX = cellX; - cellPrevZ = cellZ; - cellX = fCellX; - cellZ = fCellZ; - - if ((cellPrevX != cellX) && (cellPrevZ != cellZ)) { - find_surface_on_ray_cell(cellX, cellPrevZ, orig, normalized_dir, dir_length, hit_surface, hit_pos, &max_length, flags); - find_surface_on_ray_cell(cellPrevX, cellZ, orig, normalized_dir, dir_length, hit_surface, hit_pos, &max_length, flags); - } - } -} - diff --git a/src/engine/surface_collision.h b/src/engine/surface_collision.h index eb174505..81a2a4bf 100644 --- a/src/engine/surface_collision.h +++ b/src/engine/surface_collision.h @@ -46,6 +46,5 @@ s32 find_water_level_and_floor(s32 x, s32 z, struct Surface **pfloor); s32 find_water_level(s32 x, s32 z); s32 find_poison_gas_level(s32 x, s32 z); void debug_surface_list_info(f32 xPos, f32 zPos); -void find_surface_on_ray(Vec3f orig, Vec3f dir, struct Surface **hit_surface, Vec3f hit_pos, s32 flags); #endif // SURFACE_COLLISION_H