use crate::resource::vertex_index::VertexIndex;
use crate::resource::GpuMesh2d;
use crate::scene::sprite::SpriteSheet;
use crate::scene::SceneNode2d;
use glamx::Vec2;
use std::cell::RefCell;
use std::rc::Rc;
pub struct Tilemap {
node: SceneNode2d,
columns: u32,
rows: u32,
tile_size: Vec2,
sheet: SpriteSheet,
tiles: Vec<u32>,
}
impl Tilemap {
pub const EMPTY: u32 = u32::MAX;
pub fn new(columns: u32, rows: u32, tile_size: Vec2, sheet: SpriteSheet) -> Tilemap {
let tiles = vec![Self::EMPTY; (columns * rows) as usize];
let mesh = build_mesh(columns, rows, tile_size, &sheet, &tiles);
let node = SceneNode2d::mesh(Rc::new(RefCell::new(mesh)), Vec2::ONE);
Tilemap {
node,
columns,
rows,
tile_size,
sheet,
tiles,
}
}
pub fn node(&self) -> SceneNode2d {
self.node.clone()
}
pub fn dimensions(&self) -> (u32, u32) {
(self.columns, self.rows)
}
pub fn tile(&self, col: u32, row: u32) -> u32 {
if col < self.columns && row < self.rows {
self.tiles[(row * self.columns + col) as usize]
} else {
Self::EMPTY
}
}
pub fn set_tile(&mut self, col: u32, row: u32, index: u32) {
if col >= self.columns || row >= self.rows {
return;
}
self.tiles[(row * self.columns + col) as usize] = index;
self.rebuild();
}
pub fn fill(&mut self, indices: &[u32]) {
for (dst, &src) in self.tiles.iter_mut().zip(indices.iter()) {
*dst = src;
}
self.rebuild();
}
fn rebuild(&mut self) {
let uv_inset = self
.node
.data()
.object()
.map(|o| {
let (w, h) = o.data().texture().size;
Vec2::new(0.05 / w as f32, 0.05 / h as f32)
})
.unwrap_or(Vec2::ZERO);
let (coords, faces, uvs) = build_mesh_data(
self.columns,
self.rows,
self.tile_size,
&self.sheet,
&self.tiles,
uv_inset,
);
self.node.modify_vertices(&mut |v| {
v.clear();
v.extend_from_slice(&coords);
});
self.node.modify_faces(&mut |f| {
f.clear();
f.extend_from_slice(&faces);
});
self.node.modify_uvs(&mut |u| {
u.clear();
u.extend_from_slice(&uvs);
});
}
}
fn build_mesh_data(
columns: u32,
rows: u32,
tile_size: Vec2,
sheet: &SpriteSheet,
tiles: &[u32],
uv_inset: Vec2,
) -> (Vec<Vec2>, Vec<[VertexIndex; 3]>, Vec<Vec2>) {
let mut coords = Vec::new();
let mut uvs = Vec::new();
let mut faces = Vec::new();
let half = Vec2::new(columns as f32, rows as f32) * tile_size * 0.5;
for row in 0..rows {
for col in 0..columns {
let index = tiles[(row * columns + col) as usize];
if index == Tilemap::EMPTY {
continue;
}
let x0 = col as f32 * tile_size.x - half.x;
let x1 = x0 + tile_size.x;
let y1 = half.y - row as f32 * tile_size.y;
let y0 = y1 - tile_size.y;
let (frame_min, frame_max) = sheet.frame_uv(index);
let uv_min = frame_min + uv_inset;
let uv_max = frame_max - uv_inset;
let base = coords.len() as VertexIndex;
coords.push(Vec2::new(x0, y1));
coords.push(Vec2::new(x1, y1));
coords.push(Vec2::new(x1, y0));
coords.push(Vec2::new(x0, y0));
uvs.push(Vec2::new(uv_min.x, uv_min.y));
uvs.push(Vec2::new(uv_max.x, uv_min.y));
uvs.push(Vec2::new(uv_max.x, uv_max.y));
uvs.push(Vec2::new(uv_min.x, uv_max.y));
faces.push([base, base + 1, base + 2]);
faces.push([base, base + 2, base + 3]);
}
}
if coords.is_empty() {
coords.push(Vec2::ZERO);
uvs.push(Vec2::ZERO);
faces.push([0, 0, 0]);
}
(coords, faces, uvs)
}
fn build_mesh(
columns: u32,
rows: u32,
tile_size: Vec2,
sheet: &SpriteSheet,
tiles: &[u32],
) -> GpuMesh2d {
let (coords, faces, uvs) = build_mesh_data(columns, rows, tile_size, sheet, tiles, Vec2::ZERO);
GpuMesh2d::new(coords, faces, Some(uvs), true)
}