Skip to content

Commit

Permalink
Shroud, combine IsVisible and IsExplored into a single function.
Browse files Browse the repository at this point in the history
  • Loading branch information
anvilvapre authored and teinarss committed Aug 28, 2022
1 parent cc1f10d commit 6e54746
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 38 deletions.
5 changes: 3 additions & 2 deletions OpenRA.Game/Traits/Player/FrozenActorLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,15 @@ public void UpdateVisibility()
// PERF: Avoid LINQ.
foreach (var puv in Footprint)
{
if (shroud.IsVisible(puv))
var cv = shroud.GetVisibility(puv);
if (cv.HasFlag(Shroud.CellVisibility.Visible))
{
Visible = false;
Shrouded = false;
break;
}

if (Shrouded && shroud.IsExplored(puv))
if (Shrouded && cv.HasFlag(Shroud.CellVisibility.Explored))
Shrouded = false;
}

Expand Down
52 changes: 52 additions & 0 deletions OpenRA.Game/Traits/Player/Shroud.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ public ShroudSource(SourceType type, PPos[] projectedCells)
}
}

// Visible is not a super set of Explored. IsExplored may return false even if IsVisible returns true.
[Flags]
public enum CellVisibility : byte { Hidden = 0x0, Explored = 0x1, Visible = 0x2 }

readonly Actor self;
readonly ShroudInfo info;
readonly Map map;
Expand Down Expand Up @@ -423,5 +427,53 @@ public bool Contains(PPos uv)
// about explored here: any of the CellLayers would have been suitable.
return explored.Contains(uv);
}

// PERF: Combine IsExplored and IsVisible.
public CellVisibility GetVisibility(PPos puv)
{
var state = CellVisibility.Hidden;

if (Disabled)
{
if (FogEnabled)
{
// Shroud disabled, Fog enabled
if (resolvedType.Contains(puv))
{
state |= CellVisibility.Explored;

if (resolvedType[puv] == ShroudCellType.Visible)
state |= CellVisibility.Visible;
}
}
else if (map.Contains(puv))
state |= CellVisibility.Explored | CellVisibility.Visible;
}
else
{
if (FogEnabled)
{
// Shroud and Fog enabled
if (resolvedType.Contains(puv))
{
var rt = resolvedType[puv];
if (rt == ShroudCellType.Visible)
state |= CellVisibility.Explored | CellVisibility.Visible;
else if (rt > ShroudCellType.Shroud)
state |= CellVisibility.Explored;
}
}
else if (resolvedType.Contains(puv))
{
// We do not set Explored since IsExplored may return false.
state |= CellVisibility.Visible;

if (resolvedType[puv] > ShroudCellType.Shroud)
state |= CellVisibility.Explored;
}
}

return state;
}
}
}
116 changes: 84 additions & 32 deletions OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ enum Edges : byte
All = Top | Right | Bottom | Left
}

// Index into neighbors array.
enum Neighbor
{
Top = 0,
Right,
Bottom,
Left,
TopLeft,
TopRight,
BottomRight,
BottomLeft
}

readonly struct TileInfo
{
public readonly float3 ScreenPosition;
Expand All @@ -96,17 +109,20 @@ public TileInfo(in float3 screenPosition, byte variant)
readonly ShroudRendererInfo info;
readonly World world;
readonly Map map;
readonly Edges notVisibleEdges;
readonly (Edges, Edges) notVisibleEdgesPair;
readonly byte variantStride;
readonly byte[] edgesToSpriteIndexOffset;

// PERF: Allocate once.
readonly Shroud.CellVisibility[] neighbors = new Shroud.CellVisibility[8];

readonly CellLayer<TileInfo> tileInfos;
readonly CellLayer<bool> cellsDirty;
bool anyCellDirty;
readonly (Sprite Sprite, float Scale, float Alpha)[] fogSprites, shroudSprites;

Shroud shroud;
Func<PPos, bool> visibleUnderShroud, visibleUnderFog;
Func<PPos, Shroud.CellVisibility> cellVisibility;
TerrainSpriteLayer shroudLayer, fogLayer;
PaletteReference shroudPaletteReference, fogPaletteReference;
bool disposed;
Expand Down Expand Up @@ -161,16 +177,26 @@ public ShroudRenderer(World world, ShroudRendererInfo info)
}
}

int spriteCount;
if (info.UseExtendedIndex)
{
notVisibleEdgesPair = (Edges.AllSides, Edges.AllSides);
spriteCount = (int)Edges.All;
}
else
{
notVisibleEdgesPair = (Edges.AllCorners, Edges.AllCorners);
spriteCount = (int)Edges.AllCorners;
}

