Merge pull request #5851 from RoosterDragon/shroud-perf

Speed up shroud checks
This commit is contained in:
Paul Chote
2014-07-23 11:02:27 +12:00
9 changed files with 325 additions and 207 deletions

View File

@@ -36,17 +36,16 @@ namespace OpenRA.Graphics
unsafe unsafe
{ {
var c = (int*)bitmapData.Scan0; var colors = (int*)bitmapData.Scan0;
var stride = bitmapData.Stride / 4;
for (var x = 0; x < b.Width; x++) for (var y = 0; y < b.Height; y++)
{ {
for (var y = 0; y < b.Height; y++) for (var x = 0; x < b.Width; x++)
{ {
var mapX = x + b.Left; var mapX = x + b.Left;
var mapY = y + b.Top; var mapY = y + b.Top;
var type = tileset.GetTerrainInfo(mapTiles[mapX, mapY]); var type = tileset.GetTerrainInfo(mapTiles[mapX, mapY]);
colors[y * stride + x] = type.Color.ToArgb();
*(c + (y * bitmapData.Stride >> 2) + x) = type.Color.ToArgb();
} }
} }
} }
@@ -67,11 +66,11 @@ namespace OpenRA.Graphics
unsafe unsafe
{ {
var c = (int*)bitmapData.Scan0; var colors = (int*)bitmapData.Scan0;
var stride = bitmapData.Stride / 4;
for (var x = 0; x < b.Width; x++) for (var y = 0; y < b.Height; y++)
{ {
for (var y = 0; y < b.Height; y++) for (var x = 0; x < b.Width; x++)
{ {
var mapX = x + b.Left; var mapX = x + b.Left;
var mapY = y + b.Top; var mapY = y + b.Top;
@@ -85,7 +84,7 @@ namespace OpenRA.Graphics
if (res == null) if (res == null)
continue; continue;
*(c + (y * bitmapData.Stride >> 2) + x) = tileset[tileset.GetTerrainIndex(res)].Color.ToArgb(); colors[y * stride + x] = tileset[tileset.GetTerrainIndex(res)].Color.ToArgb();
} }
} }
} }
@@ -107,19 +106,18 @@ namespace OpenRA.Graphics
unsafe unsafe
{ {
var c = (int*)bitmapData.Scan0; var colors = (int*)bitmapData.Scan0;
var stride = bitmapData.Stride / 4;
for (var x = 0; x < b.Width; x++) for (var y = 0; y < b.Height; y++)
{ {
for (var y = 0; y < b.Height; y++) for (var x = 0; x < b.Width; x++)
{ {
var mapX = x + b.Left; var mapX = x + b.Left;
var mapY = y + b.Top; var mapY = y + b.Top;
var custom = map.CustomTerrain[mapX, mapY]; var custom = map.CustomTerrain[mapX, mapY];
if (custom == -1) if (custom == -1)
continue; continue;
colors[y * stride + x] = world.TileSet[custom].Color.ToArgb();
*(c + (y * bitmapData.Stride >> 2) + x) = world.TileSet[custom].Color.ToArgb();
} }
} }
} }
@@ -140,8 +138,8 @@ namespace OpenRA.Graphics
unsafe unsafe
{ {
var c = (int*)bitmapData.Scan0; var colors = (int*)bitmapData.Scan0;
var stride = bitmapData.Stride / 4;
foreach (var t in world.ActorsWithTrait<IRadarSignature>()) foreach (var t in world.ActorsWithTrait<IRadarSignature>())
{ {
if (world.FogObscures(t.Actor)) if (world.FogObscures(t.Actor))
@@ -152,7 +150,7 @@ namespace OpenRA.Graphics
{ {
var uv = Map.CellToMap(map.TileShape, cell); var uv = Map.CellToMap(map.TileShape, cell);
if (b.Contains(uv.X, uv.Y)) if (b.Contains(uv.X, uv.Y))
*(c + ((uv.Y - b.Top) * bitmapData.Stride >> 2) + uv.X - b.Left) = color.ToArgb(); colors[(uv.Y - b.Top) * stride + uv.X - b.Left] = color.ToArgb();
} }
} }
} }
@@ -180,15 +178,17 @@ namespace OpenRA.Graphics
unsafe unsafe
{ {
var c = (int*)bitmapData.Scan0; var colors = (int*)bitmapData.Scan0;
var stride = bitmapData.Stride / 4;
var shroudObscured = world.ShroudObscuresTest(map.Cells);
var fogObscured = world.FogObscuresTest(map.Cells);
foreach (var cell in map.Cells) foreach (var cell in map.Cells)
{ {
var uv = Map.CellToMap(map.TileShape, cell) - offset; var uv = Map.CellToMap(map.TileShape, cell) - offset;
if (world.ShroudObscures(cell)) if (shroudObscured(cell))
*(c + (uv.Y * bitmapData.Stride >> 2) + uv.X) = shroud; colors[uv.Y * stride + uv.X] = shroud;
else if (world.FogObscures(cell)) else if (fogObscured(cell))
*(c + (uv.Y * bitmapData.Stride >> 2) + uv.X) = fog; colors[uv.Y * stride + uv.X] = fog;
} }
} }

