Files
OpenRA/OpenRA.Mods.Common/Graphics/BorderedRegionRenderable.cs
RoosterDragon 9e548fd0f0 Freeze non-mutable readonly static dictionaries.
Since these will live for the lifetime of the process, it is worth freezing them on creation to improve lookup performance. If the dictionaries were iterated, change to an immutable array instead to preserve the order.
2025-12-12 09:51:27 +02:00

99 lines
3.6 KiB
C#

#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* 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, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Collections.Immutable;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Primitives;
namespace OpenRA.Mods.Common.Graphics
{
public readonly struct BorderedRegionRenderable : IRenderable, IFinalizedRenderable
{
enum Corner { TopLeft, TopRight, BottomRight, BottomLeft }
// Maps a cell offset to the index of the corner (in the 'Corner' arrays in the MapGrid.CellRamp structs)
// from which a border should be drawn. The index of the end corner will be (cornerIndex + 1) % 4.
static readonly ImmutableArray<(CVec Offset, int CornerIndex)> Offset2CornerIndex =
[
(new CVec(0, -1), (int)Corner.TopLeft),
(new CVec(1, 0), (int)Corner.TopRight),
(new CVec(0, 1), (int)Corner.BottomRight),
(new CVec(-1, 0), (int)Corner.BottomLeft),
];
readonly CPos[] region;
readonly Color color, contrastColor;
readonly float width, contrastWidth;
public BorderedRegionRenderable(CPos[] region, Color color, float width, Color contrastColor, float contrastWidth)
{
this.region = region;
this.color = color;
this.contrastColor = contrastColor;
this.width = width;
this.contrastWidth = contrastWidth;
}
readonly WPos IRenderable.Pos { get { return WPos.Zero; } }
readonly int IRenderable.ZOffset { get { return 0; } }
readonly bool IRenderable.IsDecoration { get { return true; } }
IRenderable IRenderable.WithZOffset(int newOffset) { return new BorderedRegionRenderable(region, color, width, contrastColor, contrastWidth); }
IRenderable IRenderable.OffsetBy(in WVec offset) { return new BorderedRegionRenderable(region, color, width, contrastColor, contrastWidth); }
IRenderable IRenderable.AsDecoration() { return this; }
IFinalizedRenderable IRenderable.PrepareRender(WorldRenderer wr) { return this; }
void IFinalizedRenderable.Render(WorldRenderer wr) { Draw(wr, region, color, width, contrastColor, contrastWidth); }
void IFinalizedRenderable.RenderDebugGeometry(WorldRenderer wr) { }
Rectangle IFinalizedRenderable.ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; }
public static void Draw(WorldRenderer wr, CPos[] region, Color color, float width, Color constrastColor, float constrastWidth)
{
if (width == 0 && constrastWidth == 0)
return;
var map = wr.World.Map;
var cr = Game.Renderer.RgbaColorRenderer;
foreach (var c in region)
{
var mpos = c.ToMPos(map);
if (!map.Height.Contains(mpos) || wr.World.ShroudObscures(c))
continue;
var tile = map.Tiles[mpos];
var ti = map.Rules.TerrainInfo.GetTerrainInfo(tile);
var ramp = ti?.RampType ?? 0;
var corners = map.Grid.Ramps[ramp].Corners;
var pos = map.CenterOfCell(c) - new WVec(0, 0, map.Grid.Ramps[ramp].CenterHeightOffset);
foreach (var (offset, cornerIndex) in Offset2CornerIndex)
{
// If the neighboring cell is part of the region, don't draw a border between the cells.
if (region.Contains(c + offset))
continue;
var start = wr.Viewport.WorldToViewPx(wr.Screen3DPosition(pos + corners[cornerIndex]));
var end = wr.Viewport.WorldToViewPx(wr.Screen3DPosition(pos + corners[(cornerIndex + 1) % 4]));
if (constrastWidth > 0)
cr.DrawLine(start, end, constrastWidth, constrastColor);
if (width > 0)
cr.DrawLine(start, end, width, color);
}
}
}
}
}