simulated bullet testing

This commit is contained in:
jacob 2026-01-07 08:12:14 -06:00
parent 3dc22b9bd7
commit e9ea1ec0f7
7 changed files with 656 additions and 239 deletions

View File

@ -93,6 +93,16 @@ void S_UpdateWorldFromDelta(Arena *arena, S_World *world, S_Delta *delta)
if (0)
{
}
//- Reset
if (delta->kind == S_DeltaKind_Reset)
{
// FIXME: Free list entities
world->ents_count = 0;
world->first_ent = 0;
world->last_ent = 0;
ZeroStructs(world->tiles, S_TilesCount);
ZeroStructs(world->ent_bins, world->ent_bins_count);
}
//- Raw ent
if (delta->kind == S_DeltaKind_RawEnt)
{
@ -921,14 +931,24 @@ S_CollisionResult S_CollisionResultFromShapes(S_Shape shape0, S_Shape shape1, Ve
return result;
}
S_RaycastResult S_RaycastShape(S_Shape shape, Vec2 ray_start, Vec2 ray_dir)
S_RaycastResult S_RaycastShape(S_Shape shape, Vec2 ray_start, Vec2 ray_dir, b32 extend)
{
S_RaycastResult result = Zi;
// HACK: Create line shape as large as the world and perform a collision test
Vec2 ray_shape_p0 = ray_start;
Vec2 ray_shape_p1 = AddVec2(ray_start, MulVec2(NormVec2(ray_dir), S_WorldPitch * 1.414213562));
Vec2 ray_shape_p1;
if (extend)
{
ray_shape_p1 = AddVec2(ray_start, MulVec2(NormVec2(ray_dir), S_WorldPitch * 1.414213562));
}
else
{
ray_shape_p1 = AddVec2(ray_shape_p0, ray_dir);
}
S_Shape ray_shape = S_ShapeFromDesc(.count = 2, .points = { ray_shape_p0, ray_shape_p1 });
S_DebugDrawShape(ray_shape, Color_Cyan);
S_CollisionResult cls = S_CollisionResultFromShapes(ray_shape, shape, ray_dir);
result.is_intersecting = cls.collision_points_count > 0;
@ -982,6 +1002,34 @@ S_Ent *S_NextEnt(S_Ent *e)
return result;
}
S_Ent *S_PushTempEnt(Arena *arena, S_EntList *list)
{
S_EntListNode *n = PushStruct(arena, S_EntListNode);
SllQueuePush(list->first, list->last, n);
++list->count;
S_Ent *ent = &n->ent;
*ent = S_NilEnt;
ent->valid = 1;
ent->exists = 1;
return ent;
}
void S_SpawnEntsFromList(Arena *arena, S_World *world, S_EntList ents)
{
// FIXME: Don't reinsert duplicates
// FIXME: Don't insert nil keys
for (S_EntListNode *n = ents.first; n; n = n->next)
{
S_Ent *src = &n->ent;
S_Ent *dst = PushStructNoZero(arena, S_Ent);
*dst = *src;
S_EntBin *bin = &world->ent_bins[dst->key.v % world->ent_bins_count];
DllQueuePush(world->first_ent, world->last_ent, dst);
DllQueuePushNP(bin->first, bin->last, dst, next_in_bin, prev_in_bin);
++world->ents_count;
}
}
////////////////////////////////////////////////////////////
//~ Debug draw
@ -1144,8 +1192,8 @@ void S_TickForever(WaveLaneCtx *lane)
// FIXME: Only accept world deltas from users that can edit
i64 applied_user_tile_deltas_count = 0;
S_Delta **applied_user_tile_deltas = PushStructsNoZero(frame_arena, S_Delta *, input->cmds_count);
i64 applied_user_deltas_count = 0;
S_Delta **applied_user_deltas = PushStructsNoZero(frame_arena, S_Delta *, input->cmds_count);
for (S_CmdNode *cmd_node = input->first_cmd_node; cmd_node; cmd_node = cmd_node->next)
{
S_Cmd *cmd = &cmd_node->cmd;
@ -1153,6 +1201,12 @@ void S_TickForever(WaveLaneCtx *lane)
{
S_Delta *delta = &cmd->delta;
b32 allow = 0;
b32 forward = 0;
if (delta->kind == S_DeltaKind_Reset)
{
allow = 1;
forward = 1;
}
if (delta->kind == S_DeltaKind_RawEnt)
{
allow = 1;
@ -1160,8 +1214,12 @@ void S_TickForever(WaveLaneCtx *lane)
if (delta->kind == S_DeltaKind_Tile)
{
allow = 1;
applied_user_tile_deltas[applied_user_tile_deltas_count] = delta;
applied_user_tile_deltas_count += 1;
forward = 1;
}
if (forward)
{
applied_user_deltas[applied_user_deltas_count] = delta;
applied_user_deltas_count += 1;
}
if (allow)
{
@ -1407,37 +1465,120 @@ void S_TickForever(WaveLaneCtx *lane)
//////////////////////////////
//- Move bullets
for (S_Ent *bullet = S_FirstEnt(world); bullet->valid; bullet = S_NextEnt(bullet))
{
if (bullet->is_bullet)
{
Vec2 start = bullet->bullet_start;
Vec2 end = bullet->bullet_end;
Vec2 vel = SubVec2(end, start);
bullet->bullet_start = end;
bullet->bullet_end = AddVec2(end, vel);
}
}
//////////////////////////////
//- Spawn test bullet
//- Spawn new bullets
// TODO: Remove this
// {
// b32 bullet_spawned = 0;
// for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent))
// {
// if (ent->is_bullet)
// {
// bullet_spawned = 1;
// break;
// }
// }
{
S_EntList bullets_to_spawn = Zi;
for (S_Ent *firer = S_FirstEnt(world); firer->valid; firer = S_NextEnt(firer))
{
if (firer->fire_held)
{
Xform firer_xf = firer->xf;
S_Shape firer_world_shape = S_MulXformShape(firer_xf, firer->local_shape);
// if (!bullet_spawned)
// {
S_Ent *bullet = S_PushTempEnt(frame_arena, &bullets_to_spawn);
bullet->is_bullet = 1;
bullet->key = S_RandKey();
// }
// }
f32 speed = 40 * sim_dt;
bullet->bullet_start = firer_world_shape.centroid;
bullet->bullet_end = AddVec2(bullet->bullet_start, MulVec2(NormVec2(firer->look), speed));
bullet->bullet_firer = firer->key;
}
}
S_SpawnEntsFromList(world_arena, world, bullets_to_spawn);
}
//////////////////////////////
//- Update bullets
//- Update bullet hits
// TODO: Not like this
// TODO: Separate 'hits' from bullets, so that bullets can have multiple hits
for (S_Ent *bullet = S_FirstEnt(world); bullet->valid; bullet = S_NextEnt(bullet))
{
bullet->has_hit = 0;
Vec2 ray_start = bullet->bullet_start;
Vec2 ray_end = bullet->bullet_end;
Vec2 ray_dir = SubVec2(ray_end, ray_start);
// TODO: Real raycast query
S_Ent *closest_victim = &S_NilEnt;
S_RaycastResult victim_raycast = Zi;
{
f32 closest_len_sq = Inf;
for (S_Ent *victim = S_FirstEnt(world); victim->valid; victim = S_NextEnt(victim))
{
if (victim->is_player && !S_MatchKey(victim->key, bullet->bullet_firer))
{
Xform victim_xf = victim->xf;
S_Shape victim_world_shape = S_MulXformShape(victim_xf, victim->local_shape);
S_RaycastResult raycast = S_RaycastShape(victim_world_shape, ray_start, ray_dir, 0);
if (raycast.is_intersecting)
{
f32 len_sq = Vec2LenSq(SubVec2(raycast.p, ray_start));
if (len_sq < closest_len_sq)
{
closest_len_sq = len_sq;
closest_victim = victim;
victim_raycast = raycast;
}
}
}
}
}
if (closest_victim->valid)
{
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;
S_DebugDrawPoint(bullet->bullet_end, Color_Red);
}
else
{
S_DebugDrawPoint(bullet->bullet_end, Color_Purple);
}
Rng2 bounds = Zi;
bounds.p0 = VEC2(-S_WorldPitch / 2, -S_WorldPitch / 2);
bounds.p1 = VEC2(S_WorldPitch / 2, S_WorldPitch / 2);
if (
bullet->bullet_start.x < bounds.p0.x || bullet->bullet_start.y < bounds.p0.y ||
bullet->bullet_start.x > bounds.p1.x || bullet->bullet_start.y > bounds.p1.y
)
{
bullet->exists = 0;
}
}
// {
// Struct(S_Bullet)
@ -1607,8 +1748,8 @@ void S_TickForever(WaveLaneCtx *lane)
snapshot->tick = world->tick;
snapshot->time_ns = world->time_ns;
// Forward user tile deltas
for (i64 applied_user_tile_delta_idx = 0; applied_user_tile_delta_idx < applied_user_tile_deltas_count; ++applied_user_tile_delta_idx)
// Forward user edit deltas
for (i64 applied_user_delta_idx = 0; applied_user_delta_idx < applied_user_deltas_count; ++applied_user_delta_idx)
{
S_Delta *delta = 0;
{
@ -1617,7 +1758,7 @@ void S_TickForever(WaveLaneCtx *lane)
SllQueuePush(snapshot->first_delta_node, snapshot->last_delta_node, dn);
delta = &dn->delta;
}
S_Delta *src = applied_user_tile_deltas[applied_user_tile_delta_idx];
S_Delta *src = applied_user_deltas[applied_user_delta_idx];
*delta = *src;
}

View File

@ -74,10 +74,15 @@ Struct(S_Ent)
b32 has_weapon;
S_Key bullet_firer;
b32 is_bullet;
Vec2 bullet_start;
Vec2 bullet_end;
b32 has_hit;
Vec2 hit_entry;
Vec2 hit_entry_normal;
//////////////////////////////
//- Solver data
@ -85,6 +90,19 @@ Struct(S_Ent)
f32 solved_dw;
};
Struct(S_EntListNode)
{
S_EntListNode *next;
S_Ent ent;
};
Struct(S_EntList)
{
S_EntListNode *first;
S_EntListNode *last;
i64 count;
};
Struct(S_EntBin)
{
S_Ent *first;
@ -178,6 +196,7 @@ Struct(S_World)
Enum(S_DeltaKind)
{
S_DeltaKind_Reset,
S_DeltaKind_RawEnt,
S_DeltaKind_RawTiles,
S_DeltaKind_Tile,
@ -381,7 +400,7 @@ S_ClippedLine S_ClipLineToLine(Vec2 a0, Vec2 b0, Vec2 a1, Vec2 b1, Vec2 normal);
Vec2 S_ClipPointToLine(Vec2 a, Vec2 b, Vec2 p, Vec2 normal);
S_CollisionResult S_CollisionResultFromShapes(S_Shape shape0, S_Shape shape1, Vec2 sweep);
S_RaycastResult S_RaycastShape(S_Shape shape, Vec2 ray_start, Vec2 ray_dir);
S_RaycastResult S_RaycastShape(S_Shape shape, Vec2 ray_start, Vec2 ray_dir, b32 extend);
////////////////////////////////////////////////////////////
//~ Lookup helpers
@ -394,6 +413,12 @@ S_Ent *S_EntFromKey(S_World *world, S_Key key);
S_Ent *S_FirstEnt(S_World *world);
S_Ent *S_NextEnt(S_Ent *e);
////////////////////////////////////////////////////////////
//~ List helpers
S_Ent *S_PushTempEnt(Arena *arena, S_EntList *list);
void S_SpawnEntsFromList(Arena *arena, S_World *world, S_EntList ents);
////////////////////////////////////////////////////////////
//~ Debug draw

View File

@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////
//~ Tile types
#define S_WorldPitch 96.0
#define S_WorldPitch 80.0
#define S_TilesPitch (S_WorldPitch * 2)
#define S_TilesCount (S_TilesPitch * S_TilesPitch)

View File

@ -240,6 +240,16 @@ void V_DrawRect(Rng2 rect, Vec4 srgb, V_DrawFlag flags)
V_DrawPoly(points_arr, srgb, flags);
}
void V_DrawPoint(Vec2 p, Vec4 srgb)
{
S_Shape ui_shape = S_ShapeFromDesc(
.count = 1,
.points = { p },
.radius = 5
);
V_DrawShape(ui_shape, srgb, 24, V_DrawFlag_None);
}
////////////////////////////////////////////////////////////
//~ Theme
@ -326,8 +336,8 @@ void V_TickForever(WaveLaneCtx *lane)
Vec2I32 cells_dims = VEC2I32(V_CellsPerMeter * S_WorldPitch, V_CellsPerMeter * S_WorldPitch);
// u32 max_particles = Kibi(128);
u32 max_particles = Mebi(1);
// u32 max_particles = Mebi(2);
// u32 max_particles = Mebi(1);
u32 max_particles = Mebi(2);
// u32 max_particles = Mebi(16);
// Init gpu state
@ -381,7 +391,8 @@ void V_TickForever(WaveLaneCtx *lane)
gpu_perm, cl,
// G_Format_R8_Uint,
// G_Format_R11G11B10_Float,
G_Format_R10G10B10A2_Unorm,
// G_Format_R10G10B10A2_Unorm,
G_Format_R16G16B16A16_Float,
cells_dims,
G_Layout_DirectQueue_ShaderReadWrite,
.flags = G_ResourceFlag_ZeroMemory | G_ResourceFlag_AllowShaderReadWrite
@ -394,7 +405,8 @@ void V_TickForever(WaveLaneCtx *lane)
gpu_perm, cl,
// G_Format_R8_Uint,
// G_Format_R11G11B10_Float,
G_Format_R10G10B10A2_Unorm,
// G_Format_R10G10B10A2_Unorm,
G_Format_R16G16B16A16_Float,
cells_dims,
G_Layout_DirectQueue_ShaderReadWrite,
.flags = G_ResourceFlag_ZeroMemory | G_ResourceFlag_AllowShaderReadWrite
@ -615,6 +627,7 @@ void V_TickForever(WaveLaneCtx *lane)
b32 received_unseen_tick = 0;
b32 tiles_dirty = 0;
b32 should_clear_particles = 0;
for (S_SnapshotNode *n = sim_output->first_snapshot_node; n; n = n->next)
{
S_Snapshot *snapshot = &n->snapshot;
@ -626,6 +639,11 @@ void V_TickForever(WaveLaneCtx *lane)
for (S_DeltaNode *dn = snapshot->first_delta_node; dn; dn = dn->next)
{
S_Delta *delta = &dn->delta;
if (delta->kind == S_DeltaKind_Reset)
{
tiles_dirty = 1;
should_clear_particles = 1;
}
if (delta->kind == S_DeltaKind_RawTiles || delta->kind == S_DeltaKind_Tile)
{
tiles_dirty = 1;
@ -636,32 +654,6 @@ void V_TickForever(WaveLaneCtx *lane)
}
}
//////////////////////////////
//- Prune ents
{
i64 ents_to_prune_count = 0;
S_Ent **ents_to_prune = PushStructsNoZero(frame->arena, S_Ent *, world->ents_count);
for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent))
{
if (ent->exists <= 0)
{
ents_to_prune[ents_to_prune_count] = ent;
ents_to_prune_count += 1;
}
}
for (i64 prune_idx = 0; prune_idx < ents_to_prune_count; ++prune_idx)
{
// FIXME: Add to free list
S_Ent *ent = ents_to_prune[prune_idx];
S_EntBin *bin = &world->ent_bins[ent->key.v % world->ent_bins_count];
DllQueueRemoveNP(bin->first, bin->last, ent, next_in_bin, prev_in_bin);
DllQueueRemove(world->first_ent, world->last_ent, ent);
world->ents_count -= 1;
}
}
//////////////////////////////
//- Copy sim debug info
@ -2198,6 +2190,7 @@ void V_TickForever(WaveLaneCtx *lane)
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
{
UI_BuildLabelF("World seed: 0x%F", FmtHex(world->seed));
UI_BuildLabelF("Entities count: %F", FmtSint(world->ents_count));
}
UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y);
{
@ -2390,7 +2383,6 @@ void V_TickForever(WaveLaneCtx *lane)
//////////////////////////////
//- Process vis commands
b32 should_clear_particles = 0;
for (V_CmdNode *cmd_node = frame->first_cmd_node; cmd_node; cmd_node = cmd_node->next)
{
String cmd_name = cmd_node->cmd.name;
@ -2478,14 +2470,28 @@ void V_TickForever(WaveLaneCtx *lane)
}
} break;
case V_CmdKind_reset_world:
case V_CmdKind_spawn:
{
// Reset world
Vec2 player_pos = VEC2(5, 0);
if (kind == V_CmdKind_reset_world)
{
S_Cmd *cmd = V_PushSimCmd(S_CmdKind_Delta);
cmd->delta.kind = S_DeltaKind_Reset;
}
else
{
player_pos = frame->world_cursor;
}
// Spawn player
{
S_Cmd *cmd = V_PushSimCmd(S_CmdKind_Delta);
cmd->delta.kind = S_DeltaKind_RawEnt;
S_Ent *ent = &cmd->delta.ent;
*ent = S_NilEnt;
ent->key = V.player_key;
ent->xf = XformFromPos(frame->world_cursor);
ent->xf = XformFromPos(player_pos);
ent->move_speed = 0.075;
ent->is_player = 1;
ent->local_shape = S_ShapeFromDesc(
@ -2495,6 +2501,7 @@ void V_TickForever(WaveLaneCtx *lane)
);
ent->has_weapon = 1;
ent->exists = 1;
}
} break;
case V_CmdKind_spawn_dummy:
@ -2600,64 +2607,155 @@ void V_TickForever(WaveLaneCtx *lane)
//////////////////////////////
//- Spawn test bullet particles
//- Push test bullet particles
// TODO: Not like this
{
PERSIST Vec2 start = {5, 5};
PERSIST Vec2 end = {3, -20};
if (frame->held_buttons[Button_G])
for (S_Ent *bullet = S_FirstEnt(world); bullet->valid; bullet = S_NextEnt(bullet))
{
end = frame->world_cursor;
if (!last_frame->held_buttons[Button_G])
if (bullet->is_bullet)
{
start = end;
// FIXME: Truncate bullet trail at hit
Vec2 start = bullet->bullet_start;
Vec2 end = bullet->bullet_end;
b32 skip = 0;
if (bullet->has_hit)
{
Vec2 hit_pos = bullet->hit_entry;
if (DotVec2(SubVec2(hit_pos, start), SubVec2(end, start)) < 0)
{
skip = 1;
}
// V_DrawPoint(MulXformV2(frame->xf.world_to_ui, start), Color_Red);
// V_DrawPoint(MulXformV2(frame->xf.world_to_ui, end), Color_Purple);
end = hit_pos;
}
Vec2 vel = SubVec2(end, start);
if (!skip)
{
f32 trail_len = Vec2Len(SubVec2(end, start));
f32 particles_count = MaxF32(trail_len * 64, 1);
// f32 particles_count = MaxF32(trail_len * 10000, 1);
// f32 particles_count = MaxF32(trail_len * 512, 1);
// f32 particles_count = MaxF32(trail_len * 64, 1);
f32 particles_count = MaxF32(trail_len * 32, 1);
// f32 angle = AngleFromVec2(PerpVec2(SubVec2(end, start)));
f32 angle = AngleFromVec2(NegVec2(SubVec2(end, start)));
f32 angle = AngleFromVec2(PerpVec2(SubVec2(end, start)));
// f32 angle = AngleFromVec2(NegVec2(SubVec2(end, start)));
V_Emitter *emitter = V_PushEmitter(particles_count);
emitter->lifetime = 1;
emitter->lifetime_spread = 1;
// emitter->flags |= V_ParticleFlag_StainOnPrune;
// emitter->flags |= V_ParticleFlag_StainTrail;
// emitter->lifetime = 1;
// emitter->lifetime_spread = 2;
emitter->lifetime = 0.15;
// emitter->lifetime = 0.04;
emitter->lifetime_spread = emitter->lifetime * 2;
emitter->angle = angle;
// emitter->angle_spread = Tau / 4;
emitter->angle_spread = Tau / 4;
emitter->start = start;
emitter->end = end;
// emitter->color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1));
emitter->color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1));
// emitter->color_lin = LinearFromSrgb(VEC4(0.8, 0.8, 0.8, 0.25));
emitter->color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1));
// emitter->color_spread = LinearFromSrgb(VEC4(0, 0, 0, 0.2));
emitter->speed = 0;
emitter->speed_spread = 1;
// emitter->speed = 1;
// emitter->speed_spread = 1;
// emitter->velocity_falloff = 1;
// emitter->velocity_falloff_spread = 0;
}
}
}
// {
// PERSIST Vec2 start = {5, 5};
// PERSIST Vec2 end = {3, -20};
// b32 skip = 1;
// if (frame->held_buttons[Button_G])
// {
// end = frame->world_cursor;
// if (!last_frame->held_buttons[Button_G])
// {
// start = end;
// }
// skip = 0;
// }
// Vec2 vel = SubVec2(end, start);
// f32 trail_len = Vec2Len(SubVec2(end, start));
// // f32 particles_count = MaxF32(trail_len * 10000, 1);
// // f32 particles_count = MaxF32(trail_len * 512, 1);
// // f32 particles_count = MaxF32(trail_len * 64, 1);
// f32 particles_count = MaxF32(trail_len * 32, 1);
// f32 angle = AngleFromVec2(PerpVec2(SubVec2(end, start)));
// // f32 angle = AngleFromVec2(NegVec2(SubVec2(end, start)));
// V_Emitter *emitter = V_PushEmitter(particles_count);
// // emitter->flags |= V_ParticleFlag_StainOnPrune;
// // emitter->flags |= V_ParticleFlag_StainTrail;
// // emitter->lifetime = 1;
// // emitter->lifetime_spread = 2;
// emitter->lifetime = 0.25;
// emitter->lifetime_spread = emitter->lifetime * 2;
// emitter->angle = angle;
// // emitter->angle_spread = Tau / 4;
// emitter->angle_spread = Tau / 4;
// emitter->start = start;
// emitter->end = end;
// // emitter->color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1));
// // emitter->color_lin = LinearFromSrgb(VEC4(0.8, 0.8, 0.8, 0.25));
// emitter->color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1));
// // emitter->color_spread = LinearFromSrgb(VEC4(0, 0, 0, 0.2));
// emitter->speed = 0;
// emitter->speed_spread = 1;
emitter->speed = 1;
emitter->speed_spread = 1;
// // emitter->speed = 1;
// // emitter->speed_spread = 1;
emitter->velocity_falloff = 0;
emitter->velocity_falloff_spread = 0;
// // emitter->velocity_falloff = 1;
// // emitter->velocity_falloff_spread = 0;
start = end;
end = AddVec2(end, vel);
}
// start = end;
// end = AddVec2(end, vel);
// }
@ -2680,53 +2778,33 @@ void V_TickForever(WaveLaneCtx *lane)
//////////////////////////////
//- Spawn test blood particles
//- Push test blood particles
// TODO: Not like this
{
for (S_Ent *firer = S_FirstEnt(world); firer->valid; firer = S_NextEnt(firer))
{
if (firer->fire_held)
{
Xform firer_xf = firer->xf;
S_Shape firer_world_shape = S_MulXformShape(firer_xf, firer->local_shape);
Vec2 ray_start = firer_world_shape.centroid;
Vec2 ray_dir = firer->look;
// TODO: Real raycast query
S_Ent *closest_victim = &S_NilEnt;
S_RaycastResult victim_raycast = Zi;
{
f32 closest_len_sq = Inf;
for (S_Ent *victim = S_FirstEnt(world); victim->valid; victim = S_NextEnt(victim))
{
if (victim != firer)
{
Xform victim_xf = victim->xf;
S_Shape victim_world_shape = S_MulXformShape(victim_xf, victim->local_shape);
S_RaycastResult raycast = S_RaycastShape(victim_world_shape, ray_start, ray_dir);
if (raycast.is_intersecting)
{
f32 len_sq = Vec2LenSq(SubVec2(raycast.p, ray_start));
if (len_sq < closest_len_sq)
{
closest_len_sq = len_sq;
closest_victim = victim;
victim_raycast = raycast;
}
}
}
}
}
if (closest_victim->valid)
{
for (S_Ent *bullet = S_FirstEnt(world); bullet->valid; bullet = S_NextEnt(bullet))
{
if (bullet->is_bullet && bullet->has_hit)
{
// Vec2 bullet_start = bullet->start;
// Vec2 bullet_end = bullet->end;
Vec2 hit_entry = bullet->hit_entry;
Vec2 hit_entry_normal = bullet->hit_entry_normal;
Vec2 bullet_vel = SubVec2(bullet->bullet_end, bullet->bullet_start);
V_DrawLine(bullet->bullet_start, bullet->bullet_end, Color_Cyan);
V_ParticleFlag flags = 0;
flags |= V_ParticleFlag_PruneWhenStill;
flags |= V_ParticleFlag_StainOnPrune;
if (TweakBool("Emitter stain trail", 1))
if (TweakBool("Emitter stain trail", 0))
{
flags |= V_ParticleFlag_StainTrail;
}
@ -2739,15 +2817,17 @@ void V_TickForever(WaveLaneCtx *lane)
V_Emitter *emitter = V_PushEmitter(count);
emitter->flags = flags;
Vec2 dir = victim_raycast.normal;
emitter->start = victim_raycast.p;
// Vec2 dir = hit_entry_normal;
Vec2 dir = NormVec2(NegVec2(bullet_vel));
emitter->start = hit_entry;
emitter->end = emitter->start;
emitter->speed = speed;
emitter->speed_spread = speed * 2;
emitter->velocity_falloff = falloff;
emitter->velocity_falloff_spread = falloff * 2;
emitter->velocity_falloff_spread = falloff * 1.5;
emitter->angle = AngleFromVec2(dir);
// emitter->angle_spread = Tau / 4;
@ -2755,7 +2835,7 @@ void V_TickForever(WaveLaneCtx *lane)
// emitter->angle_spread = Tau / 32;
emitter->color_lin = LinearFromSrgb(VEC4(0.5, 0.1, 0.1, 1));
emitter->color_spread = VEC3(0.1, 0, 0);
emitter->color_spread = VEC4(0.1, 0, 0, 0);
// emitter->color = LinearFromSrgb(Vec4(0.5, 0.1, 0.1, 1));
@ -2767,20 +2847,113 @@ void V_TickForever(WaveLaneCtx *lane)
// V_DrawPoint(victim_raycast.p, Color_Green);
// V_DrawLine(victim_raycast.p, AddVec2(victim_raycast.p, MulVec2(victim_raycast.normal, 0.5)), Color_White);
}
}
}
// for (S_QueryResult query = S_FirstRaycast(wrold, ray_start, ray_dir); query.
// S_RaycastWorldResult hits = S_RaycastWorld(world, ray_start, ray_dir)
// {
// for (S_Ent *firer = S_FirstEnt(world); firer->valid; firer = S_NextEnt(firer))
// {
// if (firer->fire_held)
// {
// Xform firer_xf = firer->xf;
// S_Shape firer_world_shape = S_MulXformShape(firer_xf, firer->local_shape);
// Vec2 ray_start = firer_world_shape.centroid;
// Vec2 ray_dir = firer->look;
// // TODO: Real raycast query
// S_Ent *closest_victim = &S_NilEnt;
// S_RaycastResult victim_raycast = Zi;
// {
// f32 closest_len_sq = Inf;
// for (S_Ent *victim = S_FirstEnt(world); victim->valid; victim = S_NextEnt(victim))
// {
// if (victim != firer)
// {
// Xform victim_xf = victim->xf;
// S_Shape victim_world_shape = S_MulXformShape(victim_xf, victim->local_shape);
// S_RaycastResult raycast = S_RaycastShape(victim_world_shape, ray_start, ray_dir);
// if (raycast.is_intersecting)
// {
// f32 len_sq = Vec2LenSq(SubVec2(raycast.p, ray_start));
// if (len_sq < closest_len_sq)
// {
// closest_len_sq = len_sq;
// closest_victim = victim;
// victim_raycast = raycast;
// }
// }
// }
// }
// }
// if (closest_victim->valid)
// {
// V_ParticleFlag flags = 0;
// flags |= V_ParticleFlag_PruneWhenStill;
// flags |= V_ParticleFlag_StainOnPrune;
// if (TweakBool("Emitter stain trail", 1))
// {
// flags |= V_ParticleFlag_StainTrail;
// }
// // f32 count = TweakFloat("Emitter count", 50, 0, 10000);
// f32 count = TweakFloat("Emitter count", 50, 1, 1000);
// f32 speed = TweakFloat("Emitter speed", 20, 0, 100);
// f32 falloff = TweakFloat("Emitter falloff", 50, 0, 100);
// f32 angle_spread = TweakFloat("Emitter angle spread", 0.1, 0, 1) * Tau;
// V_Emitter *emitter = V_PushEmitter(count);
// emitter->flags = flags;
// Vec2 dir = victim_raycast.normal;
// emitter->start = victim_raycast.p;
// emitter->end = emitter->start;
// emitter->speed = speed;
// emitter->speed_spread = speed * 2;
// emitter->velocity_falloff = falloff;
// emitter->velocity_falloff_spread = falloff * 1.5;
// emitter->angle = AngleFromVec2(dir);
// // emitter->angle_spread = Tau / 4;
// emitter->angle_spread = angle_spread;
// // emitter->angle_spread = Tau / 32;
// emitter->color_lin = LinearFromSrgb(VEC4(0.5, 0.1, 0.1, 1));
// emitter->color_spread = VEC4(0.1, 0, 0, 0);
// // emitter->color = LinearFromSrgb(Vec4(0.5, 0.1, 0.1, 1));
// emitter->seed = RandU64FromState(&frame->rand);
// // emitter->angle_spread = 1;
// // emitter->angle_spread = 0.5;
// // emitter->angle_spread = Tau;
// // V_DrawPoint(victim_raycast.p, Color_Green);
// // V_DrawLine(victim_raycast.p, AddVec2(victim_raycast.p, MulVec2(victim_raycast.normal, 0.5)), Color_White);
// }
// // for (S_QueryResult query = S_FirstRaycast(wrold, ray_start, ray_dir); query.
// // S_RaycastWorldResult hits = S_RaycastWorld(world, ray_start, ray_dir)
// // {
// // }
// }
// }
// }
}
}
}
//////////////////////////////
//- Spawn test emitter
// Spawn test emitter
//- Push test emitter
if (frame->held_buttons[Button_F])
{
@ -2870,12 +3043,7 @@ void V_TickForever(WaveLaneCtx *lane)
case S_DebugDrawKind_Point:
{
Vec2 ui_p = MulXformV2(frame->xf.world_to_ui, desc->point.p);
S_Shape ui_shape = S_ShapeFromDesc(
.count = 1,
.points = { ui_p },
.radius = radius
);
V_DrawShape(ui_shape, color, detail, V_DrawFlag_None);
V_DrawPoint(ui_p, color);
} break;
case S_DebugDrawKind_Line:
@ -2896,8 +3064,8 @@ void V_TickForever(WaveLaneCtx *lane)
case S_DebugDrawKind_Shape:
{
S_Shape ui_shape = S_MulXformShape(frame->xf.world_to_ui, desc->shape);
// V_DrawShape(ui_shape, color, detail, V_DrawFlag_Line);
V_DrawShape(ui_shape, color, detail, V_DrawFlag_None);
V_DrawShape(ui_shape, color, detail, V_DrawFlag_Line);
// V_DrawShape(ui_shape, color, detail, V_DrawFlag_None);
} break;
}
}
@ -3095,6 +3263,32 @@ void V_TickForever(WaveLaneCtx *lane)
}
}
//////////////////////////////
//- Prune ents
{
i64 ents_to_prune_count = 0;
S_Ent **ents_to_prune = PushStructsNoZero(frame->arena, S_Ent *, world->ents_count);
for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent))
{
if (ent->exists <= 0)
{
ents_to_prune[ents_to_prune_count] = ent;
ents_to_prune_count += 1;
}
}
for (i64 prune_idx = 0; prune_idx < ents_to_prune_count; ++prune_idx)
{
// FIXME: Add to free list
S_Ent *ent = ents_to_prune[prune_idx];
S_EntBin *bin = &world->ent_bins[ent->key.v % world->ent_bins_count];
DllQueueRemoveNP(bin->first, bin->last, ent, next_in_bin, prev_in_bin);
DllQueueRemove(world->first_ent, world->last_ent, ent);
world->ents_count -= 1;
}
}
//////////////////////////////
//- End frame

