changeset 639:a8e0219162b6

Implement AnmRunner.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Thu, 04 Jul 2019 15:21:46 +0200
parents a806f28e94fc
children 37d151fe000b
files src/lib.rs src/th06/anm0.rs src/th06/anm0_vm.rs src/th06/mod.rs
diffstat 4 files changed, 376 insertions(+), 103 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
 #![deny(missing_docs)]
+#![feature(concat_idents)]
 
 //! Crate implementing various Touhou formats.
 
--- a/src/th06/anm0.rs
+++ b/src/th06/anm0.rs
@@ -3,33 +3,47 @@
 use nom::{
     IResult,
     bytes::complete::{tag, take_while_m_n},
-    number::complete::{le_u8, le_u16, le_u32, le_f32},
+    number::complete::{le_u8, le_u16, le_u32, le_i32, le_f32},
 };
 use std::collections::HashMap;
 
 /// Coordinates of a sprite into the image.
 #[derive(Debug, Clone)]
 pub struct Sprite {
-    index: u32,
-    x: f32,
-    y: f32,
-    width: f32,
-    height: f32,
+    /// Index inside the anm0.
+    pub index: u32,
+
+    /// X coordinate in the sprite sheet.
+    pub x: f32,
+
+    /// Y coordinate in the sprite sheet.
+    pub y: f32,
+
+    /// Width of the sprite.
+    pub width: f32,
+
+    /// Height of the sprite.
+    pub height: f32,
 }
 
 /// A single instruction, part of a `Script`.
 #[derive(Debug, Clone)]
-struct Instruction {
-    time: u16,
-    opcode: u8,
-    args: Vec<Arg>,
+pub struct Call {
+    /// Time at which this instruction will be called.
+    pub time: u16,
+
+    /// The instruction to call.
+    pub instr: Instruction,
 }
 
 /// Script driving an animation.
 #[derive(Debug, Clone)]
 pub struct Script {
-    instructions: Vec<Instruction>,
-    interrupts: HashMap<u32, u8>
+    /// List of instructions in this script.
+    pub instructions: Vec<Call>,
+
+    /// List of interrupts in this script.
+    pub interrupts: HashMap<i32, u8>
 }
 
 /// Main struct of the ANM0 animation format.
@@ -89,90 +103,67 @@ fn parse_sprite(i: &[u8]) -> IResult<&[u
     }))
 }
 
