Skip to content

Commit

Permalink
Merge pull request #137 from FFXIV-CombatReborn/mergeWIP2
Browse files Browse the repository at this point in the history
performance improvements
  • Loading branch information
CarnifexOptimus authored Jun 21, 2024
2 parents 93b26ab + f6e3777 commit 0eba4a5
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 97 deletions.
32 changes: 8 additions & 24 deletions BossMod/BossModule/AOEShapes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,11 @@ public sealed record class AOEShapeCustom(IEnumerable<Shape> UnionShapes, IEnume
private static readonly Dictionary<(string, bool), RelSimplifiedComplexPolygon> _polygonCache = [];
private readonly Dictionary<(string, WPos, WPos, Angle, bool), bool> _checkCache = [];
private static readonly Dictionary<(string, WPos, Angle, bool), Func<WPos, float>> _distanceFuncCache = [];
private readonly string sha512key = CreateCacheKey(UnionShapes, DifferenceShapes ?? []);

private RelSimplifiedComplexPolygon GetCombinedPolygon(WPos origin)
{
var cacheKey = (CreateCacheKey(UnionShapes, DifferenceShapes ?? []), InvertForbiddenZone);
var cacheKey = (sha512key, InvertForbiddenZone);
if (_polygonCache.TryGetValue(cacheKey, out var cachedResult))
return cachedResult;

Expand All @@ -180,14 +181,13 @@ private RelSimplifiedComplexPolygon GetCombinedPolygon(WPos origin)

var clipper = new PolygonClipper();
var finalResult = clipper.Difference(unionOperands, differenceOperands);

_polygonCache[cacheKey] = finalResult;
return finalResult;
}

public override bool Check(WPos position, WPos origin, Angle rotation)
{
var cacheKey = (CreateCacheKey(UnionShapes, DifferenceShapes ?? []), position, origin, rotation, InvertForbiddenZone);
var cacheKey = (sha512key, position, origin, rotation, InvertForbiddenZone);
if (_checkCache.TryGetValue(cacheKey, out var cachedResult))
return cachedResult;
var combinedPolygon = GetCombinedPolygon(origin);
Expand All @@ -205,7 +205,7 @@ private static string CreateCacheKey(IEnumerable<Shape> unionShapes, IEnumerable
return Shape.ComputeSHA512(combinedKey);
}

public override void Draw(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.AOE) => arena.ZoneRelPoly(CreateCacheKey(UnionShapes, DifferenceShapes ?? []), GetCombinedPolygon(origin), color);
public override void Draw(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.AOE) => arena.ZoneRelPoly(sha512key, GetCombinedPolygon(origin), color);

public override void Outline(MiniArena arena, WPos origin, Angle rotation, uint color = ArenaColor.Danger)
{
Expand Down Expand Up @@ -239,27 +239,11 @@ public override void Outline(MiniArena arena, WPos origin, Angle rotation, uint

public override Func<WPos, float> Distance(WPos origin, Angle rotation)
{
// TODO: Distance maps should probably be cloned instead of being saved in a dictionary
var cacheKey = (CreateCacheKey(UnionShapes, DifferenceShapes ?? []), origin, rotation, InvertForbiddenZone);
var cacheKey = (sha512key, origin, rotation, InvertForbiddenZone);
if (_distanceFuncCache.TryGetValue(cacheKey, out var cachedFunc))
return cachedFunc;
var unionDistanceFuncs = UnionShapes.Select(shape => shape.Distance()).ToList();
var differenceDistanceFuncs = (DifferenceShapes ?? []).Select(shape => shape.Distance()).ToList();

float combinedDistanceFunc(WPos p)
{
var minUnionDist = unionDistanceFuncs.Min(func => func(p));
var maxDifferenceDist = differenceDistanceFuncs.Count != 0 ? differenceDistanceFuncs.Max(func => -func(p)) : float.MinValue;
return Math.Max(minUnionDist, maxDifferenceDist);
}

float finalFunc(WPos p)
{
var result = combinedDistanceFunc(p);
return InvertForbiddenZone ? -result : result;
}

_distanceFuncCache[cacheKey] = finalFunc;
return finalFunc;
var result = InvertForbiddenZone ? ShapeDistance.InvertedPolygonWithHoles(origin, GetCombinedPolygon(origin)) : ShapeDistance.PolygonWithHoles(origin, GetCombinedPolygon(origin));
_distanceFuncCache[cacheKey] = result;
return result;
}
}
19 changes: 10 additions & 9 deletions BossMod/BossModule/ArenaBounds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ private Pathfinding.Map BuildMap()
var (halfWidth, halfHeight) = CalculatePolygonProperties(polygon);
var map = new Pathfinding.Map(MapResolution, Center, halfWidth, halfHeight);

// due to being an embarassingly parallel problem this is faster than using a proper ShapeDistance func
Parallel.ForEach(map.EnumeratePixels(), (pixel) =>
{
var (x, y, pos) = pixel;
Expand All @@ -285,7 +286,6 @@ private Pathfinding.Map BuildMap()
var allPointsInside = samplePoints.All(polygon.Contains);
map.Pixels[y * map.Width + x].MaxG = allPointsInside ? float.MaxValue : 0;
});

return map;
}

Expand All @@ -312,22 +312,23 @@ private List<WDir> GenerateSamplePoints(WDir relativeCenter, float resolution)
// for convenience third list will optionally perform additional unions at the end
public record class ArenaBoundsComplex : ArenaBoundsCustom
{

public ArenaBoundsComplex(IEnumerable<Shape> UnionShapes, IEnumerable<Shape>? DifferenceShapes = null, IEnumerable<Shape>? AdditionalShapes = null, float MapResolution = 0.5f, float Offset = 0)
: base(BuildBounds(UnionShapes, DifferenceShapes ?? [], AdditionalShapes ?? [], MapResolution, Offset))
: base(BuildBounds(UnionShapes, DifferenceShapes, AdditionalShapes, MapResolution, Offset, out var center))
{
var properties = CalculatePolygonProperties(UnionShapes, DifferenceShapes ?? [], AdditionalShapes ?? []);
Center = properties.Center;
Center = center;
}

private static ArenaBoundsCustom BuildBounds(IEnumerable<Shape> unionShapes, IEnumerable<Shape> differenceShapes, IEnumerable<Shape> additionalShapes, float mapResolution, float offset)
private static ArenaBoundsCustom BuildBounds(IEnumerable<Shape> unionShapes, IEnumerable<Shape>? differenceShapes, IEnumerable<Shape>? additionalShapes, float mapResolution, float offset, out WPos center)
{
var props = CalculatePolygonProperties(unionShapes, differenceShapes, additionalShapes);
return new ArenaBoundsCustom(props.Radius, props.Poly, mapResolution, offset);
var cacheKey = CreateCacheKey(unionShapes, differenceShapes ?? [], additionalShapes ?? []);
var properties = CalculatePolygonProperties(cacheKey, unionShapes, differenceShapes ?? [], additionalShapes ?? []);
center = properties.Center;
return new ArenaBoundsCustom(properties.Radius, properties.Poly, mapResolution, offset);
}

private static (WPos Center, float Radius, RelSimplifiedComplexPolygon Poly) CalculatePolygonProperties(IEnumerable<Shape> unionShapes, IEnumerable<Shape> differenceShapes, IEnumerable<Shape> additionalShapes)
private static (WPos Center, float Radius, RelSimplifiedComplexPolygon Poly) CalculatePolygonProperties(string cacheKey, IEnumerable<Shape> unionShapes, IEnumerable<Shape> differenceShapes, IEnumerable<Shape> additionalShapes)
{
var cacheKey = CreateCacheKey(unionShapes, differenceShapes, additionalShapes);
if (StaticCache.TryGetValue(cacheKey, out var cachedResult))
return ((WPos, float, RelSimplifiedComplexPolygon))cachedResult;

Expand Down
16 changes: 8 additions & 8 deletions BossMod/BossModule/Shapes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ private bool IsConvex()
return isConvex;
}

private bool IsCounterClockwise()
private bool IsClockwise()
{
var hash = ComputeHash() + "IsCounterClockwise";
if (propertyCache.TryGetValue(hash, out var isCounterClockwise))
return isCounterClockwise;
var hash = ComputeHash() + "IsClockwise";
if (propertyCache.TryGetValue(hash, out var isClockwise))
return isClockwise;

var vertices = Vertices.ToList();
float area = 0;
Expand All @@ -101,12 +101,12 @@ private bool IsCounterClockwise()
var p1 = vertices[(i + 1) % vertices.Count];
area += (p1.X - p0.X) * (p1.Z + p0.Z);
}
isCounterClockwise = area > 0;
propertyCache[hash] = isCounterClockwise;
return isCounterClockwise;
isClockwise = area < 0;
propertyCache[hash] = isClockwise;
return isClockwise;
}

public override Func<WPos, float> Distance() => IsConvex() ? ShapeDistance.ConvexPolygon(Vertices, !IsCounterClockwise()) : ShapeDistance.ConcavePolygon(Vertices);
public override Func<WPos, float> Distance() => IsConvex() ? ShapeDistance.ConvexPolygon(Vertices, IsClockwise()) : ShapeDistance.ConcavePolygon(Vertices);

public override string ComputeHash()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ class Border(BossModule module) : Components.GenericAOEs(module)
private const float _alcoveDepth = 1;
private const float _alcoveWidth = 2;
private bool Active;
private static readonly List<Shape> labyrinth = [new PolygonCustom(ConvertToWPos(InDanger())),
new PolygonCustom(ConvertToWPos(MidDanger())), new PolygonCustom(ConvertToWPos(OutDanger()))];
private static readonly List<Shape> labyrinth = [new PolygonCustom(InDanger()), new PolygonCustom(MidDanger()), new PolygonCustom(OutDanger())];
public static readonly AOEShapeCustom customShape = new(labyrinth);
public static readonly ArenaBounds labPhase = new ArenaBoundsComplex([new Circle(BoundsCenter, 34.5f)], labyrinth);

Expand All @@ -30,23 +29,23 @@ public override void OnCastFinished(Actor caster, ActorCastInfo spell)
}
}

