rust/lib-hedgewars-engine/src/render/gear.rs
author alfadur
Thu, 27 Jan 2022 03:51:13 +0300
changeset 15850 44b49f255e31
parent 15788 19ad3fe48ea6
child 15918 8a6a2d931bae
permissions -rw-r--r--
add type safe power of two sizes

use crate::render::{
    atlas::{AtlasCollection, SpriteIndex},
    camera::Camera,
    gl::{
        Buffer, BufferType, BufferUsage, InputElement, InputFormat, InputLayout, PipelineState,
        Shader, Texture2D, TextureDataType, TextureFilter, TextureFormat, TextureInternalFormat,
        VariableBinding,
    },
};

use integral_geometry::{Rect, Size};

use png::{ColorType, Decoder, DecodingError};

use std::{
    collections::HashMap,
    ffi::OsString,
    fs::{read_dir, File},
    io,
    io::BufReader,
    mem::size_of,
    path::{Path, PathBuf},
};

const VERTEX_SHADER: &'static str = r#"
#version 330 core

uniform mat4 projection;

layout(location = 0) in vec2 position;
layout(location = 1) in vec2 texCoords;

out vec2 varTexCoords;

void main() {
    varTexCoords = texCoords;
	gl_Position = projection * vec4(position, 0.0, 1.0);
}
"#;

const PIXEL_SHADER: &'static str = r#"
#version 330 core

uniform sampler2D texture;

in vec2 varTexCoords;

out vec4 outColor;

void main() {
	 outColor = texture2D(texture, varTexCoords);
}
"#;

#[repr(C)]
#[derive(Copy, Clone)]
struct Vertex {
    position: [f32; 2],
    tex_coords: [f32; 2],
}

#[derive(PartialEq, Debug, Clone, Copy)]
#[repr(u32)]
pub enum SpriteId {
    Mine = 0,
    Grenade,
    Cheese,
    Cleaver,

    MaxSprite,
}

const SPRITE_LOAD_LIST: &[(SpriteId, &str)] = &[
    (
        SpriteId::Mine,
        "../../share/hedgewars/Data/Graphics/MineOn.png",
    ),
    (
        SpriteId::Grenade,
        "../../share/hedgewars/Data/Graphics/Bomb.png",
    ),
    (
        SpriteId::Cheese,
        "../../share/hedgewars/Data/Graphics/cheese.png",
    ),
    (
        SpriteId::Cleaver,
        "../../share/hedgewars/Data/Graphics/cleaver.png",
    ),
];

const MAX_SPRITES: usize = SpriteId::MaxSprite as usize + 1;

type SpriteTexCoords = (u32, [[f32; 2]; 4]);

pub struct GearEntry {
    position: [f32; 2],
    size: Size,
}

impl GearEntry {
    pub fn new(x: f32, y: f32, size: Size) -> Self {
        Self {
            position: [x, y],
            size,
        }
    }
}

pub struct GearRenderer {
    atlas: AtlasCollection,
    texture: Texture2D,
    allocation: Box<[SpriteTexCoords; MAX_SPRITES]>,
    shader: Shader,
    layout: InputLayout,
    vertex_buffer: Buffer,
}

struct SpriteData {
    size: Size,
    filename: PathBuf,
}

const ATLAS_SIZE: Size = Size::square(2048);

