Skip to content

Commit

Permalink
Merge pull request #540 from FFXIV-CombatReborn/mergeWIP
Browse files Browse the repository at this point in the history
some optimisations
  • Loading branch information
CarnifexOptimus authored Jan 3, 2025
2 parents 1e8c330 + 1c664ff commit 0a24d87
Show file tree
Hide file tree
Showing 10 changed files with 655 additions and 110 deletions.
7 changes: 3 additions & 4 deletions BossMod/BossModule/AOEShapes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,7 @@ public override void Outline(MiniArena arena, WPos origin, Angle rotation, uint
public override Func<WPos, float> Distance(WPos origin, Angle rotation)
{
shapeDistance ??= new PolygonWithHolesDistanceFunction(origin, Polygon ?? GetCombinedPolygon(origin));
var dis = shapeDistance.Value.Distance;
return InvertForbiddenZone ? p => -dis(p) : dis;
return InvertForbiddenZone ? shapeDistance.Value.InvertedDistance : shapeDistance.Value.Distance;
}
}

Expand All @@ -352,7 +351,7 @@ public sealed record class AOEShapeCustomAlt(RelSimplifiedComplexPolygon Poly, A
public override void Outline(MiniArena arena, WPos origin, Angle rotation, uint color = 0) => arena.AddComplexPolygon(origin, (rotation + DirectionOffset).ToDirection(), Poly, color);
public override Func<WPos, float> Distance(WPos origin, Angle rotation)
{
var shapeDistance = new PolygonWithHolesDistanceFunction(origin, Poly.Transform(default, (-rotation - DirectionOffset).ToDirection())).Distance;
return InvertForbiddenZone ? p => -shapeDistance(p) : shapeDistance;
return InvertForbiddenZone ? new PolygonWithHolesDistanceFunction(origin, Poly.Transform(default, (-rotation - DirectionOffset).ToDirection())).InvertedDistance
: new PolygonWithHolesDistanceFunction(origin, Poly.Transform(default, (-rotation - DirectionOffset).ToDirection())).Distance;
}
}
55 changes: 42 additions & 13 deletions BossMod/BossModule/ArenaBounds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ public abstract record class ArenaBounds(float Radius, float MapResolution, floa
// fields below are used for clipping & drawing borders
public const float Half = 0.5f;
public readonly PolygonClipper Clipper = new();
public float MaxApproxError { get; private set; }
public RelSimplifiedComplexPolygon ShapeSimplified { get; private set; } = new();
public List<RelTriangle> ShapeTriangulation { get; private set; } = [];
public float MaxApproxError;
public RelSimplifiedComplexPolygon ShapeSimplified = new();
public List<RelTriangle> ShapeTriangulation = [];
private readonly PolygonClipper.Operand _clipOperand = new();
public readonly Dictionary<object, object> Cache = [];

Expand Down Expand Up @@ -40,7 +40,7 @@ public float ScreenHalfSize
public abstract WDir ClampToBounds(WDir offset);

// functions for clipping various shapes to bounds; all shapes are expected to be defined relative to bounds center
public List<RelTriangle> ClipAndTriangulate(IEnumerable<WDir> poly) => Clipper.Intersect(new(poly), _clipOperand).Triangulate();
public List<RelTriangle> ClipAndTriangulate(WDir[] poly) => Clipper.Intersect(new PolygonClipper.Operand((ReadOnlySpan<WDir>)poly), _clipOperand).Triangulate();
public List<RelTriangle> ClipAndTriangulate(RelSimplifiedComplexPolygon poly) => Clipper.Intersect(new(poly), _clipOperand).Triangulate();

public List<RelTriangle> ClipAndTriangulateCone(WDir centerOffset, float innerRadius, float outerRadius, Angle centerDirection, Angle halfAngle)
Expand All @@ -50,25 +50,54 @@ public List<RelTriangle> ClipAndTriangulateCone(WDir centerOffset, float innerRa
return [];

var fullCircle = halfAngle.Rad >= MathF.PI;
var donut = innerRadius > 0;
var donut = innerRadius != 0;
var points = (donut, fullCircle) switch
{
(false, false) => CurveApprox.CircleSector(outerRadius, centerDirection - halfAngle, centerDirection + halfAngle, MaxApproxError),
(false, true) => CurveApprox.Circle(outerRadius, MaxApproxError),
(true, false) => CurveApprox.DonutSector(innerRadius, outerRadius, centerDirection - halfAngle, centerDirection + halfAngle, MaxApproxError),
(true, true) => CurveApprox.Donut(innerRadius, outerRadius, MaxApproxError),
};
return ClipAndTriangulate(points.Select(p => p + centerOffset));
for (var i = 0; i < points.Length; ++i)
{
points[i] += centerOffset;
}
return ClipAndTriangulate(points);
}

public List<RelTriangle> ClipAndTriangulateCircle(WDir centerOffset, float radius)
=> ClipAndTriangulate(CurveApprox.Circle(radius, MaxApproxError).Select(p => p + centerOffset));
{
var points = CurveApprox.Circle(radius, MaxApproxError);
for (var i = 0; i < points.Length; ++i)
{
points[i] += centerOffset;
}
return ClipAndTriangulate(points);
}

public List<RelTriangle> ClipAndTriangulateCapsule(WDir centerOffset, WDir direction, float radius, float length)
=> ClipAndTriangulate(CurveApprox.Capsule(direction, length, radius, CurveApprox.ScreenError).Select(p => p + centerOffset));
{
var points = CurveApprox.Capsule(direction, length, radius, MaxApproxError);
for (var i = 0; i < points.Length; ++i)
{
points[i] += centerOffset;
}
return ClipAndTriangulate(points);
}

public List<RelTriangle> ClipAndTriangulateDonut(WDir centerOffset, float innerRadius, float outerRadius)
=> innerRadius < outerRadius && innerRadius >= 0
? ClipAndTriangulate(CurveApprox.Donut(innerRadius, outerRadius, MaxApproxError).Select(p => p + centerOffset))
: [];
{
if (innerRadius < outerRadius && innerRadius >= 0)
{
var points = CurveApprox.Donut(innerRadius, outerRadius, MaxApproxError);
for (var i = 0; i < points.Length; ++i)
{
points[i] += centerOffset;
}
return ClipAndTriangulate(points);
}
return [];
}

public List<RelTriangle> ClipAndTriangulateTri(WDir oa, WDir ob, WDir oc)
=> ClipAndTriangulate([oa, ob, oc]);
Expand Down Expand Up @@ -128,7 +157,7 @@ public override WDir ClampToBounds(WDir offset)
private Pathfinding.Map BuildMap()
{
var map = new Pathfinding.Map(MapResolution, default, Radius, Radius);
map.BlockPixelsInsideConvex(p => -ShapeDistance.Circle(default, Radius)(p), -1, 0);
map.BlockPixelsInsideConvex(ShapeDistance.InvertedCircle(default, Radius), -1, 0);
return map;
}
}
Expand All @@ -149,7 +178,7 @@ private static float CalculateScaleFactor(Angle Rotation)
private Pathfinding.Map BuildMap()
{
var map = new Pathfinding.Map(MapResolution, default, HalfWidth, HalfHeight, Rotation);
map.BlockPixelsInsideConvex(p => -ShapeDistance.Rect(default, Rotation, HalfHeight, HalfHeight, HalfWidth)(p), -1, 0);
map.BlockPixelsInsideConvex(ShapeDistance.InvertedRect(default, Rotation, HalfHeight, HalfHeight, HalfWidth), -1, 0);
return map;
}

