Merge pull request #8781 from pchote/heightmap-shroud

Add plumbing for heighmap-aware shroud and map bounds checks.
This commit is contained in:
Oliver Brakmann
2015-07-28 15:35:51 +02:00
22 changed files with 626 additions and 292 deletions

View File

@@ -20,7 +20,7 @@ namespace OpenRA.Mods.Common.Traits
public CreatesShroud(Actor self, CreatesShroudInfo info)
: base(self, info)
{
addCellsToPlayerShroud = (p, c) => p.Shroud.AddShroudGeneration(self, c);
addCellsToPlayerShroud = (p, uv) => p.Shroud.AddProjectedShroudGeneration(self, uv);
removeCellsFromPlayerShroud = p => p.Shroud.RemoveShroudGeneration(self);
isDisabled = () => self.IsDisabled();
}

View File

@@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.Traits
readonly FrozenUnderFogInfo info;
readonly bool startsRevealed;
readonly MPos[] footprint;
readonly PPos[] footprint;
readonly Lazy<IToolTip> tooltip;
readonly Lazy<Health> health;
@@ -47,10 +47,12 @@ namespace OpenRA.Mods.Common.Traits
{
this.info = info;
var map = init.World.Map;
// Spawned actors (e.g. building husks) shouldn't be revealed
startsRevealed = info.StartsRevealed && !init.Contains<ParentActorInit>();
var footprintCells = FootprintUtils.Tiles(init.Self).ToList();
footprint = footprintCells.Select(cell => cell.ToMPos(init.World.Map)).ToArray();
footprint = footprintCells.SelectMany(c => map.ProjectedCellsCovering(c.ToMPos(map))).ToArray();
tooltip = Exts.Lazy(() => init.Self.TraitsImplementing<IToolTip>().FirstOrDefault());
health = Exts.Lazy(() => init.Self.TraitOrDefault<Health>());

View File

@@ -27,14 +27,14 @@ namespace OpenRA.Mods.Common.Traits
public class RevealsShroud : ITick, ISync, INotifyAddedToWorld, INotifyRemovedFromWorld
{
static readonly CPos[] NoCells = { };
static readonly PPos[] NoCells = { };
readonly RevealsShroudInfo info;
readonly bool lobbyShroudFogDisabled;
[Sync] CPos cachedLocation;
[Sync] bool cachedDisabled;
protected Action<Player, CPos[]> addCellsToPlayerShroud;
protected Action<Player, PPos[]> addCellsToPlayerShroud;
protected Action<Player> removeCellsFromPlayerShroud;
protected Func<bool> isDisabled;
@@ -43,12 +43,12 @@ namespace OpenRA.Mods.Common.Traits
this.info = info;
lobbyShroudFogDisabled = !self.World.LobbyInfo.GlobalSettings.Shroud && !self.World.LobbyInfo.GlobalSettings.Fog;
addCellsToPlayerShroud = (p, c) => p.Shroud.AddVisibility(self, c);
addCellsToPlayerShroud = (p, uv) => p.Shroud.AddProjectedVisibility(self, uv);
removeCellsFromPlayerShroud = p => p.Shroud.RemoveVisibility(self);
isDisabled = () => false;
}
CPos[] Cells(Actor self)
PPos[] ProjectedCells(Actor self)
{
var map = self.World.Map;
var range = Range;
@@ -57,10 +57,10 @@ namespace OpenRA.Mods.Common.Traits
if (info.Type == VisibilityType.Footprint)
return self.OccupiesSpace.OccupiedCells()
.SelectMany(kv => Shroud.CellsInRange(map, kv.First, range))
.Distinct().ToArray();
.SelectMany(kv => Shroud.ProjectedCellsInRange(map, kv.First, range))
.Distinct().ToArray();
return Shroud.CellsInRange(map, self.CenterPosition, range)
return Shroud.ProjectedCellsInRange(map, self.CenterPosition, range)
.ToArray();
}
@@ -69,15 +69,18 @@ namespace OpenRA.Mods.Common.Traits
if (lobbyShroudFogDisabled || !self.IsInWorld)
return;
var location = self.Location;
var centerPosition = self.CenterPosition;
var projectedPos = centerPosition - new WVec(0, centerPosition.Z, centerPosition.Z);
var projectedLocation = self.World.Map.CellContaining(projectedPos);
var disabled = isDisabled();
if (cachedLocation == location && cachedDisabled == disabled)
if (cachedLocation == projectedLocation && cachedDisabled == disabled)
return;
cachedLocation = location;
cachedLocation = projectedLocation;
cachedDisabled = disabled;
var cells = Cells(self);
var cells = ProjectedCells(self);
foreach (var p in self.World.Players)
{
removeCellsFromPlayerShroud(p);
@@ -87,10 +90,11 @@ namespace OpenRA.Mods.Common.Traits
public void AddedToWorld(Actor self)
{
cachedLocation = self.Location;
var centerPosition = self.CenterPosition;
var projectedPos = centerPosition - new WVec(0, centerPosition.Z, centerPosition.Z);
cachedLocation = self.World.Map.CellContaining(projectedPos);
cachedDisabled = isDisabled();
var cells = Cells(self);
var cells = ProjectedCells(self);
foreach (var p in self.World.Players)
addCellsToPlayerShroud(p, cells);
}

View File

@@ -64,10 +64,10 @@ namespace OpenRA.Mods.Common.Traits
var map = world.Map;
foreach (var p in Start.Keys)
{
var cells = Shroud.CellsInRange(map, Start[p], info.InitialExploreRange);
var cells = Shroud.ProjectedCellsInRange(map, Start[p], info.InitialExploreRange);
foreach (var q in world.Players)
if (p.IsAlliedWith(q))
q.Shroud.Explore(world, cells);
q.Shroud.ExploreProjectedCells(world, cells);
}
// Set viewport

View File

@@ -60,23 +60,24 @@ namespace OpenRA.Mods.Common.Traits
var doDim = refreshTick - world.WorldTick <= 0;
if (doDim) refreshTick = world.WorldTick + 20;
var map = wr.World.Map;
foreach (var pair in layers)
{
var c = (pair.Key != null) ? pair.Key.Color.RGB : Color.PaleTurquoise;
var layer = pair.Value;
// Only render quads in viewing range:
foreach (var cell in wr.Viewport.VisibleCellsInsideBounds)
foreach (var uv in wr.Viewport.VisibleCellsInsideBounds.CandidateMapCoords)
{
if (layer[cell] <= 0)
if (layer[uv] <= 0)
continue;
var w = Math.Max(0, Math.Min(layer[cell], 128));
var w = Math.Max(0, Math.Min(layer[uv], 128));
if (doDim)
layer[cell] = layer[cell] * 5 / 6;
layer[uv] = layer[uv] * 5 / 6;
// TODO: This doesn't make sense for isometric terrain
var pos = wr.World.Map.CenterOfCell(cell);
var pos = wr.World.Map.CenterOfCell(uv.ToCPos(map));
var tl = wr.ScreenPxPosition(pos - new WVec(512, 512, 0));
var br = wr.ScreenPxPosition(pos + new WVec(511, 511, 0));
qr.FillRect(RectangleF.FromLTRB(tl.X, tl.Y, br.X, br.Y), Color.FromArgb(w, c));

View File

@@ -91,11 +91,11 @@ namespace OpenRA.Mods.Common.Traits
readonly CellLayer<TileInfo> tileInfos;
readonly Sprite[] fogSprites, shroudSprites;
readonly HashSet<CPos> cellsDirty = new HashSet<CPos>();
readonly HashSet<CPos> cellsAndNeighborsDirty = new HashSet<CPos>();
readonly HashSet<PPos> cellsDirty = new HashSet<PPos>();
readonly HashSet<PPos> cellsAndNeighborsDirty = new HashSet<PPos>();
Shroud currentShroud;
Func<MPos, bool> visibleUnderShroud, visibleUnderFog;
Func<PPos, bool> visibleUnderShroud, visibleUnderFog;
TerrainSpriteLayer shroudLayer, fogLayer;
public ShroudRenderer(World world, ShroudRendererInfo info)
@@ -158,20 +158,22 @@ namespace OpenRA.Mods.Common.Traits
// This includes the region outside the visible area to cover any sprites peeking outside the map
foreach (var uv in w.Map.AllCells.MapCoords)
{
var screen = wr.ScreenPosition(w.Map.CenterOfCell(uv.ToCPos(map)));
var pos = w.Map.CenterOfCell(uv.ToCPos(map));
var screen = wr.ScreenPosition(pos - new WVec(0, 0, pos.Z));
var variant = (byte)Game.CosmeticRandom.Next(info.ShroudVariants.Length);
tileInfos[uv] = new TileInfo(screen, variant);
}
DirtyCells(map.AllCells);
// Dirty the whole projected space
DirtyCells(map.AllCells.MapCoords.Select(uv => (PPos)uv));
// All tiles are visible in the editor
if (w.Type == WorldType.Editor)
visibleUnderShroud = _ => true;
else
visibleUnderShroud = map.Contains;
visibleUnderShroud = puv => map.Contains(puv);
visibleUnderFog = map.Contains;
visibleUnderFog = puv => map.Contains(puv);
var shroudSheet = shroudSprites[0].Sheet;
if (shroudSprites.Any(s => s.Sheet != shroudSheet))
@@ -193,25 +195,25 @@ namespace OpenRA.Mods.Common.Traits
fogLayer = new TerrainSpriteLayer(w, wr, fogSheet, fogBlend, wr.Palette(info.FogPalette), false);
}
Edges GetEdges(MPos uv, Func<MPos, bool> isVisible)
Edges GetEdges(PPos puv, Func<PPos, bool> isVisible)
{
if (!isVisible(uv))
if (!isVisible(puv))
return notVisibleEdges;
var cell = uv.ToCPos(map);
var cell = ((MPos)puv).ToCPos(map);
// If a side is shrouded then we also count the corners.
var edge = Edges.None;
if (!isVisible((cell + new CVec(0, -1)).ToMPos(map))) edge |= Edges.Top;
if (!isVisible((cell + new CVec(1, 0)).ToMPos(map))) edge |= Edges.Right;
if (!isVisible((cell + new CVec(0, 1)).ToMPos(map))) edge |= Edges.Bottom;
if (!isVisible((cell + new CVec(-1, 0)).ToMPos(map))) edge |= Edges.Left;
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((cell + new CVec(-1, -1)).ToMPos(map))) edge |= Edges.TopLeft;
if (!isVisible((cell + new CVec(1, -1)).ToMPos(map))) edge |= Edges.TopRight;
if (!isVisible((cell + new CVec(1, 1)).ToMPos(map))) edge |= Edges.BottomRight;
if (!isVisible((cell + new CVec(-1, 1)).ToMPos(map))) edge |= Edges.BottomLeft;
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;
// RA provides a set of frames for tiles with shrouded
// corners but unshrouded edges. We want to detect this
@@ -222,7 +224,7 @@ namespace OpenRA.Mods.Common.Traits
return info.UseExtendedIndex ? edge ^ ucorner : edge & Edges.AllCorners;
}
void DirtyCells(IEnumerable<CPos> cells)
void DirtyCells(IEnumerable<PPos> cells)
{
cellsDirty.UnionWith(cells);
}
@@ -244,36 +246,37 @@ namespace OpenRA.Mods.Common.Traits
}
else
{
visibleUnderShroud = map.Contains;
visibleUnderFog = map.Contains;
visibleUnderShroud = puv => map.Contains(puv);
visibleUnderFog = puv => map.Contains(puv);
}
currentShroud = shroud;
DirtyCells(map.CellsInsideBounds);
DirtyCells(map.ProjectedCellBounds);
}
// We need to update newly dirtied areas of the shroud.
// Expand the dirty area to cover the neighboring cells, since shroud is affected by neighboring cells.
foreach (var cell in cellsDirty)
foreach (var uv in cellsDirty)
{
cellsAndNeighborsDirty.Add(cell);
cellsAndNeighborsDirty.Add(uv);
var cell = ((MPos)uv).ToCPos(map);
foreach (var direction in CVec.Directions)
cellsAndNeighborsDirty.Add(cell + direction);
cellsAndNeighborsDirty.Add((PPos)(cell + direction).ToMPos(map));
}
foreach (var cell in cellsAndNeighborsDirty)
foreach (var puv in cellsAndNeighborsDirty)
{
var uv = cell.ToMPos(map.TileShape);
var uv = (MPos)puv;
if (!tileInfos.Contains(uv))
continue;
var tileInfo = tileInfos[uv];
var shroudSprite = GetSprite(shroudSprites, GetEdges(uv, visibleUnderShroud), tileInfo.Variant);
var shroudSprite = GetSprite(shroudSprites, GetEdges(puv, visibleUnderShroud), tileInfo.Variant);
var shroudPos = tileInfo.ScreenPosition;
if (shroudSprite != null)
shroudPos += shroudSprite.Offset - 0.5f * shroudSprite.Size;
var fogSprite = GetSprite(fogSprites, GetEdges(uv, visibleUnderFog), tileInfo.Variant);
var fogSprite = GetSprite(fogSprites, GetEdges(puv, visibleUnderFog), tileInfo.Variant);
var fogPos = tileInfo.ScreenPosition;
if (fogSprite != null)
fogPos += fogSprite.Offset - 0.5f * fogSprite.Size;

View File

@@ -57,8 +57,11 @@ 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.AllVisibleCells.MapCoords)
foreach (var uv in wr.Viewport.AllVisibleCells.CandidateMapCoords)
{
if (!map.MapHeight.Value.Contains(uv))
continue;
var height = (int)map.MapHeight.Value[uv];
var tile = map.MapTiles.Value[uv];
var ti = tileSet.GetTileInfo(tile);
@@ -80,6 +83,22 @@ namespace OpenRA.Mods.Common.Traits
lr.LineWidth = 1;
}
// Projected cell coordinates for the current cell
var projectedCorners = map.CellCorners[0];
lr.LineWidth = 3;
foreach (var puv in map.ProjectedCellsCovering(mouseCell))
{
var pos = map.CenterOfCell(((MPos)puv).ToCPos(map));
var screen = projectedCorners.Select(c => wr.ScreenPxPosition(pos + c - new WVec(0, 0, pos.Z)).ToFloat2()).ToArray();
for (var i = 0; i < 4; i++)
{
var j = (i + 1) % 4;
lr.DrawLine(screen[i], screen[j], Color.Navy);
}
}
lr.LineWidth = 1;
}
}
}