impl GearRenderer {
    pub fn new() -> Self {
        let mut atlas = AtlasCollection::new(ATLAS_SIZE);

        let texture = Texture2D::new(
            ATLAS_SIZE,
            TextureInternalFormat::Rgba8,
            TextureFilter::Linear,
        );

        let mut allocation = Box::new([Default::default(); MAX_SPRITES]);

        for (sprite, file) in SPRITE_LOAD_LIST {
            let path = Path::new(file);
            let size = load_sprite_size(path).expect(&format!("Unable to open {}", file));
            let index = atlas
                .insert_sprite(size)
                .expect(&format!("Could not store sprite {:?}", sprite));
            let (texture_index, rect) = atlas.get_rect(index).unwrap();

            let mut pixels = vec![255u8; size.area() * 4].into_boxed_slice();
            load_sprite_pixels(path, &mut pixels).expect("Unable to load Graphics");

            texture.update(
                rect,
                &pixels,
                None,
                TextureFormat::Rgba,
                TextureDataType::UnsignedByte,
            );

            let mut tex_coords = [
                [rect.left() as f32, rect.bottom() as f32 + 1.0],
                [rect.right() as f32 + 1.0, rect.bottom() as f32 + 1.0],
                [rect.left() as f32, rect.top() as f32],
                [rect.right() as f32 + 1.0, rect.top() as f32],
            ]; //.map(|n| n as f32 / ATLAS_SIZE as f32);

            for coords in &mut tex_coords {
                coords[0] /= ATLAS_SIZE.width as f32;
                coords[1] /= ATLAS_SIZE.height as f32;
            }

            allocation[*sprite as usize] = (texture_index, tex_coords);
        }

        let shader = Shader::new(
            VERTEX_SHADER,
            Some(PIXEL_SHADER),
            &[VariableBinding::Sampler("texture", 0)],
        )
        .unwrap();

        let layout = InputLayout::new(vec![
            InputElement {
                shader_slot: 0,
                buffer_slot: 0,
                format: InputFormat::Float(gl::FLOAT, false),
                components: 2,
                stride: size_of::<Vertex>() as u32,
                offset: 0,
            },
            InputElement {
                shader_slot: 1,
                buffer_slot: 0,
                format: InputFormat::Float(gl::FLOAT, false),
                components: 2,
                stride: size_of::<Vertex>() as u32,
                offset: size_of::<[f32; 2]>() as u32,
            },
        ]);

        let vertex_buffer = Buffer::empty(BufferType::Array, BufferUsage::DynamicDraw);

        Self {
            atlas,
            texture,
            allocation,
            shader,
            layout,
            vertex_buffer,
        }
    }

    pub fn render(&mut self, camera: &Camera, entries: &[GearEntry]) {
        let mut data = Vec::with_capacity(entries.len() * 6);

        for (index, entry) in entries.iter().enumerate() {
            let sprite_id = match index & 0b11 {
                0 => SpriteId::Mine,
                1 => SpriteId::Grenade,
                2 => SpriteId::Cheese,
                _ => SpriteId::Cleaver,
            };
            let sprite_coords = &self.allocation[sprite_id as usize].1;

            let v = [
                Vertex {
                    position: [
                        entry.position[0] - entry.size.width as f32 / 2.0,
                        entry.position[1] + entry.size.height as f32 / 2.0,
                    ],
                    tex_coords: sprite_coords[0],
                },
                Vertex {
                    position: [
                        entry.position[0] + entry.size.width as f32 / 2.0,
                        entry.position[1] + entry.size.height as f32 / 2.0,
                    ],
                    tex_coords: sprite_coords[1],
                },
                Vertex {
                    position: [
                        entry.position[0] - entry.size.width as f32 / 2.0,
                        entry.position[1] - entry.size.height as f32 / 2.0,
                    ],
                    tex_coords: sprite_coords[2],
                },
                Vertex {
                    position: [
                        entry.position[0] + entry.size.width as f32 / 2.0,
                        entry.position[1] - entry.size.height as f32 / 2.0,
                    ],
                    tex_coords: sprite_coords[3],
                },
            ];

            data.extend_from_slice(&[v[0], v[1], v[2], v[1], v[3], v[2]]);
        }

        let projection = camera.projection();
        self.shader.bind();
        self.shader.set_matrix("projection", projection.as_ptr());
        self.shader.bind_texture_2d(0, &self.texture);

        self.vertex_buffer.write_typed(&data);
        let _buffer_bind = self.layout.bind(&[(0, &self.vertex_buffer)], None);

        let _state = PipelineState::new().with_blend();

        unsafe {
            gl::DrawArrays(gl::TRIANGLES, 0, entries.len() as i32 * 6);
        }
    }
}

fn load_sprite_pixels(path: &Path, buffer: &mut [u8]) -> io::Result<Size> {
    let decoder = Decoder::new(BufReader::new(File::open(path)?));
    let (info, mut reader) = decoder.read_info()?;

    let size = Size::new(info.width as usize, info.height as usize);
    reader.next_frame(buffer)?;
    Ok(size)
}

fn load_sprite_size(path: &Path) -> io::Result<Size> {
    let decoder = Decoder::new(BufReader::new(File::open(path)?));
    let (info, mut reader) = decoder.read_info()?;

    let size = Size::new(info.width as usize, info.height as usize);
    Ok(size)
}

fn load_sprites(path: &Path) -> io::Result<Vec<SpriteData>> {
    let mut result = vec![];
    for file in read_dir(path)? {
        let file = file?;
        if let Some(extension) = file.path().extension() {
            if extension == "png" {
                let path = file.path();
                let sprite = load_sprite_size(&path)?;
                result.push(SpriteData {
                    size: sprite,
                    filename: path,
                });
            }
        }
    }
    Ok(result)
}