use crate::math::{Position, Size, Vec2, Color};
use crate::EngineResult;
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
pub type TileId = u32;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TileDefinition {
pub id: TileId,
pub name: String,
pub texture_path: Option<String>,
pub texture_rect: Option<(f32, f32, f32, f32)>,
pub solid: bool,
pub color: Color,
}
impl TileDefinition {
pub fn new(id: TileId, name: impl Into<String>) -> Self {
Self {
id,
name: name.into(),
texture_path: None,
texture_rect: None,
solid: false,
color: Color::new(1.0, 1.0, 1.0, 1.0),
}
}
pub fn with_texture(mut self, path: impl Into<String>) -> Self {
self.texture_path = Some(path.into());
self
}
pub fn with_texture_rect(mut self, x: f32, y: f32, width: f32, height: f32) -> Self {
self.texture_rect = Some((x, y, width, height));
self
}
pub fn solid(mut self) -> Self {
self.solid = true;
self
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = color;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tile {
pub tile_id: TileId,
pub position: Position,
pub rotation: f32,
pub scale: Vec2,
pub visible: bool,
pub custom_color: Option<Color>,
}
impl Default for Tile {
fn default() -> Self {
Self {
tile_id: 0,
position: Position::new(0.0, 0.0),
rotation: 0.0,
scale: Vec2::new(1.0, 1.0),
visible: true,
custom_color: None,
}
}
}
impl Tile {
pub fn new(tile_id: TileId, position: Position) -> Self {
Self {
tile_id,
position,
..Default::default()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TileLayer {
pub name: String,
pub tiles: Vec<Vec<Option<Tile>>>,
pub visible: bool,
pub opacity: f32,
pub z_order: i32,
}
impl TileLayer {
pub fn new(name: impl Into<String>, width: usize, height: usize) -> Self {
Self {
name: name.into(),
tiles: vec![vec![None; width]; height],
visible: true,
opacity: 1.0,
z_order: 0,
}
}
pub fn set_tile(&mut self, x: usize, y: usize, tile: Option<Tile>) {
if y < self.tiles.len() && x < self.tiles[y].len() {
self.tiles[y][x] = tile;
}
}
pub fn get_tile(&self, x: usize, y: usize) -> Option<&Tile> {
self.tiles.get(y)?.get(x)?.as_ref()
}
pub fn get_tile_mut(&mut self, x: usize, y: usize) -> Option<&mut Tile> {
self.tiles.get_mut(y)?.get_mut(x)?.as_mut()
}
pub fn width(&self) -> usize {
self.tiles.get(0).map_or(0, |row| row.len())
}
pub fn height(&self) -> usize {
self.tiles.len()
}
pub fn fill_rect(&mut self, x: usize, y: usize, width: usize, height: usize, tile_id: TileId) {
for row in y..y.min(self.height()).saturating_add(height) {
for col in x..x.min(self.width()).saturating_add(width) {
if let Some(tile_row) = self.tiles.get_mut(row) {
if let Some(tile_slot) = tile_row.get_mut(col) {
*tile_slot = Some(Tile::new(tile_id, Position::new(0.0, 0.0)));
}
}
}
}
}
pub fn clear_rect(&mut self, x: usize, y: usize, width: usize, height: usize) {
for row in y..y.min(self.height()).saturating_add(height) {
for col in x..x.min(self.width()).saturating_add(width) {
if let Some(tile_row) = self.tiles.get_mut(row) {
if let Some(tile_slot) = tile_row.get_mut(col) {
*tile_slot = None;
}
}
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tilemap {
pub tile_definitions: HashMap<TileId, TileDefinition>,
pub layers: Vec<TileLayer>,
pub tile_size: Size,
pub position: Position,
pub scale: Vec2,
}
impl Tilemap {
pub fn new(tile_width: f32, tile_height: f32) -> Self {
Self {
tile_definitions: HashMap::new(),
layers: Vec::new(),
tile_size: Size::new(tile_width, tile_height),
position: Position::new(0.0, 0.0),
scale: Vec2::new(1.0, 1.0),
}
}
pub fn add_tile_definition(&mut self, definition: TileDefinition) {
self.tile_definitions.insert(definition.id, definition);
}
pub fn get_tile_definition(&self, tile_id: TileId) -> Option<&TileDefinition> {
self.tile_definitions.get(&tile_id)
}
pub fn add_layer(&mut self, layer: TileLayer) {
self.layers.push(layer);
self.layers.sort_by_key(|layer| layer.z_order);
}
pub fn get_layer(&self, index: usize) -> Option<&TileLayer> {
self.layers.get(index)
}
pub fn get_layer_mut(&mut self, index: usize) -> Option<&mut TileLayer> {
self.layers.get_mut(index)
}
pub fn get_layer_by_name(&self, name: &str) -> Option<&TileLayer> {
self.layers.iter().find(|layer| layer.name == name)
}
pub fn get_layer_by_name_mut(&mut self, name: &str) -> Option<&mut TileLayer> {
self.layers.iter_mut().find(|layer| layer.name == name)
}
pub fn world_to_tile_coords(&self, world_pos: Position) -> (i32, i32) {
let local_x = (world_pos.x - self.position.x) / self.scale.x;
let local_y = (world_pos.y - self.position.y) / self.scale.y;
let tile_x = (local_x / self.tile_size.x).floor() as i32;
let tile_y = (local_y / self.tile_size.y).floor() as i32;
(tile_x, tile_y)
}
pub fn tile_to_world_coords(&self, tile_x: i32, tile_y: i32) -> Position {
let world_x = self.position.x + (tile_x as f32 * self.tile_size.x * self.scale.x);
let world_y = self.position.y + (tile_y as f32 * self.tile_size.y * self.scale.y);
Position::new(world_x, world_y)
}
pub fn get_tile_at_world_pos(&self, layer_index: usize, world_pos: Position) -> Option<&Tile> {
let (tile_x, tile_y) = self.world_to_tile_coords(world_pos);
if tile_x >= 0 && tile_y >= 0 {
self.get_layer(layer_index)?
.get_tile(tile_x as usize, tile_y as usize)
} else {
None
}
}
pub fn is_solid_at(&self, world_pos: Position) -> bool {
for layer in &self.layers {
if !layer.visible {
continue;
}
let (tile_x, tile_y) = self.world_to_tile_coords(world_pos);
if tile_x >= 0 && tile_y >= 0 {
if let Some(tile) = layer.get_tile(tile_x as usize, tile_y as usize) {
if let Some(definition) = self.get_tile_definition(tile.tile_id) {
if definition.solid && tile.visible {
return true;
}
}
}
}
}
false
}
pub fn get_solid_tiles_in_area(&self, min_pos: Position, max_pos: Position) -> Vec<(i32, i32, Position)> {
let mut solid_tiles = Vec::new();
let (min_tile_x, min_tile_y) = self.world_to_tile_coords(min_pos);
let (max_tile_x, max_tile_y) = self.world_to_tile_coords(max_pos);
for layer in &self.layers {
if !layer.visible {
continue;
}
for tile_y in min_tile_y..=max_tile_y {
for tile_x in min_tile_x..=max_tile_x {
if tile_x >= 0 && tile_y >= 0 {
let ux = tile_x as usize;
let uy = tile_y as usize;
if let Some(tile) = layer.get_tile(ux, uy) {
if let Some(definition) = self.get_tile_definition(tile.tile_id) {
if definition.solid && tile.visible {
let world_pos = self.tile_to_world_coords(tile_x, tile_y);
solid_tiles.push((tile_x, tile_y, world_pos));
}
}
}
}
}
}
}
solid_tiles
}
pub fn load_from_csv(&mut self, layer_name: &str, csv_data: &str, default_tile_id: TileId) -> EngineResult<()> {
let lines: Vec<&str> = csv_data.lines().collect();
let height = lines.len();
let width = if height > 0 {
lines[0].split(',').count()
} else {
0
};
let mut layer = TileLayer::new(layer_name, width, height);
for (y, line) in lines.iter().enumerate() {
for (x, cell) in line.split(',').enumerate() {
let tile_id: TileId = cell.trim().parse().unwrap_or(0);
if tile_id != 0 {
let world_pos = self.tile_to_world_coords(x as i32, y as i32);
layer.set_tile(x, y, Some(Tile::new(tile_id, world_pos)));
}
}
}
self.add_layer(layer);
Ok(())
}
pub fn save_layer_to_csv(&self, layer_index: usize) -> Option<String> {
let layer = self.get_layer(layer_index)?;
let mut csv = String::new();
for row in &layer.tiles {
let row_data: Vec<String> = row.iter()
.map(|tile| {
tile.as_ref()
.map_or("0".to_string(), |t| t.tile_id.to_string())
})
.collect();
csv.push_str(&row_data.join(","));
csv.push('\n');
}
Some(csv)
}
}
pub struct TilemapRenderer {
}
impl TilemapRenderer {
pub fn new() -> Self {
Self {}
}
pub fn render_tilemap(&self, tilemap: &Tilemap) -> EngineResult<Vec<TileRenderData>> {
let mut render_data = Vec::new();
for layer in &tilemap.layers {
if !layer.visible {
continue;
}
for (y, row) in layer.tiles.iter().enumerate() {
for (x, tile_opt) in row.iter().enumerate() {
if let Some(tile) = tile_opt {
if !tile.visible {
continue;
}
if let Some(definition) = tilemap.get_tile_definition(tile.tile_id) {
let world_pos = tilemap.tile_to_world_coords(x as i32, y as i32);
render_data.push(TileRenderData {
position: Position::new(
world_pos.x + tile.position.x,
world_pos.y + tile.position.y,
),
size: Size::new(
tilemap.tile_size.x * tile.scale.x,
tilemap.tile_size.y * tile.scale.y,
),
rotation: tile.rotation,
color: tile.custom_color.unwrap_or(definition.color),
texture_path: definition.texture_path.clone(),
texture_rect: definition.texture_rect,
z_order: layer.z_order,
opacity: layer.opacity,
});
}
}
}
}
}
render_data.sort_by_key(|data| data.z_order);
Ok(render_data)
}
}
#[derive(Debug, Clone)]
pub struct TileRenderData {
pub position: Position,
pub size: Size,
pub rotation: f32,
pub color: Color,
pub texture_path: Option<String>,
pub texture_rect: Option<(f32, f32, f32, f32)>,
pub z_order: i32,
pub opacity: f32,
}