Mercurial > touhou
diff src/th06/anm0_vm.rs @ 639:a8e0219162b6
Implement AnmRunner.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Thu, 04 Jul 2019 15:21:46 +0200 |
parents | |
children | 37d151fe000b |
line wrap: on
line diff
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(); + } + } +}