fix: remove self-intersecting loops from polygon offset
Polygon offset at concave corners creates geometry that folds back through itself. Added RemoveSelfIntersections() to Polygon that detects non-adjacent edge crossings and removes the smaller loop at each crossing. Applied to both collision detection and rendering. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using OpenNest.Math;
|
||||||
|
|
||||||
namespace OpenNest.Geometry
|
namespace OpenNest.Geometry
|
||||||
{
|
{
|
||||||
@@ -473,6 +474,118 @@ namespace OpenNest.Geometry
|
|||||||
get { return EntityType.Polygon; }
|
get { return EntityType.Polygon; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes self-intersecting loops from the polygon by finding non-adjacent
|
||||||
|
/// edge crossings and keeping the larger contour at each crossing.
|
||||||
|
/// </summary>
|
||||||
|
public void RemoveSelfIntersections()
|
||||||
|
{
|
||||||
|
if (!IsClosed() || Vertices.Count < 5)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool found = true;
|
||||||
|
|
||||||
|
while (found)
|
||||||
|
{
|
||||||
|
found = false;
|
||||||
|
int n = Vertices.Count - 1; // exclude closing vertex
|
||||||
|
|
||||||
|
for (int i = 0; i < n && !found; i++)
|
||||||
|
{
|
||||||
|
var a1 = Vertices[i];
|
||||||
|
var a2 = Vertices[i + 1];
|
||||||
|
|
||||||
|
for (int j = i + 2; j < n && !found; j++)
|
||||||
|
{
|
||||||
|
// Skip edges that share a vertex (first and last edge)
|
||||||
|
if (i == 0 && j == n - 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var b1 = Vertices[j];
|
||||||
|
var b2 = Vertices[j + 1];
|
||||||
|
|
||||||
|
Vector pt;
|
||||||
|
|
||||||
|
if (SegmentsIntersect(a1, a2, b1, b2, out pt))
|
||||||
|
{
|
||||||
|
// Two loops formed by the crossing:
|
||||||
|
// Loop A: vertices[0..i], pt, vertices[j+1..n-1], close
|
||||||
|
// Loop B: pt, vertices[i+1..j], close
|
||||||
|
var loopA = new List<Vector>();
|
||||||
|
|
||||||
|
for (int k = 0; k <= i; k++)
|
||||||
|
loopA.Add(Vertices[k]);
|
||||||
|
|
||||||
|
loopA.Add(pt);
|
||||||
|
|
||||||
|
for (int k = j + 1; k < n; k++)
|
||||||
|
loopA.Add(Vertices[k]);
|
||||||
|
|
||||||
|
loopA.Add(loopA[0]);
|
||||||
|
|
||||||
|
var loopB = new List<Vector>();
|
||||||
|
loopB.Add(pt);
|
||||||
|
|
||||||
|
for (int k = i + 1; k <= j; k++)
|
||||||
|
loopB.Add(Vertices[k]);
|
||||||
|
|
||||||
|
loopB.Add(pt);
|
||||||
|
|
||||||
|
var areaA = System.Math.Abs(CalculateArea(loopA));
|
||||||
|
var areaB = System.Math.Abs(CalculateArea(loopB));
|
||||||
|
|
||||||
|
Vertices = areaA >= areaB ? loopA : loopB;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool SegmentsIntersect(Vector a1, Vector a2, Vector b1, Vector b2, out Vector pt)
|
||||||
|
{
|
||||||
|
var da = a2 - a1;
|
||||||
|
var db = b2 - b1;
|
||||||
|
var cross = da.X * db.Y - da.Y * db.X;
|
||||||
|
|
||||||
|
if (cross.IsEqualTo(0.0))
|
||||||
|
{
|
||||||
|
pt = Vector.Zero;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dc = b1 - a1;
|
||||||
|
var t = (dc.X * db.Y - dc.Y * db.X) / cross;
|
||||||
|
var u = (dc.X * da.Y - dc.Y * da.X) / cross;
|
||||||
|
|
||||||
|
if (t > Tolerance.Epsilon && t < 1.0 - Tolerance.Epsilon &&
|
||||||
|
u > Tolerance.Epsilon && u < 1.0 - Tolerance.Epsilon)
|
||||||
|
{
|
||||||
|
pt = new Vector(a1.X + t * da.X, a1.Y + t * da.Y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pt = Vector.Zero;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CalculateArea(List<Vector> vertices)
|
||||||
|
{
|
||||||
|
double xsum = 0;
|
||||||
|
double ysum = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < vertices.Count - 1; i++)
|
||||||
|
{
|
||||||
|
var current = vertices[i];
|
||||||
|
var next = vertices[i + 1];
|
||||||
|
|
||||||
|
xsum += current.X * next.Y;
|
||||||
|
ysum += current.Y * next.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (xsum - ysum) * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
internal void Cleanup()
|
internal void Cleanup()
|
||||||
{
|
{
|
||||||
for (int i = Vertices.Count - 1; i > 0; i--)
|
for (int i = Vertices.Count - 1; i > 0; i--)
|
||||||
|
|||||||
@@ -789,6 +789,7 @@ namespace OpenNest
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var polygon = offsetEntity.ToPolygonWithTolerance(PushChordTolerance);
|
var polygon = offsetEntity.ToPolygonWithTolerance(PushChordTolerance);
|
||||||
|
polygon.RemoveSelfIntersections();
|
||||||
polygon.Offset(part.Location);
|
polygon.Offset(part.Location);
|
||||||
lines.AddRange(polygon.ToLines());
|
lines.AddRange(polygon.ToLines());
|
||||||
}
|
}
|
||||||
@@ -810,6 +811,7 @@ namespace OpenNest
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var polygon = offsetEntity.ToPolygonWithTolerance(PushChordTolerance);
|
var polygon = offsetEntity.ToPolygonWithTolerance(PushChordTolerance);
|
||||||
|
polygon.RemoveSelfIntersections();
|
||||||
polygon.Offset(part.Location);
|
polygon.Offset(part.Location);
|
||||||
lines.AddRange(GetDirectionalLines(polygon, facingDirection));
|
lines.AddRange(GetDirectionalLines(polygon, facingDirection));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -459,9 +459,20 @@ namespace OpenNest.Controls
|
|||||||
if (offsetEntity == null)
|
if (offsetEntity == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
offsetEntity.Offset(part.Location);
|
var polygon = offsetEntity.ToPolygonWithTolerance(0.01);
|
||||||
|
polygon.RemoveSelfIntersections();
|
||||||
|
polygon.Offset(part.Location);
|
||||||
|
|
||||||
var path = GraphicsHelper.GetGraphicsPath(offsetEntity);
|
if (polygon.Vertices.Count < 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var pts = new PointF[polygon.Vertices.Count];
|
||||||
|
|
||||||
|
for (int j = 0; j < pts.Length; j++)
|
||||||
|
pts[j] = new PointF((float)polygon.Vertices[j].X, (float)polygon.Vertices[j].Y);
|
||||||
|
|
||||||
|
var path = new GraphicsPath();
|
||||||
|
path.AddLines(pts);
|
||||||
path.Transform(Matrix);
|
path.Transform(Matrix);
|
||||||
g.DrawPath(offsetPen, path);
|
g.DrawPath(offsetPen, path);
|
||||||
path.Dispose();
|
path.Dispose();
|
||||||
|
|||||||
Reference in New Issue
Block a user