using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Theriapolis.Core; using Theriapolis.Core.World; using Theriapolis.Core.World.Generation; namespace Theriapolis.Game.Rendering; /// /// Renders the world map: biome tiles, rivers, roads, rail, and settlement icons. /// Draw order: terrain → roads → rivers → rail → settlements. /// public sealed class WorldMapRenderer : IMapView, IDisposable { private readonly WorldGenContext _ctx; private readonly TileAtlas _atlas; private readonly LineFeatureRenderer _lineRenderer; private bool _disposed; // Zoom level below which settlement labels are hidden to avoid clutter private const float LabelMinZoom = 0.5f; // Min tier to show at low zoom (hide tier 4 villages when zoomed out) private const float SettleHideZoom = 0.08f; public WorldMapRenderer(WorldGenContext ctx, TileAtlas atlas) { _ctx = ctx; _atlas = atlas; _lineRenderer = new LineFeatureRenderer(atlas.GraphicsDevice, ctx); } public void Draw(SpriteBatch sb, Camera2D camera, GameTime gameTime) { DrawTerrain(sb, camera); _lineRenderer.Draw(sb, camera); DrawSettlements(sb, camera); } private void DrawTerrain(SpriteBatch sb, Camera2D camera) { var (x0, y0, x1, y1) = camera.VisibleTileRect(); int tilePixels = C.WORLD_TILE_PIXELS; sb.Begin( transformMatrix: camera.TransformMatrix, samplerState: SamplerState.PointClamp, sortMode: SpriteSortMode.Deferred); for (int ty = y0; ty <= y1; ty++) for (int tx = x0; tx <= x1; tx++) { ref var tile = ref _ctx.World.TileAt(tx, ty); var tex = _atlas.GetTile(tile.Biome); var dest = new Rectangle(tx * tilePixels, ty * tilePixels, tilePixels, tilePixels); sb.Draw(tex, dest, Color.White); } sb.End(); } private void DrawSettlements(SpriteBatch sb, Camera2D camera) { if (_ctx.World.Settlements.Count == 0) return; sb.Begin( transformMatrix: camera.TransformMatrix, samplerState: SamplerState.LinearClamp, sortMode: SpriteSortMode.Deferred, blendState: BlendState.AlphaBlend); bool hideSmall = camera.Zoom < SettleHideZoom; foreach (var s in _ctx.World.Settlements) { if (hideSmall && s.Tier >= 4) continue; var icon = _atlas.GetSettlementIcon(s.Tier); float wx = s.TileX * C.WORLD_TILE_PIXELS + C.WORLD_TILE_PIXELS * 0.5f; float wy = s.TileY * C.WORLD_TILE_PIXELS + C.WORLD_TILE_PIXELS * 0.5f; var origin = new Vector2(icon.Width * 0.5f, icon.Height * 0.5f); sb.Draw(icon, position: new Vector2(wx, wy), sourceRectangle: null, color: Color.White, rotation: 0f, origin: origin, scale: 1f, effects: SpriteEffects.None, layerDepth: 0f); } sb.End(); } public void Dispose() { if (_disposed) return; _disposed = true; _lineRenderer.Dispose(); } }