From 2a3a446fdcf099e0285b0ad1f5bd5bb388825a2e Mon Sep 17 00:00:00 2001 From: jacob Date: Sat, 4 Apr 2026 07:09:38 -0500 Subject: [PATCH] bloomy bullet particles --- src/pp/pp.c | 2 +- src/pp/pp_vis/pp_vis_core.c | 270 ++++++++++++++++++++------------ src/pp/pp_vis/pp_vis_gpu.g | 47 ++++-- src/pp/pp_vis/pp_vis_gpu.gh | 3 +- src/pp/pp_vis/pp_vis_shared.cgh | 59 +++++-- 5 files changed, 253 insertions(+), 128 deletions(-) diff --git a/src/pp/pp.c b/src/pp/pp.c index c0da42f9..84ecd68f 100644 --- a/src/pp/pp.c +++ b/src/pp/pp.c @@ -3212,7 +3212,7 @@ void P_StepFrame(P_Frame *frame) f32 speed_falloff = 0; if (weapon->is_uzi) { - initial_speed = TweakFloat("Bullet speed", 50, 1, 100); + initial_speed = TweakFloat("Bullet speed", 75, 1, 100); } else if (weapon->is_launcher) { diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index db39c068..00f96d23 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -3018,6 +3018,7 @@ void V_TickForever(WaveLaneCtx *lane) { if (event->is_first_observation) { + f64 seconds_since_observation = SecondsFromNs(frame->time_ns - event->initial_observation_time_ns); u64 angle_offset_basis = 0xf55070bae6e1d70c; f32 rand_angle = (Norm24(MixU64s(event->key.v, angle_offset_basis)) * 2 - 1); @@ -3033,51 +3034,159 @@ void V_TickForever(WaveLaneCtx *lane) Vec2 p0 = event->trail_p0; Vec2 p1 = event->trail_p1; - //- Smoke trail particles + f32 trail_len = Vec2Len(SubVec2(p1, p0)); + + Vec2 trail_velocity = MulVec2(SubVec2(p1, p0), SIM_TICKS_PER_SECOND); + f32 trail_speed = Vec2Len(trail_velocity); + + //- Bullet smoke + { + f32 particles_count = trail_len * frame->dt * Kibi(16); // Particles per meter per second + particles_count = MaxF32(particles_count, 1); + + f32 angle = AngleFromVec2(PerpVec2(SubVec2(p1, p0))); + // f32 angle = AngleFromVec2(NegVec2(SubVec2(p1, p0))); + + V_Emitter emitter = Zi; + { + emitter.kind = V_ParticleKind_BulletSmoke; + emitter.count = particles_count; + + f32 angle_spread = Tau / 4.0; + + emitter.angle.min = angle - angle_spread / 2; + emitter.angle.max = angle + angle_spread / 2; + + emitter.pos.p0 = p0; + emitter.pos.p1 = p1; + + // emitter.color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1)); + + // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1)); + + emitter.speed.min = -1; + emitter.speed.max = 1; + } + V_PushParticles(emitter); + } + + + //- Bullet trail // { - // f32 trail_len = Vec2Len(SubVec2(p1, p0)); - // f32 particles_count = trail_len * frame->dt * Kibi(8); // Particles per meter per second - // particles_count = MaxF32(particles_count, 1); - - // f32 angle = AngleFromVec2(PerpVec2(SubVec2(p1, p0))); - // // f32 angle = AngleFromVec2(NegVec2(SubVec2(p1, p0))); - // V_Emitter emitter = Zi; - // { - // emitter.kind = V_ParticleKind_BulletTrail; - // emitter.count = particles_count; + // emitter.kind = V_ParticleKind_BulletTrail; + // f32 particles_count = trail_len * P_CellsPerMeter; + // emitter.count = particles_count; - // f32 angle_spread = Tau / 4.0; + // f32 angle = AngleFromVec2(SubVec2(p1, p0)); - // emitter.angle.min = angle - angle_spread / 2; - // emitter.angle.max = angle + angle_spread / 2; + // // f32 angle_spread = Tau / 4.0; - // emitter.pos.p0 = p0; - // emitter.pos.p1 = p1; + // // emitter.angle.min = angle - angle_spread / 2; + // // emitter.angle.max = angle + angle_spread / 2; - // // emitter.color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1)); + // emitter.angle.min = + // emitter.angle.max = angle; - // // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1)); + // emitter.pos.p0 = p0; + // emitter.pos.p1 = p1; + + // // emitter.speed.min = + // // emitter.speed.max = + + // // emitter.color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1)); + + // // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1)); + // // emitter.speed.min = + // // emitter.speed.max = Vec2Len(SubVec2(p1, p0)) / frame->dt; - // emitter.speed.min = -1; - // emitter.speed.max = 1; - // } // V_PushParticles(emitter); // } + + + + + + + // //- Bullet particle + // { + // V_Emitter emitter = Zi; + // emitter.kind = V_ParticleKind_Bullet; + // // emitter.count = MaxF32(1000 * frame->dt, 1); + // emitter.count = 1; + + // f32 angle = AngleFromVec2(SubVec2(p1, p0)); + + // // f32 angle_spread = Tau / 4.0; + + // // emitter.angle.min = angle - angle_spread / 2; + // // emitter.angle.max = angle + angle_spread / 2; + + // emitter.angle.min = + // emitter.angle.max = angle; + + // emitter.pos.p0 = + // emitter.pos.p1 = p1; + + // // emitter.speed.min = + // // emitter.speed.max = + + // // emitter.color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1)); + + // // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1)); + // emitter.speed.min = + // emitter.speed.max = Vec2Len(SubVec2(p1, p0)) / frame->dt; + + // V_PushParticles(emitter); + // } + + if (event->is_first_trail) { - //- Muzzle spark particles + //- Bullet particle { V_Emitter emitter = Zi; - emitter.kind = V_ParticleKind_Flash; + emitter.kind = V_ParticleKind_Bullet; + // emitter.count = MaxF32(1000 * frame->dt, 1); + emitter.count = 1; + + f32 angle = AngleFromVec2(SubVec2(p1, p0)); + + // f32 angle_spread = Tau / 4.0; + + // emitter.angle.min = angle - angle_spread / 2; + // emitter.angle.max = angle + angle_spread / 2; + + emitter.angle.min = + emitter.angle.max = angle; + + emitter.pos.p0 = + emitter.pos.p1 = p0; + + // emitter.speed.min = + // emitter.speed.max = + + // emitter.color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1)); + + // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1)); + emitter.speed.min = + emitter.speed.max = trail_speed; + + V_PushParticles(emitter); + } + + //- Wide muzzle flash particles + { + V_Emitter emitter = Zi; + emitter.kind = V_ParticleKind_MuzzleWide; // emitter.count = MaxF32(1000 * frame->dt, 1); // emitter.count = 128; emitter.count = 8; f32 angle = AngleFromVec2(SubVec2(p1, p0)); - f32 angle_spread = Tau / 2; + f32 angle_spread = Tau / 3; emitter.angle.min = angle - angle_spread / 2; emitter.angle.max = angle + angle_spread / 2; @@ -3091,7 +3200,7 @@ void V_TickForever(WaveLaneCtx *lane) // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1)); - emitter.speed.min = 5; + emitter.speed.min = 10; emitter.speed.max = 20; // emitter.angle.min = angle - angle_spread / 2; @@ -3116,53 +3225,53 @@ void V_TickForever(WaveLaneCtx *lane) } - //- Wide muzzle flash particles - { - V_Emitter emitter = Zi; - emitter.kind = V_ParticleKind_Spark; - // emitter.count = MaxF32(1000 * frame->dt, 1); - // emitter.count = 128; - emitter.count = 1; + //- Spark particles + // { + // V_Emitter emitter = Zi; + // emitter.kind = V_ParticleKind_Spark; + // // emitter.count = MaxF32(1000 * frame->dt, 1); + // // emitter.count = 128; + // emitter.count = 1; - f32 angle = AngleFromVec2(SubVec2(p1, p0)); + // f32 angle = AngleFromVec2(SubVec2(p1, p0)); - f32 angle_spread = Tau / 2; - emitter.angle.min = angle - angle_spread / 2; - emitter.angle.max = angle + angle_spread / 2; + // f32 angle_spread = Tau / 2; + // emitter.angle.min = angle - angle_spread / 2; + // emitter.angle.max = angle + angle_spread / 2; - emitter.pos.p0 = - emitter.pos.p1 = p0; + // emitter.pos.p0 = + // emitter.pos.p1 = p0; - // emitter.lifetime.min = 0.1 - // emitter.lifetime.max = 0.5; + // // emitter.lifetime.min = 0.1 + // // emitter.lifetime.max = 0.5; - // 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.6, 0.2, 1)); + // // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1)); - emitter.speed.min = 20; - emitter.speed.max = 100; + // emitter.speed.min = 20; + // emitter.speed.max = 100; - // emitter.angle.min = angle - angle_spread / 2; - // emitter.angle.max = angle + angle_spread / 2; + // // emitter.angle.min = angle - angle_spread / 2; + // // emitter.angle.max = angle + angle_spread / 2; - // emitter.angle.min = - // emitter.angle.max = angle; + // // emitter.angle.min = + // // emitter.angle.max = angle; - emitter.pos.p0 = - emitter.pos.p1 = p0; + // emitter.pos.p0 = + // emitter.pos.p1 = p0; - // emitter.speed.min = - // emitter.speed.max = + // // emitter.speed.min = + // // emitter.speed.max = - // 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.6, 0.2, 1)); - // emitter.speed.min = - // emitter.speed.max = Vec2Len(SubVec2(p1, p0)) / frame->dt; + // // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1)); + // // emitter.speed.min = + // // emitter.speed.max = Vec2Len(SubVec2(p1, p0)) / frame->dt; - V_PushParticles(emitter); - } + // V_PushParticles(emitter); + // } @@ -3173,7 +3282,7 @@ void V_TickForever(WaveLaneCtx *lane) //- Narrow muzzle flash particles { V_Emitter emitter = Zi; - emitter.kind = V_ParticleKind_Flash; + emitter.kind = V_ParticleKind_MuzzleNarrow; // emitter.count = MaxF32(1000 * frame->dt, 1); // emitter.count = 128; emitter.count = 1; @@ -3181,7 +3290,7 @@ void V_TickForever(WaveLaneCtx *lane) f32 angle = AngleFromVec2(SubVec2(p1, p0)); // f32 angle_spread = Tau / 64; - f32 angle_spread = 0; + f32 angle_spread = Tau / 64; emitter.angle.min = angle - angle_spread / 2; emitter.angle.max = angle + angle_spread / 2; @@ -3195,8 +3304,8 @@ void V_TickForever(WaveLaneCtx *lane) // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1)); - emitter.speed.min = 35; - emitter.speed.max = 35; + emitter.speed.min = 45; + emitter.speed.max = 45; // emitter.angle.min = angle - angle_spread / 2; // emitter.angle.max = angle + angle_spread / 2; @@ -3275,43 +3384,6 @@ void V_TickForever(WaveLaneCtx *lane) // V_PushParticles(emitter); // } - - - - - - - //- Test bullet particle - // { - // V_Emitter emitter = Zi; - // emitter.kind = V_ParticleKind_Fire; - // // emitter.count = MaxF32(1000 * frame->dt, 1); - // emitter.count = 1; - - // f32 angle = AngleFromVec2(SubVec2(p1, p0)); - - // // f32 angle_spread = Tau / 4.0; - - // // emitter.angle.min = angle - angle_spread / 2; - // // emitter.angle.max = angle + angle_spread / 2; - - // emitter.angle.min = - // emitter.angle.max = angle; - - // emitter.pos.p0 = - // emitter.pos.p1 = p0; - - // // emitter.speed.min = - // // emitter.speed.max = - - // // emitter.color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1)); - - // // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1)); - // emitter.speed.min = - // emitter.speed.max = Vec2Len(SubVec2(p1, p0)) / frame->dt; - - // V_PushParticles(emitter); - // } } } diff --git a/src/pp/pp_vis/pp_vis_gpu.g b/src/pp/pp_vis/pp_vis_gpu.g index a04b529d..86128080 100644 --- a/src/pp/pp_vis/pp_vis_gpu.g +++ b/src/pp/pp_vis/pp_vis_gpu.g @@ -9,12 +9,19 @@ f32 V_RandFromPos(Vec3 pos) return rand; } -Vec4 V_ColorFromParticle(V_ParticleDesc desc, u32 particle_idx, f32 alive_seconds, u32 density) +f32 V_LifetimeFromParticleDesc(V_ParticleDesc desc, u32 particle_idx) +{ + u64 seed = MixU64(V_ParticleLifetimeBasis ^ particle_idx); + f32 rand_lifetime = Norm16(seed >> 16); + f32 result = desc.lifetime_min + (desc.lifetime_max - desc.lifetime_min) * rand_lifetime; + return result; +} + +Vec4 V_ColorFromParticleDesc(V_ParticleDesc desc, u32 particle_idx, f32 alive_seconds, u32 density) { Vec4 result = 0; u64 seed = MixU64(V_ParticleColorBasis ^ particle_idx); f32 rand_color = Norm16(seed >> 0); - f32 rand_lifetime = Norm16(seed >> 16); result = desc.base_color; @@ -49,16 +56,12 @@ Vec4 V_ColorFromParticle(V_ParticleDesc desc, u32 particle_idx, f32 alive_second result.rgb = result.rgb + (rand_color - 0.5) * 0.05; - if (desc.lifetime_min < Inf && alive_seconds > 0) + if (desc.flags & V_ParticleFlag_FadeLifetime && desc.lifetime_min < Inf && alive_seconds > 0) { - f32 lifetime = desc.lifetime_min + (desc.lifetime_max - desc.lifetime_min) * rand_lifetime; + f32 lifetime = V_LifetimeFromParticleDesc(desc, particle_idx); f32 lifetime_complete = saturate(alive_seconds / lifetime); - f32 v = lifetime_complete * lifetime_complete; - - // result.a *= 1.0 - lifetime_complete; result.a *= 1.0 - v; - // result.rgb *= 1.0 - lifetime_complete; } @@ -171,7 +174,7 @@ ComputeShader(V_PrepareCellsCS) u32 particle_idx = packed & ((1 << 24) - 1); f32 alive_seconds = 0; - Vec4 base_color = V_ColorFromParticle(desc, particle_idx, 0, density); + Vec4 base_color = V_ColorFromParticleDesc(desc, particle_idx, 0, density); Vec4 dry_color = base_color * desc.dry_factor; base_color.rgb *= base_color.a; @@ -481,6 +484,15 @@ ComputeShader(V_SimParticlesCS) particle.pos = lerp(emitter.pos.p0, emitter.pos.p1, rand_offset); particle.velocity = Vec2(cos(initial_angle), sin(initial_angle)) * initial_speed; } + else + { + V_ParticleDesc desc = V_DescFromParticleKind((V_ParticleKind)particle.kind); + particle.alive_seconds += frame.dt; + if (particle.alive_seconds > V_LifetimeFromParticleDesc(desc, particle_idx)) + { + prune = 1; + } + } ////////////////////////////// //- Simulate @@ -618,6 +630,10 @@ ComputeShader(V_SimParticlesCS) collision = 1; done = 1; { + if (AnyBit(desc.flags, V_ParticleFlag_HaltOnCollision)) + { + particle.velocity = Vec2(0, 0); + } if (stepped_x) { if (!AnyBit(desc.flags, V_ParticleFlag_NoReflect)) @@ -702,8 +718,6 @@ ComputeShader(V_SimParticlesCS) particle.pos = p0 + (p1 - p0) * t; } - - particle.alive_seconds += frame.dt; } ////////////////////////////// @@ -934,7 +948,7 @@ ComputeShader(V_CompositeCS) u32 density = densities[cell_pos]; V_ParticleDesc desc = V_DescFromParticleKind(particle_kind); - Vec4 cell_color = V_ColorFromParticle(desc, particle_idx, particle.alive_seconds, density); + Vec4 cell_color = V_ColorFromParticleDesc(desc, particle_idx, particle.alive_seconds, density); cell_color.rgb *= cell_color.a; if (layer == V_ParticleLayer_Ground) @@ -986,7 +1000,7 @@ ComputeShader(V_CompositeCS) // u32 density = densities[cell_pos]; // V_ParticleDesc desc = V_DescFromParticleKind(particle_kind); // u32 particle_idx = packed & ((1 << 24) - 1); - // Vec4 cell_color = V_ColorFromParticle(desc, particle_idx, density); + // Vec4 cell_color = V_ColorFromParticleDesc(desc, particle_idx, density); // cell_color.rgb *= cell_color.a; // if (layer == V_ParticleLayer_Ground) @@ -1277,8 +1291,13 @@ ComputeShader(V_BloomDownCS) f32 knee_weight = 1; if (is_first_pass) { + // f32 luminance = LuminanceFromColor(src); + // f32 max_rgb = max(max(src.r, src.g), src.b); // So that we can get bloom on colors with high rgb, not just high luminance + f32 luminance = LuminanceFromColor(src); - f32 max_rgb = max(max(src.r, src.g), src.b); // So that we can get bloom on colors with high rgb, not just high luminance + f32 max_rgb = -1; // So that we can get bloom on colors with high rgb, not just high luminance + + f32 bright = max(luminance, (max_rgb - 1.0) * 0.5); if (bright > 0) { diff --git a/src/pp/pp_vis/pp_vis_gpu.gh b/src/pp/pp_vis/pp_vis_gpu.gh index 40124e87..9275bb31 100644 --- a/src/pp/pp_vis/pp_vis_gpu.gh +++ b/src/pp/pp_vis/pp_vis_gpu.gh @@ -45,7 +45,8 @@ Struct(V_DVertPSOutput) //~ Helpers f32 V_RandFromPos(Vec3 pos); -Vec4 V_ColorFromParticle(V_ParticleDesc desc, u32 particle_idx, f32 alive_seconds, u32 density); +f32 V_LifetimeFromParticleDesc(V_ParticleDesc desc, u32 particle_idx); +Vec4 V_ColorFromParticleDesc(V_ParticleDesc desc, u32 particle_idx, f32 alive_seconds, u32 density); //////////////////////////////////////////////////////////// //~ Shaders diff --git a/src/pp/pp_vis/pp_vis_shared.cgh b/src/pp/pp_vis/pp_vis_shared.cgh index bee2edb7..cc9e4257 100644 --- a/src/pp/pp_vis/pp_vis_shared.cgh +++ b/src/pp/pp_vis/pp_vis_shared.cgh @@ -24,6 +24,7 @@ G_DeclRegister(i32, V_GpuReg_MipIdx, 4); #define V_ParticleColorBasis 0x569aa8341ecc0ea3ull #define V_ParticleCellBasis 0xf60c0cff344b0c5dull #define V_ParticleStainBasis 0x3c64e8226d98d376ull +#define V_ParticleLifetimeBasis 0x4969cf8f60816abfull Enum(V_ParticleFlag) { @@ -32,6 +33,8 @@ Enum(V_ParticleFlag) V_ParticleFlag_NoReflect = (1 << 2), V_ParticleFlag_OnlyCollideWithWalls = (1 << 3), V_ParticleFlag_GasBlend = (1 << 4), + V_ParticleFlag_FadeLifetime = (1 << 5), + V_ParticleFlag_HaltOnCollision = (1 << 6), }; Enum(V_ParticleLayer) @@ -102,26 +105,16 @@ Enum(V_ParticleLayer) /* Flags */ V_ParticleFlag_StainWhenPruned, \ /* Layer */ V_ParticleLayer_Mid, \ /* Stain rate, pen chance */ 0, 0, \ - /* Lifetime */ 0.2, 5, \ + /* Lifetime */ 0.2, 0.3, \ /* Prune speed threshold */ 0.1, \ /* Base color */ VEC4(2, 0.5, 0, 1), \ /* Dry color factor */ VEC4(0.2, 0.1, 0.0, 1) \ ) \ \ /* Air particles */ \ - X( \ - /* Name */ BulletTrail, \ - /* Flags */ V_ParticleFlag_OnlyCollideWithWalls | V_ParticleFlag_GasBlend, \ - /* Layer */ V_ParticleLayer_Mid, \ - /* Stain rate, pen chance */ 0, 0, \ - /* Lifetime */ 0.075, 0.075, \ - /* Prune speed threshold */ 0.01, \ - /* Base color */ VEC4(0.8, 0.6, 0.2, 0.25), \ - /* Dry color factor */ VEC4(1, 1, 1, 1) \ - ) \ X( \ /* Name */ Smoke, \ - /* Flags */ V_ParticleFlag_OnlyCollideWithWalls | V_ParticleFlag_GasBlend, \ + /* Flags */ V_ParticleFlag_OnlyCollideWithWalls | V_ParticleFlag_GasBlend | V_ParticleFlag_FadeLifetime, \ /* Layer */ V_ParticleLayer_Air, \ /* Stain rate, pen chance */ 0, 0, \ /* Lifetime */ Inf, Inf, \ @@ -130,7 +123,17 @@ Enum(V_ParticleLayer) /* Dry color factor */ VEC4(1, 1, 1, 1) \ ) \ X( \ - /* Name */ Flash, \ + /* Name */ BulletSmoke, \ + /* Flags */ V_ParticleFlag_OnlyCollideWithWalls | V_ParticleFlag_GasBlend | V_ParticleFlag_FadeLifetime, \ + /* Layer */ V_ParticleLayer_Mid, \ + /* Stain rate, pen chance */ 0, 0, \ + /* Lifetime */ 0.075, 0.2, \ + /* Prune speed threshold */ 0.00, \ + /* Base color */ VEC4(0.8, 0.6, 0.2, 0.005), \ + /* Dry color factor */ VEC4(1, 1, 1, 1) \ + ) \ + X( \ + /* Name */ MuzzleWide, \ /* Flags */ V_ParticleFlag_None, \ /* Layer */ V_ParticleLayer_Air, \ /* Stain rate, pen chance */ 0, 0, \ @@ -138,6 +141,36 @@ Enum(V_ParticleLayer) /* Prune speed threshold */ 0, \ /* Base color */ VEC4(10, 3.5, 0, 1), \ /* Dry color factor */ VEC4(0.2, 0.1, 0.0, 1) \ + ) \ + X( \ + /* Name */ MuzzleNarrow, \ + /* Flags */ V_ParticleFlag_None, \ + /* Layer */ V_ParticleLayer_Air, \ + /* Stain rate, pen chance */ 0, 0, \ + /* Lifetime */ 0.0, 0.05, \ + /* Prune speed threshold */ 0, \ + /* Base color */ VEC4(10, 3.5, 0, 1), \ + /* Dry color factor */ VEC4(0.2, 0.1, 0.0, 1) \ + ) \ + X( \ + /* Name */ BulletTrail, \ + /* Flags */ V_ParticleFlag_OnlyCollideWithWalls | V_ParticleFlag_FadeLifetime, \ + /* Layer */ V_ParticleLayer_Air, \ + /* Stain rate, pen chance */ 0, 0, \ + /* Lifetime */ 0.075, 0.075, \ + /* Prune speed threshold */ 0, \ + /* Base color */ VEC4(3, 1.5, 0, 1), \ + /* Dry color factor */ VEC4(0.2, 0.1, 0.0, 1) \ + ) \ + X( \ + /* Name */ Bullet, \ + /* Flags */ V_ParticleFlag_NoReflect | V_ParticleFlag_HaltOnCollision, \ + /* Layer */ V_ParticleLayer_Air, \ + /* Stain rate, pen chance */ 0, 0, \ + /* Lifetime */ Inf, Inf, \ + /* Prune speed threshold */ 0.01, \ + /* Base color */ VEC4(5, 1.75, 0.75, 1), \ + /* Dry color factor */ VEC4(0.2, 0.1, 0.0, 1) \ ) \ \ /* Test particles */ \