changeset 787:7f9b3f5001c2

interpreters: Make Interpolator generic over N This was a workaround for Rust < 1.51 which didn’t support const generics yet, but we’ve had tat for close to five years now!
author Link Mauve <linkmauve@linkmauve.fr>
date Mon, 15 Dec 2025 11:34:58 +0100
parents 7e940ebeb5fd
children f56b10812b77
files interpreters/src/th06/anm0.rs interpreters/src/th06/enemy.rs interpreters/src/th06/interpolator.rs interpreters/src/th06/std.rs
diffstat 4 files changed, 85 insertions(+), 94 deletions(-) [+]
line wrap: on
line diff
--- a/interpreters/src/th06/anm0.rs
+++ b/interpreters/src/th06/anm0.rs
@@ -6,7 +6,7 @@
     Call,
     Instruction,
 };
-use crate::th06::interpolator::{Interpolator1, Interpolator2, Interpolator3, Formula};
+use crate::th06::interpolator::{Interpolator, Formula};
 use touhou_utils::math::Mat4;
 use touhou_utils::prng::Prng;
 use std::cell::RefCell;
@@ -45,11 +45,11 @@
     mirrored: bool,
     corner_relative_placement: bool,
 
-    scale_interpolator: Option<Interpolator2<f32>>,
-    fade_interpolator: Option<Interpolator1<f32>>, // XXX: should be u8!
-    offset_interpolator: Option<Interpolator3<f32>>,
-    rotation_interpolator: Option<Interpolator3<f32>>,
-    color_interpolator: Option<Interpolator3<f32>>, // XXX: should be u8!
+    scale_interpolator: Option<Interpolator<f32, 2>>,
+    fade_interpolator: Option<Interpolator<f32, 1>>, // XXX: should be u8!
+    offset_interpolator: Option<Interpolator<f32, 3>>,
+    rotation_interpolator: Option<Interpolator<f32, 3>>,
+    color_interpolator: Option<Interpolator<f32, 3>>, // XXX: should be u8!
 
     anm: Option<Anm0>,
 
@@ -409,7 +409,7 @@
                 sprite.scale_speed = [ssx, ssy];
             }
             Instruction::Fade(new_alpha, duration) => {
-                sprite.fade_interpolator = Some(Interpolator1::new([sprite.color[3] as f32], sprite.frame, [new_alpha as f32], sprite.frame + duration, Formula::Linear));
+                sprite.fade_interpolator = Some(Interpolator::new([sprite.color[3] as f32], sprite.frame, [new_alpha as f32], sprite.frame + duration, Formula::Linear));
             }
             Instruction::SetBlendmodeAlphablend() => {
                 sprite.blendfunc = 1;
@@ -433,13 +433,13 @@
                 sprite.dest_offset = [x, y, z];
             }
             Instruction::MoveToLinear(x, y, z, duration) => {
-                sprite.offset_interpolator = Some(Interpolator3::new(sprite.dest_offset, sprite.frame, [x, y, z], sprite.frame + duration, Formula::Linear));
+                sprite.offset_interpolator = Some(Interpolator::new(sprite.dest_offset, sprite.frame, [x, y, z], sprite.frame + duration, Formula::Linear));
             }
             Instruction::MoveToDecel(x, y, z, duration) => {
-                sprite.offset_interpolator = Some(Interpolator3::new(sprite.dest_offset, sprite.frame, [x, y, z], sprite.frame + duration, Formula::InvertPower2));
+                sprite.offset_interpolator = Some(Interpolator::new(sprite.dest_offset, sprite.frame, [x, y, z], sprite.frame + duration, Formula::InvertPower2));
             }
             Instruction::MoveToAccel(x, y, z, duration) => {
-                sprite.offset_interpolator = Some(Interpolator3::new(sprite.dest_offset, sprite.frame, [x, y, z], sprite.frame + duration, Formula::Power2));
+                sprite.offset_interpolator = Some(Interpolator::new(sprite.dest_offset, sprite.frame, [x, y, z], sprite.frame + duration, Formula::Power2));
             }
             Instruction::Wait() => {
                 self.waiting = true;
@@ -471,7 +471,7 @@
                 sprite.visible = (visible & 1) != 0;
             }
             Instruction::ScaleIn(sx, sy, duration) => {
-                sprite.scale_interpolator = Some(Interpolator2::new(sprite.rescale, sprite.frame, [sx, sy], sprite.frame + duration, Formula::Linear));
+                sprite.scale_interpolator = Some(Interpolator::new(sprite.rescale, sprite.frame, [sx, sy], sprite.frame + duration, Formula::Linear));
             }
             Instruction::Todo(_todo) => {
                 // TODO.
--- a/interpreters/src/th06/enemy.rs
+++ b/interpreters/src/th06/enemy.rs
@@ -3,7 +3,7 @@
 use touhou_formats::th06::anm0::Anm0;
 use touhou_formats::th06::ecl::Rank;
 use crate::th06::anm0::{Sprite, AnmRunner};
-use crate::th06::interpolator::{Interpolator1, Interpolator2};
+use crate::th06::interpolator::Interpolator;
 use touhou_utils::prng::Prng;
 use std::cell::RefCell;
 use std::collections::HashMap;
@@ -304,8 +304,8 @@
     options: Vec<Element>,
 
     // Interpolators.
-    pub(crate) interpolator: Option<Interpolator2<f32>>,
-    pub(crate) speed_interpolator: Option<Interpolator1<f32>>,
+    pub(crate) interpolator: Option<Interpolator<f32, 2>>,
+    pub(crate) speed_interpolator: Option<Interpolator<f32, 1>>,
 
     // Misc stuff, do we need them?
     pub(crate) anm0: Weak<RefCell<[Anm0; 2]>>,
--- a/interpreters/src/th06/interpolator.rs
+++ b/interpreters/src/th06/interpolator.rs
@@ -17,80 +17,71 @@
     }
 }
 
