Merge pull request #8474 from pchote/remove-shroud-tests

Remove region assumptions from fast shroud tests
This commit is contained in:
Pavel Penev
2015-06-16 12:08:08 +03:00
17 changed files with 165 additions and 95 deletions

View File

@@ -180,8 +180,8 @@ namespace OpenRA.Graphics
{
var colors = (int*)bitmapData.Scan0;
var stride = bitmapData.Stride / 4;
var shroudObscured = world.ShroudObscuresTest(map.CellsInsideBounds);
var fogObscured = world.FogObscuresTest(map.CellsInsideBounds);
var shroudObscured = world.ShroudObscuresTest;
var fogObscured = world.FogObscuresTest;
foreach (var uv in map.CellsInsideBounds.MapCoords)
{
var bitmapXy = new int2(uv.U - b.Left, uv.V - b.Top);

View File

@@ -24,7 +24,9 @@ namespace OpenRA.Graphics
theater = wr.Theater;
mapTiles = world.Map.MapTiles.Value;
terrain = new TerrainSpriteLayer(world, wr, theater.Sheet, BlendMode.Alpha, wr.Palette("terrain"));
terrain = new TerrainSpriteLayer(world, wr, theater.Sheet, BlendMode.Alpha,
wr.Palette("terrain"), wr.World.Type != WorldType.Editor);
foreach (var cell in world.Map.AllCells)
UpdateCell(cell);

View File

@@ -25,6 +25,7 @@ namespace OpenRA.Graphics
readonly Vertex[] vertices;
readonly HashSet<int> dirtyRows = new HashSet<int>();
readonly int rowStride;
readonly bool restrictToBounds;
readonly WorldRenderer worldRenderer;
readonly Map map;
@@ -34,9 +35,10 @@ namespace OpenRA.Graphics
float paletteIndex;
public TerrainSpriteLayer(World world, WorldRenderer wr, Sheet sheet, BlendMode blendMode, PaletteReference palette)
public TerrainSpriteLayer(World world, WorldRenderer wr, Sheet sheet, BlendMode blendMode, PaletteReference palette, bool restrictToBounds)
{
worldRenderer = wr;
this.restrictToBounds = restrictToBounds;
this.sheet = sheet;
this.blendMode = blendMode;
paletteIndex = palette.TextureIndex;
@@ -92,7 +94,7 @@ namespace OpenRA.Graphics
public void Draw(Viewport viewport)
{
var cells = viewport.VisibleCells;
var cells = restrictToBounds ? viewport.VisibleCellsInsideBounds : viewport.AllVisibleCells;
// Only draw the rows that are visible.
var firstRow = cells.MapCoords.TopLeft.V;

View File

@@ -53,6 +53,9 @@ namespace OpenRA.Graphics
CellRegion cells;
bool cellsDirty = true;
CellRegion allCells;
bool allCellsDirty = true;
float zoom = 1f;
public float Zoom
{
@@ -66,6 +69,7 @@ namespace OpenRA.Graphics
zoom = value;
viewportSize = (1f / zoom * new float2(Game.Renderer.Resolution)).ToInt2();
cellsDirty = true;
allCellsDirty = true;
}
}
@@ -187,6 +191,7 @@ namespace OpenRA.Graphics
{
CenterLocation = worldRenderer.ScreenPxPosition(pos).Clamp(mapBounds);
cellsDirty = true;
allCellsDirty = true;
}
public void Scroll(float2 delta, bool ignoreBorders)
@@ -194,6 +199,7 @@ namespace OpenRA.Graphics
// Convert scroll delta from world-px to viewport-px
CenterLocation += (1f / Zoom * delta).ToInt2();
cellsDirty = true;
allCellsDirty = true;
if (!ignoreBorders)
CenterLocation = CenterLocation.Clamp(mapBounds);
@@ -207,8 +213,8 @@ namespace OpenRA.Graphics
{
// Visible rectangle in world coordinates (expanded to the corners of the cells)
var map = worldRenderer.World.Map;
var ctl = map.CenterOfCell(VisibleCells.TopLeft) - new WVec(512, 512, 0);
var cbr = map.CenterOfCell(VisibleCells.BottomRight) + new WVec(512, 512, 0);
var ctl = map.CenterOfCell(VisibleCellsInsideBounds.TopLeft) - new WVec(512, 512, 0);
var cbr = map.CenterOfCell(VisibleCellsInsideBounds.BottomRight) + new WVec(512, 512, 0);
// Convert to screen coordinates
var tl = WorldToViewPx(worldRenderer.ScreenPxPosition(ctl - new WVec(0, 0, ctl.Z))).Clamp(ScreenClip);
@@ -220,42 +226,61 @@ namespace OpenRA.Graphics
}
}
public CellRegion VisibleCells
CellRegion CalculateVisibleCells(bool insideBounds)
{
var map = worldRenderer.World.Map;
var wtl = worldRenderer.Position(TopLeft);
var wbr = worldRenderer.Position(BottomRight);
// Map editor shows the full map (including the area outside the regular bounds)
Func<MPos, MPos> clamp = map.Clamp;
if (!insideBounds)
clamp = map.MapTiles.Value.Clamp;
// Due to diamond tile staggering, we need to adjust the top-left bounds outwards by half a cell.
if (map.TileShape == TileShape.Diamond)
wtl -= new WVec(512, 512, 0);
// Visible rectangle in map coordinates.
var dy = map.TileShape == TileShape.Diamond ? 512 : 1024;
var ctl = new MPos(wtl.X / 1024, wtl.Y / dy);
var cbr = new MPos(wbr.X / 1024, wbr.Y / dy);
var tl = clamp(ctl).ToCPos(map.TileShape);
// Also need to account for height of cells in rows below the bottom.
var heightPadding = map.TileShape == TileShape.Diamond ? 3 : 0;
var br = clamp(new MPos(cbr.U, cbr.V + heightPadding + maxGroundHeight / 2 + 1)).ToCPos(map.TileShape);
return new CellRegion(map.TileShape, tl, br);
}
public CellRegion VisibleCellsInsideBounds
{
get
{
if (cellsDirty)
{
var map = worldRenderer.World.Map;
var wtl = worldRenderer.Position(TopLeft);
var wbr = worldRenderer.Position(BottomRight);
// Map editor shows the full map (including the area outside the regular bounds)
Func<MPos, MPos> clamp = map.Clamp;
if (worldRenderer.World.Type == WorldType.Editor)
clamp = map.MapTiles.Value.Clamp;
// Due to diamond tile staggering, we need to adjust the top-left bounds outwards by half a cell.
if (map.TileShape == TileShape.Diamond)
wtl -= new WVec(512, 512, 0);
// Visible rectangle in map coordinates.
var dy = map.TileShape == TileShape.Diamond ? 512 : 1024;
var ctl = new MPos(wtl.X / 1024, wtl.Y / dy);
var cbr = new MPos(wbr.X / 1024, wbr.Y / dy);
var tl = clamp(ctl).ToCPos(map.TileShape);
// Also need to account for height of cells in rows below the bottom.
var heightPadding = map.TileShape == TileShape.Diamond ? 3 : 0;
var br = clamp(new MPos(cbr.U, cbr.V + heightPadding + maxGroundHeight / 2 + 1)).ToCPos(map.TileShape);
cells = new CellRegion(map.TileShape, tl, br);
cells = CalculateVisibleCells(false);
cellsDirty = false;
}
return cells;
}
}
public CellRegion AllVisibleCells
{
get
{
if (allCellsDirty)
{
allCells = CalculateVisibleCells(false);
allCellsDirty = false;
}
return allCells;
}
}
}
}