private static IEnumerable<WDir> RingBorder(Angle centerOffset, float ringRadius, bool innerBorder)
private static IEnumerable<WPos> RingBorder(Angle centerOffset, float ringRadius, bool innerBorder)
{
float offsetMultiplier = innerBorder ? -1 : 1;
var halfWidth = (_alcoveWidth / ringRadius).Radians();
for (var i = 0; i < 8; ++i)
{
var centerAlcove = centerOffset + i * 45.Degrees();
foreach (var p in CurveApprox.CircleArc(ringRadius + offsetMultiplier * (_ringHalfWidth + _alcoveDepth), centerAlcove - halfWidth, centerAlcove + halfWidth, Shape.MaxApproxError))
foreach (var p in CurveApprox.CircleArc(BoundsCenter, ringRadius + offsetMultiplier * (_ringHalfWidth + _alcoveDepth), centerAlcove - halfWidth, centerAlcove + halfWidth, Shape.MaxApproxError))
yield return p;
foreach (var p in CurveApprox.CircleArc(ringRadius + offsetMultiplier * _ringHalfWidth, centerAlcove + halfWidth, centerAlcove + 45.Degrees() - halfWidth, Shape.MaxApproxError))
foreach (var p in CurveApprox.CircleArc(BoundsCenter, ringRadius + offsetMultiplier * _ringHalfWidth, centerAlcove + halfWidth, centerAlcove + 45.Degrees() - halfWidth, Shape.MaxApproxError))
yield return p;
}
}

