Files
OpenRA/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs
2021-06-29 18:33:21 -05:00

212 lines
6.6 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2021 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
{
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
public class TerrainRendererInfo : TraitInfo, ITiledTerrainRendererInfo
{
bool ITiledTerrainRendererInfo.ValidateTileSprites(ITemplatedTerrainInfo terrainInfo, Action<string> onError)
{
var missingImages = new HashSet<string>();
var failed = false;
Action<uint, string> onMissingImage = (id, f) =>
{
onError($"\tTemplate `{id}` references sprite `{f}` that does not exist.");
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 `{t.Key}` references frame {i} that does not exist in sprite `{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 = terrainInfo.Palette;
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<IRenderOverlay>())
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 => 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<IRenderable> ITiledTerrainRenderer.RenderUIPreview(WorldRenderer wr, TerrainTemplateInfo t, int2 origin, float scale)
{
if (!(t is DefaultTerrainTemplateInfo template))
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 ?? terrainInfo.Palette;
yield return new UISpriteRenderable(sprite, WPos.Zero, origin + offset, 0, wr.Palette(palette), scale);
}
}
}
IEnumerable<IRenderable> 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 ?? terrainInfo.Palette);
yield return new SpriteRenderable(sprite, origin, offset, 0, palette, 1f, 1f, float3.Ones, TintModifiers.None, false);
}
}
}
}
}