aboutsummaryrefslogtreecommitdiff
path: root/crates/fparkan-material/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/fparkan-material/src/lib.rs')
-rw-r--r--crates/fparkan-material/src/lib.rs57
1 files changed, 57 insertions, 0 deletions
diff --git a/crates/fparkan-material/src/lib.rs b/crates/fparkan-material/src/lib.rs
index da0b716..082d10c 100644
--- a/crates/fparkan-material/src/lib.rs
+++ b/crates/fparkan-material/src/lib.rs
@@ -717,6 +717,10 @@ fn interpolate_coefficients(
mask: u32,
fraction: f32,
) -> MaterialCoefficients {
+ // World3D 0x3030 receives source and destination wrappers one dword before
+ // each D3DMATERIAL7 body. Its +4/+14/+24/+34 offsets therefore select
+ // parameters 4..6, 0..2, 8..10, and 12..14; +0x20 selects parameter 3.
+ // The other alpha/extra fields, power, and page remain from the left phase.
let left = left.coefficients();
let right = right.coefficients();
let mut output = left;
@@ -1306,6 +1310,59 @@ mod tests {
}
#[test]
+ fn material_animation_interpolation_masks_match_native_rebased_offsets() {
+ let left = phase_with_parameters(
+ [
+ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180,
+ ],
+ b"LEFT",
+ );
+ let right = phase_with_parameters(
+ [
+ 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230,
+ 240,
+ ],
+ b"RIGHT",
+ );
+ let rgb = |first: u8| [first, first + 10, first + 20].map(|value| f32::from(value) / 255.0);
+ let cases = [
+ (0, rgb(10), rgb(50), rgb(90), rgb(130), 0.4),
+ (1, rgb(40), rgb(50), rgb(90), rgb(130), 0.4),
+ (2, rgb(10), rgb(80), rgb(90), rgb(130), 0.4),
+ (4, rgb(10), rgb(50), rgb(120), rgb(130), 0.4),
+ (8, rgb(10), rgb(50), rgb(90), rgb(160), 0.4),
+ (16, rgb(10), rgb(50), rgb(90), rgb(130), 0.7),
+ (0x1f, rgb(40), rgb(80), rgb(120), rgb(160), 0.7),
+ ];
+
+ for (mask, additive, directional, specular, extra, opacity) in cases {
+ let output = interpolate_coefficients(&left, &right, mask, 0.5);
+ for (actual, expected) in [
+ (output.additive_rgb, additive),
+ (output.directional_rgb, directional),
+ (output.specular_rgb, specular),
+ (output.extra_rgb, extra),
+ ] {
+ for channel in 0..3 {
+ assert!(
+ (actual[channel] - expected[channel]).abs() < 1e-6,
+ "mask {mask:#x}, channel {channel}: got {}, expected {}",
+ actual[channel],
+ expected[channel]
+ );
+ }
+ }
+
+ assert!((output.opacity - opacity).abs() < 1e-6, "mask {mask:#x}");
+ assert_eq!(output.directional_extra, 80.0 / 255.0);
+ assert_eq!(output.specular_extra, 120.0 / 255.0);
+ assert_eq!(output.extra_alpha, 160.0 / 255.0);
+ assert_eq!(output.power, 170);
+ assert_eq!(output.page_index, -76);
+ }
+ }
+
+ #[test]
fn material_ping_pong_reverses_local_time_and_clamp_holds_last_phase() {
let phases = vec![
phase_with_parameters([0; 18], b"A"),