diff --git a/crates/fx/src/draw.rs b/crates/fx/src/draw.rs index 0a6bb68..8d557a7 100644 --- a/crates/fx/src/draw.rs +++ b/crates/fx/src/draw.rs @@ -1,6 +1,6 @@ use fx_iw4::{ FxElemType, FxRandomChannel, FxTrailEmittedVert, FxTrailSegmentDrawState, FxTrailVertex, - draw_elem_handler_present, elem_norm_time, elem_random_seed, sample_life_span_msec, sample_u16, + draw_elem_handler_present, elem_norm_time, sample_life_span_msec, sample_u16, spark_fountain_cluster_draw_allows, spark_fountain_slot_for_handle, trail_compute_u, trail_emit_index_quad, trail_emit_segment_verts, trail_uncompress_basis, vec3_length_sq, vec3_normalize, @@ -375,8 +375,7 @@ 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(effect.random_seed, elem.sequence, elem.msec_begin); + let elem_random_seed = elem.random_seed; let origin = crate::spark::spark_elem_world_origin( elem.origin, elem.flags, @@ -423,7 +422,7 @@ 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 seed = elem_random_seed(effect.random_seed, elem.sequence, elem.msec_begin); + let seed = elem.random_seed; let Some(size1) = on_spark_size1(FxSparkDrawQuery { def_name: effect.def_name.as_str(), catalog_index: effect.catalog_index, @@ -468,8 +467,7 @@ 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(effect.random_seed, elem.sequence, elem.msec_begin); + let elem_random_seed = elem.random_seed; let origin = crate::spark::spark_elem_world_origin( elem.origin, elem.flags, @@ -534,8 +532,7 @@ 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(effect.random_seed, elem.sequence, elem.msec_begin); + let elem_random_seed = elem.random_seed; let origin = crate::spark::spark_elem_world_origin( elem.origin, elem.flags, @@ -574,8 +571,7 @@ 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(effect.random_seed, elem.sequence, elem.msec_begin); + let elem_random_seed = elem.random_seed; let origin = crate::spark::spark_elem_world_origin( elem.origin, elem.flags, @@ -626,7 +622,7 @@ 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(effect.random_seed, elem.sequence, elem.msec_begin); + let elem_random_seed = elem.random_seed; let origin = crate::spark::spark_elem_world_origin( elem.origin, elem.flags, diff --git a/crates/fx/src/elem.rs b/crates/fx/src/elem.rs index ee7908a..07d2eff 100644 --- a/crates/fx/src/elem.rs +++ b/crates/fx/src/elem.rs @@ -22,6 +22,9 @@ pub struct FxElemSlot { pub msec_begin: i32, pub life_span_msec: i32, + /// `elem_random_seed` of the owning effect, sequence and `msec_begin`, + /// none of which change while the element lives. + pub random_seed: u64, pub base_vel: [f32; 3], pub origin: [f32; 3], @@ -54,6 +57,7 @@ impl Default for FxElemSlot { prev_elem_handle: FX_ELEM_HANDLE_NONE, msec_begin: 0, life_span_msec: 0, + random_seed: 0, base_vel: [0.0; 3], origin: [0.0; 3], spawn_origin: [[0.0; 2]; 3], diff --git a/crates/fx/src/sort.rs b/crates/fx/src/sort.rs index 2ad728e..c758087 100644 --- a/crates/fx/src/sort.rs +++ b/crates/fx/src/sort.rs @@ -145,7 +145,7 @@ fn sort_distance( } let elem = host.elems.get(elem_slot)?; let effect = host.effect_at(effect_slot)?; - let seed = fx_iw4::elem_random_seed(effect.random_seed, elem.sequence, elem.msec_begin); + let seed = elem.random_seed; let world = spark_elem_world_origin( elem.origin, elem.flags, diff --git a/crates/fx/src/spawn.rs b/crates/fx/src/spawn.rs index 1deb76b..3816866 100644 --- a/crates/fx/src/spawn.rs +++ b/crates/fx/src/spawn.rs @@ -476,6 +476,7 @@ fn spawn_elem( prev_elem_handle: FX_ELEM_HANDLE_NONE, msec_begin, life_span_msec: life, + random_seed: life_idx, base_vel: [0.0; 3], origin, spawn_origin: elem_def.spawn_origin, diff --git a/crates/fx/src/update.rs b/crates/fx/src/update.rs index 421a094..2497a5f 100644 --- a/crates/fx/src/update.rs +++ b/crates/fx/src/update.rs @@ -2,8 +2,8 @@ use fx_iw4::{ FX_ELEM_TYPE_SPARK_CLOUD, FX_ELEM_TYPE_SPARK_FOUNTAIN, FX_STATUS_HAS_PENDING_LOOP_ELEMS, FX_STATUS_REF_COUNT_MASK_IW4, FxOrientFrame, FxUpdateEffectBolt, axis_to_quat, begin_iterating_over_effects_exclusive, bolt_compose_orientation, bolt_mark_lost, - elem_norm_time, elem_random_seed, elem_uses_collision, end_iterating_over_effects, - end_iterating_runs_gc, get_orientation, unit_quat_to_axis, update_effect_bolt, vector_vectors, + elem_norm_time, elem_uses_collision, end_iterating_over_effects, end_iterating_runs_gc, + get_orientation, unit_quat_to_axis, update_effect_bolt, vector_vectors, }; use std::collections::HashMap; @@ -384,22 +384,20 @@ fn pending_collide_for_elem( } let def_index = elem.def_index; let base_vel = elem.base_vel; - let sequence = elem.sequence; + let elem_seed = elem.random_seed; let origin = elem.origin; let msec_begin = elem.msec_begin; let life_msec = elem.life_span_msec.max(1); let flags = elem.flags; - let (def_name, catalog_index, effect_seed, now, alt) = match host.effect_at(effect_slot) { + let (def_name, catalog_index, now, alt) = match host.effect_at(effect_slot) { Some(e) => ( slot_def_name(e), e.catalog_index, - e.random_seed, e.frame_now(), e.frame_when_played(), ), None => return None, }; - let elem_seed = elem_random_seed(effect_seed, sequence, msec_begin); let spawn = host .elems .get(slot) @@ -614,22 +612,20 @@ fn update_element( let msec_begin = elem.msec_begin; let life_msec = elem.life_span_msec.max(1); let base_vel = elem.base_vel; - let sequence = elem.sequence; + let elem_seed = elem.random_seed; let origin = elem.origin; let elem_type = elem.elem_type; let spark_handle = elem.spark_cloud_handle; let at_rest_fraction = elem.at_rest_fraction; - let (def_name, catalog_index, effect_seed, now, alt) = match host.effect_at(effect_slot) { + let (def_name, catalog_index, now, alt) = match host.effect_at(effect_slot) { Some(e) => ( slot_def_name(e), e.catalog_index, - e.random_seed, e.frame_now(), e.frame_when_played(), ), None => return true, }; - let elem_seed = elem_random_seed(effect_seed, sequence, msec_begin); let spawn = host .elems .get(slot) @@ -666,7 +662,7 @@ fn update_element( life_ms, dt_sec, base_vel, - elem_random_seed: elem_random_seed(effect_seed, sequence, msec_begin), + elem_random_seed: elem_seed, origin, prev_msec, msec_now, @@ -694,7 +690,6 @@ fn update_element( } } let emit_residual = host.elems.get(slot).map(|e| e.emit_residual).unwrap_or(0); - let elem_seed = elem_random_seed(effect_seed, sequence, msec_begin); if let Some(sched) = on_emit(FxEmitQuery { def_name: def_name.as_str(), catalog_index, @@ -797,25 +792,17 @@ fn update_element( def_index, age_msec: msec_now.saturating_sub(msec_begin).max(0), life_msec, - elem_random_seed: elem_random_seed(effect_seed, sequence, msec_begin), + elem_random_seed: elem_seed, norm_time: elem_norm_time(msec_now.saturating_sub(msec_begin).max(0), life_msec), }) { let (origin_now, at_rest_now, spawn, seed) = match host.elems.get(slot) { - Some(e) => { - let seed = elem_random_seed(effect_seed, sequence, msec_begin); - ( - e.origin, - e.at_rest_fraction, - Some(e.orient_spawn_params(seed)), - seed, - ) - } - None => ( - origin, - at_rest_fraction, - None, - elem_random_seed(effect_seed, sequence, msec_begin), + Some(e) => ( + e.origin, + e.at_rest_fraction, + Some(e.orient_spawn_params(elem_seed)), + elem_seed, ), + None => (origin, at_rest_fraction, None, elem_seed), }; let world = spark_elem_world_origin(origin_now, flags, &now, &alt, spawn); let axis = spark_elem_axis( diff --git a/crates/fx_iw4/src/integrate.rs b/crates/fx_iw4/src/integrate.rs index d5d188f..7f2cb7e 100644 --- a/crates/fx_iw4/src/integrate.rs +++ b/crates/fx_iw4/src/integrate.rs @@ -7,22 +7,22 @@ pub struct FxElemVec3Range { } impl FxElemVec3Range { - fn sampled(self, seed: u64) -> [f32; 3] { - core::array::from_fn(|axis| { - self.base[axis] - + self.amplitude[axis] - * crate::random::sample_f32( - seed, - [ - crate::random::FxRandomChannel::VelocityX, - crate::random::FxRandomChannel::VelocityY, - crate::random::FxRandomChannel::VelocityZ, - ][axis], - ) - }) + fn sampled(self, random: [f32; 3]) -> [f32; 3] { + core::array::from_fn(|axis| self.base[axis] + self.amplitude[axis] * random[axis]) } } +/// Every sample of an element's velocity graph shares these three draws, and +/// each one is a hash, so a graph walk takes them once. +fn velocity_random(seed: u64) -> [f32; 3] { + use crate::random::{FxRandomChannel, sample_f32}; + [ + sample_f32(seed, FxRandomChannel::VelocityX), + sample_f32(seed, FxRandomChannel::VelocityY), + sample_f32(seed, FxRandomChannel::VelocityZ), + ] +} + pub fn integrate_velocity_graph( samples: &[FxElemVec3Range], start_age01: f32, @@ -41,6 +41,7 @@ pub fn integrate_velocity_graph( let segment_count = samples.len() - 1; let segment_span_ms = life_ms * segment_count as f32; + let random = velocity_random(seed); let mut delta = [0.0f32; 3]; let first = (libm::floorf(start * segment_count as f32) as usize).min(segment_count - 1); let end_excl = @@ -53,8 +54,8 @@ pub fn integrate_velocity_graph( if hi <= lo { continue; } - let v0 = sample_lerp(samples, lo, seed); - let v1 = sample_lerp(samples, hi, seed); + let v0 = sample_lerp(samples, lo, random); + let v1 = sample_lerp(samples, hi, random); let w = 0.5 * (hi - lo) * segment_span_ms; delta[0] += (v0[0] + v1[0]) * w; delta[1] += (v0[1] + v1[1]) * w; @@ -65,7 +66,7 @@ pub fn integrate_velocity_graph( #[inline] pub fn sample_vel_graph_at_age(samples: &[FxElemVec3Range], age01: f32, seed: u64) -> [f32; 3] { - sample_lerp(samples, age01, seed) + sample_lerp(samples, age01, velocity_random(seed)) } fn clamp01(x: f32) -> f32 { @@ -78,15 +79,15 @@ fn clamp01(x: f32) -> f32 { } } -fn sample_lerp(samples: &[FxElemVec3Range], age01: f32, seed: u64) -> [f32; 3] { +fn sample_lerp(samples: &[FxElemVec3Range], age01: f32, random: [f32; 3]) -> [f32; 3] { let last = samples.len() - 1; let t = clamp01(age01); let seg = t * last as f32; let i = (libm::floorf(seg) as usize).min(last); let j = (i + 1).min(last); let f = if i == j { 0.0 } else { seg - i as f32 }; - let a = samples[i].sampled(seed); - let b = samples[j].sampled(seed); + let a = samples[i].sampled(random); + let b = samples[j].sampled(random); [ a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f,