View File

@@ -12,7 +12,6 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using OpenRA.Graphics;
namespace OpenRA namespace OpenRA
{ {
@@ -21,7 +20,7 @@ namespace OpenRA
{ {
public readonly Size Size; public readonly Size Size;
public readonly TileShape Shape; public readonly TileShape Shape;
T[] entries; readonly T[] entries;
public CellLayer(Map map) public CellLayer(Map map)
: this(map.TileShape, new Size(map.MapSize.X, map.MapSize.Y)) { } : this(map.TileShape, new Size(map.MapSize.X, map.MapSize.Y)) { }
@@ -37,35 +36,27 @@ namespace OpenRA
int Index(CPos cell) int Index(CPos cell)
{ {
var uv = Map.CellToMap(Shape, cell); var uv = Map.CellToMap(Shape, cell);
return uv.Y * Size.Width + uv.X; return Index(uv.X, uv.Y);
}
// Resolve an array index from map coordinates
int Index(int u, int v)
{
return v * Size.Width + u;
} }
/// <summary>Gets or sets the <see cref="OpenRA.CellLayer"/> using cell coordinates</summary> /// <summary>Gets or sets the <see cref="OpenRA.CellLayer"/> using cell coordinates</summary>
public T this[CPos cell] public T this[CPos cell]
{ {
get get { return entries[Index(cell)]; }
{ set { entries[Index(cell)] = value; }
return entries[Index(cell)];
}
set
{
entries[Index(cell)] = value;
}
} }
/// <summary>Gets or sets the layer contents using raw map coordinates (not CPos!)</summary> /// <summary>Gets or sets the layer contents using raw map coordinates (not CPos!)</summary>
public T this[int u, int v] public T this[int u, int v]
{ {
get get { return entries[Index(u, v)]; }
{ set { entries[Index(u, v)] = value; }
return entries[v * Size.Width + u];
}
set
{
entries[v * Size.Width + u] = value;
}
} }
/// <summary>Clears the layer contents with a known value</summary> /// <summary>Clears the layer contents with a known value</summary>

View File

@@ -11,8 +11,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Linq;
using OpenRA.Graphics;
namespace OpenRA namespace OpenRA
{ {
@@ -50,29 +49,69 @@ namespace OpenRA
return new CellRegion(region.shape, tl, br); return new CellRegion(region.shape, tl, br);
} }
/// <summary>Returns the minimal region that covers at least the specified cells.</summary>
public static CellRegion BoundingRegion(TileShape shape, IEnumerable<CPos> cells)
{
if (cells == null || !cells.Any())
throw new ArgumentException("cells must not be null or empty.", "cells");
var minX = int.MaxValue;
var minY = int.MaxValue;
var maxX = int.MinValue;
var maxY = int.MinValue;
foreach (var cell in cells)
{
if (minX > cell.X)
minX = cell.X;
if (maxX < cell.X)
maxX = cell.X;
if (minY > cell.Y)
minY = cell.Y;
if (maxY < cell.Y)
maxY = cell.Y;
}
return new CellRegion(shape, new CPos(minX, minY), new CPos(maxX, maxY));
}
public bool Contains(CellRegion region)
{
return
TopLeft.X <= region.TopLeft.X && TopLeft.Y <= region.TopLeft.Y &&
BottomRight.X >= region.BottomRight.X && BottomRight.Y >= region.BottomRight.Y;
}
public bool Contains(CPos cell) public bool Contains(CPos cell)
{ {
var uv = Map.CellToMap(shape, cell); var uv = Map.CellToMap(shape, cell);
return uv.X >= mapTopLeft.X && uv.X <= mapBottomRight.X && uv.Y >= mapTopLeft.Y && uv.Y <= mapBottomRight.Y; return uv.X >= mapTopLeft.X && uv.X <= mapBottomRight.X && uv.Y >= mapTopLeft.Y && uv.Y <= mapBottomRight.Y;
} }
public IEnumerator<CPos> GetEnumerator() public CellRegionEnumerator GetEnumerator()
{ {
return new CellRegionEnumerator(this); return new CellRegionEnumerator(this);
} }
IEnumerator<CPos> IEnumerable<CPos>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
{ {
return GetEnumerator(); return GetEnumerator();
} }
class CellRegionEnumerator : IEnumerator<CPos> public class CellRegionEnumerator : IEnumerator<CPos>
{ {
readonly CellRegion r; readonly CellRegion r;
// Current position, in map coordinates // Current position, in map coordinates
int u, v; int u, v;
// Current position, in cell coordinates
CPos current;
public CellRegionEnumerator(CellRegion region) public CellRegionEnumerator(CellRegion region)
{ {
r = region; r = region;
@@ -94,6 +133,7 @@ namespace OpenRA
return false; return false;
} }
current = Map.MapToCell(r.shape, new CPos(u, v));
return true; return true;
} }
@@ -104,7 +144,7 @@ namespace OpenRA
v = r.mapTopLeft.Y; v = r.mapTopLeft.Y;
} }
public CPos Current { get { return Map.MapToCell(r.shape, new CPos(u, v)); } } public CPos Current { get { return current; } }
object IEnumerator.Current { get { return Current; } } object IEnumerator.Current { get { return Current; } }
public void Dispose() { } public void Dispose() { }
} }

