using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Tiled;
namespace MonoGame.Extended.Tilemaps
{
///
/// Represents a tilemap loaded from any supported format.
///
///
/// A tilemap is a collection of layers organized into a grid-based structure.
/// Layers can contain tiles, objects, or images, and support various rendering
/// features like parallax scrolling, opacity, and tinting.
///
public class Tilemap
{
///
/// Gets or sets the name of the tilemap.
///
public string Name { get; set; }
///
/// Gets the width of the tilemap in tiles.
///
public int Width { get; }
///
/// Gets the height of the tilemap in tiles.
///
public int Height { get; }
///
/// Gets the width of each tile in pixels.
///
public int TileWidth { get; }
///
/// Gets the height of each tile in pixels.
///
public int TileHeight { get; }
///
/// Gets the orientation of the tilemap.
///
public TilemapOrientation Orientation { get; }
///
/// Gets or sets the background color of the tilemap.
///
public Color? BackgroundColor { get; set; }
///
/// Gets the custom properties of the tilemap.
///
public TilemapProperties Properties { get; }
///
/// Gets the collection of tilesets used by this tilemap.
///
public TilemapTilesetCollection Tilesets { get; }
///
/// Gets the collection of layers in this tilemap.
///
public TilemapLayerCollection Layers { get; }
///
/// Gets the world-space bounds of the tilemap.
///
public Rectangle WorldBounds
{
get
{
// For orthogonal maps, bounds are straightforward
// For isometric/hexagonal maps, this is an axis-aligned bounding box
return Orientation switch
{
TilemapOrientation.Orthogonal => new Rectangle(0, 0, Width * TileWidth, Height * TileHeight),
// Isometric maps have a diamond shape, so we need to calculate the AABB
TilemapOrientation.Isometric => CalculateIsometricBounds(),
// For staggered and hexagonal, use orthogonal approximation for now
// TODO: Implement precise bounds for staggered/hexagonal in future
TilemapOrientation.Staggered => new Rectangle(0, 0, Width * TileWidth, Height * TileHeight),
TilemapOrientation.Hexagonal => new Rectangle(0, 0, Width * TileWidth, Height * TileHeight),
_ => new Rectangle(0, 0, Width * TileWidth, Height * TileHeight)
};
}
}
///
/// Initializes a new instance of the class.
///
/// The name of the tilemap.
/// The width of the tilemap in tiles.
/// The height of the tilemap in tiles.
/// The width of each tile in pixels.
/// The height of each tile in pixels.
/// The orientation of the tilemap.
public Tilemap(string name, int width, int height, int tileWidth, int tileHeight, TilemapOrientation orientation)
{
Name = name;
Width = width;
Height = height;
TileWidth = tileWidth;
TileHeight = tileHeight;
Orientation = orientation;
Properties = new TilemapProperties();
Tilesets = new TilemapTilesetCollection();
Layers = new TilemapLayerCollection();
}
///
/// Converts tile coordinates to world-space position.
///
/// The tile X coordinate.
/// The tile Y coordinate.
/// The position in world space, where (0,0) represents the top-left corner of the map.
///
/// This transformation accounts for the map's orientation and tile dimensions.
/// For isometric maps, the calculation differs from orthogonal maps.
///
public Point TileToWorldPosition(int x, int y)
{
return Orientation switch
{
TilemapOrientation.Orthogonal => TileToWorldOrthogonal(x, y),
TilemapOrientation.Isometric => TileToWorldIsometric(x, y),
TilemapOrientation.Staggered => TileToWorldStaggered(x, y),
TilemapOrientation.Hexagonal => TileToWorldHexagonal(x, y),
_ => TileToWorldOrthogonal(x, y)
};
}
///
/// Converts world-space position to tile coordinates.
///
/// The position in world space.
/// The tile coordinates.
public Point WorldToTilePosition(Vector2 worldPosition)
{
return Orientation switch
{
TilemapOrientation.Orthogonal => WorldToTileOrthogonal(worldPosition),
TilemapOrientation.Isometric => WorldToTileIsometric(worldPosition),
TilemapOrientation.Staggered => WorldToTileStaggered(worldPosition),
TilemapOrientation.Hexagonal => WorldToTileHexagonal(worldPosition),
_ => WorldToTileOrthogonal(worldPosition),
};
}
#region Helper Methods
private Rectangle CalculateIsometricBounds()
{
// Isometric maps form a diamond shape
// The width and height in world space are:
// width = (mapWidth * mapHeight) * (tileWidth / 2)
// height = (mapWidth * mapHeight) * (tileHeight / 2)
int worldWidth = (Width + Height) * (TileWidth / 2);
int worldHeight = (Width + Height) * (TileHeight / 2);
return new Rectangle(0, 0, worldWidth, worldHeight);
}
private Point TileToWorldOrthogonal(int x, int y)
{
return new Point(x * TileWidth, y * TileHeight);
}
private Point TileToWorldIsometric(int x, int y)
{
// Isometric transformation:
// worldX = (x - y) * (tileWidth / 2)
// worldY = (x + y) * (tileHeight / 2)
int worldX = (x - y) * (TileWidth / 2);
int worldY = (x + y) * (TileHeight / 2);
return new Point(worldX, worldY);
}
private Point TileToWorldStaggered(int x, int y)
{
// TODO: Implement staggered transformation in future
// For now, use orthogonal as approximation
return TileToWorldOrthogonal(x, y);
}
private Point TileToWorldHexagonal(int x, int y)
{
// TODO: Implement hexagonal transformation in future
// For now, use orthogonal as approximation
return TileToWorldOrthogonal(x, y);
}
private Point WorldToTileOrthogonal(Vector2 worldPosition)
{
int tileX = (int)(worldPosition.X / TileWidth);
int tileY = (int)(worldPosition.Y / TileHeight);
return new Point(tileX, tileY);
}
private Point WorldToTileIsometric(Vector2 worldPosition)
{
// Inverse isometric transformation:
// x = (worldX / (tileWidth/2) + worldY / (tileHeight/2)) / 2
// y = (worldY / (tileHeight/2) - worldX / (tileWidth/2)) / 2
float halfTileWidth = TileWidth * 0.5f;
float halfTileHeight = TileHeight * 0.5f;
float normalizedX = worldPosition.X / halfTileWidth;
float normalizedY = worldPosition.Y / halfTileHeight;
int tileX = (int)((normalizedX + normalizedY) * 0.5f);
int tileY = (int)((normalizedY - normalizedX) * 0.5f);
return new Point(tileX, tileY);
}
private Point WorldToTileStaggered(Vector2 worldPosition)
{
// TODO: Implement staggered transformation in future
// For now, use orthogonal as approximation
return WorldToTileOrthogonal(worldPosition);
}
private Point WorldToTileHexagonal(Vector2 worldPosition)
{
// TODO: Implement hexagonal transformation in future
// For now, use orthogonal as approximation
return WorldToTileOrthogonal(worldPosition);
}
#endregion
}
}