212 lines
6.6 KiB
C#
212 lines
6.6 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright (c) The OpenRA Developers and Contributors
|
|
* 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;
|
|
void OnMissingImage(uint id, string 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 = scale * (new float2(u * ts.Width, (v - 0.5f * tileInfo.Height) * ts.Height) - 0.5f * sprite.Size.XY);
|
|
var palette = template.Palette ?? terrainInfo.Palette;
|
|
|
|
yield return new UISpriteRenderable(sprite, WPos.Zero, origin + offset.ToInt2(), 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|