Mercurial > touhou
changeset 643:01849ffd0180
Add an anmrenderer binary.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Fri, 02 Aug 2019 20:24:45 +0200 |
parents | 9e40bd5cc26d |
children | f983a4c98410 |
files | Cargo.toml examples/anmrenderer.rs src/th06/anm0.rs src/th06/anm0_vm.rs src/util/math.rs src/util/mod.rs |
diffstat | 6 files changed, 539 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,10 @@ license = "GPL-3.0-or-later" [dependencies] nom = "5" +image = { version = "0.22", default-features = false, features = ["png_codec"] } +luminance = { version = "*", path = "../luminance/luminance" } +luminance-glfw = { version = "*", path = "../luminance/luminance-glfw", default-features = false } +luminance-derive = { version = "*", path = "../luminance/luminance-derive" } + +[profile.dev] +panic = "abort"
new file mode 100644 --- /dev/null +++ b/examples/anmrenderer.rs @@ -0,0 +1,231 @@ +use image::GenericImageView; +use luminance::context::GraphicsContext; +use luminance::framebuffer::Framebuffer; +use luminance::pipeline::BoundTexture; +use luminance::pixel::{RGB, Floating}; +use luminance::render_state::RenderState; +use luminance::shader::program::{Program, Uniform}; +use luminance::tess::{Mode, TessBuilder}; +use luminance::texture::{Dim2, Flat, Sampler, Texture, GenMipmaps}; +use luminance_derive::{Semantics, Vertex, UniformInterface}; +use luminance_glfw::event::{Action, Key, WindowEvent}; +use luminance_glfw::surface::{GlfwSurface, Surface, WindowDim, WindowOpt}; +use touhou::th06::anm0::Anm0; +use touhou::th06::anm0_vm::{AnmRunner, Sprite, Vertex as FakeVertex}; +use touhou::util::math::{perspective, setup_camera}; +use std::cell::RefCell; +use std::fs::File; +use std::io::{BufReader, Read}; +use std::rc::Rc; +use std::env; +use std::path::Path; + +const VS: &str = r#" +in ivec3 in_position; +in vec2 in_texcoord; +in uvec4 in_color; + +uniform mat4 mvp; + +out vec2 texcoord; +out vec4 color; + +void main() +{ + gl_Position = mvp * vec4(vec3(in_position), 1.0); + texcoord = vec2(in_texcoord); + + // Normalized from the u8 being passed. + color = vec4(in_color) / 255.; +} +"#; + +const FS: &str = r#" +in vec2 texcoord; +in vec4 color; + +uniform sampler2D color_map; + +out vec4 frag_color; + +void main() +{ + frag_color = texture(color_map, texcoord) * color; +} +"#; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Semantics)] +pub enum Semantics { + #[sem(name = "in_position", repr = "[i16; 3]", wrapper = "VertexPosition")] + Position, + + #[sem(name = "in_texcoord", repr = "[f32; 2]", wrapper = "VertexTexcoord")] + Texcoord, + + #[sem(name = "in_color", repr = "[u8; 4]", wrapper = "VertexColor")] + Color, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Vertex)] +#[vertex(sem = "Semantics")] +struct Vertex { + pos: VertexPosition, + uv: VertexTexcoord, + rgba: VertexColor, +} + +#[derive(UniformInterface)] +struct ShaderInterface { + // the 'static lifetime acts as “anything” here + color_map: Uniform<&'static BoundTexture<'static, Flat, Dim2, Floating>>, + + #[uniform(name = "mvp")] + mvp: Uniform<[[f32; 4]; 4]>, +} + +fn main() { + // Parse arguments. + let args: Vec<_> = env::args().collect(); + if args.len() != 4 { + eprintln!("Usage: {} <ANM file> <PNG file> <script number>", args[0]); + return; + } + let anm_filename = &args[1]; + let png_filename = &args[2]; + let script: u8 = args[3].parse().expect("number"); + + // Open the ANM file. + let file = File::open(anm_filename).unwrap(); + let mut file = BufReader::new(file); + let mut buf = vec![]; + file.read_to_end(&mut buf).unwrap(); + let anm0 = Anm0::from_slice(&buf).unwrap(); + + if !anm0.scripts.contains_key(&script) { + eprintln!("This anm0 doesn’t contain a script named {}.", script); + return; + } + + // Create the sprite. + let sprite = Rc::new(RefCell::new(Sprite::new(0., 0.))); + + // Create the AnmRunner from the ANM and the sprite. + let mut anm_runner = AnmRunner::new(&anm0, script, sprite.clone(), 0); + + assert_eq!(std::mem::size_of::<Vertex>(), std::mem::size_of::<FakeVertex>()); + let mut vertices: [Vertex; 4] = unsafe { std::mem::uninitialized() }; + fill_vertices(sprite.clone(), &mut vertices); + + let mut surface = GlfwSurface::new(WindowDim::Windowed(384, 448), "Touhou", WindowOpt::default()).unwrap(); + + // Open the image atlas matching this ANM. + println!("{} {}", anm0.first_name, png_filename); + let tex = load_from_disk(&mut surface, Path::new(png_filename)).expect("texture loading"); + + // set the uniform interface to our type so that we can read textures from the shader + let (program, _) = + Program::<Semantics, (), ShaderInterface>::from_strings(None, VS, None, FS).expect("program creation"); + + let mut tess = TessBuilder::new(&mut surface) + .add_vertices(vertices) + .set_mode(Mode::TriangleFan) + .build() + .unwrap(); + + let mut back_buffer = Framebuffer::back_buffer(surface.size()); + let mut frame = 0; + let mut i = 0; + + 'app: loop { + for event in surface.poll_events() { + match event { + WindowEvent::Close | WindowEvent::Key(Key::Escape, _, Action::Release, _) => break 'app, + + WindowEvent::FramebufferSize(width, height) => { + back_buffer = Framebuffer::back_buffer([width as u32, height as u32]); + } + + _ => (), + } + } + + { + let mut slice = tess + .as_slice_mut() + .unwrap(); + + anm_runner.run_frame(); + fill_vertices_ptr(sprite.clone(), slice.as_mut_ptr()); + } + + // here, we need to bind the pipeline variable; it will enable us to bind the texture to the GPU + // and use it in the shader + surface + .pipeline_builder() + .pipeline(&back_buffer, [0., 0., 0., 0.], |pipeline, shd_gate| { + // bind our fancy texture to the GPU: it gives us a bound texture we can use with the shader + let bound_tex = pipeline.bind_texture(&tex); + + shd_gate.shade(&program, |rdr_gate, iface| { + // update the texture; strictly speaking, this update doesn’t do much: it just tells the GPU + // to use the texture passed as argument (no allocation or copy is performed) + iface.color_map.update(&bound_tex); + //let mvp = ortho_2d(0., 384., 448., 0.); + let proj = perspective(0.5235987755982988, 384. / 448., 101010101./2010101., 101010101./10101.); + let view = setup_camera(0., 0., 1.); + let mvp = view * proj; + //println!("{:#?}", mvp); + // TODO: check how to pass by reference. + iface.mvp.update(*mvp.borrow_inner()); + + rdr_gate.render(RenderState::default(), |tess_gate| { + // render the tessellation to the surface the regular way and let the vertex shader’s + // magic do the rest! + tess_gate.render(&mut surface, (&tess).into()); + }); + }); + }); + + surface.swap_buffers(); + } +} + +fn fill_vertices_ptr(sprite: Rc<RefCell<Sprite>>, vertices: *mut Vertex) { + let mut fake_vertices = unsafe { std::mem::transmute::<*mut Vertex, &mut [FakeVertex; 4]>(vertices) }; + sprite.borrow().fill_vertices(&mut fake_vertices); +} + +fn fill_vertices(sprite: Rc<RefCell<Sprite>>, vertices: &mut [Vertex; 4]) { + let mut fake_vertices = unsafe { std::mem::transmute::<&mut [Vertex; 4], &mut [FakeVertex; 4]>(vertices) }; + sprite.borrow().fill_vertices(&mut fake_vertices); +} + +fn load_from_disk(surface: &mut GlfwSurface, path: &Path) -> Option<Texture<Flat, Dim2, RGB>> { + // load the texture into memory as a whole bloc (i.e. no streaming) + match image::open(&path) { + Ok(img) => { + let (width, height) = img.dimensions(); + let texels = img + .pixels() + .map(|(x, y, rgb)| (rgb[0], rgb[1], rgb[2])) + .collect::<Vec<_>>(); + + // create the luminance texture; the third argument is the number of mipmaps we want (leave it + // to 0 for now) and the latest is a the sampler to use when sampling the texels in the + // shader (we’ll just use the default one) + let tex = + Texture::new(surface, [width, height], 0, &Sampler::default()).expect("luminance texture creation"); + + // the first argument disables mipmap generation (we don’t care so far) + tex.upload(GenMipmaps::No, &texels); + + Some(tex) + } + + Err(e) => { + eprintln!("cannot open image {}: {}", path.display(), e); + None + } + } +}
--- a/src/th06/anm0.rs +++ b/src/th06/anm0.rs @@ -76,6 +76,12 @@ impl Anm0 { assert_eq!(anm0.len(), 1); Ok(anm0[0].clone()) } + + /// TODO + pub fn inv_size(&self) -> (f32, f32) { + let (x, y) = self.size; + (1. / x as f32, 1. / y as f32) + } } fn parse_name(i: &[u8], is_present: bool) -> IResult<&[u8], String> {
--- a/src/th06/anm0_vm.rs +++ b/src/th06/anm0_vm.rs @@ -7,9 +7,22 @@ use crate::th06::anm0::{ Instruction, }; use crate::th06::interpolator::{Interpolator1, Interpolator2, Interpolator3, Formula}; +use crate::util::math::Mat4; use std::cell::RefCell; use std::rc::Rc; +/// 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 { @@ -61,9 +74,88 @@ impl Sprite { } } + /// TODO + pub fn fill_vertices(&self, vertices: &mut [Vertex; 4]) { + 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.); + } + + 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. {
new file mode 100644 --- /dev/null +++ b/src/util/math.rs @@ -0,0 +1,202 @@ +//! Various helpers to deal with vectors and matrices. + +/// A 4×4 f32 matrix type. +pub struct Mat4 { + inner: [[f32; 4]; 4] +} + +impl Mat4 { + /// Create a new matrix from a set of 16 f32. + pub fn new(inner: [[f32; 4]; 4]) -> Mat4 { + Mat4 { + inner + } + } + + fn zero() -> Mat4 { + Mat4 { + inner: [[0.; 4]; 4] + } + } + + fn identity() -> Mat4 { + Mat4 { + inner: [[1., 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.]] + } + } + + /// Immutably borrow the array of f32 inside this matrix. + pub fn borrow_inner(&self) -> &[[f32; 4]; 4] { + &self.inner + } + + /// Scale the matrix in 2D. + pub fn scale2d(&mut self, x: f32, y: f32) { + for i in 0..4 { + self.inner[0][i] *= x; + self.inner[1][i] *= y; + } + } + + /// Flip the matrix. + pub fn flip(&mut self) { + for i in 0..4 { + self.inner[0][i] = -self.inner[0][i]; + } + } + + /// Rotate the matrix around its x angle (in radians). + pub fn rotate_x(&mut self, angle: f32) { + let mut lines: [f32; 8] = [0.; 8]; + let cos_a = angle.cos(); + let sin_a = angle.sin(); + for i in 0..4 { + lines[ i] = self.inner[0][i]; + lines[4 + i] = self.inner[1][i]; + } + for i in 0..4 { + self.inner[1][i] = cos_a * lines[i] - sin_a * lines[4+i]; + self.inner[2][i] = sin_a * lines[i] + cos_a * lines[4+i]; + } + } + + /// Rotate the matrix around its y angle (in radians). + pub fn rotate_y(&mut self, angle: f32) { + let mut lines: [f32; 8] = [0.; 8]; + let cos_a = angle.cos(); + let sin_a = angle.sin(); + for i in 0..4 { + lines[ i] = self.inner[0][i]; + lines[4 + i] = self.inner[2][i]; + } + for i in 0..4 { + self.inner[0][i] = cos_a * lines[i] + sin_a * lines[4+i]; + self.inner[2][i] = -sin_a * lines[i] + cos_a * lines[4+i]; + } + } + + /// Rotate the matrix around its z angle (in radians). + pub fn rotate_z(&mut self, angle: f32) { + let mut lines: [f32; 8] = [0.; 8]; + let cos_a = angle.cos(); + let sin_a = angle.sin(); + for i in 0..4 { + lines[ i] = self.inner[0][i]; + lines[4 + i] = self.inner[1][i]; + } + for i in 0..4 { + self.inner[0][i] = cos_a * lines[i] - sin_a * lines[4+i]; + self.inner[1][i] = sin_a * lines[i] + cos_a * lines[4+i]; + } + } + + /// Translate the matrix by a 3D offset. + pub fn translate(&mut self, offset: [f32; 3]) { + let mut item: [f32; 3] = [0.; 3]; + for i in 0..3 { + item[i] = self.inner[3][i] * offset[i]; + } + for i in 0..3 { + for j in 0..4 { + self.inner[i][j] += item[i]; + } + } + } + + /// Translate the matrix by a 2D offset. + pub fn translate_2d(&mut self, x: f32, y: f32) { + let offset = [x, y, 0.]; + self.translate(offset); + } +} + +impl std::ops::Mul<Mat4> for Mat4 { + type Output = Mat4; + fn mul(self, rhs: Mat4) -> Mat4 { + let mut tmp = Mat4::zero(); + for i in 0..4 { + for j in 0..4 { + for k in 0..4 { + tmp.inner[i][j] += self.inner[i][k] * rhs.inner[k][j]; + } + } + } + tmp + } +} + +/// Create an orthographic projection matrix. +pub fn ortho_2d(left: f32, right: f32, bottom: f32, top: f32) -> Mat4 { + let mut mat = Mat4::identity(); + mat.inner[0][0] = 2. / (right - left); + mat.inner[1][1] = 2. / (top - bottom); + mat.inner[2][2] = -1.; + mat.inner[3][0] = -(right + left) / (right - left); + mat.inner[3][1] = -(top + bottom) / (top - bottom); + mat +} + +/// Setup a camera view matrix. +pub fn setup_camera(dx: f32, dy: f32, dz: f32) -> Mat4 { + // Some explanations on the magic constants: + // 192. = 384. / 2. = width / 2. + // 224. = 448. / 2. = height / 2. + // 835.979370 = 224./math.tan(math.radians(15)) = (height/2.)/math.tan(math.radians(fov/2)) + // This is so that objects on the (O, x, y) plane use pixel coordinates + look_at([192., 224., -835.979370 * dz], [192. + dx, 224. - dy, 0.], [0., -1., 0.]) +} + +/// Creates a perspective projection matrix. +pub fn perspective(fov_y: f32, aspect: f32, z_near: f32, z_far: f32) -> Mat4 { + let top = (fov_y / 2.).tan() * z_near; + let bottom = -top; + let left = -top * aspect; + let right = top * aspect; + + let mut mat = Mat4::identity(); + mat.inner[0][0] = (2. * z_near) / (right - left); + mat.inner[1][1] = (2. * z_near) / (top - bottom); + mat.inner[2][2] = -(z_far + z_near) / (z_far - z_near); + mat.inner[2][3] = -1.; + mat.inner[3][2] = -(2. * z_far * z_near) / (z_far - z_near); + mat.inner[3][3] = 0.; + mat +} + +type Vec3 = [f32; 3]; + +fn look_at(eye: Vec3, center: Vec3, up: Vec3) -> Mat4 { + let f = normalize(sub(center, eye)); + let u = normalize(up); + let s = normalize(cross(f, u)); + let u = cross(s, f); + + Mat4::new([[s[0], u[0], -f[0], 0.], + [s[1], u[1], -f[1], 0.], + [s[2], u[2], -f[2], 0.], + [-dot(s, eye), -dot(u, eye), dot(f, eye), 1.]]) +} + +fn sub(a: Vec3, b: Vec3) -> Vec3 { + [a[0] - b[0], + a[1] - b[1], + a[2] - b[2]] +} + +fn normalize(vec: Vec3) -> Vec3 { + let normal = 1. / (vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]).sqrt(); + [vec[0] * normal, vec[1] * normal, vec[2] * normal] +} + +fn cross(a: Vec3, b: Vec3) -> Vec3 { + [a[1] * b[2] - b[1] * a[2], + a[2] * b[0] - b[2] * a[0], + a[0] * b[1] - b[0] * a[1]] +} + +fn dot(a: Vec3, b: Vec3) -> f32 { + a[0] * b[0] + a[1] * b[1] + a[2] * b[2] +}