diff --git a/OpenRA.Game/Graphics/Minimap.cs b/OpenRA.Game/Graphics/Minimap.cs index f81c6860b4..3db0bd3117 100644 --- a/OpenRA.Game/Graphics/Minimap.cs +++ b/OpenRA.Game/Graphics/Minimap.cs @@ -36,17 +36,16 @@ namespace OpenRA.Graphics unsafe { - var c = (int*)bitmapData.Scan0; - - for (var x = 0; x < b.Width; x++) + var colors = (int*)bitmapData.Scan0; + var stride = bitmapData.Stride / 4; + 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 mapY = y + b.Top; var type = tileset.GetTerrainInfo(mapTiles[mapX, mapY]); - - *(c + (y * bitmapData.Stride >> 2) + x) = type.Color.ToArgb(); + colors[y * stride + x] = type.Color.ToArgb(); } } } @@ -67,11 +66,11 @@ namespace OpenRA.Graphics unsafe { - var c = (int*)bitmapData.Scan0; - - for (var x = 0; x < b.Width; x++) + var colors = (int*)bitmapData.Scan0; + var stride = bitmapData.Stride / 4; + 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 mapY = y + b.Top; @@ -85,7 +84,7 @@ namespace OpenRA.Graphics if (res == null) 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 { - var c = (int*)bitmapData.Scan0; - - for (var x = 0; x < b.Width; x++) + var colors = (int*)bitmapData.Scan0; + var stride = bitmapData.Stride / 4; + 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 mapY = y + b.Top; var custom = map.CustomTerrain[mapX, mapY]; if (custom == -1) continue; - - *(c + (y * bitmapData.Stride >> 2) + x) = world.TileSet[custom].Color.ToArgb(); + colors[y * stride + x] = world.TileSet[custom].Color.ToArgb(); } } } @@ -140,8 +138,8 @@ namespace OpenRA.Graphics unsafe { - var c = (int*)bitmapData.Scan0; - + var colors = (int*)bitmapData.Scan0; + var stride = bitmapData.Stride / 4; foreach (var t in world.ActorsWithTrait()) { if (world.FogObscures(t.Actor)) @@ -152,7 +150,7 @@ namespace OpenRA.Graphics { var uv = Map.CellToMap(map.TileShape, cell); 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 { - 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) { var uv = Map.CellToMap(map.TileShape, cell) - offset; - if (world.ShroudObscures(cell)) - *(c + (uv.Y * bitmapData.Stride >> 2) + uv.X) = shroud; - else if (world.FogObscures(cell)) - *(c + (uv.Y * bitmapData.Stride >> 2) + uv.X) = fog; + if (shroudObscured(cell)) + colors[uv.Y * stride + uv.X] = shroud; + else if (fogObscured(cell)) + colors[uv.Y * stride + uv.X] = fog; } } diff --git a/OpenRA.Game/Map/CellLayer.cs b/OpenRA.Game/Map/CellLayer.cs index 3c0f444840..9b18333442 100644 --- a/OpenRA.Game/Map/CellLayer.cs +++ b/OpenRA.Game/Map/CellLayer.cs @@ -12,7 +12,6 @@ using System; using System.Collections; using System.Collections.Generic; using System.Drawing; -using OpenRA.Graphics; namespace OpenRA { @@ -21,7 +20,7 @@ namespace OpenRA { public readonly Size Size; public readonly TileShape Shape; - T[] entries; + readonly T[] entries; public CellLayer(Map map) : this(map.TileShape, new Size(map.MapSize.X, map.MapSize.Y)) { } @@ -37,35 +36,27 @@ namespace OpenRA int Index(CPos 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; } /// Gets or sets the using cell coordinates public T this[CPos cell] { - get - { - return entries[Index(cell)]; - } - - set - { - entries[Index(cell)] = value; - } + get { return entries[Index(cell)]; } + set { entries[Index(cell)] = value; } } /// Gets or sets the layer contents using raw map coordinates (not CPos!) public T this[int u, int v] { - get - { - return entries[v * Size.Width + u]; - } - - set - { - entries[v * Size.Width + u] = value; - } + get { return entries[Index(u, v)]; } + set { entries[Index(u, v)] = value; } } /// Clears the layer contents with a known value diff --git a/OpenRA.Game/Map/CellRegion.cs b/OpenRA.Game/Map/CellRegion.cs index ce7316ecd1..ef7fc6debd 100644 --- a/OpenRA.Game/Map/CellRegion.cs +++ b/OpenRA.Game/Map/CellRegion.cs @@ -11,8 +11,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Drawing; -using OpenRA.Graphics; +using System.Linq; namespace OpenRA { @@ -50,29 +49,69 @@ namespace OpenRA return new CellRegion(region.shape, tl, br); } + /// Returns the minimal region that covers at least the specified cells. + public static CellRegion BoundingRegion(TileShape shape, IEnumerable 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) { var uv = Map.CellToMap(shape, cell); return uv.X >= mapTopLeft.X && uv.X <= mapBottomRight.X && uv.Y >= mapTopLeft.Y && uv.Y <= mapBottomRight.Y; } - public IEnumerator GetEnumerator() + public CellRegionEnumerator GetEnumerator() { return new CellRegionEnumerator(this); } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } - class CellRegionEnumerator : IEnumerator + public class CellRegionEnumerator : IEnumerator { readonly CellRegion r; // Current position, in map coordinates int u, v; + // Current position, in cell coordinates + CPos current; + public CellRegionEnumerator(CellRegion region) { r = region; @@ -94,6 +133,7 @@ namespace OpenRA return false; } + current = Map.MapToCell(r.shape, new CPos(u, v)); return true; } @@ -104,7 +144,7 @@ namespace OpenRA 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; } } public void Dispose() { } } diff --git a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs index dea1db7658..544bc3ea43 100755 --- a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs +++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs @@ -24,11 +24,12 @@ namespace OpenRA.Traits public class FrozenActor { public readonly CPos[] Footprint; + public readonly CellRegion FootprintRegion; public readonly WPos CenterPosition; public readonly Rectangle Bounds; readonly Actor actor; - public IRenderable[] Renderables { set; private get; } + public IRenderable[] Renderables { private get; set; } public Player Owner; public string TooltipName; @@ -39,10 +40,12 @@ namespace OpenRA.Traits public bool Visible; - public FrozenActor(Actor self, IEnumerable footprint) + public FrozenActor(Actor self, CPos[] footprint, CellRegion footprintRegion) { actor = self; - Footprint = footprint.ToArray(); + Footprint = footprint; + FootprintRegion = footprintRegion; + CenterPosition = self.CenterPosition; Bounds = self.Bounds.Value; } @@ -55,16 +58,7 @@ namespace OpenRA.Traits int flashTicks; public void Tick(World world, Shroud shroud) { - Visible = true; - foreach (var pos in Footprint) - { - if (shroud.IsVisible(pos)) - { - Visible = false; - break; - } - } - + Visible = !Footprint.Any(shroud.IsVisibleTest(FootprintRegion)); if (flashTicks > 0) flashTicks--; } @@ -85,6 +79,7 @@ namespace OpenRA.Traits return Renderables.Concat(Renderables.Where(r => !r.IsDecoration) .Select(r => r.WithPalette(highlight))); } + return Renderables; } diff --git a/OpenRA.Game/Traits/World/Shroud.cs b/OpenRA.Game/Traits/World/Shroud.cs index 0928f0e159..6e51f639f4 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -10,7 +10,6 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.Linq; namespace OpenRA.Traits @@ -23,31 +22,37 @@ namespace OpenRA.Traits public class Shroud { - [Sync] public bool Disabled = false; + [Sync] public bool Disabled; - Actor self; - Map map; + readonly Actor self; + readonly Map map; - CellLayer visibleCount; - CellLayer generatedShroudCount; - CellLayer explored; + readonly CellLayer visibleCount; + readonly CellLayer generatedShroudCount; + readonly CellLayer explored; readonly Lazy fogVisibilities; // Cache of visibility that was added, so no matter what crazy trait code does, it // can't make us invalid. - Dictionary visibility = new Dictionary(); - Dictionary generation = new Dictionary(); + readonly Dictionary visibility = new Dictionary(); + readonly Dictionary generation = new Dictionary(); public int Hash { get; private set; } + static readonly Func TruthPredicate = cell => true; + readonly Func fastExploredTest; + readonly Func slowExploredTest; + readonly Func fastVisibleTest; + readonly Func slowVisibleTest; + public Shroud(Actor self) { this.self = self; map = self.World.Map; - visibleCount = new CellLayer(map); - generatedShroudCount = new CellLayer(map); + visibleCount = new CellLayer(map); + generatedShroudCount = new CellLayer(map); explored = new CellLayer(map); self.World.ActorAdded += AddVisibility; @@ -57,6 +62,11 @@ namespace OpenRA.Traits self.World.ActorRemoved += RemoveShroudGeneration; fogVisibilities = Exts.Lazy(() => self.TraitsImplementing().ToArray()); + + fastExploredTest = IsExploredCore; + slowExploredTest = IsExplored; + fastVisibleTest = IsVisibleCore; + slowVisibleTest = IsVisible; } void Invalidate() @@ -187,14 +197,17 @@ namespace OpenRA.Traits public void Explore(World world, CPos center, WRange range) { - foreach (var q in FindVisibleTiles(world, center, range)) - explored[q] = true; + foreach (var c in FindVisibleTiles(world, center, range)) + explored[c] = true; Invalidate(); } 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) if (s.explored[cell]) explored[cell] = true; @@ -222,10 +235,32 @@ namespace OpenRA.Traits if (!map.Contains(cell)) return false; - if (Disabled || !self.World.LobbyInfo.GlobalSettings.Shroud) + if (!ShroudEnabled) 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 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) @@ -238,12 +273,33 @@ namespace OpenRA.Traits if (!map.Contains(cell)) return false; - if (Disabled || !self.World.LobbyInfo.GlobalSettings.Fog) + if (!FogEnabled) return true; + return IsVisibleCore(cell); + } + + bool FogEnabled { get { return !Disabled && self.World.LobbyInfo.GlobalSettings.Fog; } } + + bool IsVisibleCore(CPos cell) + { return visibleCount[cell] > 0; } + public Func 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 public bool IsVisible(Actor a) { diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 6d1aa45475..50b04194e5 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -10,7 +10,6 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.Linq; using OpenRA.Effects; using OpenRA.FileFormats; @@ -25,6 +24,7 @@ namespace OpenRA { public class World { + static readonly Func FalsePredicate = cell => false; internal readonly TraitDictionary traitDict = new TraitDictionary(); readonly HashSet actors = new HashSet(); readonly List effects = new List(); @@ -54,6 +54,24 @@ namespace OpenRA 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(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(p); } + + public Func FogObscuresTest(CellRegion region) + { + var rp = RenderPlayer; + if (rp == null) + return FalsePredicate; + var predicate = rp.Shroud.IsVisibleTest(region); + return cell => !predicate(cell); + } + + public Func ShroudObscuresTest(CellRegion region) + { + var rp = RenderPlayer; + if (rp == null) + return FalsePredicate; + var predicate = rp.Shroud.IsExploredTest(region); + return cell => !predicate(cell); + } public bool IsReplay { diff --git a/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs b/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs index 5ee0ab5384..2baaf8ae8e 100644 --- a/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs +++ b/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs @@ -29,19 +29,24 @@ namespace OpenRA.Mods.RA { [Sync] public int VisibilityHash; - bool initialized, startsRevealed; + readonly bool startsRevealed; readonly CPos[] footprint; - Lazy tooltip; - Lazy health; + readonly CellRegion footprintRegion; - Dictionary visible; - Dictionary frozen; + readonly Lazy tooltip; + readonly Lazy health; + + readonly Dictionary visible; + readonly Dictionary frozen; + + bool initialized; public FrozenUnderFog(ActorInitializer init, FrozenUnderFogInfo info) { // Spawned actors (e.g. building husks) shouldn't be revealed startsRevealed = info.StartsRevealed && !init.Contains(); footprint = FootprintUtils.Tiles(init.self).ToArray(); + footprintRegion = CellRegion.BoundingRegion(init.world.Map.TileShape, footprint); tooltip = Exts.Lazy(() => init.self.TraitsImplementing().FirstOrDefault()); tooltip = Exts.Lazy(() => init.self.TraitsImplementing().FirstOrDefault()); health = Exts.Lazy(() => init.self.TraitOrDefault()); @@ -63,15 +68,7 @@ namespace OpenRA.Mods.RA VisibilityHash = 0; foreach (var p in self.World.Players) { - var isVisible = false; - foreach (var pos in footprint) - { - if (p.Shroud.IsVisible(pos)) - { - isVisible = true; - break; - } - } + var isVisible = footprint.Any(p.Shroud.IsVisibleTest(footprintRegion)); visible[p] = isVisible; if (isVisible) VisibilityHash += p.ClientIndex; @@ -82,7 +79,7 @@ namespace OpenRA.Mods.RA foreach (var p in self.World.Players) { visible[p] |= startsRevealed; - p.PlayerActor.Trait().Add(frozen[p] = new FrozenActor(self, footprint)); + p.PlayerActor.Trait().Add(frozen[p] = new FrozenActor(self, footprint, footprintRegion)); } initialized = true; diff --git a/OpenRA.Mods.RA/ShroudRenderer.cs b/OpenRA.Mods.RA/ShroudRenderer.cs index 10a44b1fc5..3a726b1c1b 100644 --- a/OpenRA.Mods.RA/ShroudRenderer.cs +++ b/OpenRA.Mods.RA/ShroudRenderer.cs @@ -9,9 +9,6 @@ #endregion using System; -using System.Drawing; -using System.Linq; -using OpenRA; using OpenRA.Graphics; using OpenRA.Traits; @@ -47,52 +44,81 @@ namespace OpenRA.Mods.RA 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 int Variant; + public readonly byte Variant; public Sprite Fog; public Sprite Shroud; - public ShroudTile(CPos position, float2 screenPosition, int variant) + public ShroudTile(float2 screenPosition, byte variant) { - Position = position; ScreenPosition = screenPosition; Variant = variant; + + Fog = null; + Shroud = null; } } readonly ShroudRendererInfo info; readonly Sprite[] shroudSprites, fogSprites; - readonly int[] spriteMap; + readonly byte[] spriteMap; readonly CellLayer tiles; - readonly int variantStride; + readonly byte variantStride; readonly Map map; + readonly Edges notVisibleEdges; + + bool clearedForNullShroud; + int lastShroudHash; + CellRegion updatedRegion; PaletteReference fogPalette, shroudPalette; - int shroudHash; 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; map = world.Map; tiles = new CellLayer(map); - // Force update on first render - shroudHash = -1; - // 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; - variantStride = info.Index.Length + (info.OverrideFullShroud != null ? 1 : 0); + variantStride = (byte)(info.Index.Length + (info.OverrideFullShroud != null ? 1 : 0)); shroudSprites = new Sprite[variantCount * variantStride]; fogSprites = new Sprite[variantCount * variantStride]; @@ -115,31 +141,33 @@ namespace OpenRA.Mods.RA } // 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++) - spriteMap[info.Index[i]] = i; + spriteMap[info.Index[i]] = (byte)i; 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 isVisible) { - if (!s.IsVisible(p)) - return useExtendedIndex ? 240 : 15; + if (!isVisible(p)) + return notVisibleEdges; // If a side is shrouded then we also count the corners - var u = 0; - if (!s.IsVisible(p + new CVec(0, -1))) u |= 0x13; - if (!s.IsVisible(p + new CVec(1, 0))) u |= 0x26; - if (!s.IsVisible(p + new CVec(0, 1))) u |= 0x4C; - if (!s.IsVisible(p + new CVec(-1, 0))) u |= 0x89; + var u = Edges.None; + if (!isVisible(p + new CVec(0, -1))) u |= Edges.Top; + if (!isVisible(p + new CVec(1, 0))) u |= Edges.Right; + if (!isVisible(p + new CVec(0, 1))) u |= Edges.Bottom; + if (!isVisible(p + new CVec(-1, 0))) u |= Edges.Left; - var uside = u & 0x0F; - if (!s.IsVisible(p + new CVec(-1, -1))) u |= 0x01; - if (!s.IsVisible(p + new CVec(1, -1))) u |= 0x02; - if (!s.IsVisible(p + new CVec(1, 1))) u |= 0x04; - if (!s.IsVisible(p + new CVec(-1, 1))) u |= 0x08; + var ucorner = u & Edges.AllCorners; + if (!isVisible(p + new CVec(-1, -1))) u |= Edges.TopLeft; + if (!isVisible(p + new CVec(1, -1))) u |= Edges.TopRight; + if (!isVisible(p + new CVec(1, 1))) u |= Edges.BottomRight; + if (!isVisible(p + new CVec(-1, 1))) u |= Edges.BottomLeft; // RA provides a set of frames for tiles with shrouded // 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 // bits that are enabled twice, which gives the behavior // 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)) - return useExtendedIndex ? 240 : 15; + var u = Edges.None; + 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 u = 0; - if (!s.IsExplored(p + new CVec(0, -1))) u |= 0x13; - if (!s.IsExplored(p + new CVec(1, 0))) u |= 0x26; - if (!s.IsExplored(p + new CVec(0, 1))) u |= 0x4C; - if (!s.IsExplored(p + new CVec(-1, 0))) u |= 0x89; + var ucorner = u & Edges.AllCorners; + if (!map.Contains(p + new CVec(-1, -1))) u |= Edges.TopLeft; + if (!map.Contains(p + new CVec(1, -1))) u |= Edges.TopRight; + if (!map.Contains(p + new CVec(1, 1))) u |= Edges.BottomRight; + if (!map.Contains(p + new CVec(-1, 1))) u |= Edges.BottomLeft; - var uside = u & 0x0F; - 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; + return info.UseExtendedIndex ? u ^ ucorner : u & Edges.AllCorners; } 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)) { var screen = wr.ScreenPosition(w.Map.CenterOfCell(cell)); - var variant = Game.CosmeticRandom.Next(info.ShroudVariants.Length); - tiles[cell] = new ShroudTile(cell, screen, variant); + var variant = (byte)Game.CosmeticRandom.Next(info.ShroudVariants.Length); + tiles[cell] = new ShroudTile(screen, variant); // Set the cells outside the border so they don't need to be touched again if (!map.Contains(cell)) { - var index = info.UseExtendedIndex ? 240 : 15; - tiles[cell].Shroud = shroudSprites[variant * variantStride + spriteMap[index]]; + var shroudTile = tiles[cell]; + shroudTile.Shroud = GetTile(shroudSprites, notVisibleEdges, variant); + tiles[cell] = shroudTile; } } @@ -216,50 +218,68 @@ namespace OpenRA.Mods.RA 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 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 (shroudHash == hash) - return; - - shroudHash = hash; - if (shroud == null) + if (shroud != null) { - // Players with no shroud see the whole map so we only need to set the edges - foreach (var cell in map.Cells) - { - var t = tiles[cell]; - var shrouded = ObserverShroudedEdges(map, t.Position, info.UseExtendedIndex); + // If the current shroud hasn't changed and we have already updated the specified area, we don't need to do anything. + if (lastShroudHash == shroud.Hash && !clearedForNullShroud && updatedRegion.Contains(region)) + return; - t.Shroud = shrouded != 0 ? shroudSprites[t.Variant * variantStride + spriteMap[shrouded]] : null; - t.Fog = shrouded != 0 ? fogSprites[t.Variant * variantStride + spriteMap[shrouded]] : null; - } + lastShroudHash = shroud.Hash; + clearedForNullShroud = false; + updatedRegion = region; + UpdateShroud(shroud); } - else + else if (!clearedForNullShroud) { - foreach (var cell in map.Cells) - { - var t = tiles[cell]; - var shrouded = ShroudedEdges(shroud, t.Position, info.UseExtendedIndex); - var fogged = FoggedEdges(shroud, t.Position, info.UseExtendedIndex); + // We need to clear any applied shroud. + clearedForNullShroud = true; + updatedRegion = new CellRegion(map.TileShape, new CPos(0, 0), new CPos(-1, -1)); + UpdateNullShroud(); + } + } - t.Shroud = shrouded != 0 ? shroudSprites[t.Variant * variantStride + spriteMap[shrouded]] : null; - t.Fog = fogged != 0 ? fogSprites[t.Variant * variantStride + spriteMap[fogged]] : null; - } + void UpdateShroud(Shroud shroud) + { + 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) { - Update(shroud); + Update(shroud, wr.Viewport.VisibleCells); foreach (var cell in CellRegion.Expand(wr.Viewport.VisibleCells, 1)) { diff --git a/OpenRA.Mods.RA/World/ResourceLayer.cs b/OpenRA.Mods.RA/World/ResourceLayer.cs index 02eac34b10..b7bf86fbe5 100644 --- a/OpenRA.Mods.RA/World/ResourceLayer.cs +++ b/OpenRA.Mods.RA/World/ResourceLayer.cs @@ -34,9 +34,10 @@ namespace OpenRA.Mods.RA public void Render(WorldRenderer wr) { + var shroudObscured = world.ShroudObscuresTest(wr.Viewport.VisibleCells); foreach (var cell in wr.Viewport.VisibleCells) { - if (world.ShroudObscures(cell)) + if (shroudObscured(cell)) continue; var c = render[cell];