// Mapping of shrouded directions -> sprite index
edgesToSpriteIndexOffset = new byte[(byte)(info.UseExtendedIndex ? Edges.All : Edges.AllCorners) + 1];
edgesToSpriteIndexOffset = new byte[spriteCount + 1];
for (var i = 0; i < info.Index.Length; i++)
edgesToSpriteIndexOffset[info.Index[i]] = (byte)i;

if (info.OverrideFullShroud != null)
edgesToSpriteIndexOffset[info.OverrideShroudIndex] = (byte)(variantStride - 1);

notVisibleEdges = info.UseExtendedIndex ? Edges.AllSides : Edges.AllCorners;

world.RenderPlayerChanged += WorldOnRenderPlayerChanged;
}

Expand All @@ -188,11 +214,9 @@ void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr)

// All tiles are visible in the editor
if (w.Type == WorldType.Editor)
visibleUnderShroud = _ => true;
cellVisibility = puv => Shroud.CellVisibility.Visible;
else
visibleUnderShroud = puv => map.Contains(puv);

visibleUnderFog = puv => map.Contains(puv);
cellVisibility = puv => (map.Contains(puv) ? Shroud.CellVisibility.Visible | Shroud.CellVisibility.Explored : Shroud.CellVisibility.Hidden);

var shroudBlend = shroudSprites[0].Sprite.BlendMode;
if (shroudSprites.Any(s => s.Sprite.BlendMode != shroudBlend))
Expand All @@ -211,33 +235,61 @@ void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr)
WorldOnRenderPlayerChanged(world.RenderPlayer);
}

Edges GetEdges(PPos puv, Func<PPos, bool> isVisible)
Shroud.CellVisibility[] GetNeighborsVisbility(PPos puv)
{
if (!isVisible(puv))
return notVisibleEdges;

var cell = ((MPos)puv).ToCPos(map);
neighbors[(int)Neighbor.Top] = cellVisibility((PPos)(cell + new CVec(0, -1)).ToMPos(map));
neighbors[(int)Neighbor.Right] = cellVisibility((PPos)(cell + new CVec(1, 0)).ToMPos(map));
neighbors[(int)Neighbor.Bottom] = cellVisibility((PPos)(cell + new CVec(0, 1)).ToMPos(map));
neighbors[(int)Neighbor.Left] = cellVisibility((PPos)(cell + new CVec(-1, 0)).ToMPos(map));

neighbors[(int)Neighbor.TopLeft] = cellVisibility((PPos)(cell + new CVec(-1, -1)).ToMPos(map));
neighbors[(int)Neighbor.TopRight] = cellVisibility((PPos)(cell + new CVec(1, -1)).ToMPos(map));
neighbors[(int)Neighbor.BottomRight] = cellVisibility((PPos)(cell + new CVec(1, 1)).ToMPos(map));
neighbors[(int)Neighbor.BottomLeft] = cellVisibility((PPos)(cell + new CVec(-1, 1)).ToMPos(map));

return neighbors;
}

Edges GetEdges(Shroud.CellVisibility[] neighbors, Shroud.CellVisibility visibleMask)
{
// If a side is shrouded then we also count the corners.
var edge = Edges.None;
if (!isVisible((PPos)(cell + new CVec(0, -1)).ToMPos(map))) edge |= Edges.Top;
if (!isVisible((PPos)(cell + new CVec(1, 0)).ToMPos(map))) edge |= Edges.Right;
if (!isVisible((PPos)(cell + new CVec(0, 1)).ToMPos(map))) edge |= Edges.Bottom;
if (!isVisible((PPos)(cell + new CVec(-1, 0)).ToMPos(map))) edge |= Edges.Left;

var ucorner = edge & Edges.AllCorners;
if (!isVisible((PPos)(cell + new CVec(-1, -1)).ToMPos(map))) edge |= Edges.TopLeft;
if (!isVisible((PPos)(cell + new CVec(1, -1)).ToMPos(map))) edge |= Edges.TopRight;
if (!isVisible((PPos)(cell + new CVec(1, 1)).ToMPos(map))) edge |= Edges.BottomRight;
if (!isVisible((PPos)(cell + new CVec(-1, 1)).ToMPos(map))) edge |= Edges.BottomLeft;
var edges = Edges.None;
if ((neighbors[(int)Neighbor.Top] & visibleMask) == 0) edges |= Edges.Top;
if ((neighbors[(int)Neighbor.Right] & visibleMask) == 0) edges |= Edges.Right;
if ((neighbors[(int)Neighbor.Bottom] & visibleMask) == 0) edges |= Edges.Bottom;
if ((neighbors[(int)Neighbor.Left] & visibleMask) == 0) edges |= Edges.Left;

var ucorner = edges & Edges.AllCorners;
if ((neighbors[(int)Neighbor.TopLeft] & visibleMask) == 0) edges |= Edges.TopLeft;
if ((neighbors[(int)Neighbor.TopRight] & visibleMask) == 0) edges |= Edges.TopRight;
if ((neighbors[(int)Neighbor.BottomRight] & visibleMask) == 0) edges |= Edges.BottomRight;
if ((neighbors[(int)Neighbor.BottomLeft] & visibleMask) == 0) edges |= Edges.BottomLeft;

// 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
// bits that are enabled twice, which gives the sprite offset
// we want here.
return info.UseExtendedIndex ? edge ^ ucorner : edge & Edges.AllCorners;
return info.UseExtendedIndex ? edges ^ ucorner : edges & Edges.AllCorners;
}

