mirror of
https://github.com/vladtrc/iw4L
synced 2026-10-26 02:11:39 -04:00
Draw spark fountains where they were sprayed, at their own size
Shooting a vending machine on mp_terminal drew large flat yellow, white and red sheets next to it and made the game stutter. A spark fountain that runs relative to its effect was sprayed from the effect-local spawn origin, and the custom draw then placed the cells with the cloud matrix T(pos) R(quat) S(vis scale). Prop sparks (props/credit_card_touchpad_exp on the card touchpads, and explosions/powerlines_* from the vending machine's tv_explosion) carry a vis scale of 150, so every spark was drawn 150 times too big; barrelexp and small_vehicle_explosion (scale 10) were 10 times too big. New cells also had every sample time at 0, which ends the ribbon at origins[3]: about 1,780 untraced cells stacked one 150-unit quad on the effect point until the tracer, 16 cells per update, reached them. The spray now gets the world spawn point (spawn_origin_world), untraced cells start as an unobstructed arc (spark_fountain_integrate_miss_cell), and the custom draw is submitted with a unit placement (spark_fountain_draw_cloud). The cells are already in world space and the fountain shader only applies view and projection, so the vis scale stays a draw gate and no longer scales the vertices. Bounce traces now run in the same space as world collision. The elem keeps drifting on its velocity graph (0.4 units/ms on most fountains) while the sparks stay where they were sprayed, so the fountain is now culled around the spray point, with the radius grown by how far a spark can have flown by then (max speed * t + |gravity| * t^2 / 2, times the bounce fraction when it is above 1). Sparks no longer follow an effect that moves after spraying them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+12
-7
@@ -17,6 +17,7 @@ pub struct FxDrawElemContext<'a> {
|
||||
pub def_index: u8,
|
||||
pub elem_type: u8,
|
||||
pub origin: [f32; 3],
|
||||
pub cull_reach: f32,
|
||||
pub axis: [[f32; 3]; 3],
|
||||
pub norm_time: f32,
|
||||
pub age_msec: i32,
|
||||
@@ -393,6 +394,7 @@ fn draw_one_elem(
|
||||
def_index: elem.def_index,
|
||||
elem_type: elem.elem_type,
|
||||
origin,
|
||||
cull_reach: 0.0,
|
||||
axis: effect.axis,
|
||||
norm_time: norm,
|
||||
age_msec: age,
|
||||
@@ -472,19 +474,19 @@ fn draw_one_elem(
|
||||
let age = host.msec_now.wrapping_sub(elem.msec_begin);
|
||||
let norm = elem_norm_time(age, elem.life_span_msec);
|
||||
let elem_random_seed = elem.random_seed;
|
||||
let origin = crate::spark::spark_elem_world_origin(
|
||||
elem.origin,
|
||||
elem.flags,
|
||||
&effect.frame_now(),
|
||||
&effect.frame_when_played(),
|
||||
Some(elem.orient_spawn_params(elem_random_seed)),
|
||||
);
|
||||
let Some((origin, cull_reach)) =
|
||||
crate::spark_fountain::spark_fountain_reach(host, elem.spark_cloud_handle, age)
|
||||
else {
|
||||
out.skipped_spark_fountain = out.skipped_spark_fountain.saturating_add(1);
|
||||
return;
|
||||
};
|
||||
let ctx = FxDrawElemContext {
|
||||
def_name: effect.def_name.as_str(),
|
||||
catalog_index: effect.catalog_index,
|
||||
def_index: elem.def_index,
|
||||
elem_type: elem.elem_type,
|
||||
origin,
|
||||
cull_reach,
|
||||
axis: effect.axis,
|
||||
norm_time: norm,
|
||||
age_msec: age,
|
||||
@@ -550,6 +552,7 @@ fn draw_one_elem(
|
||||
def_index: elem.def_index,
|
||||
elem_type: elem.elem_type,
|
||||
origin,
|
||||
cull_reach: 0.0,
|
||||
axis: effect.axis,
|
||||
norm_time: norm,
|
||||
age_msec: age,
|
||||
@@ -589,6 +592,7 @@ fn draw_one_elem(
|
||||
def_index: elem.def_index,
|
||||
elem_type: elem.elem_type,
|
||||
origin,
|
||||
cull_reach: 0.0,
|
||||
axis: effect.axis,
|
||||
norm_time: norm,
|
||||
age_msec: age,
|
||||
@@ -640,6 +644,7 @@ fn draw_one_elem(
|
||||
def_index: elem.def_index,
|
||||
elem_type: elem.elem_type,
|
||||
origin,
|
||||
cull_reach: 0.0,
|
||||
axis: effect.axis,
|
||||
norm_time: norm,
|
||||
age_msec: age,
|
||||
|
||||
@@ -3,9 +3,9 @@ use fx_iw4::{
|
||||
FX_SPARK_FOUNTAIN_INTEGRATE_BUDGET, FX_SPARK_FOUNTAIN_INTEGRATE_CELLS, msvcrt_rand,
|
||||
spark_fountain_accel_from_gravity, spark_fountain_cone_dir, spark_fountain_handle_for_slot,
|
||||
spark_fountain_integrate_cell, spark_fountain_integrate_cell_begin,
|
||||
spark_fountain_isotropic_dir, spark_fountain_mark_ready, spark_fountain_slot_for_handle,
|
||||
spark_fountain_spark_n_clamped, spark_fountain_speed, spark_fountain_spray_dir,
|
||||
spark_fountain_update_keyframe_cursor,
|
||||
spark_fountain_integrate_miss_cell, spark_fountain_isotropic_dir, spark_fountain_mark_ready,
|
||||
spark_fountain_slot_for_handle, spark_fountain_spark_n_clamped, spark_fountain_speed,
|
||||
spark_fountain_spray_dir, spark_fountain_update_keyframe_cursor,
|
||||
};
|
||||
|
||||
use crate::system::FxSystemHost;
|
||||
@@ -32,6 +32,8 @@ pub struct FxSparkFountainClusterSlot {
|
||||
pub loop_time: f32,
|
||||
pub boost_time: f32,
|
||||
pub boost_factor: f32,
|
||||
pub origin: [f32; 3],
|
||||
pub max_speed: f32,
|
||||
pub mesh_idx: [u16; FX_SPARK_FOUNTAIN_CLUSTER_MESH_MAX as usize],
|
||||
}
|
||||
|
||||
@@ -51,6 +53,8 @@ impl Default for FxSparkFountainClusterSlot {
|
||||
loop_time: 0.0,
|
||||
boost_time: 0.0,
|
||||
boost_factor: 0.0,
|
||||
origin: [0.0; 3],
|
||||
max_speed: 0.0,
|
||||
mesh_idx: [FX_SPARK_FOUNTAIN_HANDLE_NONE; FX_SPARK_FOUNTAIN_CLUSTER_MESH_MAX as usize],
|
||||
}
|
||||
}
|
||||
@@ -215,15 +219,14 @@ pub(crate) fn spray_spark_fountain(
|
||||
let rx = msvcrt_rand(&mut hold);
|
||||
let dir = spark_fountain_spray_dir(cone_axis, [rx, ry, rz], vel_cone_frac);
|
||||
let speed = spark_fountain_speed(msvcrt_rand(&mut hold), vel_min, vel_max);
|
||||
let (times, origins, vels) = spark_fountain_integrate_miss_cell(
|
||||
origin,
|
||||
[dir[0] * speed, dir[1] * speed, dir[2] * speed],
|
||||
);
|
||||
host.spark_fountain_meshes[mesh].cells[cell as usize] = FxSparkFountainCell {
|
||||
times: [0.0; 4],
|
||||
origins: [origin, [0.0; 3], [0.0; 3], [0.0; 3]],
|
||||
vels: [
|
||||
[dir[0] * speed, dir[1] * speed, dir[2] * speed],
|
||||
[0.0; 3],
|
||||
[0.0; 3],
|
||||
[0.0; 3],
|
||||
],
|
||||
times,
|
||||
origins,
|
||||
vels,
|
||||
};
|
||||
cell = cell.saturating_add(1);
|
||||
}
|
||||
@@ -243,9 +246,26 @@ pub(crate) fn spray_spark_fountain(
|
||||
host.spark_fountains[dense].loop_time = loop_time;
|
||||
host.spark_fountains[dense].boost_time = boost_time;
|
||||
host.spark_fountains[dense].boost_factor = boost_factor;
|
||||
host.spark_fountains[dense].origin = origin;
|
||||
host.spark_fountains[dense].max_speed = vel_min.abs().max(vel_max.abs());
|
||||
FountainSpray::Ready
|
||||
}
|
||||
|
||||
pub(crate) fn spark_fountain_reach(
|
||||
host: &FxSystemHost,
|
||||
handle: u16,
|
||||
age_msec: i32,
|
||||
) -> Option<([f32; 3], f32)> {
|
||||
use fx_iw4::{spark_fountain_boost, spark_fountain_wrap_loop_time};
|
||||
let dense = spark_fountain_slot_for_handle(handle)?;
|
||||
let cluster = host.spark_fountains.get(dense).filter(|c| c.occupied)?;
|
||||
let (warped, _) =
|
||||
spark_fountain_boost(cluster.boost_time, cluster.boost_factor, age_msec as f32);
|
||||
let t = spark_fountain_wrap_loop_time(warped, cluster.loop_time).max(0.0);
|
||||
let path = cluster.max_speed * t + 0.5 * cluster.gravity.abs() * t * t;
|
||||
Some((cluster.origin, path * cluster.bounce_frac.abs().max(1.0)))
|
||||
}
|
||||
|
||||
pub(crate) fn emit_spark_fountain_custom_cells(
|
||||
host: &FxSystemHost,
|
||||
handle: u16,
|
||||
|
||||
@@ -529,10 +529,11 @@ fn spawn_elem(
|
||||
match crate::spark_fountain::alloc_spark_fountain(host) {
|
||||
Some(fountain_handle) => {
|
||||
host.elems[elem_slot].spark_cloud_handle = fountain_handle;
|
||||
// Fountain cells are world space in every run mode.
|
||||
match crate::spark_fountain::spray_spark_fountain(
|
||||
host,
|
||||
fountain_handle,
|
||||
origin,
|
||||
spawn_origin_world(elem_def, effect_origin, effect_axis, life_idx),
|
||||
fx_iw4::get_elem_angles_axis(
|
||||
elem_def.spawn_angles,
|
||||
elem_def.angular_velocity,
|
||||
|
||||
@@ -297,7 +297,7 @@ pub use spark_fountain::{
|
||||
spark_fountain_accel_from_gravity, spark_fountain_atlas_uv, spark_fountain_ballistic,
|
||||
spark_fountain_boost, spark_fountain_bounce_vel, spark_fountain_cell_indices,
|
||||
spark_fountain_cell_verts, spark_fountain_cluster_draw_allows, spark_fountain_cone_dir,
|
||||
spark_fountain_cone_is_isotropic, spark_fountain_def_allows_draw,
|
||||
spark_fountain_cone_is_isotropic, spark_fountain_def_allows_draw, spark_fountain_draw_cloud,
|
||||
spark_fountain_draw_clouds_allows, spark_fountain_generate_ribbon,
|
||||
spark_fountain_handle_for_slot, spark_fountain_hit_time, spark_fountain_hit_time_abs,
|
||||
spark_fountain_index_count, spark_fountain_integrate_cell, spark_fountain_integrate_cell_begin,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::particle_cloud::{GfxPosTexVertex, particle_spark_cell_indices};
|
||||
use crate::particle_cloud::{GfxParticleCloud, GfxPosTexVertex, particle_spark_cell_indices};
|
||||
use crate::vec::vec3_normalize;
|
||||
|
||||
pub const FX_SPARK_FOUNTAIN_CLUSTER_STRIDE: usize = 0x40;
|
||||
@@ -578,6 +578,16 @@ pub fn spark_fountain_atlas_uv(col_bits: u8, row_bits: u8, cell: u32) -> [f32; 4
|
||||
[u0, u_span, v0, v_span]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn spark_fountain_draw_cloud(cloud: GfxParticleCloud) -> GfxParticleCloud {
|
||||
GfxParticleCloud {
|
||||
quat: [0.0, 0.0, 0.0, 1.0],
|
||||
pos: [0.0; 3],
|
||||
placement_scale: 1.0,
|
||||
..cloud
|
||||
}
|
||||
}
|
||||
|
||||
pub const R_PARTICLE_CLOUD_CUSTOM_CAP: u32 = 0x40;
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -171,12 +171,13 @@ impl FxParticleCloudPlan {
|
||||
sort_key,
|
||||
material_sorted_index,
|
||||
});
|
||||
let placed = fx_iw4::spark_fountain_draw_cloud(cloud);
|
||||
let empty = fx_iw4::empty_particle_cloud();
|
||||
self.draws.push(FxParticleCloudDraw {
|
||||
material,
|
||||
index_start,
|
||||
index_count: spark_fountain_index_count(cells.len() as u32),
|
||||
clouds: [cloud, empty, empty],
|
||||
clouds: [placed, empty, empty],
|
||||
});
|
||||
self.custom_live = self.custom_live.saturating_add(1);
|
||||
Some(self.draws.len() as u32 - 1)
|
||||
|
||||
@@ -1359,7 +1359,7 @@ pub fn build_fx_verts(
|
||||
ctx.origin,
|
||||
size0,
|
||||
size1,
|
||||
scale,
|
||||
scale + ctx.cull_reach,
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user