-#[derive(Debug, Clone, Copy)]
-enum Arg {
-    U8(u8),
-    U32(u32),
-    F32(f32),
-    Ptr(u8),
-}
-
-fn parse_u8_arg(i: &[u8]) -> IResult<&[u8], Arg> {
-    let (i, value) = le_u8(i)?;
-    Ok((i, Arg::U8(value)))
-}
+macro_rules! declare_anm_instructions {
+    ($($opcode:tt => fn $name:ident($($arg:ident: $arg_type:ident),*)),*,) => {
+        /// Available instructions in an `Anm0`.
+        #[allow(missing_docs)]
+        #[derive(Debug, Clone, Copy)]
+        pub enum Instruction {
+            $(
+                $name($($arg_type),*)
+            ),*
+        }
 
-fn parse_u32_arg(i: &[u8]) -> IResult<&[u8], Arg> {
-    let (i, value) = le_u32(i)?;
-    Ok((i, Arg::U32(value)))
-}
-
-fn parse_f32_arg(i: &[u8]) -> IResult<&[u8], Arg> {
-    let (i, value) = le_f32(i)?;
-    Ok((i, Arg::F32(value)))
-}
-
-macro_rules! declare_anm_instructions {
-    ($($opcode:tt => fn $name:ident($($arg:ident: $arg_type:ty),*)),*,) => {
-        fn parse_instruction_args(input: &[u8], opcode: u8) -> IResult<&[u8], Vec<Arg>> {
-            let mut args = vec![];
+        fn parse_instruction_args(input: &[u8], opcode: u8) -> IResult<&[u8], Instruction> {
             let mut i = &input[..];
-            match opcode {
+            let instr = match opcode {
                 $(
                     $opcode => {
                         $(
-                            let (i2, data) = match stringify!($arg_type) {
-                                "u8" => parse_u8_arg(i),
-                                "u32" => parse_u32_arg(i),
-                                "f32" => parse_f32_arg(i),
-                                "x8" => parse_u8_arg(i),
-                                _ => unreachable!(),
-                            }?;
+                            let (i2, $arg) = concat_idents!(le_, $arg_type)(i)?;
                             i = i2;
-                            if stringify!($arg_type) != "x8" {
-                                args.push(data);
-                            }
                         )*
+                        Instruction::$name($($arg),*)
                     }
                 )*
                 _ => unreachable!()
-            }
-            Ok((i, args))
+            };
+            Ok((i, instr))
         }
     };
 }
 
 declare_anm_instructions!{
-    0 => fn delete(),
-    1 => fn set_sprite(sprite_number: u32),
-    2 => fn set_scale(sx: f32, sy: f32),
-    3 => fn set_alpha(alpha: u32),
-    4 => fn set_color(red: u8, green: u8, blue: u8, XXX: x8),
-    5 => fn jump(instruction: u32),
-    7 => fn toggle_mirrored(),
-    9 => fn set_3d_rotations(x: f32, y: f32, z: f32),
-    10 => fn set_3d_rotations_speed(x: f32, y: f32, z: f32),
-    11 => fn set_scale_speed(sx: f32, sy: f32),
-    12 => fn fade(alpha: u32, duration: u32),
-    13 => fn set_blendmode_add(),
-    14 => fn set_blendmode_alphablend(),
-    15 => fn keep_still(),
-    16 => fn set_random_sprite(min_index: u32, amplitude: u32),
-    17 => fn set_3d_translation(x: f32, y: f32, z: f32),
-    18 => fn move_to_linear(x: f32, y: f32, z: f32, duration: u32),
-    19 => fn move_to_decel(x: f32, y: f32, z: f32, duration: u32),
-    20 => fn move_to_accel(x: f32, y: f32, z: f32, duration: u32),
-    21 => fn wait(),
-    22 => fn interrupt_label(label: u32),
-    23 => fn set_corner_relative_placement(),
-    24 => fn wait_ex(),
-    25 => fn set_allow_offset(allow: u32), // TODO: better name
-    26 => fn set_automatic_orientation(automatic: u32),
-    27 => fn shift_texture_x(dx: f32),
-    28 => fn shift_texture_y(dy: f32),
-    29 => fn set_visible(visible: u32),
-    30 => fn scale_in(sx: f32, sy: f32, duration: u32),
-    31 => fn TODO(TODO: u32),
+    0 => fn Delete(),
+    1 => fn LoadSprite(sprite_number: u32),
+    2 => fn SetScale(sx: f32, sy: f32),
+    3 => fn SetAlpha(alpha: u32),
+    4 => fn SetColor(red: u8, green: u8, blue: u8/*, XXX: x8*/),
+    5 => fn Jump(instruction: u32),
+    7 => fn ToggleMirrored(),
+    9 => fn SetRotations3d(x: f32, y: f32, z: f32),
+    10 => fn SetRotationsSpeed3d(x: f32, y: f32, z: f32),
+    11 => fn SetScaleSpeed(sx: f32, sy: f32),
+    12 => fn Fade(alpha: u32, duration: u32),
+    13 => fn SetBlendmodeAdd(),
+    14 => fn SetBlendmodeAlphablend(),
+    15 => fn KeepStill(),
+    16 => fn LoadRandomSprite(min_index: u32, amplitude: u32),
+    17 => fn Move(x: f32, y: f32, z: f32),
+    18 => fn MoveToLinear(x: f32, y: f32, z: f32, duration: u32),
+    19 => fn MoveToDecel(x: f32, y: f32, z: f32, duration: u32),
+    20 => fn MoveToAccel(x: f32, y: f32, z: f32, duration: u32),
+    21 => fn Wait(),
+    22 => fn InterruptLabel(label: i32),
+    23 => fn SetCornerRelativePlacement(),
+    24 => fn WaitEx(),
+    25 => fn SetAllowOffset(allow: u32), // TODO: better name
+    26 => fn SetAutomaticOrientation(automatic: u32),
+    27 => fn ShiftTextureX(dx: f32),
+    28 => fn ShiftTextureY(dy: f32),
+    29 => fn SetVisible(visible: u32),
+    30 => fn ScaleIn(sx: f32, sy: f32, duration: u32),
+    31 => fn Todo(todo: u32),
 }
 
 fn parse_anm0(input: &[u8]) -> IResult<&[u8], Vec<Anm0>> {
@@ -243,8 +234,8 @@ fn parse_anm0(input: &[u8]) -> IResult<&
                 let (i2, opcode) = le_u8(i2)?;
                 // TODO: maybe check against the size of parsed data?
                 let (i2, _size) = le_u8(i2)?;
-                let (i2, args) = parse_instruction_args(i2, opcode)?;
-                instructions.push(Instruction { time, opcode, args });
+                let (i2, instr) = parse_instruction_args(i2, opcode)?;
+                instructions.push(Call { time, instr });
                 i = i2;
                 if opcode == 0 {
                     break;
@@ -252,29 +243,18 @@ fn parse_anm0(input: &[u8]) -> IResult<&
             }
             let mut interrupts = HashMap::new();
             let mut j = 0;
-            for Instruction { time: _, opcode, args } in &mut instructions {
-                match opcode {
-                    5 => {
-                        let offset = match args[0] {
-                            Arg::U32(offset) => offset as usize,
-                            _ => panic!("Wrong argument type for jump!"),
-                        };
-                        let result = instruction_offsets.binary_search(&offset);
-                        let ptr = match result {
-                            Ok(ptr) => ptr as u8,
+            for Call { time: _, instr } in &mut instructions {
+                match instr {
+                    Instruction::Jump(ref mut offset) => {
+                        let result = instruction_offsets.binary_search(&(*offset as usize));
+                        match result {
+                            Ok(ptr) => *offset = ptr as u32,
                             // TODO: make that a recoverable error instead.
                             Err(ptr) => panic!("Instruction offset not found for pointer: {}", ptr),
-                        };
-                        args[0] = Arg::Ptr(ptr);
+                        }
                     }
-                    22 => {
-                        // TODO: maybe also remove this instruction, as the label is already
-                        // present.
-                        if let Arg::U32(interrupt) = args[0] {
-                            interrupts.insert(interrupt, j + 1);
-                        } else {
-                            panic!("Wrong argument type for interrupt!");
-                        }
+                    Instruction::InterruptLabel(interrupt) => {
+                        interrupts.insert(*interrupt, j + 1);
                     }
                     _ => ()
                 }
new file mode 100644
--- /dev/null
+++ b/src/th06/anm0_vm.rs
@@ -0,0 +1,291 @@
+//! Animation runner.
+
+use crate::th06::anm0::{
+    Script,
+    Anm0,
+    Call,
+    Instruction,
+};
+use std::cell::RefCell;
+use std::rc::Rc;
+
+#[derive(Debug, Clone)]
+struct Interpolator;
+
+/// Base visual element.
+#[derive(Debug, Clone, Default)]
+pub struct Sprite {
+    blendfunc: u32,
+    frame: u16,
+
+    width_override: f32,
+    height_override: f32,
+    angle: f32,
+
+    removed: bool,
+    changed: bool,
+    visible: bool,
+    force_rotation: bool,
+    automatic_orientation: bool,
+    allow_dest_offset: bool,
+    mirrored: bool,
+    corner_relative_placement: bool,
+
+    scale_interpolator: Option<Interpolator>,
+    fade_interpolator: Option<Interpolator>,
+    offset_interpolator: Option<Interpolator>,
+    rotation_interpolator: Option<Interpolator>,
+    color_interpolator: Option<Interpolator>,
+
+    anm: Option<Anm0>,
+
+    dest_offset: [f32; 3],
+    texcoords: [f32; 4],
+    texoffsets: [f32; 2],
+    rescale: [f32; 2],
+    scale_speed: [f32; 2],
+    rotations_3d: [f32; 3],
+    rotations_speed_3d: [f32; 3],
+    color: [u8; 4],
+}
+
+impl Sprite {
+    /// Create a new sprite.
+    pub fn new() -> Sprite {
+        Default::default()
+    }
+
+    /// Update sprite values from the interpolators.
+    pub fn update(&mut self) {
+        self.changed = false;
+    }
+}
+
+/// Interpreter for `Anm0` instructions to update a `Sprite`.
+pub struct AnmRunner {
+    anm: Anm0,
+    sprite: Rc<RefCell<Sprite>>,
+    running: bool,
+    sprite_index_offset: u32,
+    script: Script,
+    instruction_pointer: usize,
+    frame: u16,
+    waiting: bool,
+    variables: ([i32; 4], [f32; 4], [i32; 4]),
+    timeout: Option<u16>,
+}
+
+impl AnmRunner {
+    /// Create a new `AnmRunner`.
+    pub fn new(anm: &Anm0, script_id: u8, sprite: Rc<RefCell<Sprite>>, sprite_index_offset: u32) -> AnmRunner {
+        let mut runner = AnmRunner {
+            anm: anm.clone(),
+            sprite: sprite,
+            running: true,
+            waiting: false,
+
+            script: anm.scripts[&script_id].clone(),
+            frame: 0,
+            timeout: None,
+            instruction_pointer: 0,
+            variables: ([0,  0,  0,  0 ],
+                        [0., 0., 0., 0.],
+                        [0,  0,  0,  0 ]),
+
+            sprite_index_offset: sprite_index_offset,
+        };
+        runner.run_frame();
+        runner.sprite_index_offset = 0;
+        runner
+    }
+
+    /// Trigger an interrupt.
+    pub fn interrupt(&mut self, interrupt: i32) -> bool {
+        let mut new_ip = self.script.interrupts.get(&interrupt);
+        if new_ip.is_none() {
+            new_ip = self.script.interrupts.get(&-1);
+        }
+        let new_ip = if let Some(new_ip) = new_ip {
+            *new_ip as usize
+        } else {
+            return false;
+        };
+        self.instruction_pointer = new_ip;
+        let Call { time: frame, instr: _ } = &self.script.instructions[self.instruction_pointer];
+        self.frame = *frame;
+        self.waiting = false;
+        self.sprite.borrow_mut().visible = true;
+        true
+    }
+
+    /// Advance the Anm of a single frame.
+    pub fn run_frame(&mut self) -> bool {
+        if !self.running {
+            return false;
+        }
+
+        while self.running && !self.waiting {
+            let Call { time: frame, instr } = self.script.instructions[self.instruction_pointer];
+            let frame = frame.clone();
+
+            if frame > self.frame {
+                break;
+            } else {
+                self.instruction_pointer += 1;
+            }
+
+            if frame == self.frame {
+                self.run_instruction(instr);
+                self.sprite.borrow_mut().changed = true;
+            }
+        }
+
+        if !self.waiting {
+            self.frame += 1;
+        } else if let Some(timeout) = self.timeout {
+            if timeout == self.sprite.borrow().frame { // TODO: check if it’s happening at the correct frame.
+                self.waiting = false;
+            }
+        }
+
+        self.sprite.borrow_mut().update();
+
+        self.running
+    }
+
+    fn run_instruction(&mut self, instruction: Instruction) {
+        let mut sprite = self.sprite.borrow_mut();
+        match instruction {
+            Instruction::Delete() => {
+                sprite.removed = true;
+                self.running = false;
+            }
+            Instruction::LoadSprite(sprite_index) => {
+                sprite.anm = Some(self.anm.clone());
+                let texcoords = &self.anm.sprites[(sprite_index + self.sprite_index_offset) as usize];
+                sprite.texcoords = [texcoords.x, texcoords.y, texcoords.width, texcoords.height];
+            }
+            Instruction::SetScale(sx, sy) => {
+                sprite.rescale = [sx, sy];
+            }
+            Instruction::SetAlpha(alpha) => {
+                // TODO: check this modulo.
+                sprite.color[3] = (alpha % 256) as u8;
+            }
+            Instruction::SetColor(b, g, r) => {
+                if sprite.fade_interpolator.is_none() {
+                    sprite.color[0] = r;
+                    sprite.color[1] = g;
+                    sprite.color[2] = b;
+                }
+            }
+            Instruction::Jump(pointer) => {
+                // TODO: is that really how it works?
+                self.instruction_pointer = pointer as usize;
+                self.frame = self.script.instructions[pointer as usize].time;
+            }
+            Instruction::ToggleMirrored() => {
+                sprite.mirrored = !sprite.mirrored;
+            }
+            Instruction::SetRotations3d(rx, ry, rz) => {
+                sprite.rotations_3d = [rx, ry, rz];
+            }
+            Instruction::SetRotationsSpeed3d(srx, sry, srz) => {
+                sprite.rotations_speed_3d = [srx, sry, srz];
+            }
+            Instruction::SetScaleSpeed(ssx, ssy) => {
+                sprite.scale_speed = [ssx, ssy];
+            }
+            Instruction::Fade(new_alpha, duration) => {
+                // XXX: implement fade().
+                //sprite.fade(duration, new_alpha)
+            }
+            Instruction::SetBlendmodeAlphablend() => {
+                sprite.blendfunc = 1;
+            }
+            Instruction::SetBlendmodeAdd() => {
+                sprite.blendfunc = 0;
+            }
+            Instruction::KeepStill() => {
+                self.running = false;
+            }
+            Instruction::LoadRandomSprite(min_index, amplitude) => {
+                //self.load_sprite(min_index + randrange(amp))
+            }
+            Instruction::Move(x, y, z) => {
+                sprite.dest_offset = [x, y, z];
+            }
+            Instruction::MoveToLinear(x, y, z, duration) => {
+                // XXX: implement move_in().
+                //sprite.move_in(duration, x, y, z);
+            }
+            Instruction::MoveToDecel(x, y, z, duration) => {
+                // XXX: implement move_in().
+                //sprite.move_in(duration, x, y, z, 2*x - x**2);
+            }
+            Instruction::MoveToAccel(x, y, z, duration) => {
+                // XXX: implement move_in().
+                //sprite.move_in(duration, x, y, z, x**2);
+            }
+            Instruction::Wait() => {
+                self.waiting = true;
+            }
+            // There is nothing to do here.
+            Instruction::InterruptLabel(label) => (),
+            Instruction::SetCornerRelativePlacement() => {
+                sprite.corner_relative_placement = true;
+            }
+            Instruction::WaitEx() => {
+                sprite.visible = false;
+                self.waiting = true;
+            }
+            Instruction::SetAllowOffset(value) => {
+                sprite.allow_dest_offset = value == 1
+            }
+            Instruction::SetAutomaticOrientation(value) => {
+                sprite.automatic_orientation = value == 1
+            }
+            Instruction::ShiftTextureX(dx) => {
+                let [tox, toy] = sprite.texoffsets;
+                sprite.texoffsets = [tox + dx, toy];
+            }
+            Instruction::ShiftTextureY(dy) => {
+                let [tox, toy] = sprite.texoffsets;
+                sprite.texoffsets = [tox, toy + dy];
+            }
+            Instruction::SetVisible(visible) => {
+                sprite.visible = (visible & 1) != 0;
+            }
+            Instruction::ScaleIn(sx, sy, duration) => {
+                // TODO: implement scale_in().
+                //sprite.scale_in(duration, sx, sy)
+            }
+            Instruction::Todo(todo) => {
+                // TODO.
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::io::{self, Read};
+    use std::fs::File;
+
+    #[test]
+    fn anm_runner() {
+        let file = File::open("/home/linkmauve/games/pc/東方/TH06 ~ The Embodiment of Scarlet Devil/CM/player01.anm").unwrap();
+        let mut file = io::BufReader::new(file);
+        let mut buf = vec![];
+        file.read_to_end(&mut buf).unwrap();
+        let anm0 = Anm0::from_slice(&buf).unwrap();
+        assert_eq!(anm0.size, (256, 256));
+        assert_eq!(anm0.format, 5);
+        let sprite = Rc::new(RefCell::new(Sprite::new()));
+        let mut anm_runner = AnmRunner::new(&anm0, 1, sprite.clone(), 0);
+        for _ in 0..50 {
+            anm_runner.run_frame();
+        }
+    }
+}
--- a/src/th06/mod.rs
+++ b/src/th06/mod.rs
@@ -2,3 +2,4 @@
 
 pub mod pbg3;
 pub mod anm0;
+pub mod anm0_vm;