View File

@@ -870,7 +870,7 @@ namespace OpenRA
// it rounds the actual distance up to the next integer so that this call
// will return any cells that intersect with the requested range circle.
// The returned positions are sorted by distance from the center.
public IEnumerable<CPos> FindTilesInAnnulus(CPos center, int minRange, int maxRange)
public IEnumerable<CPos> FindTilesInAnnulus(CPos center, int minRange, int maxRange, bool allowOutsideBounds = false)
{
if (maxRange < minRange)
throw new ArgumentOutOfRangeException("maxRange", "Maximum range is less than the minimum range.");
@@ -878,20 +878,24 @@ namespace OpenRA
if (maxRange > TilesByDistance.Length)
throw new ArgumentOutOfRangeException("maxRange", "The requested range ({0}) exceeds the maximum allowed ({1})".F(maxRange, MaxTilesInCircleRange));
Func<CPos, bool> valid = Contains;
if (allowOutsideBounds)
valid = MapTiles.Value.Contains;
for (var i = minRange; i <= maxRange; i++)
{
foreach (var offset in TilesByDistance[i])
{
var t = offset + center;
if (Contains(t))
if (valid(t))
yield return t;
}
}
}
public IEnumerable<CPos> FindTilesInCircle(CPos center, int maxRange)
public IEnumerable<CPos> FindTilesInCircle(CPos center, int maxRange, bool allowOutsideBounds = false)
{
return FindTilesInAnnulus(center, 0, maxRange);
return FindTilesInAnnulus(center, 0, maxRange, allowOutsideBounds);
}
}
}