(Edges, Edges) GetEdges(PPos puv)
{
var cv = cellVisibility(puv);

// If a cell is covered by shroud, then all neigbhors are covered by shroud and fog.
if (cv == Shroud.CellVisibility.Hidden)
return notVisibleEdgesPair;

var ncv = GetNeighborsVisbility(puv);

// If a cell is covered by fog, then all neigbhors are as well.
var edgesFog = cv.HasFlag(Shroud.CellVisibility.Visible) ? GetEdges(ncv, Shroud.CellVisibility.Visible) : notVisibleEdgesPair.Item2;

var edgesShroud = GetEdges(ncv, Shroud.CellVisibility.Explored | Shroud.CellVisibility.Visible);
return (edgesShroud, edgesFog);
}

void WorldOnRenderPlayerChanged(Player player)
Expand All @@ -251,14 +303,13 @@ void WorldOnRenderPlayerChanged(Player player)

if (newShroud != null)
{
visibleUnderShroud = puv => newShroud.IsExplored(puv);
visibleUnderFog = puv => newShroud.IsVisible(puv);
cellVisibility = puv => newShroud.GetVisibility(puv);
newShroud.OnShroudChanged += UpdateShroudCell;
}
else
{
visibleUnderShroud = puv => map.Contains(puv);
visibleUnderFog = puv => map.Contains(puv);
// Visible under shroud: Explored. Visible under fog: Visible.
cellVisibility = puv => (map.Contains(puv) ? Shroud.CellVisibility.Visible | Shroud.CellVisibility.Explored : Shroud.CellVisibility.Hidden);
}

shroud = newShroud;
Expand Down Expand Up @@ -287,12 +338,13 @@ void UpdateShroud(IEnumerable<PPos> region)
cellsDirty[uv] = false;

var tileInfo = tileInfos[uv];
var shroudSprite = GetSprite(shroudSprites, GetEdges(puv, visibleUnderShroud), tileInfo.Variant);
var (edgesShroud, edgesFog) = GetEdges(puv);
var shroudSprite = GetSprite(shroudSprites, edgesShroud, tileInfo.Variant);
var shroudPos = tileInfo.ScreenPosition;
if (shroudSprite.Sprite != null)
shroudPos += shroudSprite.Sprite.Offset - 0.5f * shroudSprite.Sprite.Size;

var fogSprite = GetSprite(fogSprites, GetEdges(puv, visibleUnderFog), tileInfo.Variant);
var fogSprite = GetSprite(fogSprites, edgesFog, tileInfo.Variant);
var fogPos = tileInfo.ScreenPosition;
if (fogSprite.Sprite != null)
fogPos += fogSprite.Sprite.Offset - 0.5f * fogSprite.Sprite.Size;
Expand Down
12 changes: 8 additions & 4 deletions OpenRA.Mods.Common/Widgets/RadarWidget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ namespace OpenRA.Mods.Common.Widgets
{
public sealed class RadarWidget : Widget, IDisposable
{
public readonly int ColorFog = Color.FromArgb(128, Color.Black).ToArgb();
public readonly int ColorShroud = Color.Black.ToArgb();

public string WorldInteractionController = null;
public int AnimationLength = 5;
public string RadarOnlineSound = null;
Expand Down Expand Up @@ -240,10 +243,11 @@ void UpdateTerrainColor(MPos uv)
void UpdateShroudCell(PPos puv)
{
var color = 0;
if (!currentPlayer.Shroud.IsExplored(puv))
color = Color.Black.ToArgb();
else if (!currentPlayer.Shroud.IsVisible(puv))
color = Color.FromArgb(128, Color.Black).ToArgb();
var cv = currentPlayer.Shroud.GetVisibility(puv);
if (cv == Shroud.CellVisibility.Hidden)
color = ColorShroud;
else if (cv.HasFlag(Shroud.CellVisibility.Visible))
color = ColorFog;

var stride = radarSheet.Size.Width;
unsafe
Expand Down

0 comments on commit 6e54746

Please sign in to comment.