View File

@@ -151,8 +151,8 @@ namespace OpenRA.Mods.Common.UtilityCommands
Author = "Westwood Studios"
};
var tl = new MPos(offsetX, offsetY);
var br = new MPos(offsetX + width - 1, offsetY + height - 1);
var tl = new PPos(offsetX, offsetY);
var br = new PPos(offsetX + width - 1, offsetY + height - 1);
map.SetBounds(tl, br);
if (legacyMapFormat == IniMapFormat.RedAlert)

View File

@@ -64,8 +64,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var tileset = modRules.TileSets[tilesetDropDown.Text];
var map = new Map(tileset, width + 2, height + maxTerrainHeight + 2);
var tl = new MPos(1, 1);
var br = new MPos(width, height + maxTerrainHeight);
var tl = new PPos(1, 1);
var br = new PPos(width, height + maxTerrainHeight);
map.SetBounds(tl, br);
map.PlayerDefinitions = new MapPlayers(map.Rules, map.SpawnPoints.Value.Length).ToMiniYaml();

View File

@@ -34,7 +34,7 @@ namespace OpenRA.Mods.Common.Widgets
readonly WorldRenderer worldRenderer;
readonly RadarPings radarPings;
readonly HashSet<CPos> dirtyShroudCells = new HashSet<CPos>();
readonly HashSet<PPos> dirtyShroudCells = new HashSet<PPos>();
float radarMinimapHeight;
int frame;
@@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.Widgets
actorSprite = new Sprite(radarSheet, new Rectangle(0, height, width, height), TextureChannel.Alpha);
// Set initial terrain data
foreach (var cell in world.Map.CellsInsideBounds)
foreach (var cell in world.Map.AllCells)
UpdateTerrainCell(cell);
world.Map.MapTiles.Value.CellEntryChanged += UpdateTerrainCell;
@@ -120,35 +120,38 @@ namespace OpenRA.Mods.Common.Widgets
}
}
void UpdateShroudCell(CPos cell)
void UpdateShroudCell(PPos projectedCell)
{
if (!world.Map.Contains(cell))
if (!world.Map.Bounds.Contains(projectedCell.U, projectedCell.V))
return;
var stride = radarSheet.Size.Width;
var uv = cell.ToMPos(world.Map);
var dx = shroudSprite.Bounds.Left - world.Map.Bounds.Left;
var dy = shroudSprite.Bounds.Top - world.Map.Bounds.Top;
var color = 0;
if (world.ShroudObscures(cell))
color = Color.Black.ToArgb();
else if (world.FogObscures(cell))
color = Color.FromArgb(128, Color.Black).ToArgb();
var rp = world.RenderPlayer;
if (rp != null)
{
if (!rp.Shroud.IsExplored(projectedCell))
color = Color.Black.ToArgb();
else if (!rp.Shroud.IsVisible(projectedCell))
color = Color.FromArgb(128, Color.Black).ToArgb();
}
unsafe
{
fixed (byte* colorBytes = &radarData[0])
{
var colors = (int*)colorBytes;
colors[(uv.V + dy) * stride + uv.U + dx] = color;
colors[(projectedCell.V + dy) * stride + projectedCell.U + dx] = color;
}
}
}
void MarkShroudDirty(IEnumerable<CPos> cellsChanged)
void MarkShroudDirty(IEnumerable<PPos> projectedCellsChanged)
{
dirtyShroudCells.UnionWith(cellsChanged);
dirtyShroudCells.UnionWith(projectedCellsChanged);
}
public override string GetCursor(int2 pos)
@@ -290,7 +293,7 @@ namespace OpenRA.Mods.Common.Widgets
if (newRenderShroud != null)
{
// Redraw the full shroud sprite
MarkShroudDirty(world.Map.CellsInsideBounds);
MarkShroudDirty(world.Map.AllCells.MapCoords.Select(uv => (PPos)uv));
// Update the notification binding
newRenderShroud.CellsChanged += MarkShroudDirty;