#region Copyright & License Information /* * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections.Generic; using System.IO; using OpenRA.Graphics; using OpenRA.Mods.Common.Terrain; using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { public class TerrainRendererInfo : TraitInfo, ITiledTerrainRendererInfo { bool ITiledTerrainRendererInfo.ValidateTileSprites(ITemplatedTerrainInfo terrainInfo, Action onError) { var missingImages = new HashSet(); var failed = false; Action onMissingImage = (id, f) => { onError("\tTemplate `{0}` references sprite `{1}` that does not exist.".F(id, f)); missingImages.Add(f); failed = true; }; var tileCache = new DefaultTileCache((DefaultTerrain)terrainInfo, onMissingImage); foreach (var t in terrainInfo.Templates) { var templateInfo = (DefaultTerrainTemplateInfo)t.Value; for (var v = 0; v < templateInfo.Images.Length; v++) { if (!missingImages.Contains(templateInfo.Images[v])) { for (var i = 0; i < t.Value.TilesCount; i++) { if (t.Value[i] == null || tileCache.HasTileSprite(new TerrainTile(t.Key, (byte)i), v)) continue; onError("\tTemplate `{0}` references frame {1} that does not exist in sprite `{2}`.".F(t.Key, i, templateInfo.Images[v])); failed = true; } } } } return failed; } public override object Create(ActorInitializer init) { return new TerrainRenderer(init.World); } } public sealed class TerrainRenderer : IRenderTerrain, IWorldLoaded, INotifyActorDisposing, ITiledTerrainRenderer { readonly Map map; TerrainSpriteLayer spriteLayer; readonly DefaultTerrain terrainInfo; readonly DefaultTileCache tileCache; WorldRenderer worldRenderer; bool disposed; public TerrainRenderer(World world) { map = world.Map; terrainInfo = map.Rules.TerrainInfo as DefaultTerrain; if (terrainInfo == null) throw new InvalidDataException("TerrainRenderer can only be used with the DefaultTerrain parser"); tileCache = new DefaultTileCache(terrainInfo); } void IWorldLoaded.WorldLoaded(World world, WorldRenderer wr) { worldRenderer = wr; spriteLayer = new TerrainSpriteLayer(world, wr, tileCache.MissingTile, BlendMode.Alpha, world.Type != WorldType.Editor); foreach (var cell in map.AllCells) UpdateCell(cell); map.Tiles.CellEntryChanged += UpdateCell; map.Height.CellEntryChanged += UpdateCell; } public void UpdateCell(CPos cell) { var tile = map.Tiles[cell]; var palette = TileSet.TerrainPaletteInternalName; if (terrainInfo.Templates.TryGetValue(tile.Type, out var template)) palette = ((DefaultTerrainTemplateInfo)template).Palette ?? palette; var sprite = tileCache.TileSprite(tile); var paletteReference = worldRenderer.Palette(palette); spriteLayer.Update(cell, sprite, paletteReference); } void IRenderTerrain.RenderTerrain(WorldRenderer wr, Viewport viewport) { spriteLayer.Draw(wr.Viewport); foreach (var r in wr.World.WorldActor.TraitsImplementing()) r.Render(wr); } void INotifyActorDisposing.Disposing(Actor self) { if (disposed) return; map.Tiles.CellEntryChanged -= UpdateCell; map.Height.CellEntryChanged -= UpdateCell; spriteLayer.Dispose(); tileCache.Dispose(); disposed = true; } Sprite ITiledTerrainRenderer.MissingTile { get { return tileCache.MissingTile; } } Sprite ITiledTerrainRenderer.TileSprite(TerrainTile r, int? variant) { return tileCache.TileSprite(r, variant); } Rectangle ITiledTerrainRenderer.TemplateBounds(TerrainTemplateInfo template) { Rectangle? templateRect = null; var tileSize = map.Grid.TileSize; var i = 0; for (var y = 0; y < template.Size.Y; y++) { for (var x = 0; x < template.Size.X; x++) { var tile = new TerrainTile(template.Id, (byte)(i++)); if (!terrainInfo.TryGetTileInfo(tile, out var tileInfo)) continue; var sprite = tileCache.TileSprite(tile); var u = map.Grid.Type == MapGridType.Rectangular ? x : (x - y) / 2f; var v = map.Grid.Type == MapGridType.Rectangular ? y : (x + y) / 2f; var tl = new float2(u * tileSize.Width, (v - 0.5f * tileInfo.Height) * tileSize.Height) - 0.5f * sprite.Size; var rect = new Rectangle((int)(tl.X + sprite.Offset.X), (int)(tl.Y + sprite.Offset.Y), (int)sprite.Size.X, (int)sprite.Size.Y); templateRect = templateRect.HasValue ? Rectangle.Union(templateRect.Value, rect) : rect; } } return templateRect ?? Rectangle.Empty; } IEnumerable ITiledTerrainRenderer.RenderUIPreview(WorldRenderer wr, TerrainTemplateInfo t, int2 origin, float scale) { var template = t as DefaultTerrainTemplateInfo; if (template == null) yield break; var ts = map.Grid.TileSize; var gridType = map.Grid.Type; var i = 0; for (var y = 0; y < template.Size.Y; y++) { for (var x = 0; x < template.Size.X; x++) { var tile = new TerrainTile(template.Id, (byte)i++); if (!terrainInfo.TryGetTileInfo(tile, out var tileInfo)) continue; var sprite = tileCache.TileSprite(tile, 0); var u = gridType == MapGridType.Rectangular ? x : (x - y) / 2f; var v = gridType == MapGridType.Rectangular ? y : (x + y) / 2f; var offset = (new float2(u * ts.Width, (v - 0.5f * tileInfo.Height) * ts.Height) - 0.5f * sprite.Size.XY).ToInt2(); var palette = template.Palette ?? TileSet.TerrainPaletteInternalName; yield return new UISpriteRenderable(sprite, WPos.Zero, origin + offset, 0, wr.Palette(palette), scale); } } } IEnumerable ITiledTerrainRenderer.RenderPreview(WorldRenderer wr, TerrainTemplateInfo t, WPos origin) { if (!(t is DefaultTerrainTemplateInfo template)) yield break; var i = 0; for (var y = 0; y < template.Size.Y; y++) { for (var x = 0; x < template.Size.X; x++) { var tile = new TerrainTile(template.Id, (byte)i++); if (!terrainInfo.TryGetTileInfo(tile, out var tileInfo)) continue; var sprite = tileCache.TileSprite(tile, 0); var offset = map.Offset(new CVec(x, y), tileInfo.Height); var palette = wr.Palette(template.Palette ?? TileSet.TerrainPaletteInternalName); yield return new SpriteRenderable(sprite, origin, offset, 0, palette, 1, false); } } } } }