Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

some optimisations #540

Merged
merged 1 commit into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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