private static IEnumerable<WDir> RepeatFirst(IEnumerable<WDir> pts)
private static IEnumerable<WPos> RepeatFirst(IEnumerable<WPos> pts)
{
WDir? first = null;
WPos? first = null;
foreach (var p in pts)
{
first ??= p;
Expand All @@ -56,21 +55,19 @@ private static IEnumerable<WDir> RepeatFirst(IEnumerable<WDir> pts)
yield return first.Value;
}

private static IEnumerable<WDir> InDanger() => RingBorder(22.5f.Degrees(), _innerRingRadius, true);
private static IEnumerable<WPos> InDanger() => RingBorder(22.5f.Degrees(), _innerRingRadius, true);

private static IEnumerable<WDir> MidDanger()
private static IEnumerable<WPos> MidDanger()
{
var outerRing = RepeatFirst(RingBorder(0.Degrees(), _outerRingRadius, true));
var innerRing = RepeatFirst(RingBorder(22.5f.Degrees(), _innerRingRadius, false)).Reverse();
return outerRing.Concat(innerRing);
}

private static IEnumerable<WDir> OutDanger()
private static IEnumerable<WPos> OutDanger()
{
var outerBoundary = RepeatFirst(CurveApprox.Circle(34.6f, Shape.MaxApproxError));
var outerBoundary = RepeatFirst(CurveApprox.Circle(BoundsCenter, 34.6f, Shape.MaxApproxError));
var innerRing = RepeatFirst(RingBorder(0.Degrees(), _outerRingRadius, false)).Reverse();
return outerBoundary.Concat(innerRing);
}

private static IEnumerable<WPos> ConvertToWPos(IEnumerable<WDir> directions) => directions.Select(dir => new WPos(BoundsCenter.X + dir.X, BoundsCenter.Z + dir.Z));
}
47 changes: 47 additions & 0 deletions BossMod/Util/Polygon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,4 +337,51 @@ public static bool IsConvex(ReadOnlySpan<WDir> contour)
}
return cross != 0;
}

