bullet raycast against spatial grid

This commit is contained in:
jacob 2026-02-10 17:29:05 -06:00
parent 19b7601c5f
commit 45ac2bf1af
3 changed files with 178 additions and 48 deletions

View File

@ -1233,10 +1233,10 @@ P_Space P_SpaceFromEnts(Arena *arena, P_Frame *frame)
{
i64 cell_idx = y * P_WorldPitch + x;
P_SpaceCell *cell = &space.cells[cell_idx];
P_SpaceEntry *entry = PushStruct(arena, P_SpaceEntry);
SllStackPush(cell->first, entry);
entry->shape = shape;
entry->shape_id = ent->key.v;
P_SpaceEntryNode *entry_node = PushStruct(arena, P_SpaceEntryNode);
SllStackPush(cell->first, entry_node);
entry_node->entry.shape = shape;
entry_node->entry.shape_id = ent->key.v;
}
}
}
@ -1386,22 +1386,25 @@ P_Space P_SpaceFromWalls(Arena *arena, P_Frame *frame)
aabb.p1.x = MaxF32(aabb.p1.x, aabb.p0.x + 1);
aabb.p1.y = MaxF32(aabb.p1.y, aabb.p0.y + 1);
aabb = IntersectRng2(aabb, space_aabb);
u64 id = P_WallShapeIDBasis ^ MixU64s(
((u64)wall->start.x << 0) | ((u64)wall->start.y << 32),
((u64)wall->end.x << 0) | ((u64)wall->end.y << 32)
);
u64 id = P_WallShapeIDBasis ^ wall->dir;
id = MixU64s(id, (((u64)wall->start.x) | ((u64)wall->start.y << 32)));
id = MixU64s(id, (((u64)wall->end.x) | ((u64)wall->end.y << 32)));
// FIXME: Remove this
// TrueRand(StringFromStruct(&id));
for (i32 y = aabb.p0.y; y < aabb.p1.y; ++y)
{
for (i32 x = aabb.p0.x; x < aabb.p1.x; ++x)
{
i64 cell_idx = y * P_WorldPitch + x;
P_SpaceCell *cell = &space.cells[cell_idx];
P_SpaceEntry *entry = PushStruct(arena, P_SpaceEntry);
SllStackPush(cell->first, entry);
entry->shape = shape;
entry->shape_id = id;
entry->dir.x = (wall->dir == WallDir_Right) + ((wall->dir == WallDir_Left) * -1);
entry->dir.y = (wall->dir == WallDir_Down) + ((wall->dir == WallDir_Up) * -1);
P_SpaceEntryNode *entry_node = PushStruct(arena, P_SpaceEntryNode);
SllStackPush(cell->first, entry_node);
entry_node->entry.shape = shape;
entry_node->entry.shape_id = id;
entry_node->entry.dir.x = (wall->dir == WallDir_Right) + ((wall->dir == WallDir_Left) * -1);
entry_node->entry.dir.y = (wall->dir == WallDir_Down) + ((wall->dir == WallDir_Up) * -1);
}
}
}
@ -1422,6 +1425,103 @@ P_SpaceCell P_SpaceCellFromPos(P_Space *space, Vec2 pos)
return result;
}
void P_UniqueSpaceEntriesFromRay(Arena *arena, P_SpaceEntryList *result, i32 spaces_count, P_Space **spaces, Vec2 ray_p0, Vec2 ray_p1)
{
TempArena scratch = BeginScratch(arena);
{
Struct(BinEntry) { BinEntry *next; u64 shape_id; };
u64 bins_count = 256;
BinEntry **bins = PushStructs(scratch.arena, BinEntry *, bins_count);
// TODO: Clip to avoid unnecessary iterations outside of world bounds
ray_p0 = AddVec2(ray_p0, VEC2(P_WorldPitch / 2.0, P_WorldPitch / 2.0));
ray_p1 = AddVec2(ray_p1, VEC2(P_WorldPitch / 2.0, P_WorldPitch / 2.0));
Vec2I32 grid_start = Vec2I32FromVec(FloorVec2(ray_p0));
Vec2I32 grid_end = Vec2I32FromVec(FloorVec2(ray_p1));
Vec2 delta = SubVec2(ray_p1, ray_p0);
Vec2 inv_delta = RecipVec2(delta);
Vec2 step_dir = VEC2((delta.x > 0) - (delta.x < 0), (delta.y > 0) - (delta.y < 0));
Vec2 t_delta = MulVec2Vec2(step_dir, inv_delta);
Vec2 t_max = SubVec2(Vec2FromVec(grid_start), ray_p0);
t_max.x += step_dir.x > 0;
t_max.y += step_dir.y > 0;
t_max = MulVec2Vec2(t_max, inv_delta);
Vec2I32 grid_pos = grid_start;
b32 done = 0;
while (!done)
{
if (grid_pos.x >= 0 && grid_pos.y >= 0 && grid_pos.x < P_WorldPitch && grid_pos.y < P_WorldPitch)
{
if (P_tl.debug_draw_enabled)
{
Vec2 world_pos = Vec2FromVec(grid_pos);
world_pos = SubVec2(world_pos, VEC2(P_WorldPitch / 2.0, P_WorldPitch / 2.0));
P_DebugDrawRect(RNG2(world_pos, AddVec2(world_pos, VEC2(1, 1))), Color_Cyan);
}
for (i32 space_idx = 0; space_idx < spaces_count; ++space_idx)
{
P_Space *space = spaces[space_idx];
if (grid_pos.x < space->dims.x && grid_pos.y < space->dims.y)
{
i64 cell_idx = grid_pos.y * P_WorldPitch + grid_pos.x;
P_SpaceCell cell = space->cells[cell_idx];
for (
P_SpaceEntryNode *src_space_entry_node = cell.first;
src_space_entry_node;
src_space_entry_node = src_space_entry_node->next
)
{
P_SpaceEntry *src_space_entry = &src_space_entry_node->entry;
BinEntry **bin = &bins[src_space_entry->shape_id % bins_count];
BinEntry *bin_entry = *bin;
for (; bin_entry; bin_entry = bin_entry->next)
{
if (bin_entry->shape_id == src_space_entry->shape_id)
{
break;
}
}
if (!bin_entry)
{
// Entry is unique
{
bin_entry = PushStruct(scratch.arena, BinEntry);
bin_entry->shape_id = src_space_entry->shape_id;
SllStackPush(*bin, bin_entry);
}
{
P_SpaceEntryNode *dst = PushStruct(arena, P_SpaceEntryNode);
dst->entry = *src_space_entry;
SllQueuePush(result->first, result->last, dst);
++result->count;
}
}
}
}
}
}
if (grid_pos.x == grid_end.x && grid_pos.y == grid_end.y)
{
done = 1;
}
else if (t_max.x < t_max.y)
{
grid_pos.x += step_dir.x;
t_max.x += t_delta.x;
}
else
{
grid_pos.y += step_dir.y;
t_max.y += t_delta.y;
}
}
}
EndScratch(scratch);
}
////////////////////////////////////////////////////////////
//~ List helpers
@ -2093,8 +2193,9 @@ void P_StepFrame(P_Frame *frame)
for (i64 cell_idx = 0; cell_idx < countof(cells); ++cell_idx)
{
P_SpaceCell cell = cells[cell_idx];
for (P_SpaceEntry *space_entry = cell.first; space_entry; space_entry = space_entry->next)
for (P_SpaceEntryNode *space_entry_node = cell.first; space_entry_node; space_entry_node = space_entry_node->next)
{
P_SpaceEntry *space_entry = &space_entry_node->entry;
Rng2 aabb1 = P_BoundingBoxFromShape(space_entry->shape);
P_Ent *ent1 = P_EntFromKey(frame, (P_EntKey) { .v = space_entry->shape_id });
if (!P_MatchEntKey(ent0->key, ent1->key) && IsIntersectingRng2(aabb0, aabb1))
@ -2570,7 +2671,7 @@ void P_StepFrame(P_Frame *frame)
//////////////////////////////
//- Build post-solve space from ents
// P_Space post_solve_ents_space = P_SpaceFromEnts(scratch.arena, frame);
P_Space post_solve_ents_space = P_SpaceFromEnts(scratch.arena, frame);
//////////////////////////////
//- Move bullets
@ -2596,8 +2697,8 @@ void P_StepFrame(P_Frame *frame)
P_EntList bullets_to_spawn = Zi;
for (P_Ent *firer = P_FirstEnt(frame); !P_IsEntNil(firer); firer = P_NextEnt(firer))
{
if (firer->is_guy && firer->control.fire_held)
// if (firer->fire_presses)
if (firer->has_weapon && firer->control.fire_held)
// if (firer->has_weapon && firer->control.fire_presses)
{
// i64 fire_delta_ns = frame->time_ns - firer->last_fire_ns;
@ -2610,6 +2711,7 @@ void P_StepFrame(P_Frame *frame)
f32 spread = Tau * 0.05;
// f32 spread = Tau * 0.01;
f32 tweak_speed = TweakFloat("Bullet speed", 100, 1, 100);
// f32 tweak_speed = TweakFloat("Bullet speed", 1, 1, 100);
b32 can_fire = (firer->last_fire_ns + NsFromSeconds(1.0 / fire_rate)) <= frame->time_ns;
@ -2662,9 +2764,9 @@ void P_StepFrame(P_Frame *frame)
SPR_Ray fire_ray = wep.rays[SPR_RayKind_Ap];
fire_pos = MulAffineVec2(wep_pix_to_world_af, fire_ray.pos);
fire_dir = NormRot(MulAffineBasisVec2(wep_pix_to_world_af, fire_ray.dir));
}
// FIXME: Prevent obstructed weapons from firing through walls
}
if (can_fire)
{
@ -2708,8 +2810,6 @@ void P_StepFrame(P_Frame *frame)
//////////////////////////////
//- Update bullet hits
// TODO: Not like this
// TODO: Separate 'hits' from bullets, so that bullets can have multiple hits
for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
@ -2723,22 +2823,37 @@ void P_StepFrame(P_Frame *frame)
P_DebugDrawLine(bullet->bullet_start, bullet->bullet_end, Color_Red);
// TODO: Real raycast query
P_Ent *victim = &P_NilEnt;
P_Space *cast_spaces[] = {
&world->walls_space,
&post_solve_ents_space,
};
P_SpaceEntryList cast_entries = Zi;
P_UniqueSpaceEntriesFromRay(
scratch.arena,
&cast_entries,
countof(cast_spaces),
cast_spaces,
ray_start,
ray_end
);
P_EntKey victim_key = Zi;
P_RaycastResult victim_raycast = Zi;
{
f32 closest_len_sq = Inf;
for (P_Ent *potential_victim = P_FirstEnt(frame); !P_IsEntNil(potential_victim); potential_victim = P_NextEnt(potential_victim))
for (P_SpaceEntryNode *entry_node = cast_entries.first; entry_node; entry_node = entry_node->next)
{
if (potential_victim->is_guy && !P_MatchEntKey(potential_victim->key, bullet->bullet_firer))
P_SpaceEntry *entry = &entry_node->entry;
P_EntKey potential_victim_key = (P_EntKey) { .v = entry->shape_id };
if (!P_MatchEntKey(potential_victim_key, bullet->bullet_firer))
{
P_Shape victim_world_shape = P_WorldShapeFromEnt(potential_victim);
P_RaycastResult entrance_raycast = P_RaycastShape(victim_world_shape, ray_start, ray_dir);
P_Shape potential_victim_shape = entry->shape;
P_RaycastResult entrance_raycast = P_RaycastShape(potential_victim_shape, ray_start, ray_dir);
Vec2 entrance = entrance_raycast.p;
if (entrance_raycast.is_intersecting)
{
P_RaycastResult exit_raycast = P_RaycastShape(victim_world_shape, ray_start, NegVec2(ray_dir));
P_RaycastResult exit_raycast = P_RaycastShape(potential_victim_shape, ray_start, NegVec2(ray_dir));
Vec2 exit = exit_raycast.p;
f32 da = DotVec2(ray_dir, SubVec2(entrance, ray_start));
f32 db = DotVec2(ray_dir, SubVec2(exit, ray_start));
@ -2748,7 +2863,7 @@ void P_StepFrame(P_Frame *frame)
if (len_sq < closest_len_sq)
{
closest_len_sq = len_sq;
victim = potential_victim;
victim_key = potential_victim_key;
victim_raycast = entrance_raycast;
}
}
@ -2756,20 +2871,25 @@ void P_StepFrame(P_Frame *frame)
}
}
}
P_Ent *victim = P_EntFromKey(frame, victim_key);
// TODO: Truncate bullet trail
if (!P_IsEntNil(victim))
if (!P_IsEntKeyNil(victim_key))
{
bullet->has_hit = 1;
bullet->hit_entry = victim_raycast.p;
bullet->hit_entry_normal = victim_raycast.normal;
// bullet->bullet_end = bullet->hit_entry;
bullet->exists = 0;
}
if (!P_IsEntNil(victim))
{
// TODO: Remove this
victim->health -= 0.25;
}
// Prune out of bounds bullet
Rng2 bounds = Zi;
bounds.p0 = VEC2(-P_WorldPitch / 2, -P_WorldPitch / 2);
bounds.p1 = VEC2(P_WorldPitch / 2, P_WorldPitch / 2);

View File

@ -125,6 +125,8 @@ Struct(P_Ent)
i64 last_fire_ns;
b32 has_weapon;
//- Bullet
P_EntKey bullet_firer;
b32 is_bullet;
Vec2 bullet_start;
@ -254,15 +256,27 @@ Struct(P_ConstraintBin)
Struct(P_SpaceEntry)
{
P_SpaceEntry *next;
P_Shape shape;
u64 shape_id;
P_Shape shape;
Vec2 dir;
};
Struct(P_SpaceEntryNode)
{
P_SpaceEntryNode *next;
P_SpaceEntry entry;
};
Struct(P_SpaceEntryList)
{
i64 count;
P_SpaceEntryNode *first;
P_SpaceEntryNode *last;
};
Struct(P_SpaceCell)
{
P_SpaceEntry *first;
P_SpaceEntryNode *first;
};
Struct(P_Space)
@ -610,7 +624,9 @@ P_Constraint *P_NextConstraint(P_Constraint *c);
P_Space P_SpaceFromEnts(Arena *arena, P_Frame *frame);
P_Space P_SpaceFromWalls(Arena *arena, P_Frame *frame);
P_SpaceCell P_SpaceCellFromPos(P_Space *space, Vec2 pos);
void P_UniqueSpaceEntriesFromRay(Arena *arena, P_SpaceEntryList *result, i32 spaces_count, P_Space **spaces, Vec2 ray_p0, Vec2 ray_p1);
////////////////////////////////////////////////////////////
//~ List helpers

View File

@ -1963,22 +1963,17 @@ void V_TickForever(WaveLaneCtx *lane)
if (frame->is_looking)
{
frame->screen_cursor = frame->screen_crosshair;
if (window_frame.has_focus)
{
WND_PushCmd(window_frame, .kind = WND_CmdKind_SetLockedCursor, .v = 1);
WND_SetCursor(window_frame, WND_CursorKind_Hidden);
WND_PushCmd(window_frame, .kind = WND_CmdKind_SetCursorPos, .pos = frame->screen_cursor);
}
else
{
if (prev_frame->is_looking)
{
// Snap cursor to crosshair
frame->screen_cursor = prev_frame->screen_cursor;
WND_PushCmd(window_frame, .kind = WND_CmdKind_SetCursorPos, .pos = prev_frame->screen_cursor);
}
else
{
frame->screen_cursor = ui_frame->cursor_pos;
}
}
frame->shade_cursor = MulAffineVec2(frame->af.screen_to_shade, frame->screen_cursor);
frame->world_cursor = MulAffineVec2(frame->af.screen_to_world, frame->screen_cursor);
@ -2560,7 +2555,6 @@ void V_TickForever(WaveLaneCtx *lane)
// emitter.flags |= V_ParticleFlag_StainTrail;
emitter.count = 128;
// emitter.count = 100;
emitter.speed = 10;
emitter.color_lin = LinearFromSrgb(Color_Yellow);