From 2351c4323752ab3d3440316553544b676115db68 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sun, 6 Jul 2014 17:46:53 +0100 Subject: [PATCH 1/2] Refactoring pass. - Extract an enum for edges rather than using magic numbers for everything. - Remove duplicated code between FoggedEdges and ShroudedEdges by hosting the visibility function into a delegate. - Make minimap methods more readable. - Tidy formatting. - Make some fields readonly. - Remove unused usings. --- OpenRA.Game/Graphics/Minimap.cs | 46 +++--- OpenRA.Game/Map/CellLayer.cs | 33 ++-- OpenRA.Game/Map/CellRegion.cs | 3 - OpenRA.Game/Traits/Player/FrozenActorLayer.cs | 5 +- OpenRA.Game/Traits/World/Shroud.cs | 26 ++-- OpenRA.Game/World.cs | 1 - OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs | 15 +- OpenRA.Mods.RA/ShroudRenderer.cs | 145 +++++++++--------- 8 files changed, 129 insertions(+), 145 deletions(-) diff --git a/OpenRA.Game/Graphics/Minimap.cs b/OpenRA.Game/Graphics/Minimap.cs index f81c6860b4..c7aba5fa3b 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,15 @@ namespace OpenRA.Graphics unsafe { - var c = (int*)bitmapData.Scan0; - + var colors = (int*)bitmapData.Scan0; + var stride = bitmapData.Stride / 4; 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; + colors[uv.Y * stride + uv.X] = shroud; else if (world.FogObscures(cell)) - *(c + (uv.Y * bitmapData.Stride >> 2) + uv.X) = fog; + 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..66501c4fc1 100644 --- a/OpenRA.Game/Map/CellRegion.cs +++ b/OpenRA.Game/Map/CellRegion.cs @@ -8,11 +8,8 @@ */ #endregion -using System; using System.Collections; using System.Collections.Generic; -using System.Drawing; -using OpenRA.Graphics; namespace OpenRA { diff --git a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs index e04ff8483b..d59558eaf5 100755 --- a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs +++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 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. For more information, @@ -27,7 +27,7 @@ namespace OpenRA.Traits 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; @@ -84,6 +84,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 83c692c824..c273db386b 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 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. For more information, @@ -10,7 +10,6 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.Linq; namespace OpenRA.Traits @@ -22,21 +21,21 @@ 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; } @@ -186,14 +185,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; diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 0d91c947b9..3661df521f 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; diff --git a/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs b/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs index 75c1c39b75..dfb85070e3 100644 --- a/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs +++ b/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 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. For more information, @@ -28,13 +28,16 @@ namespace OpenRA.Mods.RA { [Sync] public int VisibilityHash; - bool initialized, startsRevealed; + readonly bool startsRevealed; readonly CPos[] footprint; - Lazy tooltip; - Lazy health; - Dictionary visible; - Dictionary frozen; + readonly Lazy tooltip; + readonly Lazy health; + + readonly Dictionary visible; + readonly Dictionary frozen; + + bool initialized; public FrozenUnderFog(ActorInitializer init, FrozenUnderFogInfo info) { diff --git a/OpenRA.Mods.RA/ShroudRenderer.cs b/OpenRA.Mods.RA/ShroudRenderer.cs index 10a44b1fc5..51ac394387 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,6 +44,27 @@ namespace OpenRA.Mods.RA public class ShroudRenderer : IRenderShroud, IWorldLoaded { + [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 + } + class ShroudTile { public readonly CPos Position; @@ -70,12 +88,19 @@ namespace OpenRA.Mods.RA readonly CellLayer tiles; readonly int variantStride; readonly Map map; + readonly Edges notVisibleEdges; 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"); + this.info = info; map = world.Map; @@ -85,12 +110,6 @@ namespace OpenRA.Mods.RA 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); shroudSprites = new Sprite[variantCount * variantStride]; @@ -121,25 +140,27 @@ namespace OpenRA.Mods.RA if (info.OverrideFullShroud != null) spriteMap[info.OverrideShroudIndex] = variantStride - 1; + + notVisibleEdges = info.UseExtendedIndex ? Edges.AllSides : Edges.AllCorners; } - static int FoggedEdges(Shroud s, CPos p, bool useExtendedIndex) + Edges GetEdges(int x, int y, Func isVisible) { - if (!s.IsVisible(p)) - return useExtendedIndex ? 240 : 15; + if (!isVisible(x, y)) + 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(x, y - 1)) u |= Edges.Top; + if (!isVisible(x + 1, y)) u |= Edges.Right; + if (!isVisible(x, y + 1)) u |= Edges.Bottom; + if (!isVisible(x - 1, y)) 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(x - 1, y - 1)) u |= Edges.TopLeft; + if (!isVisible(x + 1, y - 1)) u |= Edges.TopRight; + if (!isVisible(x + 1, y + 1)) u |= Edges.BottomRight; + if (!isVisible(x - 1, y + 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 +168,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(int x, int y) { - if (!s.IsExplored(p)) - return useExtendedIndex ? 240 : 15; + var u = Edges.None; + if (!map.Contains(new CPos(x, y - 1))) u |= Edges.Top; + if (!map.Contains(new CPos(x + 1, y))) u |= Edges.Right; + if (!map.Contains(new CPos(x, y + 1))) u |= Edges.Bottom; + if (!map.Contains(new CPos(x - 1, y))) 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(new CPos(x - 1, y - 1))) u |= Edges.TopLeft; + if (!map.Contains(new CPos(x + 1, y - 1))) u |= Edges.TopRight; + if (!map.Contains(new CPos(x + 1, y + 1))) u |= Edges.BottomRight; + if (!map.Contains(new CPos(x - 1, y + 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) @@ -206,22 +200,19 @@ namespace OpenRA.Mods.RA // 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]]; - } + tiles[cell].Shroud = GetTile(notVisibleEdges, variant); } fogPalette = wr.Palette(info.FogPalette); shroudPalette = wr.Palette(info.ShroudPalette); } - Sprite GetTile(int flags, int variant) + Sprite GetTile(Edges edges, int variant) { - if (flags == 0) + if (edges == Edges.None) return null; - return shroudSprites[variant * variantStride + spriteMap[flags]]; + return shroudSprites[variant * variantStride + spriteMap[(byte)edges]]; } void Update(Shroud shroud) @@ -237,22 +228,24 @@ namespace OpenRA.Mods.RA foreach (var cell in map.Cells) { var t = tiles[cell]; - var shrouded = ObserverShroudedEdges(map, t.Position, info.UseExtendedIndex); + var shrouded = GetObserverEdges(cell.X, cell.Y); - t.Shroud = shrouded != 0 ? shroudSprites[t.Variant * variantStride + spriteMap[shrouded]] : null; - t.Fog = shrouded != 0 ? fogSprites[t.Variant * variantStride + spriteMap[shrouded]] : null; + t.Shroud = shrouded != 0 ? shroudSprites[t.Variant * variantStride + spriteMap[(byte)shrouded]] : null; + t.Fog = shrouded != 0 ? fogSprites[t.Variant * variantStride + spriteMap[(byte)shrouded]] : null; } } else { foreach (var cell in map.Cells) { + Func visibleUnderShroud = (x, y) => shroud.IsExplored(new CPos(x, y)); + Func visibleUnderFog = (x, y) => shroud.IsVisible(new CPos(x, y)); var t = tiles[cell]; - var shrouded = ShroudedEdges(shroud, t.Position, info.UseExtendedIndex); - var fogged = FoggedEdges(shroud, t.Position, info.UseExtendedIndex); + var shrouded = GetEdges(cell.X, cell.Y, visibleUnderShroud); + var fogged = GetEdges(cell.X, cell.Y, visibleUnderFog); - t.Shroud = shrouded != 0 ? shroudSprites[t.Variant * variantStride + spriteMap[shrouded]] : null; - t.Fog = fogged != 0 ? fogSprites[t.Variant * variantStride + spriteMap[fogged]] : null; + t.Shroud = shrouded != 0 ? shroudSprites[t.Variant * variantStride + spriteMap[(byte)shrouded]] : null; + t.Fog = fogged != 0 ? fogSprites[t.Variant * variantStride + spriteMap[(byte)fogged]] : null; } } } From a512d9ad0a7174aa0815c77424b88df05fc7c32b Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sun, 6 Jul 2014 22:33:42 +0100 Subject: [PATCH 2/2] Sped up shroud rendering. - Only update shroud within the visible screen area, rather than the whole map. This improves performance on larger maps significantly when scrolling around since large portions of the shroud do not need to be updated. - Provide methods in Shroud to return delegates to check for explored/visibility for tiles within a certain region. This allows it to return more efficient delegates whenever the region is within the map bounds, or shroud/fog is disabled. In the typical case where the region is in bounds and shroud/fog is enabled, the fast check is almost twice as fast as the slow check. - Use the Shroud delegate functions in shroud rendering, frozen actors, minimap rendering and resource layer areas to provide a speedup since these areas of code can often take advantage of the fact they perform checks within the map boundary. - Cache current element in CellRegionEnumerator to prevent repeated work if the element is accessed more than once. - Decrease the size of elements in some arrays in hopes of reducing memory needs and improving cache hits. --- OpenRA.Game/Graphics/Minimap.cs | 6 +- OpenRA.Game/Map/CellRegion.cs | 49 +++++- OpenRA.Game/Traits/Player/FrozenActorLayer.cs | 18 +- OpenRA.Game/Traits/World/Shroud.cs | 68 +++++++- OpenRA.Game/World.cs | 19 +++ OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs | 14 +- OpenRA.Mods.RA/ShroudRenderer.cs | 159 ++++++++++-------- OpenRA.Mods.RA/World/ResourceLayer.cs | 3 +- 8 files changed, 235 insertions(+), 101 deletions(-) diff --git a/OpenRA.Game/Graphics/Minimap.cs b/OpenRA.Game/Graphics/Minimap.cs index c7aba5fa3b..3db0bd3117 100644 --- a/OpenRA.Game/Graphics/Minimap.cs +++ b/OpenRA.Game/Graphics/Minimap.cs @@ -180,12 +180,14 @@ namespace OpenRA.Graphics { 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)) + if (shroudObscured(cell)) colors[uv.Y * stride + uv.X] = shroud; - else if (world.FogObscures(cell)) + else if (fogObscured(cell)) colors[uv.Y * stride + uv.X] = fog; } } diff --git a/OpenRA.Game/Map/CellRegion.cs b/OpenRA.Game/Map/CellRegion.cs index 66501c4fc1..ef7fc6debd 100644 --- a/OpenRA.Game/Map/CellRegion.cs +++ b/OpenRA.Game/Map/CellRegion.cs @@ -8,8 +8,10 @@ */ #endregion +using System; using System.Collections; using System.Collections.Generic; +using System.Linq; namespace OpenRA { @@ -47,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; @@ -91,6 +133,7 @@ namespace OpenRA return false; } + current = Map.MapToCell(r.shape, new CPos(u, v)); return true; } @@ -101,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 d59558eaf5..05d299e0f7 100755 --- a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs +++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs @@ -23,6 +23,7 @@ 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; @@ -38,10 +39,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; } @@ -54,16 +57,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--; } diff --git a/OpenRA.Game/Traits/World/Shroud.cs b/OpenRA.Game/Traits/World/Shroud.cs index c273db386b..ccff0506c8 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -26,8 +26,8 @@ namespace OpenRA.Traits readonly Actor self; readonly Map map; - readonly CellLayer visibleCount; - readonly CellLayer generatedShroudCount; + readonly CellLayer visibleCount; + readonly CellLayer generatedShroudCount; readonly CellLayer explored; readonly Lazy fogVisibilities; @@ -39,13 +39,19 @@ namespace OpenRA.Traits 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; @@ -55,6 +61,11 @@ namespace OpenRA.Traits self.World.ActorRemoved += RemoveShroudGeneration; fogVisibilities = Exts.Lazy(() => self.TraitsImplementing().ToArray()); + + fastExploredTest = IsExploredCore; + slowExploredTest = IsExplored; + fastVisibleTest = IsVisibleCore; + slowVisibleTest = IsVisible; } void Invalidate() @@ -223,10 +234,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) @@ -239,12 +272,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 3661df521f..1bf06a9fd4 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -24,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(); @@ -53,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 dfb85070e3..3687ae6976 100644 --- a/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs +++ b/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs @@ -30,6 +30,7 @@ namespace OpenRA.Mods.RA readonly bool startsRevealed; readonly CPos[] footprint; + readonly CellRegion footprintRegion; readonly Lazy tooltip; readonly Lazy health; @@ -44,6 +45,7 @@ namespace OpenRA.Mods.RA // 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()); @@ -65,15 +67,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; @@ -84,7 +78,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 51ac394387..3a726b1c1b 100644 --- a/OpenRA.Mods.RA/ShroudRenderer.cs +++ b/OpenRA.Mods.RA/ShroudRenderer.cs @@ -65,33 +65,37 @@ namespace OpenRA.Mods.RA All = Top | Right | Bottom | Left } - class ShroudTile + 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) { @@ -101,17 +105,20 @@ namespace OpenRA.Mods.RA 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 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]; @@ -134,33 +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; } - Edges GetEdges(int x, int y, Func isVisible) + Edges GetEdges(CPos p, Func isVisible) { - if (!isVisible(x, y)) + if (!isVisible(p)) return notVisibleEdges; // If a side is shrouded then we also count the corners var u = Edges.None; - if (!isVisible(x, y - 1)) u |= Edges.Top; - if (!isVisible(x + 1, y)) u |= Edges.Right; - if (!isVisible(x, y + 1)) u |= Edges.Bottom; - if (!isVisible(x - 1, y)) u |= Edges.Left; + 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 ucorner = u & Edges.AllCorners; - if (!isVisible(x - 1, y - 1)) u |= Edges.TopLeft; - if (!isVisible(x + 1, y - 1)) u |= Edges.TopRight; - if (!isVisible(x + 1, y + 1)) u |= Edges.BottomRight; - if (!isVisible(x - 1, y + 1)) u |= Edges.BottomLeft; + 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 @@ -171,19 +178,19 @@ namespace OpenRA.Mods.RA return info.UseExtendedIndex ? u ^ ucorner : u & Edges.AllCorners; } - Edges GetObserverEdges(int x, int y) + Edges GetObserverEdges(CPos p) { var u = Edges.None; - if (!map.Contains(new CPos(x, y - 1))) u |= Edges.Top; - if (!map.Contains(new CPos(x + 1, y))) u |= Edges.Right; - if (!map.Contains(new CPos(x, y + 1))) u |= Edges.Bottom; - if (!map.Contains(new CPos(x - 1, y))) u |= Edges.Left; + 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; var ucorner = u & Edges.AllCorners; - if (!map.Contains(new CPos(x - 1, y - 1))) u |= Edges.TopLeft; - if (!map.Contains(new CPos(x + 1, y - 1))) u |= Edges.TopRight; - if (!map.Contains(new CPos(x + 1, y + 1))) u |= Edges.BottomRight; - if (!map.Contains(new CPos(x - 1, y + 1))) u |= Edges.BottomLeft; + 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; return info.UseExtendedIndex ? u ^ ucorner : u & Edges.AllCorners; } @@ -195,64 +202,84 @@ 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)) - tiles[cell].Shroud = GetTile(notVisibleEdges, variant); + { + var shroudTile = tiles[cell]; + shroudTile.Shroud = GetTile(shroudSprites, notVisibleEdges, variant); + tiles[cell] = shroudTile; + } } fogPalette = wr.Palette(info.FogPalette); shroudPalette = wr.Palette(info.ShroudPalette); } - Sprite GetTile(Edges edges, int variant) + Sprite GetTile(Sprite[] sprites, Edges edges, int variant) { if (edges == Edges.None) return null; - return shroudSprites[variant * variantStride + spriteMap[(byte)edges]]; + 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 = GetObserverEdges(cell.X, cell.Y); + // 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[(byte)shrouded]] : null; - t.Fog = shrouded != 0 ? fogSprites[t.Variant * variantStride + spriteMap[(byte)shrouded]] : null; - } + lastShroudHash = shroud.Hash; + clearedForNullShroud = false; + updatedRegion = region; + UpdateShroud(shroud); } - else + else if (!clearedForNullShroud) { - foreach (var cell in map.Cells) - { - Func visibleUnderShroud = (x, y) => shroud.IsExplored(new CPos(x, y)); - Func visibleUnderFog = (x, y) => shroud.IsVisible(new CPos(x, y)); - var t = tiles[cell]; - var shrouded = GetEdges(cell.X, cell.Y, visibleUnderShroud); - var fogged = GetEdges(cell.X, cell.Y, visibleUnderFog); + // 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[(byte)shrouded]] : null; - t.Fog = fogged != 0 ? fogSprites[t.Variant * variantStride + spriteMap[(byte)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];