Expand Down
28 changes: 21 additions & 7 deletions BossMod/BossModule/MiniArena.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,16 @@ public void AddDonutCone(WPos center, float innerRadius, float outerRadius, Angl
var sDirP = sDir + halfAngle.Rad;
var sDirN = sDir - halfAngle.Rad;
drawlist.PathArcTo(sCenter, innerRadius / Bounds.Radius * ScreenHalfSize, sDirP, sDirN);
drawlist.PathArcTo(sCenter, innerRadius / Bounds.Radius * ScreenHalfSize, sDirN, sDirP);
drawlist.PathArcTo(sCenter, outerRadius / Bounds.Radius * ScreenHalfSize, sDirN, sDirP);
drawlist.PathStroke(color != 0 ? color : Colors.Danger, ImDrawFlags.Closed, thickness);
}

public void AddCapsule(WPos start, WDir direction, float radius, float length, uint color = 0, float thickness = 1)
{
var dirNorm = direction.Normalized();
var halfLength = length * 0.5f;
var capsuleStart = start - dirNorm * halfLength;
var capsuleEnd = start + dirNorm * halfLength;
var halfLengthdirNorm = dirNorm * length * 0.5f;
var capsuleStart = start - halfLengthdirNorm;
var capsuleEnd = start + halfLengthdirNorm;
var orthoDir = dirNorm.OrthoR();

var drawList = ImGui.GetWindowDrawList();
Expand Down Expand Up @@ -315,9 +315,23 @@ public void ZoneRect(WPos start, WPos end, float halfWidth, uint color)
=> Zone(_triCache[TriangulationCache.GetKeyHash(9, start, end, halfWidth)] ??= _bounds.ClipAndTriangulateRect(start - Center, end - Center, halfWidth), color);
public void ZoneComplex(WPos origin, Angle direction, RelSimplifiedComplexPolygon poly, uint color)
=> Zone(_triCache[TriangulationCache.GetKeyHash(10, origin, direction, poly)] ?? Bounds.ClipAndTriangulate(poly.Transform(origin - Center, direction.ToDirection())), color);
public void ZonePoly(object key, IEnumerable<WPos> contour, uint color)
=> Zone(_triCache[TriangulationCache.GetKeyHash(11, key)] ??= _bounds.ClipAndTriangulate(contour.Select(p => p - Center)), color);
public void ZoneRelPoly(object key, IEnumerable<WDir> relContour, uint color)
public void ZonePoly(object key, WPos[] contour, uint color)
{
var hash = TriangulationCache.GetKeyHash(11, key);
var triangulation = _triCache[hash];
if (triangulation == null)
{
var adjustedContour = new WDir[contour.Length];
for (var i = 0; i < contour.Length; ++i)
{
adjustedContour[i] = contour[i] - Center;
}
triangulation = _bounds.ClipAndTriangulate(adjustedContour);
_triCache[hash] = triangulation;
}
Zone(triangulation, color);
}
public void ZoneRelPoly(object key, WDir[] relContour, uint color)
=> Zone(_triCache[TriangulationCache.GetKeyHash(12, key)] ??= _bounds.ClipAndTriangulate(relContour), color);
public void ZoneRelPoly(int key, RelSimplifiedComplexPolygon poly, uint color)
=> Zone(_triCache[key] ??= _bounds.ClipAndTriangulate(poly), color);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public override void AddAIHints(int slot, Actor actor, PartyRolesConfig.Assignme
if (Actors.Contains(actor))
hints.AddForbiddenZone(ShapeDistance.Rect(Arena.Center + new WDir(19, 0), Arena.Center + new WDir(-19, 0), 20), Activation);
else if (Chasers.Any(x => x.Target == actor))
hints.AddForbiddenZone(ShapeDistance.InvertedRect(actor.Position, 90.Degrees(), 40, 40, 3));
hints.AddForbiddenZone(ShapeDistance.InvertedRect(actor.Position, new WDir(1, 0), 40, 40, 3));
}

public override void Update()
Expand Down
5 changes: 4 additions & 1 deletion BossMod/Modules/Endwalker/Extreme/Ex7Zeromus/Ex7Zeromus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ class BigCrunchPuddle(BossModule module) : Components.LocationTargetedAOEs(modul
class BigCrunchSpread(BossModule module) : Components.SpreadFromCastTargets(module, ActionID.MakeSpell(AID.BigCrunchSpread), 5);

[ModuleInfo(BossModuleInfo.Maturity.Verified, GroupType = BossModuleInfo.GroupType.CFC, GroupID = 965, NameID = 12586, PlanLevel = 90)]
public class Ex7Zeromus(WorldState ws, Actor primary) : BossModule(ws, primary, new(100, 100), new ArenaBoundsSquare(20));
public class Ex7Zeromus(WorldState ws, Actor primary) : BossModule(ws, primary, ArenaCenter, new ArenaBoundsSquare(20))
{
public static readonly WPos ArenaCenter = new(100, 100);
}
54 changes: 29 additions & 25 deletions BossMod/Modules/Endwalker/Extreme/Ex7Zeromus/VoidMeteor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@

class MeteorImpactProximity(BossModule module) : Components.SelfTargetedAOEs(module, ActionID.MakeSpell(AID.MeteorImpactProximity), new AOEShapeCircle(10)); // TODO: verify falloff

class MeteorImpactCharge(BossModule module) : BossComponent(module)
class MeteorImpactCharge(BossModule module) : Components.GenericAOEs(module)
{
struct PlayerState
{
public Actor? TetherSource;
public int Order;
public bool Stretched;
public bool NonClipping;
public List<RelTriangle>? DangerZone;
}

public int NumCasts { get; private set; }
private int _numTethers;
private readonly List<WPos> _meteors = [];
private readonly List<WPos> _meteors = new(18);
private readonly PlayerState[] _playerStates = new PlayerState[PartyState.MaxPartySize];
private AOEInstance? _aoe;
private readonly List<PolygonCustom> polygons = new(18);

private const float _radius = 2;
private const int _ownThickness = 2;
private const int _otherThickness = 1;
private const bool _drawShadows = true;

public override void AddHints(int slot, Actor actor, TextHints hints)
{
Expand All @@ -37,29 +36,38 @@ public override void AddHints(int slot, Actor actor, TextHints hints)
hints.Add("GTFO from charges!");
}

public override IEnumerable<AOEInstance> ActiveAOEs(int slot, Actor actor) => Utils.ZeroOrOne(_aoe);

public override PlayerPriority CalcPriority(int pcSlot, Actor pc, int playerSlot, Actor player, ref uint customColor) => SourceIfActive(playerSlot) != null ? PlayerPriority.Interesting : PlayerPriority.Normal;

public override void DrawArenaBackground(int pcSlot, Actor pc)
{
if (_drawShadows && SourceIfActive(pcSlot) is var source && source != null)
if (SourceIfActive(pcSlot) is var source && source != null)
{
ref var state = ref _playerStates.AsSpan()[pcSlot];
state.DangerZone ??= BuildShadowZone(source.Position - Module.Center);
Arena.Zone(state.DangerZone, Colors.AOE);
if (_aoe == null)
{
for (var i = 0; i < _meteors.Count; ++i)
{
polygons.Add(new PolygonCustom(BuildShadowPolygon(source.Position - Arena.Center, _meteors[i] - Arena.Center, Arena.Bounds.MaxApproxError)));
}
_aoe = new(new AOEShapeCustom(polygons), Arena.Center);
}
}
base.DrawArenaBackground(pcSlot, pc);
}

public override void DrawArenaForeground(int pcSlot, Actor pc)
{
foreach (var m in _meteors)
Arena.AddCircle(m, _radius, Colors.Object);
for (var i = 0; i < _meteors.Count; ++i)
Arena.AddCircle(_meteors[i], _radius, Colors.Object);

foreach (var (slot, target) in Raid.WithSlot(true))
{
if (SourceIfActive(slot) is var source && source != null)
{
var thickness = slot == pcSlot ? _ownThickness : _otherThickness;
if (thickness > 0)
if (thickness != 0)
{
var norm = (target.Position - source.Position).Normalized().OrthoL() * 2;
var rot = Angle.FromDirection(target.Position - source.Position);
Expand Down Expand Up @@ -114,27 +122,23 @@ public override void OnTethered(Actor source, ActorTetherInfo tether)
_ => null
};

private static IEnumerable<WDir> BuildShadowPolygon(WDir sourceOffset, WDir meteorOffset)
private static List<WPos> BuildShadowPolygon(WDir sourceOffset, WDir meteorOffset, float maxerror)
{
var center = Ex7Zeromus.ArenaCenter;
var toMeteor = meteorOffset - sourceOffset;
var dirToMeteor = Angle.FromDirection(toMeteor);
var halfAngle = Angle.Asin(_radius * 2 / toMeteor.Length());
// intersection point is at dirToMeteor -+ halfAngle relative to source; relative to meteor, it is (dirToMeteor + 180) +- (90 - halfAngle)
var dirFromMeteor = dirToMeteor + 180.Degrees();
var halfAngleFromMeteor = 90.Degrees() - halfAngle;
foreach (var off in CurveApprox.CircleArc(_radius * 2, dirFromMeteor + halfAngleFromMeteor, dirFromMeteor - halfAngleFromMeteor, 0.2f))
yield return meteorOffset + off;
yield return sourceOffset + 100 * (dirToMeteor + halfAngle).ToDirection();
yield return sourceOffset + 100 * (dirToMeteor - halfAngle).ToDirection();
}

private List<RelTriangle> BuildShadowZone(WDir sourceOffset)
{
PolygonClipper.Operand set = new();
foreach (var m in _meteors)
set.AddContour(BuildShadowPolygon(sourceOffset, m - Module.Center));
var simplified = Arena.Bounds.Clipper.Simplify(set, Clipper2Lib.FillRule.NonZero);
return Arena.Bounds.ClipAndTriangulate(simplified);
var circlearc = CurveApprox.CircleArc(_radius * 2, dirFromMeteor + halfAngleFromMeteor, dirFromMeteor - halfAngleFromMeteor, maxerror);
var count = circlearc.Length;
List<WPos> vertices = new(count + 2);
for (var i = 0; i < count; ++i)
vertices.Add(meteorOffset + circlearc[i] + center);
vertices.Add(sourceOffset + 100 * (dirToMeteor + halfAngle).ToDirection() + center);
vertices.Add(sourceOffset + 100 * (dirToMeteor - halfAngle).ToDirection() + center);
return vertices;
}

private static bool IsClipped(WPos source, WPos target, WPos position) => position.InCircle(target, _radius) || position.InRect(source, target - source, _radius);
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Util/Intersect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public static bool CircleCone(WDir circleOffset, float circleRadius, float coneR
var normal = coneDir.OrthoL();
var sin = halfAngle.Sin();
var distFromAxis = circleOffset.Dot(normal);
var originInCone = (halfAngle.Rad - MathF.PI * 0.5f) switch
var originInCone = (halfAngle.Rad - Angle.HalfPi) switch
{
< 0 => correctSide && distFromAxis * distFromAxis <= lsq * sin * sin,
> 0 => correctSide || distFromAxis * distFromAxis >= lsq * sin * sin,
Expand Down
44 changes: 37 additions & 7 deletions BossMod/Util/Polygon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -575,10 +575,15 @@ static Edge[] GetEdges(ReadOnlySpan<WDir> vertices, WPos origin)
var edges = new Edge[count];

var prev = vertices[count - 1];
var originX = origin.X;
var originZ = origin.Z;

for (var i = 0; i < count; ++i)
{
var curr = vertices[i];
edges[i] = new(origin.X + prev.X, origin.Z + prev.Z, curr.X - prev.X, curr.Z - prev.Z);
var prevX = prev.X;
var prevZ = prev.Z;
edges[i] = new(originX + prevX, originZ + prevZ, curr.X - prevX, curr.Z - prevZ);
prev = curr;
}

Expand All @@ -588,22 +593,47 @@ static Edge[] GetEdges(ReadOnlySpan<WDir> vertices, WPos origin)

public readonly float Distance(WPos p)
{
var localPoint = new WDir(p.X - _origin.X, p.Z - _origin.Z);
var pX = p.X;
var pZ = p.Z;
var localPoint = new WDir(pX - _origin.X, pZ - _origin.Z);
var isInside = _polygon.Contains(localPoint);

var minDistanceSq = float.MaxValue;
var indices = _spatialIndex.Query(p.X, p.Z);

var indices = _spatialIndex.Query(pX, pZ);
for (var i = 0; i < indices.Length; ++i)
{
var edge = _edges[indices[i]];
var t = Math.Clamp(((p.X - edge.Ax) * edge.Dx + (p.Z - edge.Ay) * edge.Dy) * edge.InvLengthSq, 0, 1);
var distX = p.X - (edge.Ax + t * edge.Dx);
var distY = p.Z - (edge.Ay + t * edge.Dy);
var t = Math.Clamp(((pX - edge.Ax) * edge.Dx + (pZ - edge.Ay) * edge.Dy) * edge.InvLengthSq, 0, 1);
var distX = pX - (edge.Ax + t * edge.Dx);
var distY = pZ - (edge.Ay + t * edge.Dy);

minDistanceSq = Math.Min(minDistanceSq, distX * distX + distY * distY);
}

var minDistance = MathF.Sqrt(minDistanceSq);
return isInside ? -minDistance : minDistance;
}

public readonly float InvertedDistance(WPos p)
{
var pX = p.X;
var pZ = p.Z;
var localPoint = new WDir(pX - _origin.X, pZ - _origin.Z);
var isInside = _polygon.Contains(localPoint);
var minDistanceSq = float.MaxValue;

var indices = _spatialIndex.Query(pX, pZ);
for (var i = 0; i < indices.Length; ++i)
{
var edge = _edges[indices[i]];
var t = Math.Clamp(((pX - edge.Ax) * edge.Dx + (pZ - edge.Ay) * edge.Dy) * edge.InvLengthSq, 0, 1);
var distX = pX - (edge.Ax + t * edge.Dx);
var distY = pZ - (edge.Ay + t * edge.Dy);

minDistanceSq = Math.Min(minDistanceSq, distX * distX + distY * distY);
}

var minDistance = MathF.Sqrt(minDistanceSq);
return isInside ? minDistance : -minDistance;
}
}
1 change: 0 additions & 1 deletion BossMod/Util/Serialization.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;

Expand Down
Loading

0 comments on commit 0a24d87

Please sign in to comment.