view src/th06/anm0_vm.rs @ 740:8d29dac12219

examples: Make the common functions take AsRef<Path>.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Mon, 06 Jan 2020 22:48:09 +0100
parents 6d4802abe134
children 3555845f8cf4
line wrap: on
line source

//! Animation runner.

use crate::th06::anm0::{
    Script,
    Anm0,
    Call,
    Instruction,
};
use crate::th06::interpolator::{Interpolator1, Interpolator2, Interpolator3, Formula};
use crate::util::math::Mat4;
use crate::util::prng::Prng;
use std::cell::RefCell;
use std::rc::{Rc, Weak};

/// TODO
#[repr(C)]
#[derive(Debug)]
pub struct Vertex {
    /// XXX
    pub pos: [i16; 3],
    /// XXX
    pub uv: [f32; 2],
    /// XXX
    pub color: [u8; 4],
}

/// Base visual element.
#[derive(Debug, Clone, Default)]
pub struct Sprite {
    blendfunc: u32,
    frame: u32,

    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<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!

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

    /// Create a new sprite overriding its size.
    pub fn with_size(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()
        }
    }

    /// TODO
    pub fn fill_vertices(&self, vertices: &mut [Vertex; 4], x: f32, y: f32, z: f32) {
        let mut mat = Mat4::new([[-0.5, 0.5, 0.5, -0.5],
                                 [-0.5, -0.5, 0.5, 0.5],
                                 [0., 0., 0., 0.],
                                 [1., 1., 1., 1.]]);

        let [tx, ty, tw, th] = self.texcoords;
        let [sx, sy] = self.rescale;
        let width = if self.width_override > 0. { self.width_override } else { tw * sx };
        let height = if self.height_override > 0. { self.height_override } else { th * sy };

        mat.scale2d(width, height);
        if self.mirrored {
            mat.flip();
        }

        let [rx, ry, mut rz] = self.rotations_3d;
        if self.automatic_orientation {
            rz += std::f32::consts::PI / 2. - self.angle;
        } else if self.force_rotation {
            rz += self.angle;
        }

        if rx != 0. {
            mat.rotate_x(-rx);
        }
        if ry != 0. {
            mat.rotate_y(ry);
        }
        if rz != 0. {
            mat.rotate_z(-rz);
        }

        if self.allow_dest_offset {
            mat.translate(self.dest_offset);
        }
        if self.corner_relative_placement {
            mat.translate_2d(width / 2., height / 2.);
        }

        mat.translate([x, y, z]);

        let mat = mat.borrow_inner();
        vertices[0].pos[0] = mat[0][0] as i16;
        vertices[0].pos[1] = mat[1][0] as i16;
        vertices[0].pos[2] = mat[2][0] as i16;
        vertices[1].pos[0] = mat[0][1] as i16;
        vertices[1].pos[1] = mat[1][1] as i16;
        vertices[1].pos[2] = mat[2][1] as i16;
        vertices[2].pos[0] = mat[0][2] as i16;
        vertices[2].pos[1] = mat[1][2] as i16;
        vertices[2].pos[2] = mat[2][2] as i16;
        vertices[3].pos[0] = mat[0][3] as i16;
        vertices[3].pos[1] = mat[1][3] as i16;
        vertices[3].pos[2] = mat[2][3] as i16;

        // XXX: don’t clone here.
        let (x_1, y_1) = self.anm.clone().unwrap().inv_size();
        let [tox, toy] = self.texoffsets;
        let left = tx * x_1 + tox;
        let right = (tx + tw) * x_1 + tox;
        let bottom = ty * y_1 + toy;
        let top = (ty + th) * y_1 + toy;

        vertices[0].uv[0] = left;
        vertices[0].uv[1] = bottom;
        vertices[1].uv[0] = right;
        vertices[1].uv[1] = bottom;
        vertices[2].uv[0] = right;
        vertices[2].uv[1] = top;
        vertices[3].uv[0] = left;
        vertices[3].uv[1] = top;

        vertices[0].color = self.color;
        vertices[1].color = self.color;
        vertices[2].color = self.color;
        vertices[3].color = self.color;
    }

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

        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 {
            self.rotations_3d = interpolator.values(self.frame);
            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 {
            self.color[3] = interpolator.values(self.frame)[0] as u8;
            self.changed = true;
        }

        if let Some(ref interpolator) = self.scale_interpolator {
            self.rescale = interpolator.values(self.frame);
            self.changed = true;
        }

        if let Some(ref interpolator) = self.offset_interpolator {
            self.dest_offset = interpolator.values(self.frame);
            self.changed = true;
        }

        if let Some(ref interpolator) = self.color_interpolator {
            let color = interpolator.values(self.frame);
            // TODO: this can probably be made to look nicer.
            self.color[0] = color[0] as u8;
            self.color[1] = color[1] as u8;
            self.color[2] = color[2] as u8;
            self.changed = true;
        }
    }
}

