#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.Generic; 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 Dictionary Offset2CornerIndex = new() { { 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 o in Offset2CornerIndex) { // If the neighboring cell is part of the region, don't draw a border between the cells. if (region.Contains(c + o.Key)) continue; var start = wr.Viewport.WorldToViewPx(wr.Screen3DPosition(pos + corners[o.Value])); var end = wr.Viewport.WorldToViewPx(wr.Screen3DPosition(pos + corners[(o.Value + 1) % 4])); if (constrastWidth > 0) cr.DrawLine(start, end, constrastWidth, constrastColor); if (width > 0) cr.DrawLine(start, end, width, color); } } } } }