From e9ea1ec0f7fe41e303c8fb8877f41ad3b8c0e253 Mon Sep 17 00:00:00 2001 From: jacob Date: Wed, 7 Jan 2026 08:12:14 -0600 Subject: [PATCH] simulated bullet testing --- src/pp/pp_sim/pp_sim_core.c | 195 +++++++++-- src/pp/pp_sim/pp_sim_core.h | 27 +- src/pp/pp_sim/pp_sim_shared.cgh | 2 +- src/pp/pp_vis/pp_vis_core.c | 550 +++++++++++++++++++++----------- src/pp/pp_vis/pp_vis_core.h | 6 +- src/pp/pp_vis/pp_vis_gpu.g | 104 ++++-- src/pp/pp_vis/pp_vis_shared.cgh | 11 +- 7 files changed, 656 insertions(+), 239 deletions(-) diff --git a/src/pp/pp_sim/pp_sim_core.c b/src/pp/pp_sim/pp_sim_core.c index f8ac5a34..88fc5210 100644 --- a/src/pp/pp_sim/pp_sim_core.c +++ b/src/pp/pp_sim/pp_sim_core.c @@ -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; } diff --git a/src/pp/pp_sim/pp_sim_core.h b/src/pp/pp_sim/pp_sim_core.h index a629f936..a6a735c0 100644 --- a/src/pp/pp_sim/pp_sim_core.h +++ b/src/pp/pp_sim/pp_sim_core.h @@ -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 diff --git a/src/pp/pp_sim/pp_sim_shared.cgh b/src/pp/pp_sim/pp_sim_shared.cgh index 7522c49d..9e1bb6f6 100644 --- a/src/pp/pp_sim/pp_sim_shared.cgh +++ b/src/pp/pp_sim/pp_sim_shared.cgh @@ -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) diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index 83f1aa31..2f9564c1 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -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,23 +2470,38 @@ void V_TickForever(WaveLaneCtx *lane) } } break; + case V_CmdKind_reset_world: case V_CmdKind_spawn: { - 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->move_speed = 0.075; - ent->is_player = 1; - ent->local_shape = S_ShapeFromDesc( - .mass = 10, - .count = 1, - .radius = 0.3, - ); - ent->has_weapon = 1; - ent->exists = 1; + // 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(player_pos); + ent->move_speed = 0.075; + ent->is_player = 1; + ent->local_shape = S_ShapeFromDesc( + .mass = 10, + .count = 1, + .radius = 0.3, + ); + ent->has_weapon = 1; + ent->exists = 1; + } } break; case V_CmdKind_spawn_dummy: @@ -2600,69 +2607,160 @@ void V_TickForever(WaveLaneCtx *lane) - - ////////////////////////////// - //- Spawn test bullet particles + //- Push test bullet particles // TODO: Not like this + + + + for (S_Ent *bullet = S_FirstEnt(world); bullet->valid; bullet = S_NextEnt(bullet)) { - PERSIST Vec2 start = {5, 5}; - PERSIST Vec2 end = {3, -20}; - - - if (frame->held_buttons[Button_G]) + if (bullet->is_bullet) { - end = frame->world_cursor; - if (!last_frame->held_buttons[Button_G]) + // FIXME: Truncate bullet trail at hit + Vec2 start = bullet->bullet_start; + Vec2 end = bullet->bullet_end; + + b32 skip = 0; + if (bullet->has_hit) { - start = end; + 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; } + + if (!skip) + { + 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.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.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; + } + + } - - Vec2 vel = SubVec2(end, start); - - - f32 trail_len = Vec2Len(SubVec2(end, start)); - f32 particles_count = MaxF32(trail_len * 64, 1); - - - // 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->angle = angle; - emitter->angle_spread = Tau / 4; - - emitter->start = start; - emitter->end = end; - - - emitter->color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1)); - - // emitter->speed = 0; - // emitter->speed_spread = 1; - - emitter->speed = 1; - emitter->speed_spread = 1; - - emitter->velocity_falloff = 0; - emitter->velocity_falloff_spread = 0; - - start = end; - end = AddVec2(end, vel); } + // { + // 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->velocity_falloff = 1; + // // emitter->velocity_falloff_spread = 0; + + // start = end; + // end = AddVec2(end, vel); + // } + + + + + @@ -2680,107 +2778,182 @@ 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)) + for (S_Ent *bullet = S_FirstEnt(world); bullet->valid; bullet = S_NextEnt(bullet)) { - if (firer->fire_held) + if (bullet->is_bullet && bullet->has_hit) { - Xform firer_xf = firer->xf; - S_Shape firer_world_shape = S_MulXformShape(firer_xf, firer->local_shape); + // Vec2 bullet_start = bullet->start; + // Vec2 bullet_end = bullet->end; - Vec2 ray_start = firer_world_shape.centroid; - Vec2 ray_dir = firer->look; + Vec2 hit_entry = bullet->hit_entry; + Vec2 hit_entry_normal = bullet->hit_entry_normal; + Vec2 bullet_vel = SubVec2(bullet->bullet_end, bullet->bullet_start); - // TODO: Real raycast query - S_Ent *closest_victim = &S_NilEnt; - S_RaycastResult victim_raycast = Zi; + 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", 0)) { - 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; - } - } - } - } + 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; - if (closest_victim->valid) - { - V_ParticleFlag flags = 0; - 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; - V_Emitter *emitter = V_PushEmitter(count); - emitter->flags = flags; + // Vec2 dir = hit_entry_normal; + Vec2 dir = NormVec2(NegVec2(bullet_vel)); - Vec2 dir = victim_raycast.normal; - emitter->start = victim_raycast.p; - emitter->end = emitter->start; + emitter->start = hit_entry; + emitter->end = emitter->start; - emitter->speed = speed; - emitter->speed_spread = speed * 2; + emitter->speed = speed; + emitter->speed_spread = speed * 2; - emitter->velocity_falloff = falloff; - emitter->velocity_falloff_spread = falloff * 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->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 = VEC3(0.1, 0, 0); + 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->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; + 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) - // { - // } + // V_DrawPoint(victim_raycast.p, Color_Green); + // V_DrawLine(victim_raycast.p, AddVec2(victim_raycast.p, MulVec2(victim_raycast.normal, 0.5)), Color_White); } } } - ////////////////////////////// - //- Spawn test emitter - // Spawn test emitter + + + + + + + + // { + // 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) + // // { + // // } + // } + // } + // } + + ////////////////////////////// + //- 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 diff --git a/src/pp/pp_vis/pp_vis_core.h b/src/pp/pp_vis/pp_vis_core.h index f911bb2d..915f9c80 100644 --- a/src/pp/pp_vis/pp_vis_core.h +++ b/src/pp/pp_vis/pp_vis_core.h @@ -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 diff --git a/src/pp/pp_vis/pp_vis_gpu.g b/src/pp/pp_vis/pp_vis_gpu.g index 084f97a0..61b67be4 100644 --- a/src/pp/pp_vis/pp_vis_gpu.g +++ b/src/pp/pp_vis/pp_vis_gpu.g @@ -153,30 +153,68 @@ ComputeShader2D(V_BackdropCS, 8, 8) - // TODO: Remove this // Cells test { + RWTexture2D stains = G_Dereference(params.stains); RWTexture2D cells = G_Dereference(params.cells); Vec2 cell_pos = floor(mul(params.xf.world_to_cell, Vec3(world_pos, 1))); + + Vec4 stain = stains.Load(cell_pos); Vec4 cell = cells.Load(cell_pos); - if (cell.a != 0) - { - result = cell; - } - else - { - RWTexture2D stains = G_Dereference(params.stains); - Vec4 stain = stains.Load(cell_pos); - if (stain.a != 0) - { - result = stain; - } - } + // 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 cells = G_Dereference(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 stains = G_Dereference(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 diff --git a/src/pp/pp_vis/pp_vis_shared.cgh b/src/pp/pp_vis/pp_vis_shared.cgh index 7b4fe7dd..673a2bb5 100644 --- a/src/pp/pp_vis/pp_vis_shared.cgh +++ b/src/pp/pp_vis/pp_vis_shared.cgh @@ -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 @@ -149,7 +150,7 @@ Struct(V_Particle) Enum(V_DQuadFlag) { - V_DQuadFlag_None = 0, + V_DQuadFlag_None = 0, }; Struct(V_DQuad)