View File

@@ -28,7 +28,7 @@ namespace OpenRA.Traits
public readonly WPos CenterPosition;
public readonly Rectangle Bounds;
readonly Actor actor;
readonly Func<MPos, bool> isVisibleTest;
readonly Shroud shroud;
public Player Owner;
@@ -42,12 +42,16 @@ namespace OpenRA.Traits
public bool NeedRenderables;
public bool IsRendering { get; private set; }
public FrozenActor(Actor self, MPos[] footprint, CellRegion footprintRegion, Shroud shroud)
public FrozenActor(Actor self, MPos[] footprint, Shroud shroud)
{
actor = self;
isVisibleTest = shroud.IsVisibleTest(footprintRegion);
this.shroud = shroud;
// Consider all cells inside the map area (ignoring the current map bounds)
Footprint = footprint
.Where(m => shroud.Contains(m))
.ToArray();
Footprint = footprint;
CenterPosition = self.CenterPosition;
Bounds = self.Bounds;
@@ -75,16 +79,19 @@ namespace OpenRA.Traits
void UpdateVisibility()
{
var wasVisible = Visible;
var isVisibleTest = shroud.IsVisibleTest;
// We are doing the following LINQ manually for performance since this is a hot path.
// Visible = !Footprint.Any(isVisibleTest);
Visible = true;
foreach (var uv in Footprint)
{
if (isVisibleTest(uv))
{
Visible = false;
break;
}
}
if (Visible && !wasVisible)
NeedRenderables = true;

View File

@@ -44,10 +44,8 @@ namespace OpenRA.Traits
static readonly Func<MPos, bool> TruthPredicate = _ => true;
readonly Func<MPos, bool> shroudEdgeTest;
readonly Func<MPos, bool> fastExploredTest;
readonly Func<MPos, bool> slowExploredTest;
readonly Func<MPos, bool> fastVisibleTest;
readonly Func<MPos, bool> slowVisibleTest;
readonly Func<MPos, bool> isExploredTest;
readonly Func<MPos, bool> isVisibleTest;
public Shroud(Actor self)
{
@@ -67,10 +65,8 @@ namespace OpenRA.Traits
fogVisibilities = Exts.Lazy(() => self.TraitsImplementing<IFogVisibilityModifier>().ToArray());
shroudEdgeTest = map.Contains;
fastExploredTest = IsExploredCore;
slowExploredTest = IsExplored;
fastVisibleTest = IsVisibleCore;
slowVisibleTest = IsVisible;
isExploredTest = IsExploredCore;
isVisibleTest = IsVisibleCore;
}
void Invalidate(IEnumerable<CPos> changed)
@@ -112,7 +108,7 @@ namespace OpenRA.Traits
var limit = radius.RangeSquared;
var pos = map.CenterOfCell(position);
foreach (var cell in map.FindTilesInCircle(position, r))
foreach (var cell in map.FindTilesInCircle(position, r, true))
if ((map.CenterOfCell(cell) - pos).HorizontalLengthSquared <= limit)
yield return cell;
}
@@ -129,6 +125,11 @@ namespace OpenRA.Traits
foreach (var c in visible)
{
var uv = c.ToMPos(map);
// Force cells outside the visible bounds invisible
if (!map.Contains(uv))
continue;
visibleCount[uv]++;
explored[uv] = true;
}
@@ -147,7 +148,11 @@ namespace OpenRA.Traits
return;
foreach (var c in visible)
visibleCount[c.ToMPos(map)]--;
{
// Cells outside the visible bounds don't increment visibleCount
if (map.Contains(c))
visibleCount[c.ToMPos(map)]--;
}
visibility.Remove(a);
Invalidate(visible);
@@ -320,18 +325,21 @@ namespace OpenRA.Traits
return explored[uv] && (generatedShroudCount[uv] == 0 || visibleCount[uv] > 0);
}
public Func<MPos, bool> IsExploredTest(CellRegion region)
/// <summary>
/// Returns a fast exploration lookup that skips the usual validation.
/// The return value should not be cached across ticks, and should not
/// be called with cells outside the map bounds.
/// </summary>
public Func<MPos, bool> IsExploredTest
{
// If the region to test extends outside the map we must use the slow test that checks the map boundary every time.
if (!map.CellsInsideBounds.Contains(region))
return slowExploredTest;
get
{
// If shroud isn't enabled, then we can see everything inside the map.
if (!ShroudEnabled)
return shroudEdgeTest;
// If shroud isn't enabled, then we can see everything inside the map.
if (!ShroudEnabled)
return shroudEdgeTest;
// If shroud is enabled, we can use the fast test that just does the core check.
return fastExploredTest;
return isExploredTest;
}
}
public bool IsExplored(Actor a)
@@ -368,18 +376,22 @@ namespace OpenRA.Traits
return visibleCount[uv] > 0;
}
public Func<MPos, bool> IsVisibleTest(CellRegion region)
/// <summary>
/// Returns a fast visibility lookup that skips the usual validation.
/// The return value should not be cached across ticks, and should not
/// be called with cells outside the map bounds.
/// </summary>
public Func<MPos, bool> IsVisibleTest
{
// If the region to test extends outside the map we must use the slow test that checks the map boundary every time.
if (!map.CellsInsideBounds.Contains(region))
return slowVisibleTest;
get
{
// If fog isn't enabled, then we can see everything.
if (!FogEnabled)
return TruthPredicate;
// 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;
// If fog is enabled, we can use the fast test that just does the core check.
return isVisibleTest;
}
}
// Actors are hidden under shroud, but not under fog by default
@@ -406,5 +418,12 @@ namespace OpenRA.Traits
{
return fogVisibilities.Value.Any(f => f.HasFogVisibility(self.Owner));
}
public bool Contains(MPos uv)
{
// Check that uv is inside the map area. There is nothing special
// about explored here: any of the CellLayers would have been suitable.
return explored.Contains(uv);
}
}
}

