view src/th06/anm0_vm.rs @ 640:37d151fe000b

Implement better defaults for Sprite, and a beginning of
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Thu, 04 Jul 2019 21:47:53 +0200
parents a8e0219162b6
children a58103f2f264
line wrap: on
line source

//! 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(width_override: f32, height_override: f32) -> Sprite {
        Sprite {
            width_override,
            height_override,
            changed: true,
            visible: true,
            rescale: [1., 1.],
            color: [255, 255, 255, 255],
            ..Default::default()
        }
    }

    /// Update sprite values from the interpolators.
    pub fn update(&mut self) {
        self.frame += 1;

        let [sax, say, saz] = self.rotations_speed_3d;
        if sax != 0. || say != 0. || saz != 0. {
            let [ax, ay, az] = self.rotations_3d;
            self.rotations_3d = [ax + sax, ay + say, az + saz];
            self.changed = true;
        } else if let Some(ref interpolator) = self.rotation_interpolator {
            unimplemented!();
            self.changed = true;
        }

        let [rsx, rsy] = self.scale_speed;
        if rsx != 0. || rsy != 0. {
            let [rx, ry] = self.rescale;
            self.rescale = [rx + rsx, ry + rsy];
            self.changed = true;
        }

        if let Some(ref interpolator) = self.fade_interpolator {
            unimplemented!();
            self.changed = true;
        }

        if let Some(ref interpolator) = self.scale_interpolator {
            unimplemented!();
            self.changed = true;
        }

        if let Some(ref interpolator) = self.offset_interpolator {
            unimplemented!();
            self.changed = true;
        }

        if let Some(ref interpolator) = self.color_interpolator {
            unimplemented!();
            self.changed = true;
        }
    }
}

/// 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(0., 0.)));
        let mut anm_runner = AnmRunner::new(&anm0, 1, sprite.clone(), 0);
        for _ in 0..50 {
            anm_runner.run_frame();
        }
    }
}