fix: correct compactor circle-to-circle directional distance
The vertex-to-entity approach in DirectionalDistance only sampled 4 cardinal points per circle, missing the true closest contact when circles are offset diagonally from the push direction. This caused the distance to be overestimated, pushing circles too far and creating overlap that worsened with distance from center. Add a curve-to-curve pass that computes exact contact distance by treating the problem as a ray from one center to an expanded circle (radius = r1 + r2) at the other center. Includes arc angular range validation for arc-to-arc and arc-to-circle cases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -648,6 +648,75 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Curve-to-curve direct distance.
|
||||
// The vertex-to-entity approach misses the closest contact between two
|
||||
// curved entities (circles/arcs) because only a few cardinal vertices are
|
||||
// sampled. The true closest contact along the push direction is found by
|
||||
// treating it as a ray from one center to an expanded circle at the other
|
||||
// center (radius = r1 + r2).
|
||||
for (var i = 0; i < movingEntities.Count; i++)
|
||||
{
|
||||
var me = movingEntities[i];
|
||||
double mcx, mcy, mr;
|
||||
|
||||
if (me is Circle mc)
|
||||
{
|
||||
mcx = mc.Center.X; mcy = mc.Center.Y; mr = mc.Radius;
|
||||
}
|
||||
else if (me is Arc ma)
|
||||
{
|
||||
mcx = ma.Center.X; mcy = ma.Center.Y; mr = ma.Radius;
|
||||
}
|
||||
else continue;
|
||||
|
||||
for (var j = 0; j < stationaryEntities.Count; j++)
|
||||
{
|
||||
var se = stationaryEntities[j];
|
||||
double scx, scy, sr;
|
||||
|
||||
if (se is Circle sc)
|
||||
{
|
||||
scx = sc.Center.X; scy = sc.Center.Y; sr = sc.Radius;
|
||||
}
|
||||
else if (se is Arc sa)
|
||||
{
|
||||
scx = sa.Center.X; scy = sa.Center.Y; sr = sa.Radius;
|
||||
}
|
||||
else continue;
|
||||
|
||||
var d = RayCircleDistance(mcx, mcy, scx, scy, mr + sr, dirX, dirY);
|
||||
|
||||
if (d >= minDist || d == double.MaxValue)
|
||||
continue;
|
||||
|
||||
// For arcs, verify the contact point falls within both arcs' angular ranges.
|
||||
if (me is Arc || se is Arc)
|
||||
{
|
||||
var mx = mcx + d * dirX;
|
||||
var my = mcy + d * dirY;
|
||||
var toCx = scx - mx;
|
||||
var toCy = scy - my;
|
||||
|
||||
if (me is Arc mArc)
|
||||
{
|
||||
var angle = Angle.NormalizeRad(System.Math.Atan2(toCy, toCx));
|
||||
if (!Angle.IsBetweenRad(angle, mArc.StartAngle, mArc.EndAngle, mArc.IsReversed))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (se is Arc sArc)
|
||||
{
|
||||
var angle = Angle.NormalizeRad(System.Math.Atan2(-toCy, -toCx));
|
||||
if (!Angle.IsBetweenRad(angle, sArc.StartAngle, sArc.EndAngle, sArc.IsReversed))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
minDist = d;
|
||||
if (d <= 0) return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return minDist;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user