View File

@@ -73,27 +73,37 @@ namespace OpenRA
public bool FogObscures(Actor a) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(a); }
public bool FogObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(p); }
public bool FogObscures(WPos pos) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(pos); }
public bool FogObscures(MPos uv) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(uv); }
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(WPos pos) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(pos); }
public bool ShroudObscures(MPos uv) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(uv); }
public Func<MPos, bool> FogObscuresTest(CellRegion region)
public Func<MPos, bool> FogObscuresTest
{
var rp = RenderPlayer;
if (rp == null)
return FalsePredicate;
var predicate = rp.Shroud.IsVisibleTest(region);
return uv => !predicate(uv);
get
{
var rp = RenderPlayer;
if (rp == null)
return FalsePredicate;
var predicate = rp.Shroud.IsVisibleTest;
return uv => !predicate(uv);
}
}
public Func<MPos, bool> ShroudObscuresTest(CellRegion region)
public Func<MPos, bool> ShroudObscuresTest
{
var rp = RenderPlayer;
if (rp == null)
return FalsePredicate;
var predicate = rp.Shroud.IsExploredTest(region);
return uv => !predicate(uv);
get
{
var rp = RenderPlayer;
if (rp == null)
return FalsePredicate;
var predicate = rp.Shroud.IsExploredTest;
return uv => !predicate(uv);
}
}
public bool IsReplay

View File