View File

@@ -24,11 +24,12 @@ namespace OpenRA.Traits
public class FrozenActor public class FrozenActor
{ {
public readonly CPos[] Footprint; public readonly CPos[] Footprint;
public readonly CellRegion FootprintRegion;
public readonly WPos CenterPosition; public readonly WPos CenterPosition;
public readonly Rectangle Bounds; public readonly Rectangle Bounds;
readonly Actor actor; readonly Actor actor;
public IRenderable[] Renderables { set; private get; } public IRenderable[] Renderables { private get; set; }
public Player Owner; public Player Owner;
public string TooltipName; public string TooltipName;
@@ -39,10 +40,12 @@ namespace OpenRA.Traits
public bool Visible; public bool Visible;
public FrozenActor(Actor self, IEnumerable<CPos> footprint) public FrozenActor(Actor self, CPos[] footprint, CellRegion footprintRegion)
{ {
actor = self; actor = self;
Footprint = footprint.ToArray(); Footprint = footprint;
FootprintRegion = footprintRegion;
CenterPosition = self.CenterPosition; CenterPosition = self.CenterPosition;
Bounds = self.Bounds.Value; Bounds = self.Bounds.Value;
} }
@@ -55,16 +58,7 @@ namespace OpenRA.Traits
int flashTicks; int flashTicks;
public void Tick(World world, Shroud shroud) public void Tick(World world, Shroud shroud)
{ {
Visible = true; Visible = !Footprint.Any(shroud.IsVisibleTest(FootprintRegion));
foreach (var pos in Footprint)
{
if (shroud.IsVisible(pos))
{
Visible = false;
break;
}
}
if (flashTicks > 0) if (flashTicks > 0)
flashTicks--; flashTicks--;
} }
@@ -85,6 +79,7 @@ namespace OpenRA.Traits
return Renderables.Concat(Renderables.Where(r => !r.IsDecoration) return Renderables.Concat(Renderables.Where(r => !r.IsDecoration)
.Select(r => r.WithPalette(highlight))); .Select(r => r.WithPalette(highlight)));
} }
return Renderables; return Renderables;
} }

View File