-macro_rules! generate_interpolator {
-    ($name:ident, $n:tt) => {
-        #[derive(Debug, Clone)]
-        pub(crate) struct $name<T> {
-            start_values: [T; $n],
-            end_values: [T; $n],
-            start_frame: u32,
-            end_frame: u32,
-            formula: Formula,
-        }
-
-        impl<T> $name<T>
-        where f32: From<T>,
-              T: From<f32>,
-              T: std::ops::Sub<Output = T>,
-              T: std::ops::Add<Output = T>,
-              T: Copy,
-              T: Default,
-        {
-            pub fn new(start_values: [T; $n], start_frame: u32, end_values: [T; $n], end_frame: u32, formula: Formula) -> $name<T> {
-                $name {
-                    start_values,
-                    end_values,
-                    start_frame,
-                    end_frame,
-                    formula,
-                }
-            }
-
-            pub fn set_start(&mut self, frame: u32, values: [T; $n]) {
-                self.start_values = values;
-                self.start_frame = frame;
-            }
-
-            pub fn set_end(&mut self, frame: u32, values: [T; $n]) {
-                self.end_values = values;
-                self.end_frame = frame;
-            }
-
-            pub fn set_end_values(&mut self, values: [T; $n]) {
-                self.end_values = values;
-            }
-
-            pub fn set_end_frame(&mut self, frame: u32) {
-                self.end_frame = frame;
-            }
-
-            // XXX: Make it return [T; $n] instead, we don’t want to only do f32 here.
-            pub fn values(&self, frame: u32) -> [f32; $n] {
-                if frame + 1 >= self.end_frame {
-                    // XXX: skip the last interpolation step.
-                    // This bug is replicated from the original game.
-                    //self.start_frame = self.end_frame;
-                    //self.end_values
-                    let mut values: [f32; $n] = [Default::default(); $n];
-                    for (i, value) in self.end_values.iter().enumerate() {
-                        values[i] = f32::from(*value);
-                    }
-                    values
-                } else {
-                    let mut coeff = (frame - self.start_frame) as f32 / (self.end_frame - self.start_frame) as f32;
-                    coeff = self.formula.apply(coeff);
-                    let mut values: [f32; $n] = [Default::default(); $n];
-                    for (i, (start, end)) in self.start_values.iter().zip(&self.end_values).enumerate() {
-                        values[i] = f32::from(*start + T::from(coeff * f32::from(*end - *start)));
-                    }
-                    values
-                }
-            }
-        }
-    };
+#[derive(Debug, Clone)]
+pub(crate) struct Interpolator<T, const N: usize> {
+    start_values: [T; N],
+    end_values: [T; N],
+    start_frame: u32,
+    end_frame: u32,
+    formula: Formula,
 }
 
-generate_interpolator!(Interpolator1, 1);
-generate_interpolator!(Interpolator2, 2);
-generate_interpolator!(Interpolator3, 3);
-//generate_interpolator!(Interpolator4, 4);
+impl<T, const N: usize> Interpolator<T, N>
+where f32: From<T>,
+      T: From<f32>,
+      T: std::ops::Sub<Output = T>,
+      T: std::ops::Add<Output = T>,
+      T: Copy,
+      T: Default,
+{
+    pub fn new(start_values: [T; N], start_frame: u32, end_values: [T; N], end_frame: u32, formula: Formula) -> Interpolator<T, N> {
+        Interpolator {
+            start_values,
+            end_values,
+            start_frame,
+            end_frame,
+            formula,
+        }
+    }
+
+    pub fn set_start(&mut self, frame: u32, values: [T; N]) {
+        self.start_values = values;
+        self.start_frame = frame;
+    }
+
+    pub fn set_end(&mut self, frame: u32, values: [T; N]) {
+        self.end_values = values;
+        self.end_frame = frame;
+    }
+
+    pub fn set_end_values(&mut self, values: [T; N]) {
+        self.end_values = values;
+    }
+
+    pub fn set_end_frame(&mut self, frame: u32) {
+        self.end_frame = frame;
+    }
+
+    // XXX: Make it return [T; N] instead, we don’t want to only do f32 here.
+    pub fn values(&self, frame: u32) -> [f32; N] {
+        if frame + 1 >= self.end_frame {
+            // XXX: skip the last interpolation step.
+            // This bug is replicated from the original game.
+            //self.start_frame = self.end_frame;
+            //self.end_values
+            let mut values: [f32; N] = [Default::default(); N];
+            for (i, value) in self.end_values.iter().enumerate() {
+                values[i] = f32::from(*value);
+            }
+            values
+        } else {
+            let mut coeff = (frame - self.start_frame) as f32 / (self.end_frame - self.start_frame) as f32;
+            coeff = self.formula.apply(coeff);
+            let mut values: [f32; N] = [Default::default(); N];
+            for (i, (start, end)) in self.start_values.iter().zip(&self.end_values).enumerate() {
+                values[i] = f32::from(*start + T::from(coeff * f32::from(*end - *start)));
+            }
+            values
+        }
+    }
+}
--- a/interpreters/src/th06/std.rs
+++ b/interpreters/src/th06/std.rs
@@ -1,7 +1,7 @@
 //! Interpreter of STD files.
 
 use touhou_formats::th06::std::{Stage, Call, Instruction};
-use crate::th06::interpolator::{Interpolator3, Formula};
+use crate::th06::interpolator::{Interpolator, Formula};
 use touhou_utils::math::{Mat4, setup_camera};
 use std::cell::RefCell;
 use std::rc::Rc;
@@ -12,8 +12,8 @@
     pub stage: Rc<RefCell<Stage>>,
     frame: u32,
 
-    position: Interpolator3<f32>,
-    direction: Interpolator3<f32>,
+    position: Interpolator<f32, 3>,
+    direction: Interpolator<f32, 3>,
 
     /// XXX: no pub.
     pub fog_color: [f32; 4],
@@ -29,8 +29,8 @@
         StageRunner {
             stage,
             frame: 0,
-            position: Interpolator3::new([0., 0., 0.], 0, [0., 0., 0.], 0, Formula::Linear),
-            direction: Interpolator3::new([0., 0., 0.], 0, [0., 0., 0.], 0, Formula::Linear),
+            position: Interpolator::new([0., 0., 0.], 0, [0., 0., 0.], 0, Formula::Linear),
+            direction: Interpolator::new([0., 0., 0.], 0, [0., 0., 0.], 0, Formula::Linear),
             fog_color: [1.; 4],
             fog_near: 0.,
             fog_far: 1000.,