public static bool IsPointInsideConcavePolygon(WPos point, IEnumerable<WPos> vertices)
{
var intersections = 0;
var verticesList = vertices.ToList();
for (var i = 0; i < verticesList.Count; i++)
{
var a = verticesList[i];
var b = verticesList[(i + 1) % verticesList.Count];
if (RayIntersectsEdge(point, a, b))
intersections++;
}
return intersections % 2 != 0;
}

public static bool RayIntersectsEdge(WPos point, WPos a, WPos b)
{
if (a.Z > b.Z)
(b, a) = (a, b);
if (point.Z == a.Z || point.Z == b.Z)
point = new WPos(point.X, point.Z + 0.0001f);
if (point.Z > b.Z || point.Z < a.Z || point.X >= Math.Max(a.X, b.X))
return false;
if (point.X < Math.Min(a.X, b.X))
return true;
var red = (point.Z - a.Z) / (b.Z - a.Z);
var blue = (b.X - a.X) * red + a.X;
return point.X < blue;
}

public static float DistanceToEdge(WPos point, (WPos p1, WPos p2) edge)
{
var (p1, p2) = edge;
var edgeVector = p2 - p1;
var pointVector = point - p1;
var edgeLengthSquared = edgeVector.LengthSq();

var t = Math.Max(0, Math.Min(1, pointVector.Dot(edgeVector) / edgeLengthSquared));
var projection = p1 + t * edgeVector;
return (point - projection).Length();
}

public static (WPos, WPos) ConvertToWPos(WPos origin, (WDir, WDir) edge)
{
var (p1, p2) = edge;
return (new WPos(origin.X + p1.X, origin.Z + p1.Z), new WPos(origin.X + p2.X, origin.Z + p2.Z));
}
}
Loading

0 comments on commit 0eba4a5

Please sign in to comment.