@@ -10,7 +10,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using System.Linq; using System.Linq;
namespace OpenRA.Traits namespace OpenRA.Traits
@@ -23,31 +22,37 @@ namespace OpenRA.Traits
public class Shroud public class Shroud
{ {
[Sync] public bool Disabled = false; [Sync] public bool Disabled;
Actor self; readonly Actor self;
Map map; readonly Map map;
CellLayer<int> visibleCount; readonly CellLayer<short> visibleCount;
CellLayer<int> generatedShroudCount; readonly CellLayer<short> generatedShroudCount;
CellLayer<bool> explored; readonly CellLayer<bool> explored;
readonly Lazy<IFogVisibilityModifier[]> fogVisibilities; readonly Lazy<IFogVisibilityModifier[]> fogVisibilities;
// Cache of visibility that was added, so no matter what crazy trait code does, it // Cache of visibility that was added, so no matter what crazy trait code does, it
// can't make us invalid. // can't make us invalid.
Dictionary<Actor, CPos[]> visibility = new Dictionary<Actor, CPos[]>(); readonly Dictionary<Actor, CPos[]> visibility = new Dictionary<Actor, CPos[]>();
Dictionary<Actor, CPos[]> generation = new Dictionary<Actor, CPos[]>(); readonly Dictionary<Actor, CPos[]> generation = new Dictionary<Actor, CPos[]>();
public int Hash { get; private set; } public int Hash { get; private set; }
static readonly Func<CPos, bool> TruthPredicate = cell => true;
readonly Func<CPos, bool> fastExploredTest;
readonly Func<CPos, bool> slowExploredTest;
readonly Func<CPos, bool> fastVisibleTest;
readonly Func<CPos, bool> slowVisibleTest;
public Shroud(Actor self) public Shroud(Actor self)
{ {
this.self = self; this.self = self;
map = self.World.Map; map = self.World.Map;
visibleCount = new CellLayer<int>(map); visibleCount = new CellLayer<short>(map);
generatedShroudCount = new CellLayer<int>(map); generatedShroudCount = new CellLayer<short>(map);
explored = new CellLayer<bool>(map); explored = new CellLayer<bool>(map);
self.World.ActorAdded += AddVisibility; self.World.ActorAdded += AddVisibility;
@@ -57,6 +62,11 @@ namespace OpenRA.Traits
self.World.ActorRemoved += RemoveShroudGeneration; self.World.ActorRemoved += RemoveShroudGeneration;
fogVisibilities = Exts.Lazy(() => self.TraitsImplementing<IFogVisibilityModifier>().ToArray()); fogVisibilities = Exts.Lazy(() => self.TraitsImplementing<IFogVisibilityModifier>().ToArray());
fastExploredTest = IsExploredCore;
slowExploredTest = IsExplored;
fastVisibleTest = IsVisibleCore;
slowVisibleTest = IsVisible;
} }
void Invalidate() void Invalidate()
@@ -187,14 +197,17 @@ namespace OpenRA.Traits
public void Explore(World world, CPos center, WRange range) public void Explore(World world, CPos center, WRange range)
{ {
foreach (var q in FindVisibleTiles(world, center, range)) foreach (var c in FindVisibleTiles(world, center, range))
explored[q] = true; explored[c] = true;
Invalidate(); Invalidate();
} }
public void Explore(Shroud s) public void Explore(Shroud s)
{ {
if (map.Bounds != s.map.Bounds)
throw new ArgumentException("The map bounds of these shrouds do not match.", "s");
foreach (var cell in map.Cells) foreach (var cell in map.Cells)
if (s.explored[cell]) if (s.explored[cell])
explored[cell] = true; explored[cell] = true;
@@ -222,10 +235,32 @@ namespace OpenRA.Traits
if (!map.Contains(cell)) if (!map.Contains(cell))
return false; return false;
if (Disabled || !self.World.LobbyInfo.GlobalSettings.Shroud) if (!ShroudEnabled)
return true; return true;
return explored[cell] && (generatedShroudCount[cell] == 0 || visibleCount[cell] > 0); return IsExploredCore(cell);
}
bool ShroudEnabled { get { return !Disabled && self.World.LobbyInfo.GlobalSettings.Shroud; } }
bool IsExploredCore(CPos cell)
{
var uv = Map.CellToMap(map.TileShape, cell);
return explored[uv.X, uv.Y] && (generatedShroudCount[uv.X, uv.Y] == 0 || visibleCount[uv.X, uv.Y] > 0);
}
public Func<CPos, bool> IsExploredTest(CellRegion region)
{
// If the region to test extends outside the map we must use the slow test that checks the map boundary every time.
if (!map.Cells.Contains(region))
return slowExploredTest;
// If shroud isn't enabled, then we can see everything.
if (!ShroudEnabled)
return TruthPredicate;
// If shroud is enabled, we can use the fast test that just does the core check.
return fastExploredTest;
} }
public bool IsExplored(Actor a) public bool IsExplored(Actor a)
@@ -238,12 +273,33 @@ namespace OpenRA.Traits
if (!map.Contains(cell)) if (!map.Contains(cell))
return false; return false;
if (Disabled || !self.World.LobbyInfo.GlobalSettings.Fog) if (!FogEnabled)
return true; return true;
return IsVisibleCore(cell);
}
bool FogEnabled { get { return !Disabled && self.World.LobbyInfo.GlobalSettings.Fog; } }
bool IsVisibleCore(CPos cell)
{
return visibleCount[cell] > 0; return visibleCount[cell] > 0;
} }
public Func<CPos, bool> IsVisibleTest(CellRegion region)
{
// If the region to test extends outside the map we must use the slow test that checks the map boundary every time.
if (!map.Cells.Contains(region))
return slowVisibleTest;
// If fog isn't enabled, then we can see everything.
if (!FogEnabled)
return TruthPredicate;
// If fog is enabled, we can use the fast test that just does the core check.
return fastVisibleTest;
}
// Actors are hidden under shroud, but not under fog by default // Actors are hidden under shroud, but not under fog by default
public bool IsVisible(Actor a) public bool IsVisible(Actor a)
{ {

View File

@@ -10,7 +10,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using System.Linq; using System.Linq;
using OpenRA.Effects; using OpenRA.Effects;
using OpenRA.FileFormats; using OpenRA.FileFormats;
@@ -25,6 +24,7 @@ namespace OpenRA
{ {
public class World public class World
{ {
static readonly Func<CPos, bool> FalsePredicate = cell => false;
internal readonly TraitDictionary traitDict = new TraitDictionary(); internal readonly TraitDictionary traitDict = new TraitDictionary();
readonly HashSet<Actor> actors = new HashSet<Actor>(); readonly HashSet<Actor> actors = new HashSet<Actor>();
readonly List<IEffect> effects = new List<IEffect>(); readonly List<IEffect> effects = new List<IEffect>();
@@ -54,6 +54,24 @@ namespace OpenRA
public bool FogObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(p); } public bool FogObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(p); }
public bool ShroudObscures(Actor a) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(a); } public bool ShroudObscures(Actor a) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(a); }
public bool ShroudObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(p); } public bool ShroudObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(p); }
public Func<CPos, bool> FogObscuresTest(CellRegion region)
{
var rp = RenderPlayer;
if (rp == null)
return FalsePredicate;
var predicate = rp.Shroud.IsVisibleTest(region);
return cell => !predicate(cell);
}
public Func<CPos, bool> ShroudObscuresTest(CellRegion region)
{
var rp = RenderPlayer;
if (rp == null)
return FalsePredicate;
var predicate = rp.Shroud.IsExploredTest(region);
return cell => !predicate(cell);
}
public bool IsReplay public bool IsReplay
{ {

View File

@@ -29,19 +29,24 @@ namespace OpenRA.Mods.RA
{ {
[Sync] public int VisibilityHash; [Sync] public int VisibilityHash;
bool initialized, startsRevealed; readonly bool startsRevealed;
readonly CPos[] footprint; readonly CPos[] footprint;
Lazy<IToolTip> tooltip; readonly CellRegion footprintRegion;
Lazy<Health> health;
Dictionary<Player, bool> visible; readonly Lazy<IToolTip> tooltip;
Dictionary<Player, FrozenActor> frozen; readonly Lazy<Health> health;
readonly Dictionary<Player, bool> visible;
readonly Dictionary<Player, FrozenActor> frozen;
bool initialized;
public FrozenUnderFog(ActorInitializer init, FrozenUnderFogInfo info) public FrozenUnderFog(ActorInitializer init, FrozenUnderFogInfo info)
{ {
// Spawned actors (e.g. building husks) shouldn't be revealed // Spawned actors (e.g. building husks) shouldn't be revealed
startsRevealed = info.StartsRevealed && !init.Contains<ParentActorInit>(); startsRevealed = info.StartsRevealed && !init.Contains<ParentActorInit>();
footprint = FootprintUtils.Tiles(init.self).ToArray(); footprint = FootprintUtils.Tiles(init.self).ToArray();
footprintRegion = CellRegion.BoundingRegion(init.world.Map.TileShape, footprint);
tooltip = Exts.Lazy(() => init.self.TraitsImplementing<IToolTip>().FirstOrDefault()); tooltip = Exts.Lazy(() => init.self.TraitsImplementing<IToolTip>().FirstOrDefault());
tooltip = Exts.Lazy(() => init.self.TraitsImplementing<IToolTip>().FirstOrDefault()); tooltip = Exts.Lazy(() => init.self.TraitsImplementing<IToolTip>().FirstOrDefault());
health = Exts.Lazy(() => init.self.TraitOrDefault<Health>()); health = Exts.Lazy(() => init.self.TraitOrDefault<Health>());
@@ -63,15 +68,7 @@ namespace OpenRA.Mods.RA
VisibilityHash = 0; VisibilityHash = 0;
foreach (var p in self.World.Players) foreach (var p in self.World.Players)
{ {
var isVisible = false; var isVisible = footprint.Any(p.Shroud.IsVisibleTest(footprintRegion));
foreach (var pos in footprint)
{
if (p.Shroud.IsVisible(pos))
{
isVisible = true;
break;
}
}
visible[p] = isVisible; visible[p] = isVisible;
if (isVisible) if (isVisible)
VisibilityHash += p.ClientIndex; VisibilityHash += p.ClientIndex;
@@ -82,7 +79,7 @@ namespace OpenRA.Mods.RA
foreach (var p in self.World.Players) foreach (var p in self.World.Players)
{ {
visible[p] |= startsRevealed; visible[p] |= startsRevealed;
p.PlayerActor.Trait<FrozenActorLayer>().Add(frozen[p] = new FrozenActor(self, footprint)); p.PlayerActor.Trait<FrozenActorLayer>().Add(frozen[p] = new FrozenActor(self, footprint, footprintRegion));
} }
initialized = true; initialized = true;

View File

@@ -9,9 +9,6 @@
#endregion #endregion
using System; using System;
using System.Drawing;
using System.Linq;
using OpenRA;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Traits; using OpenRA.Traits;
@@ -47,52 +44,81 @@ namespace OpenRA.Mods.RA
public class ShroudRenderer : IRenderShroud, IWorldLoaded public class ShroudRenderer : IRenderShroud, IWorldLoaded
{ {
class ShroudTile [Flags]
enum Edges : byte
{
None = 0,
TopLeft = 0x01,
TopRight = 0x02,
BottomRight = 0x04,
BottomLeft = 0x08,
AllCorners = TopLeft | TopRight | BottomRight | BottomLeft,
TopSide = 0x10,
RightSide = 0x20,
BottomSide = 0x40,
LeftSide = 0x80,
AllSides = TopSide | RightSide | BottomSide | LeftSide,
Top = TopSide | TopLeft | TopRight,
Right = RightSide | TopRight | BottomRight,
Bottom = BottomSide | BottomRight | BottomLeft,
Left = LeftSide | TopLeft | BottomLeft,
All = Top | Right | Bottom | Left
}
struct ShroudTile
{ {
public readonly CPos Position;
public readonly float2 ScreenPosition; public readonly float2 ScreenPosition;
public readonly int Variant; public readonly byte Variant;
public Sprite Fog; public Sprite Fog;
public Sprite Shroud; public Sprite Shroud;
public ShroudTile(CPos position, float2 screenPosition, int variant) public ShroudTile(float2 screenPosition, byte variant)
{ {
Position = position;
ScreenPosition = screenPosition; ScreenPosition = screenPosition;
Variant = variant; Variant = variant;
Fog = null;
Shroud = null;
} }
} }
readonly ShroudRendererInfo info; readonly ShroudRendererInfo info;
readonly Sprite[] shroudSprites, fogSprites; readonly Sprite[] shroudSprites, fogSprites;
readonly int[] spriteMap; readonly byte[] spriteMap;
readonly CellLayer<ShroudTile> tiles; readonly CellLayer<ShroudTile> tiles;
readonly int variantStride; readonly byte variantStride;
readonly Map map; readonly Map map;
readonly Edges notVisibleEdges;
bool clearedForNullShroud;
int lastShroudHash;
CellRegion updatedRegion;
PaletteReference fogPalette, shroudPalette; PaletteReference fogPalette, shroudPalette;
int shroudHash;
public ShroudRenderer(World world, ShroudRendererInfo info) public ShroudRenderer(World world, ShroudRendererInfo info)
{ {
if (info.ShroudVariants.Length != info.FogVariants.Length)
throw new ArgumentException("ShroudRenderer must define the same number of shroud and fog variants!", "info");
if ((info.OverrideFullFog == null) ^ (info.OverrideFullShroud == null))
throw new ArgumentException("ShroudRenderer cannot define overrides for only one of shroud or fog!", "info");
if (info.ShroudVariants.Length > byte.MaxValue)
throw new ArgumentException("ShroudRenderer cannot define this many shroud and fog variants.", "info");
if (info.Index.Length >= byte.MaxValue)
throw new ArgumentException("ShroudRenderer cannot define this many indexes for shroud directions.", "info");
this.info = info; this.info = info;
map = world.Map; map = world.Map;
tiles = new CellLayer<ShroudTile>(map); tiles = new CellLayer<ShroudTile>(map);
// Force update on first render
shroudHash = -1;
// Load sprite variants // Load sprite variants
if (info.ShroudVariants.Length != info.FogVariants.Length)
throw new InvalidOperationException("ShroudRenderer must define the same number of shroud and fog variants!");
if ((info.OverrideFullFog == null) ^ (info.OverrideFullShroud == null))
throw new InvalidOperationException("ShroudRenderer cannot define overrides for only one of shroud or fog!");
var variantCount = info.ShroudVariants.Length; var variantCount = info.ShroudVariants.Length;
variantStride = info.Index.Length + (info.OverrideFullShroud != null ? 1 : 0); variantStride = (byte)(info.Index.Length + (info.OverrideFullShroud != null ? 1 : 0));
shroudSprites = new Sprite[variantCount * variantStride]; shroudSprites = new Sprite[variantCount * variantStride];
fogSprites = new Sprite[variantCount * variantStride]; fogSprites = new Sprite[variantCount * variantStride];
@@ -115,31 +141,33 @@ namespace OpenRA.Mods.RA
} }
// Mapping of shrouded directions -> sprite index // Mapping of shrouded directions -> sprite index
spriteMap = new int[info.UseExtendedIndex ? 256 : 16]; spriteMap = new byte[(byte)(info.UseExtendedIndex ? Edges.All : Edges.AllCorners) + 1];
for (var i = 0; i < info.Index.Length; i++) for (var i = 0; i < info.Index.Length; i++)
spriteMap[info.Index[i]] = i; spriteMap[info.Index[i]] = (byte)i;
if (info.OverrideFullShroud != null) if (info.OverrideFullShroud != null)
spriteMap[info.OverrideShroudIndex] = variantStride - 1; spriteMap[info.OverrideShroudIndex] = (byte)(variantStride - 1);
notVisibleEdges = info.UseExtendedIndex ? Edges.AllSides : Edges.AllCorners;
} }
static int FoggedEdges(Shroud s, CPos p, bool useExtendedIndex) Edges GetEdges(CPos p, Func<CPos, bool> isVisible)
{ {
if (!s.IsVisible(p)) if (!isVisible(p))
return useExtendedIndex ? 240 : 15; return notVisibleEdges;
// If a side is shrouded then we also count the corners // If a side is shrouded then we also count the corners
var u = 0; var u = Edges.None;
if (!s.IsVisible(p + new CVec(0, -1))) u |= 0x13; if (!isVisible(p + new CVec(0, -1))) u |= Edges.Top;
if (!s.IsVisible(p + new CVec(1, 0))) u |= 0x26; if (!isVisible(p + new CVec(1, 0))) u |= Edges.Right;
if (!s.IsVisible(p + new CVec(0, 1))) u |= 0x4C; if (!isVisible(p + new CVec(0, 1))) u |= Edges.Bottom;
if (!s.IsVisible(p + new CVec(-1, 0))) u |= 0x89; if (!isVisible(p + new CVec(-1, 0))) u |= Edges.Left;
var uside = u & 0x0F; var ucorner = u & Edges.AllCorners;
if (!s.IsVisible(p + new CVec(-1, -1))) u |= 0x01; if (!isVisible(p + new CVec(-1, -1))) u |= Edges.TopLeft;
if (!s.IsVisible(p + new CVec(1, -1))) u |= 0x02; if (!isVisible(p + new CVec(1, -1))) u |= Edges.TopRight;
if (!s.IsVisible(p + new CVec(1, 1))) u |= 0x04; if (!isVisible(p + new CVec(1, 1))) u |= Edges.BottomRight;
if (!s.IsVisible(p + new CVec(-1, 1))) u |= 0x08; if (!isVisible(p + new CVec(-1, 1))) u |= Edges.BottomLeft;
// RA provides a set of frames for tiles with shrouded // RA provides a set of frames for tiles with shrouded
// corners but unshrouded edges. We want to detect this // corners but unshrouded edges. We want to detect this
@@ -147,51 +175,24 @@ namespace OpenRA.Mods.RA
// in other combinations. The XOR turns off the corner // in other combinations. The XOR turns off the corner
// bits that are enabled twice, which gives the behavior // bits that are enabled twice, which gives the behavior
// we want here. // we want here.
return useExtendedIndex ? u ^ uside : u & 0x0F; return info.UseExtendedIndex ? u ^ ucorner : u & Edges.AllCorners;
} }
static int ShroudedEdges(Shroud s, CPos p, bool useExtendedIndex) Edges GetObserverEdges(CPos p)
{ {
if (!s.IsExplored(p)) var u = Edges.None;
return useExtendedIndex ? 240 : 15; if (!map.Contains(p + new CVec(0, -1))) u |= Edges.Top;
if (!map.Contains(p + new CVec(1, 0))) u |= Edges.Right;
if (!map.Contains(p + new CVec(0, 1))) u |= Edges.Bottom;
if (!map.Contains(p + new CVec(-1, 0))) u |= Edges.Left;
// If a side is shrouded then we also count the corners var ucorner = u & Edges.AllCorners;
var u = 0; if (!map.Contains(p + new CVec(-1, -1))) u |= Edges.TopLeft;
if (!s.IsExplored(p + new CVec(0, -1))) u |= 0x13; if (!map.Contains(p + new CVec(1, -1))) u |= Edges.TopRight;
if (!s.IsExplored(p + new CVec(1, 0))) u |= 0x26; if (!map.Contains(p + new CVec(1, 1))) u |= Edges.BottomRight;
if (!s.IsExplored(p + new CVec(0, 1))) u |= 0x4C; if (!map.Contains(p + new CVec(-1, 1))) u |= Edges.BottomLeft;
if (!s.IsExplored(p + new CVec(-1, 0))) u |= 0x89;
var uside = u & 0x0F; return info.UseExtendedIndex ? u ^ ucorner : u & Edges.AllCorners;
if (!s.IsExplored(p + new CVec(-1, -1))) u |= 0x01;
if (!s.IsExplored(p + new CVec(1, -1))) u |= 0x02;
if (!s.IsExplored(p + new CVec(1, 1))) u |= 0x04;
if (!s.IsExplored(p + new CVec(-1, 1))) u |= 0x08;
// RA provides a set of frames for tiles with shrouded
// corners but unshrouded edges. We want to detect this
// situation without breaking the edge -> corner enabling
// in other combinations. The XOR turns off the corner
// bits that are enabled twice, which gives the behavior
// we want here.
return useExtendedIndex ? u ^ uside : u & 0x0F;
}
static int ObserverShroudedEdges(Map map, CPos p, bool useExtendedIndex)
{
var u = 0;
if (!map.Contains(p + new CVec(0, -1))) u |= 0x13;
if (!map.Contains(p + new CVec(1, 0))) u |= 0x26;
if (!map.Contains(p + new CVec(0, 1))) u |= 0x4C;
if (!map.Contains(p + new CVec(-1, 0))) u |= 0x89;
var uside = u & 0x0F;
if (!map.Contains(p + new CVec(-1, -1))) u |= 0x01;
if (!map.Contains(p + new CVec(1, -1))) u |= 0x02;
if (!map.Contains(p + new CVec(1, 1))) u |= 0x04;
if (!map.Contains(p + new CVec(-1, 1))) u |= 0x08;
return useExtendedIndex ? u ^ uside : u & 0x0F;
} }
public void WorldLoaded(World w, WorldRenderer wr) public void WorldLoaded(World w, WorldRenderer wr)
@@ -201,14 +202,15 @@ namespace OpenRA.Mods.RA
foreach (var cell in CellRegion.Expand(w.Map.Cells, 1)) foreach (var cell in CellRegion.Expand(w.Map.Cells, 1))
{ {
var screen = wr.ScreenPosition(w.Map.CenterOfCell(cell)); var screen = wr.ScreenPosition(w.Map.CenterOfCell(cell));
var variant = Game.CosmeticRandom.Next(info.ShroudVariants.Length); var variant = (byte)Game.CosmeticRandom.Next(info.ShroudVariants.Length);
tiles[cell] = new ShroudTile(cell, screen, variant); tiles[cell] = new ShroudTile(screen, variant);
// Set the cells outside the border so they don't need to be touched again // Set the cells outside the border so they don't need to be touched again
if (!map.Contains(cell)) if (!map.Contains(cell))
{ {
var index = info.UseExtendedIndex ? 240 : 15; var shroudTile = tiles[cell];
tiles[cell].Shroud = shroudSprites[variant * variantStride + spriteMap[index]]; shroudTile.Shroud = GetTile(shroudSprites, notVisibleEdges, variant);
tiles[cell] = shroudTile;
} }
} }
@@ -216,50 +218,68 @@ namespace OpenRA.Mods.RA
shroudPalette = wr.Palette(info.ShroudPalette); shroudPalette = wr.Palette(info.ShroudPalette);
} }
Sprite GetTile(int flags, int variant) Sprite GetTile(Sprite[] sprites, Edges edges, int variant)
{ {
if (flags == 0) if (edges == Edges.None)
return null; return null;
return shroudSprites[variant * variantStride + spriteMap[flags]]; return sprites[variant * variantStride + spriteMap[(byte)edges]];
} }
void Update(Shroud shroud) void Update(Shroud shroud, CellRegion region)
{ {
var hash = shroud != null ? shroud.Hash : 0; if (shroud != null)
if (shroudHash == hash)
return;
shroudHash = hash;
if (shroud == null)
{ {
// Players with no shroud see the whole map so we only need to set the edges // If the current shroud hasn't changed and we have already updated the specified area, we don't need to do anything.
foreach (var cell in map.Cells) if (lastShroudHash == shroud.Hash && !clearedForNullShroud && updatedRegion.Contains(region))
{ return;
var t = tiles[cell];
var shrouded = ObserverShroudedEdges(map, t.Position, info.UseExtendedIndex);
t.Shroud = shrouded != 0 ? shroudSprites[t.Variant * variantStride + spriteMap[shrouded]] : null; lastShroudHash = shroud.Hash;
t.Fog = shrouded != 0 ? fogSprites[t.Variant * variantStride + spriteMap[shrouded]] : null; clearedForNullShroud = false;
} updatedRegion = region;
UpdateShroud(shroud);
} }
else else if (!clearedForNullShroud)
{ {
foreach (var cell in map.Cells) // We need to clear any applied shroud.
{ clearedForNullShroud = true;
var t = tiles[cell]; updatedRegion = new CellRegion(map.TileShape, new CPos(0, 0), new CPos(-1, -1));
var shrouded = ShroudedEdges(shroud, t.Position, info.UseExtendedIndex); UpdateNullShroud();
var fogged = FoggedEdges(shroud, t.Position, info.UseExtendedIndex); }
}
t.Shroud = shrouded != 0 ? shroudSprites[t.Variant * variantStride + spriteMap[shrouded]] : null; void UpdateShroud(Shroud shroud)
t.Fog = fogged != 0 ? fogSprites[t.Variant * variantStride + spriteMap[fogged]] : null; {
} var visibleUnderShroud = shroud.IsExploredTest(updatedRegion);
var visibleUnderFog = shroud.IsVisibleTest(updatedRegion);
foreach (var cell in updatedRegion)
{
var shrouded = GetEdges(cell, visibleUnderShroud);
var fogged = GetEdges(cell, visibleUnderFog);
var shroudTile = tiles[cell];
var variant = shroudTile.Variant;
shroudTile.Shroud = GetTile(shroudSprites, shrouded, variant);
shroudTile.Fog = GetTile(fogSprites, fogged, variant);
tiles[cell] = shroudTile;
}
}
void UpdateNullShroud()
{
foreach (var cell in map.Cells)
{
var edges = GetObserverEdges(cell);
var shroudTile = tiles[cell];
var variant = shroudTile.Variant;
shroudTile.Shroud = GetTile(shroudSprites, edges, variant);
shroudTile.Fog = GetTile(fogSprites, edges, variant);
tiles[cell] = shroudTile;
} }
} }
public void RenderShroud(WorldRenderer wr, Shroud shroud) public void RenderShroud(WorldRenderer wr, Shroud shroud)
{ {
Update(shroud); Update(shroud, wr.Viewport.VisibleCells);
foreach (var cell in CellRegion.Expand(wr.Viewport.VisibleCells, 1)) foreach (var cell in CellRegion.Expand(wr.Viewport.VisibleCells, 1))
{ {

View File

@@ -34,9 +34,10 @@ namespace OpenRA.Mods.RA
public void Render(WorldRenderer wr) public void Render(WorldRenderer wr)
{ {
var shroudObscured = world.ShroudObscuresTest(wr.Viewport.VisibleCells);
foreach (var cell in wr.Viewport.VisibleCells) foreach (var cell in wr.Viewport.VisibleCells)
{ {
if (world.ShroudObscures(cell)) if (shroudObscured(cell))
continue; continue;
var c = render[cell]; var c = render[cell];