/// Interpreter for `Anm0` instructions to update a `Sprite`.
pub struct AnmRunner {
    anm: Anm0,
    sprite: Rc<RefCell<Sprite>>,
    prng: Weak<RefCell<Prng>>,
    running: bool,
    sprite_index_offset: u32,
    script: Script,
    instruction_pointer: usize,
    frame: u16,
    waiting: bool,
    variables: ([i32; 4], [f32; 4], [i32; 4]),
    timeout: Option<u32>,
}

impl AnmRunner {
    /// Create a new `AnmRunner`.
    pub fn new(anm: &Anm0, script_id: u8, sprite: Rc<RefCell<Sprite>>, prng: Weak<RefCell<Prng>>, sprite_index_offset: u32) -> AnmRunner {
        let mut runner = AnmRunner {
            anm: anm.clone(),
            sprite: sprite,
            prng,
            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
    }

    /// Get a Rc from the inner Sprite.
    pub fn get_sprite(&self) -> Rc<RefCell<Sprite>> {
        self.sprite.clone()
    }

    /// 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) => {
                sprite.fade_interpolator = Some(Interpolator1::new([sprite.color[3] as f32], sprite.frame, [new_alpha as f32], sprite.frame + duration, Formula::Linear));
            }
            Instruction::SetBlendmodeAlphablend() => {
                sprite.blendfunc = 1;
            }
            Instruction::SetBlendmodeAdd() => {
                sprite.blendfunc = 0;
            }
            Instruction::KeepStill() => {
                self.running = false;
            }
            Instruction::LoadRandomSprite(min_index, mut amplitude) => {
                if amplitude > 0 {
                    let prng = self.prng.upgrade().unwrap();
                    let rand = prng.borrow_mut().get_u16();
                    amplitude = (rand as u32) % amplitude;
                }
                let sprite_index = min_index + amplitude;

                // TODO: refactor that with Instruction::LoadSprite.
                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::Move(x, y, z) => {
                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));
            }
            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));
            }
            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));
            }
            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) => {
                sprite.scale_interpolator = Some(Interpolator2::new(sprite.rescale, sprite.frame, [sx, sy], sprite.frame + duration, Formula::Linear));
            }
            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("EoSD/CM/player01.anm").unwrap();
        let mut file = io::BufReader::new(file);
        let mut buf = vec![];
        file.read_to_end(&mut buf).unwrap();
        let (_, mut anms) = Anm0::from_slice(&buf).unwrap();
        let anm0 = anms.pop().unwrap();
        assert_eq!(anm0.size, (256, 256));
        assert_eq!(anm0.format, 5);
        let sprite = Rc::new(RefCell::new(Sprite::new()));
        let prng = Rc::new(RefCell::new(Prng::new(0)));
        let mut anm_runner = AnmRunner::new(&anm0, 1, sprite.clone(), Rc::downgrade(&prng), 0);
        for _ in 0..50 {
            anm_runner.run_frame();
        }
    }
}