# HG changeset patch # User Emmanuel Gil Peyrot # Date 1562246506 -7200 # Node ID a8e0219162b6f56c86f46ff6633423509dbd09b6 # Parent a806f28e94fc944c30f5d95cb097df663537d10d Implement AnmRunner. diff --git a/src/lib.rs b/src/lib.rs --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![deny(missing_docs)] +#![feature(concat_idents)] //! Crate implementing various Touhou formats. diff --git a/src/th06/anm0.rs b/src/th06/anm0.rs --- 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, +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, - interrupts: HashMap + /// List of instructions in this script. + pub instructions: Vec, + + /// List of interrupts in this script. + pub interrupts: HashMap } /// 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> { - 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> { @@ -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); } _ => () } diff --git a/src/th06/anm0_vm.rs b/src/th06/anm0_vm.rs 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, + fade_interpolator: Option, + offset_interpolator: Option, + rotation_interpolator: Option, + color_interpolator: Option, + + anm: Option, + + 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>, + running: bool, + sprite_index_offset: u32, + script: Script, + instruction_pointer: usize, + frame: u16, + waiting: bool, + variables: ([i32; 4], [f32; 4], [i32; 4]), + timeout: Option, +} + +impl AnmRunner { + /// Create a new `AnmRunner`. + pub fn new(anm: &Anm0, script_id: u8, sprite: Rc>, 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(); + } + } +} diff --git a/src/th06/mod.rs b/src/th06/mod.rs --- a/src/th06/mod.rs +++ b/src/th06/mod.rs @@ -2,3 +2,4 @@ pub mod pbg3; pub mod anm0; +pub mod anm0_vm;