style: apply CSharpier formatting to files merged from arc-tangency branch

This commit is contained in:
aj
2026-09-20 16:54:16 -04:00
parent 54694f9b17
commit 27684c3782
12 changed files with 790 additions and 260 deletions
+209 -85
View File
@@ -15,8 +15,10 @@ public class ArcCandidate
public double MaxDeviation { get; set; }
public Box BoundingBox { get; set; }
public bool IsSelected { get; set; } = true;
/// <summary>First point of the original line segments this candidate covers.</summary>
public Vector FirstPoint { get; set; }
/// <summary>Last point of the original line segments this candidate covers.</summary>
public Vector LastPoint { get; set; }
}
@@ -46,9 +48,7 @@ public class MirrorAxisResult
var dx = p.X - Point.X;
var dy = p.Y - Point.Y;
var dot = dx * Direction.X + dy * Direction.Y;
return new Vector(
p.X - 2 * (dx - dot * Direction.X),
p.Y - 2 * (dy - dot * Direction.Y));
return new Vector(p.X - 2 * (dx - dot * Direction.X), p.Y - 2 * (dy - dot * Direction.Y));
}
}
@@ -74,9 +74,14 @@ public class GeometrySimplifier
var runStart = i;
var layerName = entities[i].Layer?.Name;
var lineCount = 0;
while (i < entities.Count && (entities[i] is Line || entities[i] is Arc) && entities[i].Layer?.Name == layerName)
while (
i < entities.Count
&& (entities[i] is Line || entities[i] is Arc)
&& entities[i].Layer?.Name == layerName
)
{
if (entities[i] is Line) lineCount++;
if (entities[i] is Line)
lineCount++;
i++;
}
var runEnd = i - 1;
@@ -90,10 +95,7 @@ public class GeometrySimplifier
public Shape Apply(Shape shape, List<ArcCandidate> candidates)
{
var selected = candidates
.Where(c => c.IsSelected)
.OrderBy(c => c.StartIndex)
.ToList();
var selected = candidates.Where(c => c.IsSelected).OrderBy(c => c.StartIndex).ToList();
var newEntities = new List<Entity>();
var i = 0;
@@ -132,11 +134,10 @@ public class GeometrySimplifier
foreach (var e in shape.Entities)
midpoints.Add(e.BoundingBox.Center);
if (midpoints.Count < 4) return MirrorAxisResult.None;
if (midpoints.Count < 4)
return MirrorAxisResult.None;
var centroid = new Vector(
midpoints.Average(p => p.X),
midpoints.Average(p => p.Y));
var centroid = new Vector(midpoints.Average(p => p.X), midpoints.Average(p => p.Y));
var cx = centroid.X;
var cy = centroid.Y;
@@ -190,8 +191,7 @@ public class GeometrySimplifier
return bestResult.Score >= 0.8 ? bestResult : MirrorAxisResult.None;
}
private static double NormalizeAngle(double angle) =>
angle < 0 ? angle + Angle.TwoPI : angle;
private static double NormalizeAngle(double angle) => angle < 0 ? angle + Angle.TwoPI : angle;
private static Vector Normalize(Vector v)
{
@@ -231,7 +231,8 @@ public class GeometrySimplifier
for (var j = 0; j < points.Count; j++)
{
if (i == j) continue;
if (i == j)
continue;
var d = reflected.DistanceTo(points[j]);
if (d < matchTol)
{
@@ -251,17 +252,20 @@ public class GeometrySimplifier
/// </summary>
public void Symmetrize(List<ArcCandidate> candidates, MirrorAxisResult axis)
{
if (!axis.IsValid || candidates.Count < 2) return;
if (!axis.IsValid || candidates.Count < 2)
return;
var paired = new HashSet<int>();
for (var i = 0; i < candidates.Count; i++)
{
if (paired.Contains(i)) continue;
if (paired.Contains(i))
continue;
var ci = candidates[i];
var ciCenter = ci.BoundingBox.Center;
if (PerpendicularDistance(ciCenter, axis.Point, axis.Direction) < 0.1) continue; // on the axis
if (PerpendicularDistance(ciCenter, axis.Point, axis.Direction) < 0.1)
continue; // on the axis
var mirrorCenter = axis.Reflect(ciCenter);
@@ -269,7 +273,8 @@ public class GeometrySimplifier
var bestDist = double.MaxValue;
for (var j = i + 1; j < candidates.Count; j++)
{
if (paired.Contains(j)) continue;
if (paired.Contains(j))
continue;
var d = mirrorCenter.DistanceTo(candidates[j].BoundingBox.Center);
if (d < bestDist)
{
@@ -279,7 +284,8 @@ public class GeometrySimplifier
}
var matchTol = System.Math.Max(ci.BoundingBox.Width, ci.BoundingBox.Length) * 0.5;
if (bestJ < 0 || bestDist > matchTol) continue;
if (bestJ < 0 || bestDist > matchTol)
continue;
paired.Add(i);
paired.Add(bestJ);
@@ -287,7 +293,10 @@ public class GeometrySimplifier
var cj = candidates[bestJ];
var sourceIdx = i;
var targetIdx = bestJ;
if (cj.LineCount > ci.LineCount || (cj.LineCount == ci.LineCount && cj.MaxDeviation < ci.MaxDeviation))
if (
cj.LineCount > ci.LineCount
|| (cj.LineCount == ci.LineCount && cj.MaxDeviation < ci.MaxDeviation)
)
{
sourceIdx = bestJ;
targetIdx = i;
@@ -323,8 +332,12 @@ public class GeometrySimplifier
var mirrorEp = axis.Reflect(ep);
// Mirroring reverses winding — swap start/end to preserve arc direction
var mirrorStart = NormalizeAngle(System.Math.Atan2(mirrorEp.Y - mirrorCenter.Y, mirrorEp.X - mirrorCenter.X));
var mirrorEnd = NormalizeAngle(System.Math.Atan2(mirrorSp.Y - mirrorCenter.Y, mirrorSp.X - mirrorCenter.X));
var mirrorStart = NormalizeAngle(
System.Math.Atan2(mirrorEp.Y - mirrorCenter.Y, mirrorEp.X - mirrorCenter.X)
);
var mirrorEnd = NormalizeAngle(
System.Math.Atan2(mirrorSp.Y - mirrorCenter.Y, mirrorSp.X - mirrorCenter.X)
);
var result = new Arc(mirrorCenter, arc.Radius, mirrorStart, mirrorEnd, arc.IsReversed);
result.Layer = arc.Layer;
@@ -332,7 +345,12 @@ public class GeometrySimplifier
return result;
}
private void FindCandidatesInRun(List<Entity> entities, int runStart, int runEnd, List<ArcCandidate> candidates)
private void FindCandidatesInRun(
List<Entity> entities,
int runStart,
int runEnd,
List<ArcCandidate> candidates
)
{
var j = runStart;
var chainedTangent = Vector.Invalid;
@@ -349,45 +367,63 @@ public class GeometrySimplifier
chainedTangent = ComputeEndTangent(result.Center, result.Points);
var arc = CreateArc(result.Center, result.Radius, result.Points, entities[j]);
candidates.Add(new ArcCandidate
{
StartIndex = j,
EndIndex = result.EndIndex,
FittedArc = arc,
MaxDeviation = result.Deviation,
BoundingBox = result.Points.GetBoundingBox(),
FirstPoint = arc.StartPoint(),
LastPoint = arc.EndPoint(),
});
candidates.Add(
new ArcCandidate
{
StartIndex = j,
EndIndex = result.EndIndex,
FittedArc = arc,
MaxDeviation = result.Deviation,
BoundingBox = result.Points.GetBoundingBox(),
FirstPoint = arc.StartPoint(),
LastPoint = arc.EndPoint(),
}
);
j = result.EndIndex + 1;
}
}
private record ArcFitResult(Vector Center, double Radius, double Deviation, List<Vector> Points, int EndIndex);
private record ArcFitResult(
Vector Center,
double Radius,
double Deviation,
List<Vector> Points,
int EndIndex
);
private ArcFitResult TryFitArcAt(List<Entity> entities, int start, int runEnd, Vector chainedTangent)
private ArcFitResult TryFitArcAt(
List<Entity> entities,
int start,
int runEnd,
Vector chainedTangent
)
{
var k = start + MinLines - 1;
if (k > runEnd) return null;
if (k > runEnd)
return null;
var points = CollectPoints(entities, start, k);
if (points.Count < 3) return null;
if (points.Count < 3)
return null;
var startTangent = EstimateStartTangent(entities, start, points, chainedTangent);
var endTangent = EstimateEndTangent(entities, k, points);
var (center, radius, dev) = TryFit(points, startTangent, endTangent);
if (!center.IsValid()) return null;
if (!center.IsValid())
return null;
// Extend the arc as far as possible
while (k + 1 <= runEnd)
{
var extPoints = CollectPoints(entities, start, k + 1);
if (extPoints.Count < 3) break;
if (extPoints.Count < 3)
break;
var extEndTangent = EstimateEndTangent(entities, k + 1, extPoints);
var (nc, nr, nd) = TryFit(extPoints, startTangent, extEndTangent);
if (!nc.IsValid()) break;
if (!nc.IsValid())
break;
k++;
center = nc;
@@ -407,7 +443,10 @@ public class GeometrySimplifier
}
private (Vector center, double radius, double deviation) TryFit(
List<Vector> points, TangentEstimate start, TangentEstimate end)
List<Vector> points,
TangentEstimate start,
TangentEstimate end
)
{
foreach (var (center, radius, dev) in FitAttempts(points, start, end))
{
@@ -434,23 +473,38 @@ public class GeometrySimplifier
/// passes exactly through both endpoints, so no gaps are introduced.
/// </summary>
private IEnumerable<(Vector center, double radius, double deviation)> FitAttempts(
List<Vector> points, TangentEstimate start, TangentEstimate end)
List<Vector> points,
TangentEstimate start,
TangentEstimate end
)
{
if (start.Trusted && !end.Trusted)
{
yield return ArcFit.FitWithStartTangent(points, start.Direction);
yield return ArcFit.FitThroughEndpointsWithTangents(points, start.Direction, end.Direction);
yield return ArcFit.FitThroughEndpointsWithTangents(
points,
start.Direction,
end.Direction
);
yield return FitWithEndTangent(points, end.Direction);
}
else if (end.Trusted && !start.Trusted)
{
yield return FitWithEndTangent(points, end.Direction);
yield return ArcFit.FitThroughEndpointsWithTangents(points, start.Direction, end.Direction);
yield return ArcFit.FitThroughEndpointsWithTangents(
points,
start.Direction,
end.Direction
);
yield return ArcFit.FitWithStartTangent(points, start.Direction);
}
else
{
yield return ArcFit.FitThroughEndpointsWithTangents(points, start.Direction, end.Direction);
yield return ArcFit.FitThroughEndpointsWithTangents(
points,
start.Direction,
end.Direction
);
yield return ArcFit.FitWithStartTangent(points, start.Direction);
yield return FitWithEndTangent(points, end.Direction);
}
@@ -463,7 +517,9 @@ public class GeometrySimplifier
/// by running the start-tangent fit on the reversed point sequence.
/// </summary>
private static (Vector center, double radius, double deviation) FitWithEndTangent(
List<Vector> points, Vector endTangent)
List<Vector> points,
Vector endTangent
)
{
var reversed = new List<Vector>(points);
reversed.Reverse();
@@ -483,7 +539,11 @@ public class GeometrySimplifier
private const double NeighborEdgeFactor = 3.0;
private static TangentEstimate EstimateStartTangent(
List<Entity> entities, int start, List<Vector> points, Vector chainedTangent)
List<Entity> entities,
int start,
List<Vector> points,
Vector chainedTangent
)
{
if (chainedTangent.IsValid())
return new TangentEstimate(chainedTangent, true);
@@ -495,23 +555,39 @@ public class GeometrySimplifier
if (start > 0)
{
var prev = entities[start - 1];
var prevEnd = prev switch { Line l => l.EndPoint, Arc a => a.EndPoint(), _ => Vector.Invalid };
var prevEnd = prev switch
{
Line l => l.EndPoint,
Arc a => a.EndPoint(),
_ => Vector.Invalid,
};
if (prevEnd.IsValid() && prevEnd.DistanceTo(points[0]) < 1e-6)
{
if (prev is Arc)
return new TangentEstimate(GetExitDirection(prev), true);
if (prev is Line prevLine && prevLine.StartPoint.DistanceTo(prevLine.EndPoint) >= NeighborEdgeFactor * firstChordLen)
if (
prev is Line prevLine
&& prevLine.StartPoint.DistanceTo(prevLine.EndPoint)
>= NeighborEdgeFactor * firstChordLen
)
return new TangentEstimate(GetExitDirection(prevLine), true);
}
}
var chord = new Vector(points[1].X - points[0].X, points[1].Y - points[0].Y);
if (points.Count >= 3)
return new TangentEstimate(EstimateVertexTangent(points[0], points[1], points[2], chord), false);
return new TangentEstimate(
EstimateVertexTangent(points[0], points[1], points[2], chord),
false
);
return new TangentEstimate(chord, false);
}
private static TangentEstimate EstimateEndTangent(List<Entity> entities, int k, List<Vector> points)
private static TangentEstimate EstimateEndTangent(
List<Entity> entities,
int k,
List<Vector> points
)
{
if (entities[k] is Arc endArc)
return new TangentEstimate(GetExitDirection(endArc), true);
@@ -520,19 +596,31 @@ public class GeometrySimplifier
if (k + 1 < entities.Count)
{
var next = entities[k + 1];
var nextStart = next switch { Line l => l.StartPoint, Arc a => a.StartPoint(), _ => Vector.Invalid };
var nextStart = next switch
{
Line l => l.StartPoint,
Arc a => a.StartPoint(),
_ => Vector.Invalid,
};
if (nextStart.IsValid() && nextStart.DistanceTo(points[^1]) < 1e-6)
{
if (next is Arc nextArc)
return new TangentEstimate(GetEntryDirection(nextArc), true);
if (next is Line nextLine && nextLine.StartPoint.DistanceTo(nextLine.EndPoint) >= NeighborEdgeFactor * lastChordLen)
if (
next is Line nextLine
&& nextLine.StartPoint.DistanceTo(nextLine.EndPoint)
>= NeighborEdgeFactor * lastChordLen
)
return new TangentEstimate(GetExitDirection(nextLine), true);
}
}
var chord = new Vector(points[^1].X - points[^2].X, points[^1].Y - points[^2].Y);
if (points.Count >= 3)
return new TangentEstimate(EstimateVertexTangent(points[^1], points[^2], points[^3], chord), false);
return new TangentEstimate(
EstimateVertexTangent(points[^1], points[^2], points[^3], chord),
false
);
return new TangentEstimate(chord, false);
}
@@ -563,14 +651,18 @@ public class GeometrySimplifier
/// <summary>
/// Returns the entry direction (tangent at start point) of an entity.
/// </summary>
private static Vector GetEntryDirection(Entity entity) => entity switch
{
Line line => new Vector(line.EndPoint.X - line.StartPoint.X, line.EndPoint.Y - line.StartPoint.Y),
Arc arc => arc.IsReversed
? new Vector(System.Math.Sin(arc.StartAngle), -System.Math.Cos(arc.StartAngle))
: new Vector(-System.Math.Sin(arc.StartAngle), System.Math.Cos(arc.StartAngle)),
_ => Vector.Invalid,
};
private static Vector GetEntryDirection(Entity entity) =>
entity switch
{
Line line => new Vector(
line.EndPoint.X - line.StartPoint.X,
line.EndPoint.Y - line.StartPoint.Y
),
Arc arc => arc.IsReversed
? new Vector(System.Math.Sin(arc.StartAngle), -System.Math.Cos(arc.StartAngle))
: new Vector(-System.Math.Sin(arc.StartAngle), System.Math.Cos(arc.StartAngle)),
_ => Vector.Invalid,
};
/// <summary>
/// Computes the tangent direction at the last point of a fitted arc,
@@ -622,9 +714,17 @@ public class GeometrySimplifier
var dInit = (maxSagitta * maxSagitta - halfChord * halfChord) / (2 * maxSagitta);
var range = System.Math.Max(System.Math.Abs(dInit) * 2, halfChord);
var dOpt = GoldenSectionMin(dInit - range, dInit + range,
d => ArcFit.MaxRadialDeviation(points, mx + d * nx, my + d * ny,
System.Math.Sqrt(halfChord * halfChord + d * d)));
var dOpt = GoldenSectionMin(
dInit - range,
dInit + range,
d =>
ArcFit.MaxRadialDeviation(
points,
mx + d * nx,
my + d * ny,
System.Math.Sqrt(halfChord * halfChord + d * d)
)
);
var center = new Vector(mx + dOpt * nx, my + dOpt * ny);
var radius = System.Math.Sqrt(halfChord * halfChord + dOpt * dOpt);
@@ -676,13 +776,22 @@ public class GeometrySimplifier
return points;
}
private static Arc CreateArc(Vector center, double radius, List<Vector> points, Entity sourceEntity)
private static Arc CreateArc(
Vector center,
double radius,
List<Vector> points,
Entity sourceEntity
)
{
var firstPoint = points[0];
var lastPoint = points[^1];
var startAngle = NormalizeAngle(System.Math.Atan2(firstPoint.Y - center.Y, firstPoint.X - center.X));
var endAngle = NormalizeAngle(System.Math.Atan2(lastPoint.Y - center.Y, lastPoint.X - center.X));
var startAngle = NormalizeAngle(
System.Math.Atan2(firstPoint.Y - center.Y, firstPoint.X - center.X)
);
var endAngle = NormalizeAngle(
System.Math.Atan2(lastPoint.Y - center.Y, lastPoint.X - center.X)
);
var isReversed = SumSignedAngles(center, points) < 0;
var arc = new Arc(center, radius, startAngle, endAngle, isReversed);
@@ -694,14 +803,18 @@ public class GeometrySimplifier
/// <summary>
/// Returns the exit direction (tangent at endpoint) of an entity.
/// </summary>
private static Vector GetExitDirection(Entity entity) => entity switch
{
Line line => new Vector(line.EndPoint.X - line.StartPoint.X, line.EndPoint.Y - line.StartPoint.Y),
Arc arc => arc.IsReversed
? new Vector(System.Math.Sin(arc.EndAngle), -System.Math.Cos(arc.EndAngle))
: new Vector(-System.Math.Sin(arc.EndAngle), System.Math.Cos(arc.EndAngle)),
_ => Vector.Invalid,
};
private static Vector GetExitDirection(Entity entity) =>
entity switch
{
Line line => new Vector(
line.EndPoint.X - line.StartPoint.X,
line.EndPoint.Y - line.StartPoint.Y
),
Arc arc => arc.IsReversed
? new Vector(System.Math.Sin(arc.EndAngle), -System.Math.Cos(arc.EndAngle))
: new Vector(-System.Math.Sin(arc.EndAngle), System.Math.Cos(arc.EndAngle)),
_ => Vector.Invalid,
};
/// <summary>
/// Sums signed angular change traversing consecutive points around a center.
@@ -715,8 +828,10 @@ public class GeometrySimplifier
var a1 = System.Math.Atan2(points[i].Y - center.Y, points[i].X - center.X);
var a2 = System.Math.Atan2(points[i + 1].Y - center.Y, points[i + 1].X - center.X);
var da = a2 - a1;
while (da > System.Math.PI) da -= Angle.TwoPI;
while (da < -System.Math.PI) da += Angle.TwoPI;
while (da > System.Math.PI)
da -= Angle.TwoPI;
while (da < -System.Math.PI)
da += Angle.TwoPI;
total += da;
}
return total;
@@ -727,7 +842,12 @@ public class GeometrySimplifier
/// back to the original line segments. This catches cases where points lie
/// on a large circle but the arc bulges far from the original straight geometry.
/// </summary>
private static double MaxArcToSegmentDeviation(List<Vector> points, Vector center, double radius, bool isReversed)
private static double MaxArcToSegmentDeviation(
List<Vector> points,
Vector center,
double radius,
bool isReversed
)
{
var startAngle = System.Math.Atan2(points[0].Y - center.Y, points[0].X - center.X);
var endAngle = System.Math.Atan2(points[^1].Y - center.Y, points[^1].X - center.X);
@@ -735,11 +855,13 @@ public class GeometrySimplifier
var sweep = endAngle - startAngle;
if (isReversed)
{
if (sweep > 0) sweep -= Angle.TwoPI;
if (sweep > 0)
sweep -= Angle.TwoPI;
}
else
{
if (sweep < 0) sweep += Angle.TwoPI;
if (sweep < 0)
sweep += Angle.TwoPI;
}
var sampleCount = System.Math.Max(10, (int)(System.Math.Abs(sweep) * radius * 10));
@@ -758,9 +880,11 @@ public class GeometrySimplifier
for (var j = 0; j < points.Count - 1; j++)
{
var dist = DistanceToSegment(arcPt, points[j], points[j + 1]);
if (dist < minDist) minDist = dist;
if (dist < minDist)
minDist = dist;
}
if (minDist > maxDev) maxDev = minDist;
if (minDist > maxDev)
maxDev = minDist;
}
return maxDev;
}