Skip to content

Commit

Permalink
Caching for clipping results.
Browse files Browse the repository at this point in the history
  • Loading branch information
awgil committed May 2, 2024
1 parent b34ceeb commit da0f56a
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 25 deletions.
2 changes: 1 addition & 1 deletion BossMod/BossModule/AOEShapes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public sealed record class AOEShapeCross(float Length, float HalfWidth, Angle Di
{
public override string ToString() => $"Cross: l={Length:f3}, w={HalfWidth * 2}, off={DirectionOffset}";
public override bool Check(WPos position, WPos origin, Angle rotation) => position.InRect(origin, rotation + DirectionOffset, Length, Length, HalfWidth) || position.InRect(origin, rotation + DirectionOffset, HalfWidth, HalfWidth, Length);
public override void Draw(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.AOE) => arena.ZonePoly(ContourPoints(origin, rotation), color);
public override void Draw(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.AOE) => arena.ZonePoly(HashCode.Combine(GetType(), Length, HalfWidth, DirectionOffset), ContourPoints(origin, rotation), color);

public override void Outline(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.Danger)
{
Expand Down
79 changes: 63 additions & 16 deletions BossMod/BossModule/MiniArena.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,38 @@ namespace BossMod;
// rotation 0 corresponds to South, and increases counterclockwise (so East is +pi/2, North is pi, West is -pi/2)
// - camera azimuth 0 correpsonds to camera looking North and increases counterclockwise
// - screen coordinates - X points left to right, Y points top to bottom
public class MiniArena(BossModuleConfig config, WPos center, ArenaBounds bounds)
public sealed class MiniArena(BossModuleConfig config, WPos center, ArenaBounds bounds)
{
public BossModuleConfig Config { get; init; } = config;
public WPos Center = center;
public ArenaBounds Bounds = bounds;
public readonly BossModuleConfig Config = config;
private WPos _center = center;
private ArenaBounds _bounds = bounds;
private readonly TriangulationCache _triCache = new();

public WPos Center
{
get => _center;
set
{
if (_center != value)
{
_center = value;
_triCache.Invalidate();
}
}
}

public ArenaBounds Bounds
{
get => _bounds;
set
{
if (!ReferenceEquals(_bounds, value))
{
_bounds = value;
_triCache.Invalidate();
}
}
}

public float ScreenHalfSize => 150 * Config.ArenaScale;
public float ScreenMarginSize => 20 * Config.ArenaScale;
Expand All @@ -34,7 +61,15 @@ public void Begin(float cameraAzimuthRadians)
var cursor = ImGui.GetCursorScreenPos();
ImGui.Dummy(fullSize);

Bounds.ScreenHalfSize = ScreenHalfSize;
if (Bounds.ScreenHalfSize != ScreenHalfSize)
{
Bounds.ScreenHalfSize = ScreenHalfSize;
_triCache.Invalidate();
}
else
{
_triCache.NextFrame();
}
ScreenCenter = cursor + centerOffset;
_cameraAzimuth = cameraAzimuthRadians;
_cameraSinAzimuth = MathF.Sin(cameraAzimuthRadians);
Expand Down Expand Up @@ -187,17 +222,29 @@ public void Zone(List<RelTriangle> triangulation, uint color)
drawlist.Flags = restoreFlags;
}

// draw zones - these are filled primitives clipped to various borders
public void ZoneCone(WPos center, float innerRadius, float outerRadius, Angle centerDirection, Angle halfAngle, uint color) => Zone(Bounds.ClipAndTriangulateCone(center - Center, innerRadius, outerRadius, centerDirection, halfAngle), color);
public void ZoneCircle(WPos center, float radius, uint color) => Zone(Bounds.ClipAndTriangulateCircle(center - Center, radius), color);
public void ZoneDonut(WPos center, float innerRadius, float outerRadius, uint color) => Zone(Bounds.ClipAndTriangulateDonut(center - Center, innerRadius, outerRadius), color);
public void ZoneTri(WPos a, WPos b, WPos c, uint color) => Zone(Bounds.ClipAndTriangulateTri(a - Center, b - Center, c - Center), color);
public void ZoneIsoscelesTri(WPos apex, WDir height, WDir halfBase, uint color) => Zone(Bounds.ClipAndTriangulateIsoscelesTri(apex - Center, height, halfBase), color);
public void ZoneIsoscelesTri(WPos apex, Angle direction, Angle halfAngle, float height, uint color) => Zone(Bounds.ClipAndTriangulateIsoscelesTri(apex - Center, direction, halfAngle, height), color);
public void ZoneRect(WPos origin, WDir direction, float lenFront, float lenBack, float halfWidth, uint color) => Zone(Bounds.ClipAndTriangulateRect(origin - Center, direction, lenFront, lenBack, halfWidth), color);
public void ZoneRect(WPos origin, Angle direction, float lenFront, float lenBack, float halfWidth, uint color) => Zone(Bounds.ClipAndTriangulateRect(origin - Center, direction, lenFront, lenBack, halfWidth), color);
public void ZoneRect(WPos start, WPos end, float halfWidth, uint color) => Zone(Bounds.ClipAndTriangulateRect(start - Center, end - Center, halfWidth), color);
public void ZonePoly(IEnumerable<WPos> contour, uint color) => Zone(Bounds.ClipAndTriangulate(contour.Select(p => p - Center)), color);
// draw zones - these are filled primitives clipped to arena border; note that triangulation is cached
public void ZoneCone(WPos center, float innerRadius, float outerRadius, Angle centerDirection, Angle halfAngle, uint color)
=> Zone(_triCache[HashCode.Combine(1, center, innerRadius, outerRadius, centerDirection, halfAngle)] ??= Bounds.ClipAndTriangulateCone(center - Center, innerRadius, outerRadius, centerDirection, halfAngle), color);
public void ZoneCircle(WPos center, float radius, uint color)
=> Zone(_triCache[HashCode.Combine(2, center, radius)] ??= Bounds.ClipAndTriangulateCircle(center - Center, radius), color);
public void ZoneDonut(WPos center, float innerRadius, float outerRadius, uint color)
=> Zone(_triCache[HashCode.Combine(3, center, innerRadius, outerRadius)] ??= Bounds.ClipAndTriangulateDonut(center - Center, innerRadius, outerRadius), color);
public void ZoneTri(WPos a, WPos b, WPos c, uint color)
=> Zone(_triCache[HashCode.Combine(4, a, b, c)] ??= Bounds.ClipAndTriangulateTri(a - Center, b - Center, c - Center), color);
public void ZoneIsoscelesTri(WPos apex, WDir height, WDir halfBase, uint color)
=> Zone(_triCache[HashCode.Combine(5, apex, height, halfBase)] ??= Bounds.ClipAndTriangulateIsoscelesTri(apex - Center, height, halfBase), color);
public void ZoneIsoscelesTri(WPos apex, Angle direction, Angle halfAngle, float height, uint color)
=> Zone(_triCache[HashCode.Combine(6, apex, direction, halfAngle, height)] ??= Bounds.ClipAndTriangulateIsoscelesTri(apex - Center, direction, halfAngle, height), color);
public void ZoneRect(WPos origin, WDir direction, float lenFront, float lenBack, float halfWidth, uint color)
=> Zone(_triCache[HashCode.Combine(7, origin, direction, lenFront, lenBack, halfWidth)] ??= Bounds.ClipAndTriangulateRect(origin - Center, direction, lenFront, lenBack, halfWidth), color);
public void ZoneRect(WPos origin, Angle direction, float lenFront, float lenBack, float halfWidth, uint color)
=> Zone(_triCache[HashCode.Combine(8, origin, direction, lenFront, lenBack, halfWidth)] ??= Bounds.ClipAndTriangulateRect(origin - Center, direction, lenFront, lenBack, halfWidth), color);
public void ZoneRect(WPos start, WPos end, float halfWidth, uint color)
=> Zone(_triCache[HashCode.Combine(9, start, end, halfWidth)] ??= Bounds.ClipAndTriangulateRect(start - Center, end - Center, halfWidth), color);
public void ZonePoly(int hash, IEnumerable<WPos> contour, uint color)
=> Zone(_triCache[HashCode.Combine(10, hash)] ??= Bounds.ClipAndTriangulate(contour.Select(p => p - Center)), color);
public void ZoneRelPoly(int hash, IEnumerable<WDir> relContour, uint color)
=> Zone(_triCache[HashCode.Combine(11, hash)] ??= Bounds.ClipAndTriangulate(relContour), color);

public void TextScreen(Vector2 center, string text, uint color, float fontSize = 17)
{
Expand Down
50 changes: 50 additions & 0 deletions BossMod/BossModule/TriangulationCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
namespace BossMod;

// clipping shapes to bounds and triangulating them is a serious time sink, so we want to cache that
// to avoid requiring tracking cache lifetime by users, we use a heuristic - we assume that if something isn't drawn for a frame, it's no longer relevant
// for that, we keep two lists (prev/curr frame cache), every frame discard old entries, every time we retrieve entry from prev frame cache we move it to curr
public sealed class TriangulationCache
{
private List<(int hash, List<RelTriangle>? triangulation)> _prev = [];
private List<(int hash, List<RelTriangle>? triangulation)> _curr = [];

// the typical usage is: var triangulation = cache[hash] ??= BuildTriangulation(...)
public ref List<RelTriangle>? this[int hash]
{
get
{
var iCurr = _curr.FindIndex(kv => kv.hash == hash);
if (iCurr < 0)
{
List<RelTriangle>? entry = null;

// see if there is entry in prev
var iPrev = _prev.FindIndex(kv => kv.hash == hash);
if (iPrev >= 0)
{
entry = _prev[iPrev].triangulation;
// swap-remove
if (iPrev + 1 < _prev.Count)
_prev[iPrev] = _prev[^1];
_prev.RemoveAt(_prev.Count - 1);
}

iCurr = _curr.Count;
_curr.Add((hash, entry));
}
return ref _curr.Ref(iCurr).triangulation;
}
}

public void NextFrame()
{
(_prev, _curr) = (_curr, _prev);
_curr.Clear();
}

public void Invalidate()
{
_prev.Clear();
_curr.Clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

class AddPhaseArena(BossModule module) : BossComponent(module)
{
private readonly float _innerRingRadius = 14.5f;
private readonly float _outerRingRadius = 27.5f;
private readonly float _ringHalfWidth = 2.5f;
private readonly float _alcoveDepth = 1;
private readonly float _alcoveWidth = 2;
private const float _innerRingRadius = 14.5f;
private const float _outerRingRadius = 27.5f;
private const float _ringHalfWidth = 2.5f;
private const float _alcoveDepth = 1;
private const float _alcoveWidth = 2;

public override void DrawArenaBackground(int pcSlot, Actor pc)
{
Arena.Zone(Module.Bounds.ClipAndTriangulate(InDanger()), ArenaColor.AOE);
Arena.Zone(Module.Bounds.ClipAndTriangulate(MidDanger()), ArenaColor.AOE);
Arena.Zone(Module.Bounds.ClipAndTriangulate(OutDanger()), ArenaColor.AOE);
Arena.ZoneRelPoly(HashCode.Combine(GetType(), 0), InDanger(), ArenaColor.AOE);
Arena.ZoneRelPoly(HashCode.Combine(GetType(), 1), MidDanger(), ArenaColor.AOE);
Arena.ZoneRelPoly(HashCode.Combine(GetType(), 2), OutDanger(), ArenaColor.AOE);
}

private IEnumerable<WDir> RingBorder(Angle centerOffset, float ringRadius, bool innerBorder)
Expand Down

0 comments on commit da0f56a

Please sign in to comment.