@@ -30,7 +30,6 @@ namespace OpenRA.Mods.Common.Traits
readonly bool startsRevealed;
readonly MPos[] footprint;
readonly CellRegion footprintRegion;
readonly Lazy<IToolTip> tooltip;
readonly Lazy<Health> health;
@@ -46,7 +45,6 @@ namespace OpenRA.Mods.Common.Traits
startsRevealed = info.StartsRevealed && !init.Contains<ParentActorInit>();
var footprintCells = FootprintUtils.Tiles(init.Self).ToList();
footprint = footprintCells.Select(cell => cell.ToMPos(init.World.Map)).ToArray();
footprintRegion = CellRegion.BoundingRegion(init.World.Map.TileShape, footprintCells);
tooltip = Exts.Lazy(() => init.Self.TraitsImplementing<IToolTip>().FirstOrDefault());
health = Exts.Lazy(() => init.Self.TraitOrDefault<Health>());
@@ -71,7 +69,7 @@ namespace OpenRA.Mods.Common.Traits
FrozenActor frozenActor;
if (!initialized)
{
frozen[player] = frozenActor = new FrozenActor(self, footprint, footprintRegion, player.Shroud);
frozen[player] = frozenActor = new FrozenActor(self, footprint, player.Shroud);
frozen[player].NeedRenderables = frozenActor.NeedRenderables = startsRevealed;
player.PlayerActor.Trait<FrozenActorLayer>().Add(frozenActor);
isVisible = visible[player] |= startsRevealed;

View File

@@ -146,7 +146,7 @@ namespace OpenRA.Mods.Common.Traits
Dirty.Clear();
foreach (var uv in wr.Viewport.VisibleCells.MapCoords)
foreach (var uv in wr.Viewport.AllVisibleCells.MapCoords)
{
var t = Tiles[uv];
if (t.Sprite != null)

View File

@@ -66,7 +66,7 @@ namespace OpenRA.Mods.Common.Traits
var layer = pair.Value;
// Only render quads in viewing range:
foreach (var cell in wr.Viewport.VisibleCells)
foreach (var cell in wr.Viewport.VisibleCellsInsideBounds)
{
if (layer[cell] <= 0)
continue;

View File

@@ -33,8 +33,8 @@ namespace OpenRA.Mods.Common.Traits
public void Render(WorldRenderer wr)
{
var shroudObscured = world.ShroudObscuresTest(wr.Viewport.VisibleCells);
foreach (var uv in wr.Viewport.VisibleCells.MapCoords)
var shroudObscured = world.ShroudObscuresTest;
foreach (var uv in wr.Viewport.VisibleCellsInsideBounds.MapCoords)
{
if (shroudObscured(uv))
continue;

View File

@@ -189,8 +189,8 @@ namespace OpenRA.Mods.Common.Traits
if (fogSprites.Any(s => s.BlendMode != fogBlend))
throw new InvalidDataException("Fog sprites must all use the same blend mode.");
shroudLayer = new TerrainSpriteLayer(w, wr, shroudSheet, shroudBlend, wr.Palette(info.ShroudPalette));
fogLayer = new TerrainSpriteLayer(w, wr, fogSheet, fogBlend, wr.Palette(info.FogPalette));
shroudLayer = new TerrainSpriteLayer(w, wr, shroudSheet, shroudBlend, wr.Palette(info.ShroudPalette), false);
fogLayer = new TerrainSpriteLayer(w, wr, fogSheet, fogBlend, wr.Palette(info.FogPalette), false);
}
Edges GetEdges(MPos uv, Func<MPos, bool> isVisible)

View File

@@ -144,7 +144,7 @@ namespace OpenRA.Mods.Common.Traits
foreach (var kv in tiles)
{
if (!wr.Viewport.VisibleCells.Contains(kv.Key))
if (!wr.Viewport.VisibleCellsInsideBounds.Contains(kv.Key))
continue;
if (world.ShroudObscures(kv.Key))

View File

@@ -57,7 +57,7 @@ namespace OpenRA.Mods.Common.Traits
var colors = wr.World.TileSet.HeightDebugColors;
var mouseCell = wr.Viewport.ViewToWorld(Viewport.LastMousePos).ToMPos(wr.World.Map);
foreach (var uv in wr.Viewport.VisibleCells.MapCoords)
foreach (var uv in wr.Viewport.VisibleCellsInsideBounds.MapCoords)
{
var height = (int)map.MapHeight.Value[uv];
var tile = map.MapTiles.Value[uv];

View File

@@ -122,6 +122,9 @@ namespace OpenRA.Mods.Common.Widgets
void UpdateShroudCell(CPos cell)
{
if (!world.Map.Contains(cell))
return;
var stride = radarSheet.Size.Width;
var uv = cell.ToMPos(world.Map);
var dx = shroudSprite.Bounds.Left - world.Map.Bounds.Left;

View File

@@ -64,7 +64,7 @@ namespace OpenRA.Mods.D2k.Traits
foreach (var kv in tiles)
{
if (!wr.Viewport.VisibleCells.Contains(kv.Key))
if (!wr.Viewport.VisibleCellsInsideBounds.Contains(kv.Key))
continue;
if (wr.World.ShroudObscures(kv.Key))