fix: eliminate endpoint gaps in EllipseConverter arc output
EllipseConverter computed arc radius from start point only, causing ~0.0009 unit gaps between consecutive arcs. Use circumcircle of (start, mid, end) points so both endpoints lie exactly on the arc. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,25 @@ namespace OpenNest.Geometry
|
||||
return new Vector(p1.X + s * n1.X, p1.Y + s * n1.Y);
|
||||
}
|
||||
|
||||
internal static Vector Circumcenter(Vector a, Vector b, Vector c)
|
||||
{
|
||||
var ax = a.X - c.X;
|
||||
var ay = a.Y - c.Y;
|
||||
var bx = b.X - c.X;
|
||||
var by = b.Y - c.Y;
|
||||
var D = 2.0 * (ax * by - ay * bx);
|
||||
|
||||
if (System.Math.Abs(D) < 1e-10)
|
||||
return Vector.Invalid;
|
||||
|
||||
var a2 = ax * ax + ay * ay;
|
||||
var b2 = bx * bx + by * by;
|
||||
var ux = (by * a2 - ay * b2) / D;
|
||||
var uy = (ax * b2 - bx * a2) / D;
|
||||
|
||||
return new Vector(ux + c.X, uy + c.Y);
|
||||
}
|
||||
|
||||
public static List<Entity> Convert(Vector center, double semiMajor, double semiMinor,
|
||||
double rotation, double startParam, double endParam, double tolerance = 0.001)
|
||||
{
|
||||
@@ -185,11 +204,20 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var p0 = EvaluatePoint(semiMajor, semiMinor, rotation, ellipseCenter, t0);
|
||||
var p1 = EvaluatePoint(semiMajor, semiMinor, rotation, ellipseCenter, t1);
|
||||
var pMid = EvaluatePoint(semiMajor, semiMinor, rotation, ellipseCenter, (t0 + t1) / 2);
|
||||
|
||||
// Use circumcircle of (p0, pMid, p1) so the arc passes through both
|
||||
// endpoints exactly, eliminating gaps between adjacent arcs.
|
||||
var cc = Circumcenter(p0, pMid, p1);
|
||||
if (cc.IsValid())
|
||||
{
|
||||
arcCenter = cc;
|
||||
radius = p0.DistanceTo(cc);
|
||||
}
|
||||
|
||||
var startAngle = System.Math.Atan2(p0.Y - arcCenter.Y, p0.X - arcCenter.X);
|
||||
var endAngle = System.Math.Atan2(p1.Y - arcCenter.Y, p1.X - arcCenter.X);
|
||||
|
||||
var pMid = EvaluatePoint(semiMajor, semiMinor, rotation, ellipseCenter, (t0 + t1) / 2);
|
||||
var points = new List<Vector> { p0, pMid, p1 };
|
||||
var isReversed = SumSignedAngles(arcCenter, points) < 0;
|
||||
|
||||
|
||||
@@ -172,15 +172,15 @@ public class EllipseConverterTests
|
||||
var current = (Arc)result[i];
|
||||
var next = (Arc)result[i + 1];
|
||||
var gap = current.EndPoint().DistanceTo(next.StartPoint());
|
||||
Assert.True(gap < 0.001,
|
||||
$"Gap of {gap:F6} between arc {i} and arc {i + 1}");
|
||||
Assert.True(gap < 1e-6,
|
||||
$"Gap of {gap:E4} between arc {i} and arc {i + 1}");
|
||||
}
|
||||
|
||||
var lastArc = (Arc)result[^1];
|
||||
var firstArc = (Arc)result[0];
|
||||
var closingGap = lastArc.EndPoint().DistanceTo(firstArc.StartPoint());
|
||||
Assert.True(closingGap < 0.001,
|
||||
$"Closing gap of {closingGap:F6}");
|
||||
Assert.True(closingGap < 1e-6,
|
||||
$"Closing gap of {closingGap:E4}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user