View File

@ -12,9 +12,10 @@
X(toggle_console, Toggle Developer Console, V_CmdDescFlag_None, V_HOTKEY( Button_GraveAccent ), ) \
X(toggle_fullscreen, Toggle Fullscreen Mode, V_CmdDescFlag_None, V_HOTKEY( Button_Enter, .alt = 1 ) ) \
X(toggle_window_topmost, Toggle Window Topmost, V_CmdDescFlag_None, V_HOTKEY( Button_F4 ), ) \
X(spawn, Spawn/Teleport Player, V_CmdDescFlag_None, V_HOTKEY( Button_T ), ) \
X(spawn_dummy, Spawn Dummy, V_CmdDescFlag_None, V_HOTKEY( Button_R ), ) \
X(spawn, Spawn/Teleport Player, V_CmdDescFlag_None, V_HOTKEY( Button_Q ), ) \
X(spawn_dummy, Spawn Dummy, V_CmdDescFlag_None, V_HOTKEY( Button_T ), ) \
X(delete, Delete entity at cursor, V_CmdDescFlag_None, V_HOTKEY( Button_M2 ), ) \
X(reset_world, Reset world, V_CmdDescFlag_None, V_HOTKEY( Button_R ), ) \
X(clear_particles, Clear particles, V_CmdDescFlag_None, V_HOTKEY( Button_C ), ) \
/* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
@ -323,6 +324,7 @@ void V_DrawPoly(Vec2Array points, Vec4 srgb, V_DrawFlag flags);
void V_DrawShape(S_Shape shape, Vec4 srgb, i32 detail, V_DrawFlag flags);
void V_DrawLine(Vec2 p0, Vec2 p1, Vec4 srgb);
void V_DrawRect(Rng2 rect, Vec4 srgb, V_DrawFlag flags);
void V_DrawPoint(Vec2 p, Vec4 srgb);
////////////////////////////////////////////////////////////
//~ Theme

View File

@ -153,30 +153,68 @@ ComputeShader2D(V_BackdropCS, 8, 8)
// TODO: Remove this
// Cells test
{
RWTexture2D<Vec4> stains = G_Dereference<Vec4>(params.stains);
RWTexture2D<Vec4> cells = G_Dereference<Vec4>(params.cells);
Vec2 cell_pos = floor(mul(params.xf.world_to_cell, Vec3(world_pos, 1)));
Vec4 cell = cells.Load(cell_pos);
if (cell.a != 0)
{
result = cell;
}
else
{
RWTexture2D<Vec4> stains = G_Dereference<Vec4>(params.stains);
Vec4 stain = stains.Load(cell_pos);
if (stain.a != 0)
{
result = stain;
}
}
Vec4 cell = cells.Load(cell_pos);
// cell.rgb *= cell.a;
// stain.rgb *= stain.a;
result.rgb = (stain.rgb * stain.a) + (result.rgb * (1.0 - stain.a));
result.a = (stain.a * 1) + (result.a * (1.0 - stain.a));
result.rgb = (cell.rgb * cell.a) + (result.rgb * (1.0 - cell.a));
result.a = (cell.a * 1) + (result.a * (1.0 - cell.a));
// Vec4 cell = cells.Load(cell_pos);
// if (cell.a != 0)
// {
// result = cell;
// }
// else
// {
// Vec4 stain = stains.Load(cell_pos);
// if (stain.a != 0)
// {
// result = stain;
// }
// }
}
// // TODO: Remove this
// // Cells test
// {
// RWTexture2D<Vec4> cells = G_Dereference<Vec4>(params.cells);
// Vec2 cell_pos = floor(mul(params.xf.world_to_cell, Vec3(world_pos, 1)));
// Vec4 cell = cells.Load(cell_pos);
// if (cell.a != 0)
// {
// result = cell;
// }
// else
// {
// RWTexture2D<Vec4> stains = G_Dereference<Vec4>(params.stains);
// Vec4 stain = stains.Load(cell_pos);
// if (stain.a != 0)
// {
// result = stain;
// }
// }
// }
// TODO: Remove this
// Stains test
// {
@ -258,7 +296,6 @@ ComputeShader(V_EmitParticlesCS, 64)
if (emitter_idx < params.emitters_count)
{
V_Emitter emitter = emitters[emitter_idx];
for (u32 i = 0; i < emitter.count; ++i)
{
u32 particle_seq = emitter.first_particle_seq + i;
@ -294,18 +331,23 @@ ComputeShader(V_SimParticlesCS, 64)
u64 seed0 = MixU64(emitter.seed + particle.seq);
u64 seed1 = MixU64(seed0);
f32 rand_speed = (f32)((seed0 >> 0) & 0xFFFF) / (f32)0xFFFF;
f32 rand_angle = (f32)((seed0 >> 16) & 0xFFFF) / (f32)0xFFFF;
f32 rand_offset = (f32)((seed0 >> 32) & 0xFFFF) / (f32)0xFFFF;
f32 rand_falloff = (f32)((seed0 >> 48) & 0xFFFF) / (f32)0xFFFF;
f32 rand_lifetime = (f32)((seed1 >> 0) & 0xFFFF) / (f32)0xFFFF;
f32 rand_r = (f32)((seed1 >> 0) & 0xFF) / (f32)0xFF;
f32 rand_g = (f32)((seed1 >> 8) & 0xFF) / (f32)0xFF;
f32 rand_b = (f32)((seed1 >> 16) & 0xFF) / (f32)0xFF;
f32 rand_a = (f32)((seed1 >> 24) & 0xFF) / (f32)0xFF;
f32 rand_lifetime = (f32)((seed1 >> 32) & 0xFFFF) / (f32)0xFFFF;
f32 speed = emitter.speed + (rand_speed - 0.5) * emitter.speed_spread;
f32 angle = emitter.angle + (rand_angle - 0.5) * emitter.angle_spread;
f32 velocity_falloff = emitter.velocity_falloff + (rand_falloff - 0.5) * emitter.velocity_falloff_spread;
f32 lifetime = emitter.lifetime + (rand_lifetime - 0.5) * emitter.lifetime_spread;
Vec3 color = emitter.color_lin.rgb + (rand_lifetime - 0.5) * emitter.color_spread;
Vec4 color = emitter.color_lin + (Vec4(rand_r, rand_g, rand_b, rand_a) - 0.5) * emitter.color_spread;
Vec2 offset = (emitter.end - emitter.start) * rand_offset;
particle.flags = emitter.flags;
@ -322,12 +364,24 @@ ComputeShader(V_SimParticlesCS, 64)
particle.emitter_init_num = 0;
}
Vec2 cell_pos = floor(mul(params.xf.world_to_cell, Vec3(particle.pos, 1)));
b32 is_in_bounds = cell_pos.x >= 0 && cell_pos.y >= 0 && cell_pos.x < countof(stains).x && cell_pos.y < countof(stains).y;
// Simulate
f32 old_exists = particle.exists;
{
particle.pos += particle.velocity * params.dt;
particle.velocity = lerp(particle.velocity, 0, particle.velocity_falloff * params.dt);
particle.lifetime -= params.dt;
if (particle.exists < 0.0001 || particle.lifetime < 0.0001 || dot(particle.velocity, particle.velocity) < (0.0001 * 0.0001))
particle.exists -= params.dt / particle.lifetime;
if ((particle.flags & V_ParticleFlag_PruneWhenStill) && (dot(particle.velocity, particle.velocity) < (0.1 * 0.1)))
{
particle.exists = 0;
}
if (particle.exists < 0.000001)
{
particle.exists = 0;
}
if (!is_in_bounds)
{
particle.exists = 0;
}
@ -336,19 +390,19 @@ ComputeShader(V_SimParticlesCS, 64)
// Commit
{
// FIXME: Atomic write
Vec2 cell_pos = floor(mul(params.xf.world_to_cell, Vec3(particle.pos, 1)));
if (cell_pos.x >= 0 && cell_pos.y >= 0 && cell_pos.x < countof(stains).x && cell_pos.y < countof(stains).y)
if (is_in_bounds)
{
b32 should_stain = 0;
if (particle.flags & V_ParticleFlag_StainTrail || ((particle.flags & V_ParticleFlag_StainOnPrune) && particle.exists == 0))
if ((particle.flags & V_ParticleFlag_StainTrail) || ((particle.flags & V_ParticleFlag_StainOnPrune) && particle.exists == 0))
{
should_stain = 1;
}
cells[cell_pos] = Vec4(particle.color, 1);
Vec4 color = particle.color;
color.a *= old_exists;
cells[cell_pos] = color;
if (should_stain)
{
stains[cell_pos] = Vec4(particle.color, 1);
stains[cell_pos] = color;
}
}
else

View File

@ -87,8 +87,9 @@ Struct(V_GpuParams)
Enum(V_ParticleFlag)
{
V_ParticleFlag_None = 0,
V_ParticleFlag_StainOnPrune = (1 << 0),
V_ParticleFlag_StainTrail = (1 << 1),
V_ParticleFlag_PruneWhenStill = (1 << 0),
V_ParticleFlag_StainOnPrune = (1 << 1),
V_ParticleFlag_StainTrail = (1 << 2),
};
Struct(V_Emitter)
@ -115,7 +116,7 @@ Struct(V_Emitter)
f32 velocity_falloff_spread;
Vec4 color_lin;
Vec3 color_spread;
Vec4 color_spread;
};
// TODO: Pack this efficiently
@ -133,7 +134,7 @@ Struct(V_Particle)
f32 lifetime;
f32 velocity_falloff;
Vec3 color;
Vec4 color;
};
#if IsLanguageC