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);
|
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,
|
public static List<Entity> Convert(Vector center, double semiMajor, double semiMinor,
|
||||||
double rotation, double startParam, double endParam, double tolerance = 0.001)
|
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 p0 = EvaluatePoint(semiMajor, semiMinor, rotation, ellipseCenter, t0);
|
||||||
var p1 = EvaluatePoint(semiMajor, semiMinor, rotation, ellipseCenter, t1);
|
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 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 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 points = new List<Vector> { p0, pMid, p1 };
|
||||||
var isReversed = SumSignedAngles(arcCenter, points) < 0;
|
var isReversed = SumSignedAngles(arcCenter, points) < 0;
|
||||||
|
|
||||||
|
|||||||
@@ -172,15 +172,15 @@ public class EllipseConverterTests
|
|||||||
var current = (Arc)result[i];
|
var current = (Arc)result[i];
|
||||||
var next = (Arc)result[i + 1];
|
var next = (Arc)result[i + 1];
|
||||||
var gap = current.EndPoint().DistanceTo(next.StartPoint());
|
var gap = current.EndPoint().DistanceTo(next.StartPoint());
|
||||||
Assert.True(gap < 0.001,
|
Assert.True(gap < 1e-6,
|
||||||
$"Gap of {gap:F6} between arc {i} and arc {i + 1}");
|
$"Gap of {gap:E4} between arc {i} and arc {i + 1}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastArc = (Arc)result[^1];
|
var lastArc = (Arc)result[^1];
|
||||||
var firstArc = (Arc)result[0];
|
var firstArc = (Arc)result[0];
|
||||||
var closingGap = lastArc.EndPoint().DistanceTo(firstArc.StartPoint());
|
var closingGap = lastArc.EndPoint().DistanceTo(firstArc.StartPoint());
|
||||||
Assert.True(closingGap < 0.001,
|
Assert.True(closingGap < 1e-6,
|
||||||
$"Closing gap of {closingGap:F6}");
|
$"Closing gap of {closingGap:E4}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
Reference in New Issue
Block a user