use crate::sprite::SpriteSheet;
#[derive(Debug, Clone, Copy)]
pub struct Tile {
pub tile_id: u32,
pub solid: bool,
pub z_index: i32,
}
impl Tile {
pub fn new(tile_id: u32) -> Self {
Self {
tile_id,
solid: false,
z_index: 0,
}
}
pub fn with_solid(mut self, solid: bool) -> Self {
self.solid = solid;
self
}
pub fn with_z_index(mut self, z_index: i32) -> Self {
self.z_index = z_index;
self
}
}
pub struct Tilemap {
pub width: usize,
pub height: usize,
pub tile_width: u32,
pub tile_height: u32,
pub tiles: Vec<Vec<Option<Tile>>>,
pub tileset: Option<SpriteSheet>,
}
impl Tilemap {
pub fn new(width: usize, height: usize, tile_width: u32, tile_height: u32) -> Self {
let tiles = vec![vec![None; width]; height];
Self {
width,
height,
tile_width,
tile_height,
tiles,
tileset: None,
}
}
pub fn load_tileset(&mut self, path: &str) -> Result<(), image::ImageError> {
let tileset = SpriteSheet::load(path, self.tile_width, self.tile_height)?;
self.tileset = Some(tileset);
Ok(())
}
pub fn set_tile(&mut self, x: usize, y: usize, tile: Option<Tile>) {
if x < self.width && y < self.height {
self.tiles[y][x] = tile;
}
}
pub fn get_tile(&self, x: usize, y: usize) -> Option<&Tile> {
if x < self.width && y < self.height {
self.tiles[y][x].as_ref()
} else {
None
}
}
pub fn world_to_tile(&self, world_x: f32, world_y: f32) -> (usize, usize) {
let tile_x = (world_x / self.tile_width as f32) as usize;
let tile_y = (world_y / self.tile_height as f32) as usize;
(tile_x, tile_y)
}
pub fn tile_to_world(&self, tile_x: usize, tile_y: usize) -> (f32, f32) {
let world_x = tile_x as f32 * self.tile_width as f32;
let world_y = tile_y as f32 * self.tile_height as f32;
(world_x, world_y)
}
pub fn is_solid(&self, x: usize, y: usize) -> bool {
self.get_tile(x, y).map_or(false, |tile| tile.solid)
}
pub fn fill_rect(&mut self, start_x: usize, start_y: usize, width: usize, height: usize, tile: Tile) {
for y in start_y..(start_y + height).min(self.height) {
for x in start_x..(start_x + width).min(self.width) {
self.tiles[y][x] = Some(tile);
}
}
}
pub fn clear(&mut self) {
for row in &mut self.tiles {
for tile in row {
*tile = None;
}
}
}
pub fn render(&self, pixels: &mut [u8], camera_x: f32, camera_y: f32, viewport_width: u32, viewport_height: u32) {
if let Some(tileset) = &self.tileset {
let start_tile_x = (camera_x / self.tile_width as f32).floor() as usize;
let start_tile_y = (camera_y / self.tile_height as f32).floor() as usize;
let end_tile_x = ((camera_x + viewport_width as f32) / self.tile_width as f32).ceil() as usize;
let end_tile_y = ((camera_y + viewport_height as f32) / self.tile_height as f32).ceil() as usize;
for tile_y in start_tile_y..end_tile_y.min(self.height) {
for tile_x in start_tile_x..end_tile_x.min(self.width) {
if let Some(tile) = &self.tiles[tile_y][tile_x] {
let world_x = tile_x as f32 * self.tile_width as f32;
let world_y = tile_y as f32 * self.tile_height as f32;
let screen_x = (world_x - camera_x) as i32;
let screen_y = (world_y - camera_y) as i32;
self.render_tile(pixels, tileset, tile.tile_id, screen_x, screen_y, viewport_width);
}
}
}
}
}
fn render_tile(&self, pixels: &mut [u8], tileset: &SpriteSheet, tile_id: u32, screen_x: i32, screen_y: i32, viewport_width: u32) {
let tiles_per_row = tileset.width / self.tile_width;
let tile_row = tile_id / tiles_per_row;
let tile_col = tile_id % tiles_per_row;
let tile_x_offset = tile_col * self.tile_width;
let tile_y_offset = tile_row * self.tile_height;
for y in 0..self.tile_height {
for x in 0..self.tile_width {
let px = screen_x + x as i32;
let py = screen_y + y as i32;
if px < 0 || py < 0 || px >= viewport_width as i32 || py >= viewport_width as i32 {
continue;
}
let tileset_x = tile_x_offset + x;
let tileset_y = tile_y_offset + y;
let tileset_index = ((tileset_y * tileset.width) + tileset_x) as usize * 4;
if tileset_index + 3 < tileset.rgba_data.len() {
let color = &tileset.rgba_data[tileset_index..tileset_index + 4];
if color[3] > 0 {
let screen_index = ((py as u32 * viewport_width) + px as u32) as usize * 4;
if screen_index + 3 < pixels.len() {
pixels[screen_index..screen_index + 4].copy_from_slice(color);
}
}
}
}
}
}
}