Merge branch 'chore/csharpier-sweep'
# Conflicts: # OpenNest.Core/Geometry/ArcFit.cs # OpenNest.Core/Geometry/GeometrySimplifier.cs # OpenNest.Posts.GravographIS/GravographISWriter.cs # OpenNest.Posts.GravographIS/NestPolylineExtractor.cs
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -12,16 +12,18 @@ namespace OpenNest.Geometry
|
||||
private Vector center;
|
||||
private bool reversed;
|
||||
|
||||
public Arc()
|
||||
{
|
||||
}
|
||||
public Arc() { }
|
||||
|
||||
public Arc(double x, double y, double r, double a1, double a2, bool reversed = false)
|
||||
: this(new Vector(x, y), r, a1, a2, reversed)
|
||||
{
|
||||
}
|
||||
: this(new Vector(x, y), r, a1, a2, reversed) { }
|
||||
|
||||
public Arc(Vector center, double radius, double startAngle, double endAngle, bool reversed = false)
|
||||
public Arc(
|
||||
Vector center,
|
||||
double radius,
|
||||
double startAngle,
|
||||
double endAngle,
|
||||
bool reversed = false
|
||||
)
|
||||
{
|
||||
this.center = center;
|
||||
this.radius = radius;
|
||||
@@ -93,8 +95,7 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFullCircle() =>
|
||||
SweepAngle() >= Angle.TwoPI - Tolerance.Epsilon;
|
||||
public bool IsFullCircle() => SweepAngle() >= Angle.TwoPI - Tolerance.Epsilon;
|
||||
|
||||
/// <summary>
|
||||
/// Angle in radians between start and end angles.
|
||||
@@ -130,10 +131,7 @@ namespace OpenNest.Geometry
|
||||
public RotationType Rotation
|
||||
{
|
||||
get { return IsReversed ? RotationType.CW : RotationType.CCW; }
|
||||
set
|
||||
{
|
||||
IsReversed = (value == RotationType.CW);
|
||||
}
|
||||
set { IsReversed = (value == RotationType.CW); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -144,7 +142,8 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
return new Vector(
|
||||
Center.X + Radius * System.Math.Cos(StartAngle),
|
||||
Center.Y + Radius * System.Math.Sin(StartAngle));
|
||||
Center.Y + Radius * System.Math.Sin(StartAngle)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -155,7 +154,8 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
return new Vector(
|
||||
Center.X + Radius * System.Math.Cos(EndAngle),
|
||||
Center.Y + Radius * System.Math.Sin(EndAngle));
|
||||
Center.Y + Radius * System.Math.Sin(EndAngle)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -166,7 +166,8 @@ namespace OpenNest.Geometry
|
||||
var midAngle = StartAngle + (IsReversed ? -SweepAngle() / 2 : SweepAngle() / 2);
|
||||
return new Vector(
|
||||
Center.X + Radius * System.Math.Cos(midAngle),
|
||||
Center.Y + Radius * System.Math.Sin(midAngle));
|
||||
Center.Y + Radius * System.Math.Sin(midAngle)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -231,7 +232,10 @@ namespace OpenNest.Geometry
|
||||
return 1;
|
||||
|
||||
var maxAngle = 2.0 * System.Math.Acos(1.0 - tolerance / Radius);
|
||||
return System.Math.Max(1, (int)System.Math.Ceiling(System.Math.Abs(SweepAngle()) / maxAngle));
|
||||
return System.Math.Max(
|
||||
1,
|
||||
(int)System.Math.Ceiling(System.Math.Abs(SweepAngle()) / maxAngle)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -242,21 +246,23 @@ namespace OpenNest.Geometry
|
||||
public List<Vector> ToPoints(int segments = 1000, bool circumscribe = false)
|
||||
{
|
||||
var points = new List<Vector>();
|
||||
var stepAngle = reversed
|
||||
? -SweepAngle() / segments
|
||||
: SweepAngle() / segments;
|
||||
var stepAngle = reversed ? -SweepAngle() / segments : SweepAngle() / segments;
|
||||
|
||||
var r = circumscribe && segments > 0
|
||||
? Radius / System.Math.Cos(System.Math.Abs(stepAngle) / 2.0)
|
||||
: Radius;
|
||||
var r =
|
||||
circumscribe && segments > 0
|
||||
? Radius / System.Math.Cos(System.Math.Abs(stepAngle) / 2.0)
|
||||
: Radius;
|
||||
|
||||
for (int i = 0; i <= segments; ++i)
|
||||
{
|
||||
var angle = stepAngle * i + StartAngle;
|
||||
|
||||
points.Add(new Vector(
|
||||
System.Math.Cos(angle) * r + Center.X,
|
||||
System.Math.Sin(angle) * r + Center.Y));
|
||||
points.Add(
|
||||
new Vector(
|
||||
System.Math.Cos(angle) * r + Center.X,
|
||||
System.Math.Sin(angle) * r + Center.Y
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return points;
|
||||
@@ -470,7 +476,8 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
return new Vector(
|
||||
System.Math.Cos(angle) * Radius + Center.X,
|
||||
System.Math.Sin(angle) * Radius + Center.Y);
|
||||
System.Math.Sin(angle) * Radius + Center.Y
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -500,7 +507,8 @@ namespace OpenNest.Geometry
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc, out List<Vector> pts)
|
||||
{
|
||||
return Intersect.Intersects(this, arc, out pts); ;
|
||||
return Intersect.Intersects(this, arc, out pts);
|
||||
;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,10 +17,14 @@ namespace OpenNest.Geometry
|
||||
|
||||
foreach (var box in boxes)
|
||||
{
|
||||
if (box.Left < minX) minX = box.Left;
|
||||
if (box.Right > maxX) maxX = box.Right;
|
||||
if (box.Bottom < minY) minY = box.Bottom;
|
||||
if (box.Top > maxY) maxY = box.Top;
|
||||
if (box.Left < minX)
|
||||
minX = box.Left;
|
||||
if (box.Right > maxX)
|
||||
maxX = box.Right;
|
||||
if (box.Bottom < minY)
|
||||
minY = box.Bottom;
|
||||
if (box.Top > maxY)
|
||||
maxY = box.Top;
|
||||
}
|
||||
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY);
|
||||
@@ -41,11 +45,15 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var vertex = pts[i];
|
||||
|
||||
if (vertex.X < minX) minX = vertex.X;
|
||||
else if (vertex.X > maxX) maxX = vertex.X;
|
||||
if (vertex.X < minX)
|
||||
minX = vertex.X;
|
||||
else if (vertex.X > maxX)
|
||||
maxX = vertex.X;
|
||||
|
||||
if (vertex.Y < minY) minY = vertex.Y;
|
||||
else if (vertex.Y > maxY) maxY = vertex.Y;
|
||||
if (vertex.Y < minY)
|
||||
minY = vertex.Y;
|
||||
else if (vertex.Y > maxY)
|
||||
maxY = vertex.Y;
|
||||
}
|
||||
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY);
|
||||
@@ -65,10 +73,14 @@ namespace OpenNest.Geometry
|
||||
|
||||
foreach (var box in items)
|
||||
{
|
||||
if (box.Left < left) left = box.Left;
|
||||
if (box.Right > right) right = box.Right;
|
||||
if (box.Bottom < bottom) bottom = box.Bottom;
|
||||
if (box.Top > top) top = box.Top;
|
||||
if (box.Left < left)
|
||||
left = box.Left;
|
||||
if (box.Right > right)
|
||||
right = box.Right;
|
||||
if (box.Bottom < bottom)
|
||||
bottom = box.Bottom;
|
||||
if (box.Top > top)
|
||||
top = box.Top;
|
||||
}
|
||||
|
||||
return new Box(left, bottom, right - left, top - bottom);
|
||||
|
||||
@@ -8,9 +8,7 @@ namespace OpenNest.Geometry
|
||||
public static readonly Box Empty = new Box();
|
||||
|
||||
public Box()
|
||||
: this(0, 0, 0, 0)
|
||||
{
|
||||
}
|
||||
: this(0, 0, 0, 0) { }
|
||||
|
||||
public Box(double x, double y, double w, double h)
|
||||
{
|
||||
@@ -117,10 +115,14 @@ namespace OpenNest.Geometry
|
||||
|
||||
public bool Intersects(Box box)
|
||||
{
|
||||
if (Left >= box.Right) return false;
|
||||
if (Right <= box.Left) return false;
|
||||
if (Top <= box.Bottom) return false;
|
||||
if (Bottom >= box.Top) return false;
|
||||
if (Left >= box.Right)
|
||||
return false;
|
||||
if (Right <= box.Left)
|
||||
return false;
|
||||
if (Top <= box.Bottom)
|
||||
return false;
|
||||
if (Bottom >= box.Top)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -146,18 +148,24 @@ namespace OpenNest.Geometry
|
||||
|
||||
public bool Contains(Box box)
|
||||
{
|
||||
if (box.Top > Top) return false;
|
||||
if (box.Left < Left) return false;
|
||||
if (box.Right > Right) return false;
|
||||
if (box.Bottom < Bottom) return false;
|
||||
if (box.Top > Top)
|
||||
return false;
|
||||
if (box.Left < Left)
|
||||
return false;
|
||||
if (box.Right > Right)
|
||||
return false;
|
||||
if (box.Bottom < Bottom)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Contains(Vector pt)
|
||||
{
|
||||
return pt.X >= Left - Tolerance.Epsilon && pt.X <= Right + Tolerance.Epsilon
|
||||
&& pt.Y >= Bottom - Tolerance.Epsilon && pt.Y <= Top + Tolerance.Epsilon;
|
||||
return pt.X >= Left - Tolerance.Epsilon
|
||||
&& pt.X <= Right + Tolerance.Epsilon
|
||||
&& pt.Y >= Bottom - Tolerance.Epsilon
|
||||
&& pt.Y <= Top + Tolerance.Epsilon;
|
||||
}
|
||||
|
||||
public bool IsHorizontalTo(Box box)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -8,14 +8,10 @@ namespace OpenNest.Geometry
|
||||
private Vector center;
|
||||
private double radius;
|
||||
|
||||
public Circle()
|
||||
{
|
||||
}
|
||||
public Circle() { }
|
||||
|
||||
public Circle(double x, double y, double radius)
|
||||
: this(new Vector(x, y), radius)
|
||||
{
|
||||
}
|
||||
: this(new Vector(x, y), radius) { }
|
||||
|
||||
public Circle(Vector center, double radius)
|
||||
{
|
||||
@@ -137,21 +133,22 @@ namespace OpenNest.Geometry
|
||||
public List<Vector> ToPoints(int segments = 1000, bool circumscribe = false)
|
||||
{
|
||||
var points = new List<Vector>();
|
||||
var stepAngle = Rotation == RotationType.CW
|
||||
? -Angle.TwoPI / segments
|
||||
: Angle.TwoPI / segments;
|
||||
var stepAngle =
|
||||
Rotation == RotationType.CW ? -Angle.TwoPI / segments : Angle.TwoPI / segments;
|
||||
|
||||
var r = circumscribe && segments > 0
|
||||
? Radius / System.Math.Cos(stepAngle / 2.0)
|
||||
: Radius;
|
||||
var r =
|
||||
circumscribe && segments > 0 ? Radius / System.Math.Cos(stepAngle / 2.0) : Radius;
|
||||
|
||||
for (int i = 0; i <= segments; ++i)
|
||||
{
|
||||
var angle = stepAngle * i;
|
||||
|
||||
points.Add(new Vector(
|
||||
System.Math.Cos(angle) * r + Center.X,
|
||||
System.Math.Sin(angle) * r + Center.Y));
|
||||
points.Add(
|
||||
new Vector(
|
||||
System.Math.Cos(angle) * r + Center.X,
|
||||
System.Math.Sin(angle) * r + Center.Y
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return points;
|
||||
@@ -278,11 +275,9 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
if (side == OffsetSide.Left && Rotation == RotationType.CCW)
|
||||
{
|
||||
return Radius <= distance ? null : new Circle(center, Radius - distance)
|
||||
{
|
||||
Layer = Layer,
|
||||
Rotation = Rotation
|
||||
};
|
||||
return Radius <= distance
|
||||
? null
|
||||
: new Circle(center, Radius - distance) { Layer = Layer, Rotation = Rotation };
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -294,11 +289,9 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
if (ContainsPoint(pt))
|
||||
{
|
||||
return Radius <= distance ? null : new Circle(center, Radius - distance)
|
||||
{
|
||||
Layer = Layer,
|
||||
Rotation = Rotation
|
||||
};
|
||||
return Radius <= distance
|
||||
? null
|
||||
: new Circle(center, Radius - distance) { Layer = Layer, Rotation = Rotation };
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -317,7 +310,8 @@ namespace OpenNest.Geometry
|
||||
|
||||
return new Vector(
|
||||
System.Math.Cos(angle) * Radius + Center.X,
|
||||
System.Math.Sin(angle) * Radius + Center.Y);
|
||||
System.Math.Sin(angle) * Radius + Center.Y
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -350,7 +344,9 @@ namespace OpenNest.Geometry
|
||||
public override bool Intersects(Circle circle)
|
||||
{
|
||||
var dist = Center.DistanceTo(circle.Center);
|
||||
return (dist < (Radius + circle.Radius) && dist > System.Math.Abs(Radius - circle.Radius));
|
||||
return (
|
||||
dist < (Radius + circle.Radius) && dist > System.Math.Abs(Radius - circle.Radius)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public static class Collision
|
||||
{
|
||||
public static CollisionResult Check(Polygon a, Polygon b,
|
||||
List<Polygon> holesA = null, List<Polygon> holesB = null)
|
||||
public static CollisionResult Check(
|
||||
Polygon a,
|
||||
Polygon b,
|
||||
List<Polygon> holesA = null,
|
||||
List<Polygon> holesB = null
|
||||
)
|
||||
{
|
||||
// Step 1: Bounding box pre-filter
|
||||
if (!BoundingBoxesOverlap(a.BoundingBox, b.BoundingBox))
|
||||
@@ -46,8 +50,12 @@ namespace OpenNest.Geometry
|
||||
return new CollisionResult(true, regions, intersectionPoints);
|
||||
}
|
||||
|
||||
public static bool HasOverlap(Polygon a, Polygon b,
|
||||
List<Polygon> holesA = null, List<Polygon> holesB = null)
|
||||
public static bool HasOverlap(
|
||||
Polygon a,
|
||||
Polygon b,
|
||||
List<Polygon> holesA = null,
|
||||
List<Polygon> holesB = null
|
||||
)
|
||||
{
|
||||
if (!BoundingBoxesOverlap(a.BoundingBox, b.BoundingBox))
|
||||
return false;
|
||||
@@ -57,8 +65,10 @@ namespace OpenNest.Geometry
|
||||
return Check(a, b, holesA, holesB).Overlaps;
|
||||
}
|
||||
|
||||
public static List<CollisionResult> CheckAll(List<Polygon> polygons,
|
||||
List<List<Polygon>> holes = null)
|
||||
public static List<CollisionResult> CheckAll(
|
||||
List<Polygon> polygons,
|
||||
List<List<Polygon>> holes = null
|
||||
)
|
||||
{
|
||||
var results = new List<CollisionResult>();
|
||||
|
||||
@@ -78,8 +88,7 @@ namespace OpenNest.Geometry
|
||||
return results;
|
||||
}
|
||||
|
||||
public static bool HasAnyOverlap(List<Polygon> polygons,
|
||||
List<List<Polygon>> holes = null)
|
||||
public static bool HasAnyOverlap(List<Polygon> polygons, List<List<Polygon>> holes = null)
|
||||
{
|
||||
for (var i = 0; i < polygons.Count; i++)
|
||||
{
|
||||
@@ -98,10 +107,8 @@ namespace OpenNest.Geometry
|
||||
|
||||
private static bool BoundingBoxesOverlap(Box a, Box b)
|
||||
{
|
||||
var overlapX = System.Math.Min(a.Right, b.Right)
|
||||
- System.Math.Max(a.Left, b.Left);
|
||||
var overlapY = System.Math.Min(a.Top, b.Top)
|
||||
- System.Math.Max(a.Bottom, b.Bottom);
|
||||
var overlapX = System.Math.Min(a.Right, b.Right) - System.Math.Max(a.Left, b.Left);
|
||||
var overlapY = System.Math.Min(a.Top, b.Top) - System.Math.Max(a.Bottom, b.Bottom);
|
||||
|
||||
return overlapX > Tolerance.Epsilon && overlapY > Tolerance.Epsilon;
|
||||
}
|
||||
@@ -164,13 +171,19 @@ namespace OpenNest.Geometry
|
||||
var output = new List<Vector>(subject.Vertices);
|
||||
|
||||
// Remove closing vertex if present
|
||||
if (output.Count > 1 && output[0].X == output[output.Count - 1].X
|
||||
&& output[0].Y == output[output.Count - 1].Y)
|
||||
if (
|
||||
output.Count > 1
|
||||
&& output[0].X == output[output.Count - 1].X
|
||||
&& output[0].Y == output[output.Count - 1].Y
|
||||
)
|
||||
output.RemoveAt(output.Count - 1);
|
||||
|
||||
var clipVerts = new List<Vector>(clip.Vertices);
|
||||
if (clipVerts.Count > 1 && clipVerts[0].X == clipVerts[clipVerts.Count - 1].X
|
||||
&& clipVerts[0].Y == clipVerts[clipVerts.Count - 1].Y)
|
||||
if (
|
||||
clipVerts.Count > 1
|
||||
&& clipVerts[0].X == clipVerts[clipVerts.Count - 1].X
|
||||
&& clipVerts[0].Y == clipVerts[clipVerts.Count - 1].Y
|
||||
)
|
||||
clipVerts.RemoveAt(clipVerts.Count - 1);
|
||||
|
||||
for (var i = 0; i < clipVerts.Count; i++)
|
||||
@@ -231,7 +244,7 @@ namespace OpenNest.Geometry
|
||||
private static double Cross(Vector edgeStart, Vector edgeEnd, Vector point)
|
||||
{
|
||||
return (edgeEnd.X - edgeStart.X) * (point.Y - edgeStart.Y)
|
||||
- (edgeEnd.Y - edgeStart.Y) * (point.X - edgeStart.X);
|
||||
- (edgeEnd.Y - edgeStart.Y) * (point.X - edgeStart.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -255,12 +268,17 @@ namespace OpenNest.Geometry
|
||||
/// <summary>
|
||||
/// Subtracts holes from overlap regions.
|
||||
/// </summary>
|
||||
private static List<Polygon> SubtractHoles(List<Polygon> regions,
|
||||
List<Polygon> holesA, List<Polygon> holesB)
|
||||
private static List<Polygon> SubtractHoles(
|
||||
List<Polygon> regions,
|
||||
List<Polygon> holesA,
|
||||
List<Polygon> holesB
|
||||
)
|
||||
{
|
||||
var allHoles = new List<Polygon>();
|
||||
if (holesA != null) allHoles.AddRange(holesA);
|
||||
if (holesB != null) allHoles.AddRange(holesB);
|
||||
if (holesA != null)
|
||||
allHoles.AddRange(holesA);
|
||||
if (holesB != null)
|
||||
allHoles.AddRange(holesB);
|
||||
|
||||
if (allHoles.Count == 0)
|
||||
return regions;
|
||||
@@ -313,9 +331,16 @@ namespace OpenNest.Geometry
|
||||
var holeCount = holeTri.IsClosed() ? holeVerts.Count - 1 : holeVerts.Count;
|
||||
var survived = false;
|
||||
for (var i = 0; i < holeCount; i++)
|
||||
survived |= AddIfPositiveArea(next,
|
||||
ClipOutsideHalfSpace(pieceTri, holeVerts[i], holeVerts[(i + 1) % holeCount]));
|
||||
if (!survived) continue; // piece lies entirely within the hole
|
||||
survived |= AddIfPositiveArea(
|
||||
next,
|
||||
ClipOutsideHalfSpace(
|
||||
pieceTri,
|
||||
holeVerts[i],
|
||||
holeVerts[(i + 1) % holeCount]
|
||||
)
|
||||
);
|
||||
if (!survived)
|
||||
continue; // piece lies entirely within the hole
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +354,11 @@ namespace OpenNest.Geometry
|
||||
/// Sutherland-Hodgman clip of a convex polygon to the strict outside of the
|
||||
/// infinite line edgeStart->edgeEnd of a CCW hole edge (Cross < -Epsilon).
|
||||
/// </summary>
|
||||
private static List<Vector> ClipOutsideHalfSpace(Polygon piece, Vector edgeStart, Vector edgeEnd)
|
||||
private static List<Vector> ClipOutsideHalfSpace(
|
||||
Polygon piece,
|
||||
Vector edgeStart,
|
||||
Vector edgeEnd
|
||||
)
|
||||
{
|
||||
var verts = piece.Vertices;
|
||||
var count = piece.IsClosed() ? verts.Count - 1 : verts.Count;
|
||||
@@ -340,22 +369,27 @@ namespace OpenNest.Geometry
|
||||
var next = verts[(i + 1) % count];
|
||||
var currentInside = Cross(edgeStart, edgeEnd, current) >= -Tolerance.Epsilon;
|
||||
var nextInside = Cross(edgeStart, edgeEnd, next) >= -Tolerance.Epsilon;
|
||||
if (!currentInside) kept.Add(current);
|
||||
if (currentInside == nextInside) continue;
|
||||
if (!currentInside)
|
||||
kept.Add(current);
|
||||
if (currentInside == nextInside)
|
||||
continue;
|
||||
var intersection = LineIntersection(edgeStart, edgeEnd, current, next);
|
||||
if (intersection.IsValid()) kept.Add(intersection);
|
||||
if (intersection.IsValid())
|
||||
kept.Add(intersection);
|
||||
}
|
||||
return kept;
|
||||
}
|
||||
|
||||
private static bool AddIfPositiveArea(List<Polygon> polygons, List<Vector> vertices)
|
||||
{
|
||||
if (vertices.Count < 3) return false;
|
||||
if (vertices.Count < 3)
|
||||
return false;
|
||||
var polygon = new Polygon();
|
||||
polygon.Vertices.AddRange(vertices);
|
||||
polygon.Close();
|
||||
polygon.UpdateBounds();
|
||||
if (polygon.Area() <= Tolerance.Epsilon) return false;
|
||||
if (polygon.Area() <= Tolerance.Epsilon)
|
||||
return false;
|
||||
polygons.Add(polygon);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,9 +5,17 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
public class CollisionResult
|
||||
{
|
||||
public static readonly CollisionResult None = new(false, new List<Polygon>(), new List<Vector>());
|
||||
public static readonly CollisionResult None = new(
|
||||
false,
|
||||
new List<Polygon>(),
|
||||
new List<Vector>()
|
||||
);
|
||||
|
||||
public CollisionResult(bool overlaps, List<Polygon> overlapRegions, List<Vector> intersectionPoints)
|
||||
public CollisionResult(
|
||||
bool overlaps,
|
||||
List<Polygon> overlapRegions,
|
||||
List<Vector> intersectionPoints
|
||||
)
|
||||
{
|
||||
Overlaps = overlaps;
|
||||
OverlapRegions = overlapRegions;
|
||||
|
||||
@@ -19,8 +19,11 @@ namespace OpenNest.Geometry
|
||||
var verts = new List<Vector>(polygon.Vertices);
|
||||
|
||||
// Remove closing vertex if polygon is closed.
|
||||
if (verts.Count > 1 && verts[0].X == verts[verts.Count - 1].X
|
||||
&& verts[0].Y == verts[verts.Count - 1].Y)
|
||||
if (
|
||||
verts.Count > 1
|
||||
&& verts[0].X == verts[verts.Count - 1].X
|
||||
&& verts[0].Y == verts[verts.Count - 1].Y
|
||||
)
|
||||
verts.RemoveAt(verts.Count - 1);
|
||||
|
||||
if (verts.Count < 3)
|
||||
@@ -84,8 +87,14 @@ namespace OpenNest.Geometry
|
||||
/// Tests whether the vertex at curr forms an ear (a convex vertex whose
|
||||
/// triangle contains no other polygon vertices).
|
||||
/// </summary>
|
||||
private static bool IsEar(Vector prev, Vector curr, Vector next,
|
||||
List<Vector> verts, List<int> indices, int n)
|
||||
private static bool IsEar(
|
||||
Vector prev,
|
||||
Vector curr,
|
||||
Vector next,
|
||||
List<Vector> verts,
|
||||
List<int> indices,
|
||||
int n
|
||||
)
|
||||
{
|
||||
// Must be convex (CCW turn).
|
||||
if (Cross(prev, curr, next) <= 0)
|
||||
|
||||
@@ -20,7 +20,10 @@ namespace OpenNest.Geometry
|
||||
|
||||
foreach (var p in sorted)
|
||||
{
|
||||
while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0)
|
||||
while (
|
||||
lower.Count >= 2
|
||||
&& Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0
|
||||
)
|
||||
lower.RemoveAt(lower.Count - 1);
|
||||
|
||||
lower.Add(p);
|
||||
@@ -32,7 +35,10 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var p = sorted[i];
|
||||
|
||||
while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0)
|
||||
while (
|
||||
upper.Count >= 2
|
||||
&& Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0
|
||||
)
|
||||
upper.RemoveAt(upper.Count - 1);
|
||||
|
||||
upper.Add(p);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -9,7 +9,13 @@ namespace OpenNest.Geometry
|
||||
private const int MaxSubdivisionDepth = 12;
|
||||
private const int DeviationSamples = 20;
|
||||
|
||||
internal static Vector EvaluatePoint(double semiMajor, double semiMinor, double rotation, Vector center, double t)
|
||||
internal static Vector EvaluatePoint(
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
Vector center,
|
||||
double t
|
||||
)
|
||||
{
|
||||
var x = semiMajor * System.Math.Cos(t);
|
||||
var y = semiMinor * System.Math.Sin(t);
|
||||
@@ -17,12 +23,15 @@ namespace OpenNest.Geometry
|
||||
var cos = System.Math.Cos(rotation);
|
||||
var sin = System.Math.Sin(rotation);
|
||||
|
||||
return new Vector(
|
||||
center.X + x * cos - y * sin,
|
||||
center.Y + x * sin + y * cos);
|
||||
return new Vector(center.X + x * cos - y * sin, center.Y + x * sin + y * cos);
|
||||
}
|
||||
|
||||
internal static Vector EvaluateTangent(double semiMajor, double semiMinor, double rotation, double t)
|
||||
internal static Vector EvaluateTangent(
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
double t
|
||||
)
|
||||
{
|
||||
var tx = -semiMajor * System.Math.Sin(t);
|
||||
var ty = semiMinor * System.Math.Cos(t);
|
||||
@@ -30,12 +39,15 @@ namespace OpenNest.Geometry
|
||||
var cos = System.Math.Cos(rotation);
|
||||
var sin = System.Math.Sin(rotation);
|
||||
|
||||
return new Vector(
|
||||
tx * cos - ty * sin,
|
||||
tx * sin + ty * cos);
|
||||
return new Vector(tx * cos - ty * sin, tx * sin + ty * cos);
|
||||
}
|
||||
|
||||
internal static Vector EvaluateNormal(double semiMajor, double semiMinor, double rotation, double t)
|
||||
internal static Vector EvaluateNormal(
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
double t
|
||||
)
|
||||
{
|
||||
// Inward normal: perpendicular to tangent, pointing toward center of curvature.
|
||||
// In local coords: N(t) = (-b*cos(t), -a*sin(t))
|
||||
@@ -45,9 +57,7 @@ namespace OpenNest.Geometry
|
||||
var cos = System.Math.Cos(rotation);
|
||||
var sin = System.Math.Sin(rotation);
|
||||
|
||||
return new Vector(
|
||||
nx * cos - ny * sin,
|
||||
nx * sin + ny * cos);
|
||||
return new Vector(nx * cos - ny * sin, nx * sin + ny * cos);
|
||||
}
|
||||
|
||||
internal static Vector IntersectNormals(Vector p1, Vector n1, Vector p2, Vector n2)
|
||||
@@ -83,11 +93,21 @@ namespace OpenNest.Geometry
|
||||
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)
|
||||
public static List<Entity> Convert(
|
||||
Vector center,
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
double startParam,
|
||||
double endParam,
|
||||
double tolerance = 0.001
|
||||
)
|
||||
{
|
||||
if (tolerance <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(tolerance), "Tolerance must be positive.");
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(tolerance),
|
||||
"Tolerance must be positive."
|
||||
);
|
||||
if (semiMajor <= 0 || semiMinor <= 0)
|
||||
throw new ArgumentOutOfRangeException("Semi-axis lengths must be positive.");
|
||||
|
||||
@@ -102,14 +122,28 @@ namespace OpenNest.Geometry
|
||||
|
||||
var entities = new List<Entity>();
|
||||
for (var i = 0; i < splits.Count - 1; i++)
|
||||
FitSegment(center, semiMajor, semiMinor, rotation,
|
||||
splits[i], splits[i + 1], tolerance, entities, 0);
|
||||
FitSegment(
|
||||
center,
|
||||
semiMajor,
|
||||
semiMinor,
|
||||
rotation,
|
||||
splits[i],
|
||||
splits[i + 1],
|
||||
tolerance,
|
||||
entities,
|
||||
0
|
||||
);
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
private static List<Entity> ConvertCircle(Vector center, double radius,
|
||||
double rotation, double startParam, double endParam)
|
||||
private static List<Entity> ConvertCircle(
|
||||
Vector center,
|
||||
double radius,
|
||||
double rotation,
|
||||
double startParam,
|
||||
double endParam
|
||||
)
|
||||
{
|
||||
var sweep = endParam - startParam;
|
||||
var isFull = System.Math.Abs(sweep - Angle.TwoPI) < 0.01;
|
||||
@@ -123,7 +157,7 @@ namespace OpenNest.Geometry
|
||||
return new List<Entity>
|
||||
{
|
||||
new Arc(center, radius, startAngle1, midAngle, false),
|
||||
new Arc(center, radius, midAngle, endAngle2, false)
|
||||
new Arc(center, radius, midAngle, endAngle2, false),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -136,7 +170,8 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var splits = new List<double> { startParam };
|
||||
|
||||
var firstQuadrant = System.Math.Ceiling(startParam / (System.Math.PI / 2)) * (System.Math.PI / 2);
|
||||
var firstQuadrant =
|
||||
System.Math.Ceiling(startParam / (System.Math.PI / 2)) * (System.Math.PI / 2);
|
||||
for (var q = firstQuadrant; q < endParam; q += System.Math.PI / 2)
|
||||
{
|
||||
if (q > startParam + 1e-10 && q < endParam - 1e-10)
|
||||
@@ -147,8 +182,17 @@ namespace OpenNest.Geometry
|
||||
return splits;
|
||||
}
|
||||
|
||||
private static void FitSegment(Vector center, double semiMajor, double semiMinor,
|
||||
double rotation, double t0, double t1, double tolerance, List<Entity> results, int depth)
|
||||
private static void FitSegment(
|
||||
Vector center,
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
double t0,
|
||||
double t1,
|
||||
double tolerance,
|
||||
List<Entity> results,
|
||||
int depth
|
||||
)
|
||||
{
|
||||
var p0 = EvaluatePoint(semiMajor, semiMinor, rotation, center, t0);
|
||||
var p1 = EvaluatePoint(semiMajor, semiMinor, rotation, center, t1);
|
||||
@@ -168,12 +212,29 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
var radius = p0.DistanceTo(arcCenter);
|
||||
var maxDev = MeasureDeviation(center, semiMajor, semiMinor, rotation,
|
||||
t0, t1, arcCenter, radius);
|
||||
var maxDev = MeasureDeviation(
|
||||
center,
|
||||
semiMajor,
|
||||
semiMinor,
|
||||
rotation,
|
||||
t0,
|
||||
t1,
|
||||
arcCenter,
|
||||
radius
|
||||
);
|
||||
|
||||
if (maxDev <= tolerance)
|
||||
{
|
||||
var arc = CreateArc(arcCenter, radius, center, semiMajor, semiMinor, rotation, t0, t1);
|
||||
var arc = CreateArc(
|
||||
arcCenter,
|
||||
radius,
|
||||
center,
|
||||
semiMajor,
|
||||
semiMinor,
|
||||
rotation,
|
||||
t0,
|
||||
t1
|
||||
);
|
||||
if (arc.SweepAngle() < Tolerance.Epsilon)
|
||||
results.Add(new Line(p0, p1));
|
||||
else
|
||||
@@ -182,13 +243,41 @@ namespace OpenNest.Geometry
|
||||
else
|
||||
{
|
||||
var tMid = (t0 + t1) / 2.0;
|
||||
FitSegment(center, semiMajor, semiMinor, rotation, t0, tMid, tolerance, results, depth + 1);
|
||||
FitSegment(center, semiMajor, semiMinor, rotation, tMid, t1, tolerance, results, depth + 1);
|
||||
FitSegment(
|
||||
center,
|
||||
semiMajor,
|
||||
semiMinor,
|
||||
rotation,
|
||||
t0,
|
||||
tMid,
|
||||
tolerance,
|
||||
results,
|
||||
depth + 1
|
||||
);
|
||||
FitSegment(
|
||||
center,
|
||||
semiMajor,
|
||||
semiMinor,
|
||||
rotation,
|
||||
tMid,
|
||||
t1,
|
||||
tolerance,
|
||||
results,
|
||||
depth + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static double MeasureDeviation(Vector center, double semiMajor, double semiMinor,
|
||||
double rotation, double t0, double t1, Vector arcCenter, double radius)
|
||||
private static double MeasureDeviation(
|
||||
Vector center,
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
double t0,
|
||||
double t1,
|
||||
Vector arcCenter,
|
||||
double radius
|
||||
)
|
||||
{
|
||||
var maxDev = 0.0;
|
||||
for (var i = 1; i <= DeviationSamples; i++)
|
||||
@@ -197,14 +286,22 @@ namespace OpenNest.Geometry
|
||||
var p = EvaluatePoint(semiMajor, semiMinor, rotation, center, t);
|
||||
var dist = p.DistanceTo(arcCenter);
|
||||
var dev = System.Math.Abs(dist - radius);
|
||||
if (dev > maxDev) maxDev = dev;
|
||||
if (dev > maxDev)
|
||||
maxDev = dev;
|
||||
}
|
||||
return maxDev;
|
||||
}
|
||||
|
||||
private static Arc CreateArc(Vector arcCenter, double radius,
|
||||
Vector ellipseCenter, double semiMajor, double semiMinor, double rotation,
|
||||
double t0, double t1)
|
||||
private static Arc CreateArc(
|
||||
Vector arcCenter,
|
||||
double radius,
|
||||
Vector ellipseCenter,
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
double t0,
|
||||
double t1
|
||||
)
|
||||
{
|
||||
var p0 = EvaluatePoint(semiMajor, semiMinor, rotation, ellipseCenter, t0);
|
||||
var p1 = EvaluatePoint(semiMajor, semiMinor, rotation, ellipseCenter, t1);
|
||||
@@ -225,8 +322,10 @@ namespace OpenNest.Geometry
|
||||
var points = new List<Vector> { p0, pMid, p1 };
|
||||
var isReversed = SumSignedAngles(arcCenter, points) < 0;
|
||||
|
||||
if (startAngle < 0) startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0) endAngle += Angle.TwoPI;
|
||||
if (startAngle < 0)
|
||||
startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0)
|
||||
endAngle += Angle.TwoPI;
|
||||
|
||||
return new Arc(arcCenter, radius, startAngle, endAngle, isReversed);
|
||||
}
|
||||
@@ -239,8 +338,10 @@ namespace OpenNest.Geometry
|
||||
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;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -331,7 +331,11 @@ namespace OpenNest.Geometry
|
||||
return points;
|
||||
}
|
||||
|
||||
public static BoundingRectangleResult FindBestRotation(this List<Entity> entities, double startAngle = 0, double endAngle = Angle.TwoPI)
|
||||
public static BoundingRectangleResult FindBestRotation(
|
||||
this List<Entity> entities,
|
||||
double startAngle = 0,
|
||||
double endAngle = Angle.TwoPI
|
||||
)
|
||||
{
|
||||
// Check for Shape entity first (recursive case returns early)
|
||||
foreach (var entity in entities)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public enum EntityType
|
||||
{
|
||||
@@ -7,6 +6,6 @@ namespace OpenNest.Geometry
|
||||
Circle,
|
||||
Line,
|
||||
Shape,
|
||||
Polygon
|
||||
Polygon,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public static class GeometryOptimizer
|
||||
{
|
||||
public static void Optimize(IList<Arc> arcs) =>
|
||||
MergePass(arcs,
|
||||
MergePass(
|
||||
arcs,
|
||||
(list, item, i) => list.GetCoradialArs(item, i),
|
||||
(Arc a, Arc b, out Arc joined) => TryJoinArcs(a, b, out joined));
|
||||
(Arc a, Arc b, out Arc joined) => TryJoinArcs(a, b, out joined)
|
||||
);
|
||||
|
||||
public static void Optimize(IList<Line> lines) =>
|
||||
MergePass(lines,
|
||||
MergePass(
|
||||
lines,
|
||||
(list, item, i) => list.GetCollinearLines(item, i),
|
||||
(Line a, Line b, out Line joined) => TryJoinLines(a, b, out joined));
|
||||
(Line a, Line b, out Line joined) => TryJoinLines(a, b, out joined)
|
||||
);
|
||||
|
||||
public static void Deduplicate(IList<Circle> circles)
|
||||
{
|
||||
@@ -23,8 +27,10 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
for (var j = i - 1; j >= 0; j--)
|
||||
{
|
||||
if (circles[i].Center.DistanceTo(circles[j].Center) <= Tolerance.Epsilon
|
||||
&& circles[i].Radius.IsEqualTo(circles[j].Radius))
|
||||
if (
|
||||
circles[i].Center.DistanceTo(circles[j].Center) <= Tolerance.Epsilon
|
||||
&& circles[i].Radius.IsEqualTo(circles[j].Radius)
|
||||
)
|
||||
{
|
||||
circles.RemoveAt(i);
|
||||
break;
|
||||
@@ -39,9 +45,11 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
for (var j = arcs.Count - 1; j >= 0; j--)
|
||||
{
|
||||
if (arcs[j].Center.DistanceTo(circles[i].Center) <= Tolerance.Epsilon
|
||||
if (
|
||||
arcs[j].Center.DistanceTo(circles[i].Center) <= Tolerance.Epsilon
|
||||
&& arcs[j].Radius.IsEqualTo(circles[i].Radius)
|
||||
&& arcs[j].IsFullCircle())
|
||||
&& arcs[j].IsFullCircle()
|
||||
)
|
||||
{
|
||||
arcs.RemoveAt(j);
|
||||
}
|
||||
@@ -51,9 +59,12 @@ namespace OpenNest.Geometry
|
||||
|
||||
private delegate bool TryJoin<T>(T a, T b, out T joined);
|
||||
|
||||
private static void MergePass<T>(IList<T> items,
|
||||
private static void MergePass<T>(
|
||||
IList<T> items,
|
||||
Func<IList<T>, T, int, List<T>> findCandidates,
|
||||
TryJoin<T> tryJoin) where T : class
|
||||
TryJoin<T> tryJoin
|
||||
)
|
||||
where T : class
|
||||
{
|
||||
for (var i = 0; i < items.Count; ++i)
|
||||
{
|
||||
@@ -117,10 +128,14 @@ namespace OpenNest.Geometry
|
||||
|
||||
if (!onPoint)
|
||||
{
|
||||
if (t1 < b2 - Tolerance.Epsilon) return false;
|
||||
if (b1 > t2 + Tolerance.Epsilon) return false;
|
||||
if (l1 > r2 + Tolerance.Epsilon) return false;
|
||||
if (r1 < l2 - Tolerance.Epsilon) return false;
|
||||
if (t1 < b2 - Tolerance.Epsilon)
|
||||
return false;
|
||||
if (b1 > t2 + Tolerance.Epsilon)
|
||||
return false;
|
||||
if (l1 > r2 + Tolerance.Epsilon)
|
||||
return false;
|
||||
if (r1 < l2 - Tolerance.Epsilon)
|
||||
return false;
|
||||
}
|
||||
|
||||
var l = l1 < l2 ? l1 : l2;
|
||||
@@ -129,9 +144,17 @@ namespace OpenNest.Geometry
|
||||
var b = b1 < b2 ? b1 : b2;
|
||||
|
||||
if (!line1.IsVertical() && line1.Slope() < 0)
|
||||
lineOut = new Line(new Vector(l, t), new Vector(r, b)) { Layer = line1.Layer, Color = line1.Color };
|
||||
lineOut = new Line(new Vector(l, t), new Vector(r, b))
|
||||
{
|
||||
Layer = line1.Layer,
|
||||
Color = line1.Color,
|
||||
};
|
||||
else
|
||||
lineOut = new Line(new Vector(l, b), new Vector(r, t)) { Layer = line1.Layer, Color = line1.Color };
|
||||
lineOut = new Line(new Vector(l, b), new Vector(r, t))
|
||||
{
|
||||
Layer = line1.Layer,
|
||||
Color = line1.Color,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -177,33 +200,47 @@ namespace OpenNest.Geometry
|
||||
if (sweep >= Angle.TwoPI - Tolerance.Epsilon)
|
||||
return false;
|
||||
|
||||
if (startAngle < 0) startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0) endAngle += Angle.TwoPI;
|
||||
if (startAngle < 0)
|
||||
startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0)
|
||||
endAngle += Angle.TwoPI;
|
||||
|
||||
arcOut = new Arc(arc1.Center, arc1.Radius, startAngle, endAngle) { Layer = arc1.Layer, Color = arc1.Color };
|
||||
arcOut = new Arc(arc1.Center, arc1.Radius, startAngle, endAngle)
|
||||
{
|
||||
Layer = arc1.Layer,
|
||||
Color = arc1.Color,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<Line> GetCollinearLines(this IList<Line> lines, Line line, int startIndex)
|
||||
private static List<Line> GetCollinearLines(
|
||||
this IList<Line> lines,
|
||||
Line line,
|
||||
int startIndex
|
||||
)
|
||||
{
|
||||
var collinearLines = new List<Line>();
|
||||
|
||||
Parallel.For(startIndex, lines.Count, index =>
|
||||
{
|
||||
var compareLine = lines[index];
|
||||
|
||||
if (Object.ReferenceEquals(line, compareLine))
|
||||
return;
|
||||
|
||||
if (!line.IsCollinearTo(compareLine))
|
||||
return;
|
||||
|
||||
lock (collinearLines)
|
||||
Parallel.For(
|
||||
startIndex,
|
||||
lines.Count,
|
||||
index =>
|
||||
{
|
||||
collinearLines.Add(compareLine);
|
||||
var compareLine = lines[index];
|
||||
|
||||
if (Object.ReferenceEquals(line, compareLine))
|
||||
return;
|
||||
|
||||
if (!line.IsCollinearTo(compareLine))
|
||||
return;
|
||||
|
||||
lock (collinearLines)
|
||||
{
|
||||
collinearLines.Add(compareLine);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
return collinearLines;
|
||||
}
|
||||
@@ -212,21 +249,25 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var coradialArcs = new List<Arc>();
|
||||
|
||||
Parallel.For(startIndex, arcs.Count, index =>
|
||||
{
|
||||
var compareArc = arcs[index];
|
||||
|
||||
if (Object.ReferenceEquals(arc, compareArc))
|
||||
return;
|
||||
|
||||
if (!arc.IsCoradialTo(compareArc))
|
||||
return;
|
||||
|
||||
lock (coradialArcs)
|
||||
Parallel.For(
|
||||
startIndex,
|
||||
arcs.Count,
|
||||
index =>
|
||||
{
|
||||
coradialArcs.Add(compareArc);
|
||||
var compareArc = arcs[index];
|
||||
|
||||
if (Object.ReferenceEquals(arc, compareArc))
|
||||
return;
|
||||
|
||||
if (!arc.IsCoradialTo(compareArc))
|
||||
return;
|
||||
|
||||
lock (coradialArcs)
|
||||
{
|
||||
coradialArcs.Add(compareArc);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
return coradialArcs;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public interface IBoundable
|
||||
{
|
||||
|
||||
@@ -28,10 +28,14 @@ namespace OpenNest.Geometry
|
||||
|
||||
for (var i = 1; i < verts.Count; i++)
|
||||
{
|
||||
if (verts[i].X < minX) minX = verts[i].X;
|
||||
if (verts[i].X > maxX) maxX = verts[i].X;
|
||||
if (verts[i].Y < minY) minY = verts[i].Y;
|
||||
if (verts[i].Y > maxY) maxY = verts[i].Y;
|
||||
if (verts[i].X < minX)
|
||||
minX = verts[i].X;
|
||||
if (verts[i].X > maxX)
|
||||
maxX = verts[i].X;
|
||||
if (verts[i].Y < minY)
|
||||
minY = verts[i].Y;
|
||||
if (verts[i].Y > maxY)
|
||||
maxY = verts[i].Y;
|
||||
}
|
||||
|
||||
// The IFP is the work area shrunk inward by the part's extent in each direction.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -18,8 +18,19 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
pts = pts.Where(pt =>
|
||||
Angle.IsBetweenRad(arc1.Center.AngleTo(pt), arc1.StartAngle, arc1.EndAngle, arc1.IsReversed) &&
|
||||
Angle.IsBetweenRad(arc2.Center.AngleTo(pt), arc2.StartAngle, arc2.EndAngle, arc2.IsReversed))
|
||||
Angle.IsBetweenRad(
|
||||
arc1.Center.AngleTo(pt),
|
||||
arc1.StartAngle,
|
||||
arc1.EndAngle,
|
||||
arc1.IsReversed
|
||||
)
|
||||
&& Angle.IsBetweenRad(
|
||||
arc2.Center.AngleTo(pt),
|
||||
arc2.StartAngle,
|
||||
arc2.EndAngle,
|
||||
arc2.IsReversed
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
@@ -35,11 +46,15 @@ namespace OpenNest.Geometry
|
||||
return false;
|
||||
}
|
||||
|
||||
pts = pts.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
pts = pts.Where(pt =>
|
||||
Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
@@ -54,11 +69,15 @@ namespace OpenNest.Geometry
|
||||
return false;
|
||||
}
|
||||
|
||||
pts = pts.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
pts = pts.Where(pt =>
|
||||
Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
@@ -74,11 +93,15 @@ namespace OpenNest.Geometry
|
||||
pts2.AddRange(pts3);
|
||||
}
|
||||
|
||||
pts = pts2.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
pts = pts2.Where(pt =>
|
||||
Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
@@ -95,11 +118,15 @@ namespace OpenNest.Geometry
|
||||
pts2.AddRange(pts3);
|
||||
}
|
||||
|
||||
pts = pts2.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
pts = pts2.Where(pt =>
|
||||
Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
@@ -123,20 +150,22 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
var d = circle2.Center - circle1.Center;
|
||||
var a = (circle1.Radius * circle1.Radius - circle2.Radius * circle2.Radius + distance * distance) / (2.0 * distance);
|
||||
var a =
|
||||
(
|
||||
circle1.Radius * circle1.Radius
|
||||
- circle2.Radius * circle2.Radius
|
||||
+ distance * distance
|
||||
) / (2.0 * distance);
|
||||
var h = System.Math.Sqrt(circle1.Radius * circle1.Radius - a * a);
|
||||
|
||||
var pt = new Vector(
|
||||
circle1.Center.X + (a * d.X) / distance,
|
||||
circle1.Center.Y + (a * d.Y) / distance);
|
||||
circle1.Center.Y + (a * d.Y) / distance
|
||||
);
|
||||
|
||||
var i1 = new Vector(
|
||||
pt.X + (h * d.Y) / distance,
|
||||
pt.Y - (h * d.X) / distance);
|
||||
var i1 = new Vector(pt.X + (h * d.Y) / distance, pt.Y - (h * d.X) / distance);
|
||||
|
||||
var i2 = new Vector(
|
||||
pt.X - (h * d.Y) / distance,
|
||||
pt.Y + (h * d.X) / distance);
|
||||
var i2 = new Vector(pt.X - (h * d.Y) / distance, pt.Y + (h * d.X) / distance);
|
||||
|
||||
pts = i1 != i2 ? new List<Vector> { i1, i2 } : new List<Vector> { i1 };
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace OpenNest.Geometry
|
||||
public static readonly Layer Default = new Layer("0")
|
||||
{
|
||||
Color = Color.White,
|
||||
IsVisible = true
|
||||
IsVisible = true,
|
||||
};
|
||||
|
||||
public Layer(string name)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -9,14 +9,10 @@ namespace OpenNest.Geometry
|
||||
internal Vector pt1;
|
||||
internal Vector pt2;
|
||||
|
||||
public Line()
|
||||
{
|
||||
}
|
||||
public Line() { }
|
||||
|
||||
public Line(double x1, double y1, double x2, double y2)
|
||||
: this(new Vector(x1, y1), new Vector(x2, y2))
|
||||
{
|
||||
}
|
||||
: this(new Vector(x1, y1), new Vector(x2, y2)) { }
|
||||
|
||||
public Line(Vector startPoint, Vector endPoint)
|
||||
{
|
||||
@@ -83,9 +79,7 @@ namespace OpenNest.Geometry
|
||||
return EndPoint;
|
||||
else
|
||||
{
|
||||
return new Vector(
|
||||
StartPoint.X + param * diff2.X,
|
||||
StartPoint.Y + param * diff2.Y);
|
||||
return new Vector(StartPoint.X + param * diff2.X, StartPoint.Y + param * diff2.Y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +366,7 @@ namespace OpenNest.Geometry
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public override sealed void UpdateBounds()
|
||||
public sealed override void UpdateBounds()
|
||||
{
|
||||
if (StartPoint.X < EndPoint.X)
|
||||
{
|
||||
@@ -429,13 +423,13 @@ namespace OpenNest.Geometry
|
||||
/// <returns>A tuple of (first, second) sub-lines.</returns>
|
||||
public (Line first, Line second) SplitAt(Vector point)
|
||||
{
|
||||
var first = point.DistanceTo(StartPoint) < Tolerance.Epsilon
|
||||
? null
|
||||
: new Line(StartPoint, point);
|
||||
var first =
|
||||
point.DistanceTo(StartPoint) < Tolerance.Epsilon
|
||||
? null
|
||||
: new Line(StartPoint, point);
|
||||
|
||||
var second = point.DistanceTo(EndPoint) < Tolerance.Epsilon
|
||||
? null
|
||||
: new Line(point, EndPoint);
|
||||
var second =
|
||||
point.DistanceTo(EndPoint) < Tolerance.Epsilon ? null : new Line(point, EndPoint);
|
||||
|
||||
return (first, second);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Clipper2Lib;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -99,14 +99,15 @@ namespace OpenNest.Geometry
|
||||
var startB = FindBottomLeft(b);
|
||||
|
||||
var result = new Polygon();
|
||||
|
||||
// The starting point of the Minkowski sum A + B is the sum of the
|
||||
// starting points of A and B. For NFP = A + (-B), this is
|
||||
|
||||
// The starting point of the Minkowski sum A + B is the sum of the
|
||||
// starting points of A and B. For NFP = A + (-B), this is
|
||||
// startA + startReflectedB.
|
||||
var current = new Vector(
|
||||
a.Vertices[startA].X + b.Vertices[startB].X,
|
||||
a.Vertices[startA].Y + b.Vertices[startB].Y);
|
||||
|
||||
a.Vertices[startA].Y + b.Vertices[startB].Y
|
||||
);
|
||||
|
||||
result.Vertices.Add(current);
|
||||
|
||||
var ia = 0;
|
||||
@@ -132,10 +133,12 @@ namespace OpenNest.Geometry
|
||||
else
|
||||
{
|
||||
var angleA = System.Math.Atan2(orderedA[ia].Y, orderedA[ia].X);
|
||||
if (angleA < 0) angleA += Angle.TwoPI;
|
||||
if (angleA < 0)
|
||||
angleA += Angle.TwoPI;
|
||||
|
||||
var angleB = System.Math.Atan2(orderedB[ib].Y, orderedB[ib].X);
|
||||
if (angleB < 0) angleB += Angle.TwoPI;
|
||||
if (angleB < 0)
|
||||
angleB += Angle.TwoPI;
|
||||
|
||||
if (angleA < angleB)
|
||||
{
|
||||
@@ -149,7 +152,8 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
edge = new Vector(
|
||||
orderedA[ia].X + orderedB[ib].X,
|
||||
orderedA[ia].Y + orderedB[ib].Y);
|
||||
orderedA[ia].Y + orderedB[ib].Y
|
||||
);
|
||||
ia++;
|
||||
ib++;
|
||||
}
|
||||
@@ -203,8 +207,10 @@ namespace OpenNest.Geometry
|
||||
|
||||
for (var i = 1; i < n; i++)
|
||||
{
|
||||
if (verts[i].Y < verts[best].Y ||
|
||||
(verts[i].Y == verts[best].Y && verts[i].X < verts[best].X))
|
||||
if (
|
||||
verts[i].Y < verts[best].Y
|
||||
|| (verts[i].Y == verts[best].Y && verts[i].X < verts[best].X)
|
||||
)
|
||||
best = i;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
public static class PolyLabel
|
||||
{
|
||||
public static Vector Find(Polygon outer, IList<Polygon> holes = null, double precision = 0.5)
|
||||
public static Vector Find(
|
||||
Polygon outer,
|
||||
IList<Polygon> holes = null,
|
||||
double precision = 0.5
|
||||
)
|
||||
{
|
||||
if (outer.Vertices.Count < 3)
|
||||
return outer.Vertices.Count > 0
|
||||
? outer.Vertices[0]
|
||||
: new Vector();
|
||||
return outer.Vertices.Count > 0 ? outer.Vertices[0] : new Vector();
|
||||
|
||||
var minX = double.MaxValue;
|
||||
var minY = double.MaxValue;
|
||||
@@ -19,10 +21,14 @@ namespace OpenNest.Geometry
|
||||
for (var i = 0; i < outer.Vertices.Count; i++)
|
||||
{
|
||||
var v = outer.Vertices[i];
|
||||
if (v.X < minX) minX = v.X;
|
||||
if (v.Y < minY) minY = v.Y;
|
||||
if (v.X > maxX) maxX = v.X;
|
||||
if (v.Y > maxY) maxY = v.Y;
|
||||
if (v.X < minX)
|
||||
minX = v.X;
|
||||
if (v.Y < minY)
|
||||
minY = v.Y;
|
||||
if (v.X > maxX)
|
||||
maxX = v.X;
|
||||
if (v.Y > maxY)
|
||||
maxY = v.Y;
|
||||
}
|
||||
|
||||
var width = maxX - minX;
|
||||
@@ -37,8 +43,8 @@ namespace OpenNest.Geometry
|
||||
var queue = new List<Cell>();
|
||||
|
||||
for (var x = minX; x < maxX; x += cellSize)
|
||||
for (var y = minY; y < maxY; y += cellSize)
|
||||
queue.Add(new Cell(x + halfCell, y + halfCell, halfCell, outer, holes));
|
||||
for (var y = minY; y < maxY; y += cellSize)
|
||||
queue.Add(new Cell(x + halfCell, y + halfCell, halfCell, outer, holes));
|
||||
|
||||
queue.Sort((a, b) => b.MaxDist.CompareTo(a.MaxDist));
|
||||
|
||||
@@ -194,7 +200,12 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
}
|
||||
|
||||
private static double PointToAllEdgesDist(double x, double y, Polygon outer, IList<Polygon> holes)
|
||||
private static double PointToAllEdgesDist(
|
||||
double x,
|
||||
double y,
|
||||
Polygon outer,
|
||||
IList<Polygon> holes
|
||||
)
|
||||
{
|
||||
var minDist = PointToPolygonDist(x, y, outer);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -107,7 +107,9 @@ namespace OpenNest.Geometry
|
||||
public RotationType RotationDirection()
|
||||
{
|
||||
if (Vertices.Count < 3)
|
||||
throw new Exception("Not enough points to determine direction. Must have at least 3 points.");
|
||||
throw new Exception(
|
||||
"Not enough points to determine direction. Must have at least 3 points."
|
||||
);
|
||||
|
||||
return CalculateArea() > 0 ? RotationType.CCW : RotationType.CW;
|
||||
}
|
||||
@@ -309,11 +311,15 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var vertex = Vertices[i];
|
||||
|
||||
if (vertex.X < minX) minX = vertex.X;
|
||||
else if (vertex.X > maxX) maxX = vertex.X;
|
||||
if (vertex.X < minX)
|
||||
minX = vertex.X;
|
||||
else if (vertex.X > maxX)
|
||||
maxX = vertex.X;
|
||||
|
||||
if (vertex.Y < minY) minY = vertex.Y;
|
||||
else if (vertex.Y > maxY) maxY = vertex.Y;
|
||||
if (vertex.Y < minY)
|
||||
minY = vertex.Y;
|
||||
else if (vertex.Y > maxY)
|
||||
maxY = vertex.Y;
|
||||
}
|
||||
|
||||
boundingBox.X = minX;
|
||||
@@ -354,10 +360,19 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var prev = (i - 1 + count) % count;
|
||||
|
||||
var a1 = new Vector(Vertices[prev].X + normals[prev].X, Vertices[prev].Y + normals[prev].Y);
|
||||
var a2 = new Vector(Vertices[i].X + normals[prev].X, Vertices[i].Y + normals[prev].Y);
|
||||
var a1 = new Vector(
|
||||
Vertices[prev].X + normals[prev].X,
|
||||
Vertices[prev].Y + normals[prev].Y
|
||||
);
|
||||
var a2 = new Vector(
|
||||
Vertices[i].X + normals[prev].X,
|
||||
Vertices[i].Y + normals[prev].Y
|
||||
);
|
||||
var b1 = new Vector(Vertices[i].X + normals[i].X, Vertices[i].Y + normals[i].Y);
|
||||
var b2 = new Vector(Vertices[(i + 1) % count].X + normals[i].X, Vertices[(i + 1) % count].Y + normals[i].Y);
|
||||
var b2 = new Vector(
|
||||
Vertices[(i + 1) % count].X + normals[i].X,
|
||||
Vertices[(i + 1) % count].Y + normals[i].Y
|
||||
);
|
||||
|
||||
var edgeA = new Line(a1, a2);
|
||||
var edgeB = new Line(b1, b2);
|
||||
@@ -365,7 +380,9 @@ namespace OpenNest.Geometry
|
||||
if (edgeA.Intersects(edgeB, out var pt) && pt.IsValid())
|
||||
result.Vertices.Add(pt);
|
||||
else
|
||||
result.Vertices.Add(new Vector(Vertices[i].X + normals[i].X, Vertices[i].Y + normals[i].Y));
|
||||
result.Vertices.Add(
|
||||
new Vector(Vertices[i].X + normals[i].X, Vertices[i].Y + normals[i].Y)
|
||||
);
|
||||
}
|
||||
|
||||
result.Close();
|
||||
@@ -379,8 +396,10 @@ namespace OpenNest.Geometry
|
||||
var left = OffsetEntity(distance, OffsetSide.Left);
|
||||
var right = OffsetEntity(distance, OffsetSide.Right);
|
||||
|
||||
if (left == null) return right;
|
||||
if (right == null) return left;
|
||||
if (left == null)
|
||||
return right;
|
||||
if (right == null)
|
||||
return left;
|
||||
|
||||
var distLeft = left.ClosestPointTo(pt).DistanceTo(pt);
|
||||
var distRight = right.ClosestPointTo(pt).DistanceTo(pt);
|
||||
@@ -581,13 +600,25 @@ namespace OpenNest.Geometry
|
||||
var bj = edgeBounds[j];
|
||||
|
||||
// Prune with bounding box check.
|
||||
if (bi.maxX < bj.minX || bj.maxX < bi.minX ||
|
||||
bi.maxY < bj.minY || bj.maxY < bi.minY)
|
||||
if (
|
||||
bi.maxX < bj.minX
|
||||
|| bj.maxX < bi.minX
|
||||
|| bi.maxY < bj.minY
|
||||
|| bj.maxY < bi.minY
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SegmentsIntersect(Vertices[i], Vertices[i + 1], Vertices[j], Vertices[j + 1], out pt))
|
||||
if (
|
||||
SegmentsIntersect(
|
||||
Vertices[i],
|
||||
Vertices[i + 1],
|
||||
Vertices[j],
|
||||
Vertices[j + 1],
|
||||
out pt
|
||||
)
|
||||
)
|
||||
{
|
||||
edgeI = i;
|
||||
edgeJ = j;
|
||||
@@ -620,7 +651,13 @@ namespace OpenNest.Geometry
|
||||
return areaA >= areaB ? loopA : loopB;
|
||||
}
|
||||
|
||||
private static bool SegmentsIntersect(Vector a1, Vector a2, Vector b1, Vector b2, out Vector pt)
|
||||
private static bool SegmentsIntersect(
|
||||
Vector a1,
|
||||
Vector a2,
|
||||
Vector b1,
|
||||
Vector b2,
|
||||
out Vector pt
|
||||
)
|
||||
{
|
||||
var da = a2 - a1;
|
||||
var db = b2 - b1;
|
||||
@@ -636,8 +673,12 @@ namespace OpenNest.Geometry
|
||||
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)
|
||||
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;
|
||||
@@ -701,8 +742,10 @@ namespace OpenNest.Geometry
|
||||
var vi = Vertices[i];
|
||||
var vj = Vertices[j];
|
||||
|
||||
if ((vi.Y > pt.Y) != (vj.Y > pt.Y) &&
|
||||
pt.X < (vj.X - vi.X) * (pt.Y - vi.Y) / (vj.Y - vi.Y) + vi.X)
|
||||
if (
|
||||
(vi.Y > pt.Y) != (vj.Y > pt.Y)
|
||||
&& pt.X < (vj.X - vi.X) * (pt.Y - vi.Y) / (vj.Y - vi.Y) + vi.X
|
||||
)
|
||||
{
|
||||
inside = !inside;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -71,18 +71,24 @@ namespace OpenNest.Geometry
|
||||
var vy = ux;
|
||||
|
||||
// Project all hull vertices onto edge direction (u) and perpendicular (v)
|
||||
double minU = double.MaxValue, maxU = double.MinValue;
|
||||
double minV = double.MaxValue, maxV = double.MinValue;
|
||||
double minU = double.MaxValue,
|
||||
maxU = double.MinValue;
|
||||
double minV = double.MaxValue,
|
||||
maxV = double.MinValue;
|
||||
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
var projU = vertices[j].X * ux + vertices[j].Y * uy;
|
||||
var projV = vertices[j].X * vx + vertices[j].Y * vy;
|
||||
|
||||
if (projU < minU) minU = projU;
|
||||
if (projU > maxU) maxU = projU;
|
||||
if (projV < minV) minV = projV;
|
||||
if (projV > maxV) maxV = projV;
|
||||
if (projU < minU)
|
||||
minU = projU;
|
||||
if (projU > maxU)
|
||||
maxU = projU;
|
||||
if (projV < minV)
|
||||
minV = projV;
|
||||
if (projV > maxV)
|
||||
maxV = projV;
|
||||
}
|
||||
|
||||
var width = maxU - minU;
|
||||
@@ -99,7 +105,11 @@ namespace OpenNest.Geometry
|
||||
return best ?? new BoundingRectangleResult(0, 0, 0);
|
||||
}
|
||||
|
||||
public static BoundingRectangleResult MinimumBoundingRectangle(Polygon hull, double startAngle, double endAngle)
|
||||
public static BoundingRectangleResult MinimumBoundingRectangle(
|
||||
Polygon hull,
|
||||
double startAngle,
|
||||
double endAngle
|
||||
)
|
||||
{
|
||||
var vertices = hull.Vertices;
|
||||
int n = hull.IsClosed() ? vertices.Count - 1 : vertices.Count;
|
||||
@@ -153,23 +163,33 @@ namespace OpenNest.Geometry
|
||||
return best ?? new BoundingRectangleResult(startAngle, 0, 0);
|
||||
}
|
||||
|
||||
private static BoundingRectangleResult EvaluateAtAngle(IList<Vector> vertices, int n, double angle)
|
||||
private static BoundingRectangleResult EvaluateAtAngle(
|
||||
IList<Vector> vertices,
|
||||
int n,
|
||||
double angle
|
||||
)
|
||||
{
|
||||
var cos = System.Math.Cos(angle);
|
||||
var sin = System.Math.Sin(angle);
|
||||
|
||||
double minU = double.MaxValue, maxU = double.MinValue;
|
||||
double minV = double.MaxValue, maxV = double.MinValue;
|
||||
double minU = double.MaxValue,
|
||||
maxU = double.MinValue;
|
||||
double minV = double.MaxValue,
|
||||
maxV = double.MinValue;
|
||||
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
var projU = vertices[j].X * cos + vertices[j].Y * sin;
|
||||
var projV = -vertices[j].X * sin + vertices[j].Y * cos;
|
||||
|
||||
if (projU < minU) minU = projU;
|
||||
if (projU > maxU) maxU = projU;
|
||||
if (projV < minV) minV = projV;
|
||||
if (projV > maxV) maxV = projV;
|
||||
if (projU < minU)
|
||||
minU = projU;
|
||||
if (projU > maxU)
|
||||
maxU = projU;
|
||||
if (projV < minV)
|
||||
minV = projV;
|
||||
if (projV > maxV)
|
||||
maxV = projV;
|
||||
}
|
||||
|
||||
var width = maxU - minU;
|
||||
|
||||
@@ -282,11 +282,7 @@ namespace OpenNest.Geometry
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)entity;
|
||||
polygon.Vertices.AddRange(new[]
|
||||
{
|
||||
line.StartPoint,
|
||||
line.EndPoint
|
||||
});
|
||||
polygon.Vertices.AddRange(new[] { line.StartPoint, line.EndPoint });
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
@@ -320,21 +316,21 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)entity;
|
||||
polygon.Vertices.AddRange(arc.ToPoints(arc.SegmentsForTolerance(tolerance), circumscribe));
|
||||
polygon.Vertices.AddRange(
|
||||
arc.ToPoints(arc.SegmentsForTolerance(tolerance), circumscribe)
|
||||
);
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)entity;
|
||||
polygon.Vertices.AddRange(new[]
|
||||
{
|
||||
line.StartPoint,
|
||||
line.EndPoint
|
||||
});
|
||||
polygon.Vertices.AddRange(new[] { line.StartPoint, line.EndPoint });
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
var circle = (Circle)entity;
|
||||
polygon.Vertices.AddRange(circle.ToPoints(circle.SegmentsForTolerance(tolerance), circumscribe));
|
||||
polygon.Vertices.AddRange(
|
||||
circle.ToPoints(circle.SegmentsForTolerance(tolerance), circumscribe)
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -462,9 +458,7 @@ namespace OpenNest.Geometry
|
||||
/// </summary>
|
||||
public override void UpdateBounds()
|
||||
{
|
||||
boundingBox = Entities.Select(geo => geo.BoundingBox)
|
||||
.ToList()
|
||||
.GetBoundingBox();
|
||||
boundingBox = Entities.Select(geo => geo.BoundingBox).ToList().GetBoundingBox();
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, OffsetSide side)
|
||||
@@ -493,22 +487,27 @@ namespace OpenNest.Geometry
|
||||
switch (entity.Type)
|
||||
{
|
||||
case EntityType.Line:
|
||||
{
|
||||
var line = (Line)entity;
|
||||
var offsetLine = (Line)offsetEntity;
|
||||
|
||||
if (lastOffsetEntity != null && lastOffsetEntity.Type == EntityType.Line)
|
||||
{
|
||||
var line = (Line)entity;
|
||||
var offsetLine = (Line)offsetEntity;
|
||||
|
||||
if (lastOffsetEntity != null && lastOffsetEntity.Type == EntityType.Line)
|
||||
{
|
||||
JoinOffsetLines(
|
||||
(Line)lastEntity, (Line)lastOffsetEntity,
|
||||
line, offsetLine,
|
||||
distance, side, offsetShape);
|
||||
}
|
||||
|
||||
offsetShape.Entities.Add(offsetLine);
|
||||
break;
|
||||
JoinOffsetLines(
|
||||
(Line)lastEntity,
|
||||
(Line)lastOffsetEntity,
|
||||
line,
|
||||
offsetLine,
|
||||
distance,
|
||||
side,
|
||||
offsetShape
|
||||
);
|
||||
}
|
||||
|
||||
offsetShape.Entities.Add(offsetLine);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
offsetShape.Entities.Add(offsetEntity);
|
||||
break;
|
||||
@@ -519,27 +518,42 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
// Close the shape: join last offset entity back to first
|
||||
if (lastOffsetEntity != null && firstOffsetEntity != null
|
||||
if (
|
||||
lastOffsetEntity != null
|
||||
&& firstOffsetEntity != null
|
||||
&& lastOffsetEntity != firstOffsetEntity
|
||||
&& lastOffsetEntity.Type == EntityType.Line
|
||||
&& firstOffsetEntity.Type == EntityType.Line)
|
||||
&& firstOffsetEntity.Type == EntityType.Line
|
||||
)
|
||||
{
|
||||
JoinOffsetLines(
|
||||
(Line)lastEntity, (Line)lastOffsetEntity,
|
||||
(Line)firstEntity, (Line)firstOffsetEntity,
|
||||
distance, side, offsetShape);
|
||||
(Line)lastEntity,
|
||||
(Line)lastOffsetEntity,
|
||||
(Line)firstEntity,
|
||||
(Line)firstOffsetEntity,
|
||||
distance,
|
||||
side,
|
||||
offsetShape
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var cutout in definedShape.Cutouts)
|
||||
offsetShape.Entities.AddRange(((Shape)cutout.OffsetEntity(distance, side)).Entities);
|
||||
offsetShape.Entities.AddRange(
|
||||
((Shape)cutout.OffsetEntity(distance, side)).Entities
|
||||
);
|
||||
|
||||
return offsetShape;
|
||||
}
|
||||
|
||||
private static void JoinOffsetLines(
|
||||
Line lastLine, Line lastOffsetLine,
|
||||
Line line, Line offsetLine,
|
||||
double distance, OffsetSide side, Shape offsetShape)
|
||||
Line lastLine,
|
||||
Line lastOffsetLine,
|
||||
Line line,
|
||||
Line offsetLine,
|
||||
double distance,
|
||||
OffsetSide side,
|
||||
Shape offsetShape
|
||||
)
|
||||
{
|
||||
// Determine if this is a convex corner using the cross product of
|
||||
// the original line directions. Convex corners need an arc; concave
|
||||
@@ -548,8 +562,9 @@ namespace OpenNest.Geometry
|
||||
var d2 = line.EndPoint - line.StartPoint;
|
||||
var cross = d1.X * d2.Y - d1.Y * d2.X;
|
||||
|
||||
var isConvex = (side == OffsetSide.Left && cross < -OpenNest.Math.Tolerance.Epsilon) ||
|
||||
(side == OffsetSide.Right && cross > OpenNest.Math.Tolerance.Epsilon);
|
||||
var isConvex =
|
||||
(side == OffsetSide.Left && cross < -OpenNest.Math.Tolerance.Epsilon)
|
||||
|| (side == OffsetSide.Right && cross > OpenNest.Math.Tolerance.Epsilon);
|
||||
|
||||
if (isConvex)
|
||||
{
|
||||
@@ -559,11 +574,13 @@ namespace OpenNest.Geometry
|
||||
line.StartPoint.AngleTo(lastOffsetLine.EndPoint),
|
||||
line.StartPoint.AngleTo(offsetLine.StartPoint),
|
||||
side == OffsetSide.Left
|
||||
);
|
||||
);
|
||||
|
||||
offsetShape.Entities.Add(arc);
|
||||
}
|
||||
else if (Intersect.IntersectsUnbounded(offsetLine, lastOffsetLine, out var intersection))
|
||||
else if (
|
||||
Intersect.IntersectsUnbounded(offsetLine, lastOffsetLine, out var intersection)
|
||||
)
|
||||
{
|
||||
offsetLine.StartPoint = intersection;
|
||||
lastOffsetLine.EndPoint = intersection;
|
||||
@@ -576,7 +593,7 @@ namespace OpenNest.Geometry
|
||||
line.StartPoint.AngleTo(lastOffsetLine.EndPoint),
|
||||
line.StartPoint.AngleTo(offsetLine.StartPoint),
|
||||
side == OffsetSide.Left
|
||||
);
|
||||
);
|
||||
|
||||
offsetShape.Entities.Add(arc);
|
||||
}
|
||||
@@ -596,8 +613,11 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var poly = ToPolygon();
|
||||
|
||||
if (poly == null || poly.Vertices.Count < 3
|
||||
|| poly.RotationDirection() == RotationType.CW)
|
||||
if (
|
||||
poly == null
|
||||
|| poly.Vertices.Count < 3
|
||||
|| poly.RotationDirection() == RotationType.CW
|
||||
)
|
||||
return OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||
|
||||
// Shape is CCW — reverse to CW so Left offset goes outward.
|
||||
@@ -611,10 +631,21 @@ namespace OpenNest.Geometry
|
||||
copy.Entities.Add(new Line(l.EndPoint, l.StartPoint) { Layer = l.Layer });
|
||||
break;
|
||||
case Arc a:
|
||||
copy.Entities.Add(new Arc(a.Center, a.Radius, a.EndAngle, a.StartAngle, !a.IsReversed) { Layer = a.Layer });
|
||||
copy.Entities.Add(
|
||||
new Arc(a.Center, a.Radius, a.EndAngle, a.StartAngle, !a.IsReversed)
|
||||
{
|
||||
Layer = a.Layer,
|
||||
}
|
||||
);
|
||||
break;
|
||||
case Circle c:
|
||||
copy.Entities.Add(new Circle(c.Center, c.Radius) { Layer = c.Layer, Rotation = RotationType.CW });
|
||||
copy.Entities.Add(
|
||||
new Circle(c.Center, c.Radius)
|
||||
{
|
||||
Layer = c.Layer,
|
||||
Rotation = RotationType.CW,
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -631,8 +662,11 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var poly = ToPolygon();
|
||||
|
||||
if (poly == null || poly.Vertices.Count < 3
|
||||
|| poly.RotationDirection() == RotationType.CCW)
|
||||
if (
|
||||
poly == null
|
||||
|| poly.Vertices.Count < 3
|
||||
|| poly.RotationDirection() == RotationType.CCW
|
||||
)
|
||||
return OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||
|
||||
// Create a reversed copy to avoid mutating shared entity objects.
|
||||
@@ -646,10 +680,21 @@ namespace OpenNest.Geometry
|
||||
copy.Entities.Add(new Line(l.EndPoint, l.StartPoint) { Layer = l.Layer });
|
||||
break;
|
||||
case Arc a:
|
||||
copy.Entities.Add(new Arc(a.Center, a.Radius, a.EndAngle, a.StartAngle, !a.IsReversed) { Layer = a.Layer });
|
||||
copy.Entities.Add(
|
||||
new Arc(a.Center, a.Radius, a.EndAngle, a.StartAngle, !a.IsReversed)
|
||||
{
|
||||
Layer = a.Layer,
|
||||
}
|
||||
);
|
||||
break;
|
||||
case Circle c:
|
||||
copy.Entities.Add(new Circle(c.Center, c.Radius) { Layer = c.Layer, Rotation = RotationType.CCW });
|
||||
copy.Entities.Add(
|
||||
new Circle(c.Center, c.Radius)
|
||||
{
|
||||
Layer = c.Layer,
|
||||
Rotation = RotationType.CCW,
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public static class ShapeBuilder
|
||||
{
|
||||
public static List<Shape> GetShapes(IEnumerable<Entity> entities, double? weldTolerance = null)
|
||||
public static List<Shape> GetShapes(
|
||||
IEnumerable<Entity> entities,
|
||||
double? weldTolerance = null
|
||||
)
|
||||
{
|
||||
var lines = new List<Line>();
|
||||
var arcs = new List<Arc>();
|
||||
@@ -141,7 +144,11 @@ namespace OpenNest.Geometry
|
||||
|
||||
private static void AddToGroup(
|
||||
List<List<(Entity entity, bool isStart, Vector point)>> groups,
|
||||
Entity entity, bool isStart, Vector point, double tolerance)
|
||||
Entity entity,
|
||||
bool isStart,
|
||||
Vector point,
|
||||
double tolerance
|
||||
)
|
||||
{
|
||||
foreach (var group in groups)
|
||||
{
|
||||
|
||||
@@ -84,8 +84,7 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var poly = shape.ToPolygon();
|
||||
|
||||
if (poly != null && poly.Vertices.Count >= 3
|
||||
&& poly.RotationDirection() != desired)
|
||||
if (poly != null && poly.Vertices.Count >= 3 && poly.RotationDirection() != desired)
|
||||
{
|
||||
shape.Reverse();
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace OpenNest.Geometry
|
||||
|
||||
public override string ToString() => $"{Width} x {Length}";
|
||||
|
||||
public string ToString(int decimalPlaces) => $"{System.Math.Round(Width, decimalPlaces)} x {System.Math.Round(Length, decimalPlaces)}";
|
||||
public string ToString(int decimalPlaces) =>
|
||||
$"{System.Math.Round(Width, decimalPlaces)} x {System.Math.Round(Length, decimalPlaces)}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -13,57 +13,72 @@ namespace OpenNest.Geometry
|
||||
private static double RayEdgeDistance(Vector vertex, Line edge, PushDirection direction)
|
||||
{
|
||||
return RayEdgeDistance(
|
||||
vertex.X, vertex.Y,
|
||||
edge.pt1.X, edge.pt1.Y, edge.pt2.X, edge.pt2.Y,
|
||||
direction);
|
||||
vertex.X,
|
||||
vertex.Y,
|
||||
edge.pt1.X,
|
||||
edge.pt1.Y,
|
||||
edge.pt2.X,
|
||||
edge.pt2.Y,
|
||||
direction
|
||||
);
|
||||
}
|
||||
|
||||
[System.Runtime.CompilerServices.MethodImpl(
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining
|
||||
)]
|
||||
private static double RayEdgeDistance(
|
||||
double vx, double vy,
|
||||
double p1x, double p1y, double p2x, double p2y,
|
||||
PushDirection direction)
|
||||
double vx,
|
||||
double vy,
|
||||
double p1x,
|
||||
double p1y,
|
||||
double p2x,
|
||||
double p2y,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left:
|
||||
case PushDirection.Right:
|
||||
{
|
||||
var dy = p2y - p1y;
|
||||
if (System.Math.Abs(dy) < Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var t = (vy - p1y) / dy;
|
||||
if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var ix = p1x + t * (p2x - p1x);
|
||||
var dist = direction == PushDirection.Left ? vx - ix : ix - vx;
|
||||
|
||||
if (dist > Tolerance.Epsilon) return dist;
|
||||
if (dist >= -Tolerance.Epsilon) return 0;
|
||||
{
|
||||
var dy = p2y - p1y;
|
||||
if (System.Math.Abs(dy) < Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
}
|
||||
|
||||
var t = (vy - p1y) / dy;
|
||||
if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var ix = p1x + t * (p2x - p1x);
|
||||
var dist = direction == PushDirection.Left ? vx - ix : ix - vx;
|
||||
|
||||
if (dist > Tolerance.Epsilon)
|
||||
return dist;
|
||||
if (dist >= -Tolerance.Epsilon)
|
||||
return 0;
|
||||
return double.MaxValue;
|
||||
}
|
||||
|
||||
case PushDirection.Down:
|
||||
case PushDirection.Up:
|
||||
{
|
||||
var dx = p2x - p1x;
|
||||
if (System.Math.Abs(dx) < Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var t = (vx - p1x) / dx;
|
||||
if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var iy = p1y + t * (p2y - p1y);
|
||||
var dist = direction == PushDirection.Down ? vy - iy : iy - vy;
|
||||
|
||||
if (dist > Tolerance.Epsilon) return dist;
|
||||
if (dist >= -Tolerance.Epsilon) return 0;
|
||||
{
|
||||
var dx = p2x - p1x;
|
||||
if (System.Math.Abs(dx) < Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
}
|
||||
|
||||
var t = (vx - p1x) / dx;
|
||||
if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var iy = p1y + t * (p2y - p1y);
|
||||
var dist = direction == PushDirection.Down ? vy - iy : iy - vy;
|
||||
|
||||
if (dist > Tolerance.Epsilon)
|
||||
return dist;
|
||||
if (dist >= -Tolerance.Epsilon)
|
||||
return 0;
|
||||
return double.MaxValue;
|
||||
}
|
||||
|
||||
default:
|
||||
return double.MaxValue;
|
||||
@@ -75,11 +90,18 @@ namespace OpenNest.Geometry
|
||||
/// Returns double.MaxValue if the ray does not hit the segment.
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.MethodImpl(
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining
|
||||
)]
|
||||
public static double RayEdgeDistance(
|
||||
double vx, double vy,
|
||||
double p1x, double p1y, double p2x, double p2y,
|
||||
double dirX, double dirY)
|
||||
double vx,
|
||||
double vy,
|
||||
double p1x,
|
||||
double p1y,
|
||||
double p2x,
|
||||
double p2y,
|
||||
double dirX,
|
||||
double dirY
|
||||
)
|
||||
{
|
||||
var ex = p2x - p1x;
|
||||
var ey = p2y - p1y;
|
||||
@@ -99,8 +121,10 @@ namespace OpenNest.Geometry
|
||||
if (s < -Tolerance.Epsilon || s > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
if (t > Tolerance.Epsilon) return t;
|
||||
if (t >= -Tolerance.Epsilon) return 0;
|
||||
if (t > Tolerance.Epsilon)
|
||||
return t;
|
||||
if (t >= -Tolerance.Epsilon)
|
||||
return 0;
|
||||
return double.MaxValue;
|
||||
}
|
||||
|
||||
@@ -109,12 +133,19 @@ namespace OpenNest.Geometry
|
||||
/// Returns false if no real intersection exists.
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.MethodImpl(
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining
|
||||
)]
|
||||
private static bool SolveRayCircle(
|
||||
double vx, double vy,
|
||||
double cx, double cy, double r,
|
||||
double dirX, double dirY,
|
||||
out double t1, out double t2)
|
||||
double vx,
|
||||
double vy,
|
||||
double cx,
|
||||
double cy,
|
||||
double r,
|
||||
double dirX,
|
||||
double dirY,
|
||||
out double t1,
|
||||
out double t2
|
||||
)
|
||||
{
|
||||
var ox = vx - cx;
|
||||
var oy = vy - cy;
|
||||
@@ -143,12 +174,20 @@ namespace OpenNest.Geometry
|
||||
/// angular span. Returns double.MaxValue if no hit.
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.MethodImpl(
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining
|
||||
)]
|
||||
public static double RayArcDistance(
|
||||
double vx, double vy,
|
||||
double cx, double cy, double r,
|
||||
double startAngle, double endAngle, bool reversed,
|
||||
double dirX, double dirY)
|
||||
double vx,
|
||||
double vy,
|
||||
double cx,
|
||||
double cy,
|
||||
double r,
|
||||
double startAngle,
|
||||
double endAngle,
|
||||
bool reversed,
|
||||
double dirX,
|
||||
double dirY
|
||||
)
|
||||
{
|
||||
if (!SolveRayCircle(vx, vy, cx, cy, r, dirX, dirY, out var t1, out var t2))
|
||||
return double.MaxValue;
|
||||
@@ -157,16 +196,18 @@ namespace OpenNest.Geometry
|
||||
|
||||
if (t1 > -Tolerance.Epsilon)
|
||||
{
|
||||
var hitAngle = Angle.NormalizeRad(System.Math.Atan2(
|
||||
vy + t1 * dirY - cy, vx + t1 * dirX - cx));
|
||||
var hitAngle = Angle.NormalizeRad(
|
||||
System.Math.Atan2(vy + t1 * dirY - cy, vx + t1 * dirX - cx)
|
||||
);
|
||||
if (Angle.IsBetweenRad(hitAngle, startAngle, endAngle, reversed))
|
||||
best = t1 > Tolerance.Epsilon ? t1 : 0;
|
||||
}
|
||||
|
||||
if (t2 > -Tolerance.Epsilon && t2 < best)
|
||||
{
|
||||
var hitAngle = Angle.NormalizeRad(System.Math.Atan2(
|
||||
vy + t2 * dirY - cy, vx + t2 * dirX - cx));
|
||||
var hitAngle = Angle.NormalizeRad(
|
||||
System.Math.Atan2(vy + t2 * dirY - cy, vx + t2 * dirX - cx)
|
||||
);
|
||||
if (Angle.IsBetweenRad(hitAngle, startAngle, endAngle, reversed))
|
||||
best = t2 > Tolerance.Epsilon ? t2 : 0;
|
||||
}
|
||||
@@ -179,19 +220,29 @@ namespace OpenNest.Geometry
|
||||
/// Returns double.MaxValue if no hit.
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.MethodImpl(
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining
|
||||
)]
|
||||
public static double RayCircleDistance(
|
||||
double vx, double vy,
|
||||
double cx, double cy, double r,
|
||||
double dirX, double dirY)
|
||||
double vx,
|
||||
double vy,
|
||||
double cx,
|
||||
double cy,
|
||||
double r,
|
||||
double dirX,
|
||||
double dirY
|
||||
)
|
||||
{
|
||||
if (!SolveRayCircle(vx, vy, cx, cy, r, dirX, dirY, out var t1, out var t2))
|
||||
return double.MaxValue;
|
||||
|
||||
if (t1 > Tolerance.Epsilon) return t1;
|
||||
if (t1 >= -Tolerance.Epsilon) return 0;
|
||||
if (t2 > Tolerance.Epsilon) return t2;
|
||||
if (t2 >= -Tolerance.Epsilon) return 0;
|
||||
if (t1 > Tolerance.Epsilon)
|
||||
return t1;
|
||||
if (t1 >= -Tolerance.Epsilon)
|
||||
return 0;
|
||||
if (t2 > Tolerance.Epsilon)
|
||||
return t2;
|
||||
if (t2 >= -Tolerance.Epsilon)
|
||||
return 0;
|
||||
|
||||
return double.MaxValue;
|
||||
}
|
||||
@@ -201,7 +252,11 @@ namespace OpenNest.Geometry
|
||||
/// any edge of movingLines contacts any edge of stationaryLines.
|
||||
/// Returns double.MaxValue if no collision path exists.
|
||||
/// </summary>
|
||||
public static double DirectionalDistance(List<Line> movingLines, List<Line> stationaryLines, PushDirection direction)
|
||||
public static double DirectionalDistance(
|
||||
List<Line> movingLines,
|
||||
List<Line> stationaryLines,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
return DirectionalDistance(movingLines, 0, 0, stationaryLines, direction);
|
||||
}
|
||||
@@ -211,8 +266,12 @@ namespace OpenNest.Geometry
|
||||
/// by (movingDx, movingDy) without creating new Line objects.
|
||||
/// </summary>
|
||||
public static double DirectionalDistance(
|
||||
List<Line> movingLines, double movingDx, double movingDy,
|
||||
List<Line> stationaryLines, PushDirection direction)
|
||||
List<Line> movingLines,
|
||||
double movingDx,
|
||||
double movingDy,
|
||||
List<Line> stationaryLines,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
var minDist = double.MaxValue;
|
||||
var movingOffset = new Vector(movingDx, movingDy);
|
||||
@@ -226,7 +285,8 @@ namespace OpenNest.Geometry
|
||||
foreach (var mv in movingVertices)
|
||||
{
|
||||
var d = OneWayDistance(mv, stationaryEdges, Vector.Zero, direction);
|
||||
if (d < minDist) minDist = d;
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
|
||||
// Case 2: Each stationary vertex -> each moving edge (opposite direction)
|
||||
@@ -239,7 +299,8 @@ namespace OpenNest.Geometry
|
||||
foreach (var sv in stationaryVertices)
|
||||
{
|
||||
var d = OneWayDistance(sv, movingEdges, movingOffset, opposite);
|
||||
if (d < minDist) minDist = d;
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
|
||||
return minDist;
|
||||
@@ -267,9 +328,12 @@ namespace OpenNest.Geometry
|
||||
/// to avoid all intermediate object allocations.
|
||||
/// </summary>
|
||||
public static double DirectionalDistance(
|
||||
(Vector start, Vector end)[] movingEdges, Vector movingOffset,
|
||||
(Vector start, Vector end)[] stationaryEdges, Vector stationaryOffset,
|
||||
PushDirection direction)
|
||||
(Vector start, Vector end)[] movingEdges,
|
||||
Vector movingOffset,
|
||||
(Vector start, Vector end)[] stationaryEdges,
|
||||
Vector stationaryOffset,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
var minDist = double.MaxValue;
|
||||
|
||||
@@ -281,7 +345,8 @@ namespace OpenNest.Geometry
|
||||
foreach (var mv in movingVertices)
|
||||
{
|
||||
var d = OneWayDistance(mv, stationaryEdges, stationaryOffset, direction);
|
||||
if (d < minDist) minDist = d;
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
|
||||
// Case 2: Each stationary vertex -> each moving edge (opposite direction)
|
||||
@@ -293,15 +358,19 @@ namespace OpenNest.Geometry
|
||||
foreach (var sv in stationaryVertices)
|
||||
{
|
||||
var d = OneWayDistance(sv, movingEdges, movingOffset, opposite);
|
||||
if (d < minDist) minDist = d;
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
|
||||
return minDist;
|
||||
}
|
||||
|
||||
public static double OneWayDistance(
|
||||
Vector vertex, (Vector start, Vector end)[] edges, Vector edgeOffset,
|
||||
PushDirection direction)
|
||||
Vector vertex,
|
||||
(Vector start, Vector end)[] edges,
|
||||
Vector edgeOffset,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
var minDist = double.MaxValue;
|
||||
var vx = vertex.X;
|
||||
@@ -315,7 +384,9 @@ namespace OpenNest.Geometry
|
||||
var e1 = edges[i].start + edgeOffset;
|
||||
var e2 = edges[i].end + edgeOffset;
|
||||
|
||||
double perpValue, edgeMin, edgeMax;
|
||||
double perpValue,
|
||||
edgeMin,
|
||||
edgeMax;
|
||||
if (horizontal)
|
||||
{
|
||||
perpValue = vy;
|
||||
@@ -337,7 +408,8 @@ namespace OpenNest.Geometry
|
||||
continue;
|
||||
|
||||
var d = RayEdgeDistance(vx, vy, e1.X, e1.Y, e2.X, e2.Y, direction);
|
||||
if (d < minDist) minDist = d;
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
|
||||
return minDist;
|
||||
@@ -347,11 +419,16 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return PushDirection.Right;
|
||||
case PushDirection.Right: return PushDirection.Left;
|
||||
case PushDirection.Up: return PushDirection.Down;
|
||||
case PushDirection.Down: return PushDirection.Up;
|
||||
default: return direction;
|
||||
case PushDirection.Left:
|
||||
return PushDirection.Right;
|
||||
case PushDirection.Right:
|
||||
return PushDirection.Left;
|
||||
case PushDirection.Up:
|
||||
return PushDirection.Down;
|
||||
case PushDirection.Down:
|
||||
return PushDirection.Up;
|
||||
default:
|
||||
return direction;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,11 +441,16 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return box.Left - boundary.Left;
|
||||
case PushDirection.Right: return boundary.Right - box.Right;
|
||||
case PushDirection.Up: return boundary.Top - box.Top;
|
||||
case PushDirection.Down: return box.Bottom - boundary.Bottom;
|
||||
default: return double.MaxValue;
|
||||
case PushDirection.Left:
|
||||
return box.Left - boundary.Left;
|
||||
case PushDirection.Right:
|
||||
return boundary.Right - box.Right;
|
||||
case PushDirection.Up:
|
||||
return boundary.Top - box.Top;
|
||||
case PushDirection.Down:
|
||||
return box.Bottom - boundary.Bottom;
|
||||
default:
|
||||
return double.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,11 +458,16 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return new Vector(-distance, 0);
|
||||
case PushDirection.Right: return new Vector(distance, 0);
|
||||
case PushDirection.Up: return new Vector(0, distance);
|
||||
case PushDirection.Down: return new Vector(0, -distance);
|
||||
default: return new Vector();
|
||||
case PushDirection.Left:
|
||||
return new Vector(-distance, 0);
|
||||
case PushDirection.Right:
|
||||
return new Vector(distance, 0);
|
||||
case PushDirection.Up:
|
||||
return new Vector(0, distance);
|
||||
case PushDirection.Down:
|
||||
return new Vector(0, -distance);
|
||||
default:
|
||||
return new Vector();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,11 +475,16 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return from.Left - to.Right;
|
||||
case PushDirection.Right: return to.Left - from.Right;
|
||||
case PushDirection.Up: return to.Bottom - from.Top;
|
||||
case PushDirection.Down: return from.Bottom - to.Top;
|
||||
default: return double.MaxValue;
|
||||
case PushDirection.Left:
|
||||
return from.Left - to.Right;
|
||||
case PushDirection.Right:
|
||||
return to.Left - from.Right;
|
||||
case PushDirection.Up:
|
||||
return to.Bottom - from.Top;
|
||||
case PushDirection.Down:
|
||||
return from.Bottom - to.Top;
|
||||
default:
|
||||
return double.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,23 +501,27 @@ namespace OpenNest.Geometry
|
||||
if (direction.X < -Tolerance.Epsilon)
|
||||
{
|
||||
var d = (box.Left - boundary.Left) / -direction.X;
|
||||
if (d < dist) dist = d;
|
||||
if (d < dist)
|
||||
dist = d;
|
||||
}
|
||||
else if (direction.X > Tolerance.Epsilon)
|
||||
{
|
||||
var d = (boundary.Right - box.Right) / direction.X;
|
||||
if (d < dist) dist = d;
|
||||
if (d < dist)
|
||||
dist = d;
|
||||
}
|
||||
|
||||
if (direction.Y < -Tolerance.Epsilon)
|
||||
{
|
||||
var d = (box.Bottom - boundary.Bottom) / -direction.Y;
|
||||
if (d < dist) dist = d;
|
||||
if (d < dist)
|
||||
dist = d;
|
||||
}
|
||||
else if (direction.Y > Tolerance.Epsilon)
|
||||
{
|
||||
var d = (boundary.Top - box.Top) / direction.Y;
|
||||
if (d < dist) dist = d;
|
||||
if (d < dist)
|
||||
dist = d;
|
||||
}
|
||||
|
||||
return dist < 0 ? 0 : dist;
|
||||
@@ -463,7 +559,11 @@ namespace OpenNest.Geometry
|
||||
/// Computes the minimum translation distance along an arbitrary unit direction
|
||||
/// before any edge of movingLines contacts any edge of stationaryLines.
|
||||
/// </summary>
|
||||
public static double DirectionalDistance(List<Line> movingLines, List<Line> stationaryLines, Vector direction)
|
||||
public static double DirectionalDistance(
|
||||
List<Line> movingLines,
|
||||
List<Line> stationaryLines,
|
||||
Vector direction
|
||||
)
|
||||
{
|
||||
var minDist = double.MaxValue;
|
||||
var dirX = direction.X;
|
||||
@@ -476,8 +576,18 @@ namespace OpenNest.Geometry
|
||||
for (var i = 0; i < stationaryLines.Count; i++)
|
||||
{
|
||||
var e = stationaryLines[i];
|
||||
var d = RayEdgeDistance(mv.X, mv.Y, e.pt1.X, e.pt1.Y, e.pt2.X, e.pt2.Y, dirX, dirY);
|
||||
if (d < minDist) minDist = d;
|
||||
var d = RayEdgeDistance(
|
||||
mv.X,
|
||||
mv.Y,
|
||||
e.pt1.X,
|
||||
e.pt1.Y,
|
||||
e.pt2.X,
|
||||
e.pt2.Y,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,8 +601,18 @@ namespace OpenNest.Geometry
|
||||
for (var i = 0; i < movingLines.Count; i++)
|
||||
{
|
||||
var e = movingLines[i];
|
||||
var d = RayEdgeDistance(sv.X, sv.Y, e.pt1.X, e.pt1.Y, e.pt2.X, e.pt2.Y, oppX, oppY);
|
||||
if (d < minDist) minDist = d;
|
||||
var d = RayEdgeDistance(
|
||||
sv.X,
|
||||
sv.Y,
|
||||
e.pt1.X,
|
||||
e.pt1.Y,
|
||||
e.pt2.X,
|
||||
e.pt2.Y,
|
||||
oppX,
|
||||
oppY
|
||||
);
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,9 +625,16 @@ namespace OpenNest.Geometry
|
||||
/// stationaryEntities. Delegates to the Vector-based overload.
|
||||
/// </summary>
|
||||
public static double DirectionalDistance(
|
||||
List<Entity> movingEntities, List<Entity> stationaryEntities, PushDirection direction)
|
||||
List<Entity> movingEntities,
|
||||
List<Entity> stationaryEntities,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
return DirectionalDistance(movingEntities, stationaryEntities, DirectionToOffset(direction, 1.0));
|
||||
return DirectionalDistance(
|
||||
movingEntities,
|
||||
stationaryEntities,
|
||||
DirectionToOffset(direction, 1.0)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -517,7 +644,10 @@ namespace OpenNest.Geometry
|
||||
/// without tessellation.
|
||||
/// </summary>
|
||||
public static double DirectionalDistance(
|
||||
List<Entity> movingEntities, List<Entity> stationaryEntities, Vector direction)
|
||||
List<Entity> movingEntities,
|
||||
List<Entity> stationaryEntities,
|
||||
Vector direction
|
||||
)
|
||||
{
|
||||
var minDist = double.MaxValue;
|
||||
var dirX = direction.X;
|
||||
@@ -536,7 +666,8 @@ namespace OpenNest.Geometry
|
||||
if (d < minDist)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0) return 0;
|
||||
if (d <= 0)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -557,7 +688,8 @@ namespace OpenNest.Geometry
|
||||
if (d < minDist)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0) return 0;
|
||||
if (d <= 0)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -566,10 +698,24 @@ namespace OpenNest.Geometry
|
||||
// Phases 1-2 sample arc endpoints and cardinal extremes, but the actual
|
||||
// closest point on a small corner arc to a straight edge may lie between
|
||||
// those samples. Use ClosestPointTo to find it and fire a ray from there.
|
||||
minDist = ArcToLineClosestDistance(movingEntities, stationaryEntities, dirX, dirY, minDist);
|
||||
if (minDist <= 0) return 0;
|
||||
minDist = ArcToLineClosestDistance(stationaryEntities, movingEntities, oppX, oppY, minDist);
|
||||
if (minDist <= 0) return 0;
|
||||
minDist = ArcToLineClosestDistance(
|
||||
movingEntities,
|
||||
stationaryEntities,
|
||||
dirX,
|
||||
dirY,
|
||||
minDist
|
||||
);
|
||||
if (minDist <= 0)
|
||||
return 0;
|
||||
minDist = ArcToLineClosestDistance(
|
||||
stationaryEntities,
|
||||
movingEntities,
|
||||
oppX,
|
||||
oppY,
|
||||
minDist
|
||||
);
|
||||
if (minDist <= 0)
|
||||
return 0;
|
||||
|
||||
// Phase 4: Curve-to-curve direct distance.
|
||||
// The vertex-to-entity approach misses the closest contact between two
|
||||
@@ -605,20 +751,35 @@ namespace OpenNest.Geometry
|
||||
if (me is Arc mArc)
|
||||
{
|
||||
var angle = Angle.NormalizeRad(System.Math.Atan2(toCy, toCx));
|
||||
if (!Angle.IsBetweenRad(angle, mArc.StartAngle, mArc.EndAngle, mArc.IsReversed))
|
||||
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))
|
||||
if (
|
||||
!Angle.IsBetweenRad(
|
||||
angle,
|
||||
sArc.StartAngle,
|
||||
sArc.EndAngle,
|
||||
sArc.IsReversed
|
||||
)
|
||||
)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
minDist = d;
|
||||
if (d <= 0) return 0;
|
||||
if (d <= 0)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,8 +787,12 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
private static double ArcToLineClosestDistance(
|
||||
List<Entity> arcEntities, List<Entity> lineEntities,
|
||||
double dirX, double dirY, double minDist)
|
||||
List<Entity> arcEntities,
|
||||
List<Entity> lineEntities,
|
||||
double dirX,
|
||||
double dirY,
|
||||
double minDist
|
||||
)
|
||||
{
|
||||
for (var i = 0; i < arcEntities.Count; i++)
|
||||
{
|
||||
@@ -662,15 +827,30 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var theta = k == 0 ? theta1 : theta2;
|
||||
|
||||
if (!Angle.IsBetweenRad(theta, arc.StartAngle, arc.EndAngle, arc.IsReversed))
|
||||
if (
|
||||
!Angle.IsBetweenRad(theta, arc.StartAngle, arc.EndAngle, arc.IsReversed)
|
||||
)
|
||||
continue;
|
||||
|
||||
var qx = cx + r * System.Math.Cos(theta);
|
||||
var qy = cy + r * System.Math.Sin(theta);
|
||||
|
||||
var d = RayEdgeDistance(qx, qy, p1x, p1y, line.pt2.X, line.pt2.Y,
|
||||
dirX, dirY);
|
||||
if (d < minDist) { minDist = d; if (d <= 0) return 0; }
|
||||
var d = RayEdgeDistance(
|
||||
qx,
|
||||
qy,
|
||||
p1x,
|
||||
p1y,
|
||||
line.pt2.X,
|
||||
line.pt2.Y,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
if (d < minDist)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -678,28 +858,54 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
private static double RayEntityDistance(
|
||||
double vx, double vy, Entity entity, double dirX, double dirY)
|
||||
double vx,
|
||||
double vy,
|
||||
Entity entity,
|
||||
double dirX,
|
||||
double dirY
|
||||
)
|
||||
{
|
||||
if (entity is Line line)
|
||||
{
|
||||
return RayEdgeDistance(vx, vy,
|
||||
line.pt1.X, line.pt1.Y, line.pt2.X, line.pt2.Y,
|
||||
dirX, dirY);
|
||||
return RayEdgeDistance(
|
||||
vx,
|
||||
vy,
|
||||
line.pt1.X,
|
||||
line.pt1.Y,
|
||||
line.pt2.X,
|
||||
line.pt2.Y,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
}
|
||||
|
||||
if (entity is Arc arc)
|
||||
{
|
||||
return RayArcDistance(vx, vy,
|
||||
arc.Center.X, arc.Center.Y, arc.Radius,
|
||||
arc.StartAngle, arc.EndAngle, arc.IsReversed,
|
||||
dirX, dirY);
|
||||
return RayArcDistance(
|
||||
vx,
|
||||
vy,
|
||||
arc.Center.X,
|
||||
arc.Center.Y,
|
||||
arc.Radius,
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
}
|
||||
|
||||
if (entity is Circle circle)
|
||||
{
|
||||
return RayCircleDistance(vx, vy,
|
||||
circle.Center.X, circle.Center.Y, circle.Radius,
|
||||
dirX, dirY);
|
||||
return RayCircleDistance(
|
||||
vx,
|
||||
vy,
|
||||
circle.Center.X,
|
||||
circle.Center.Y,
|
||||
circle.Radius,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
}
|
||||
|
||||
return double.MaxValue;
|
||||
@@ -759,7 +965,10 @@ namespace OpenNest.Geometry
|
||||
return CollectVertices(ToEdgeArray(lines), offset);
|
||||
}
|
||||
|
||||
private static HashSet<Vector> CollectVertices((Vector start, Vector end)[] edges, Vector offset)
|
||||
private static HashSet<Vector> CollectVertices(
|
||||
(Vector start, Vector end)[] edges,
|
||||
Vector offset
|
||||
)
|
||||
{
|
||||
var vertices = new HashSet<Vector>();
|
||||
for (var i = 0; i < edges.Length; i++)
|
||||
@@ -778,26 +987,48 @@ namespace OpenNest.Geometry
|
||||
return edges;
|
||||
}
|
||||
|
||||
private static void SortEdgesForPruning((Vector start, Vector end)[] edges, PushDirection direction)
|
||||
private static void SortEdgesForPruning(
|
||||
(Vector start, Vector end)[] edges,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
if (direction == PushDirection.Left || direction == PushDirection.Right)
|
||||
System.Array.Sort(edges, (a, b) =>
|
||||
System.Math.Min(a.start.Y, a.end.Y).CompareTo(System.Math.Min(b.start.Y, b.end.Y)));
|
||||
System.Array.Sort(
|
||||
edges,
|
||||
(a, b) =>
|
||||
System
|
||||
.Math.Min(a.start.Y, a.end.Y)
|
||||
.CompareTo(System.Math.Min(b.start.Y, b.end.Y))
|
||||
);
|
||||
else
|
||||
System.Array.Sort(edges, (a, b) =>
|
||||
System.Math.Min(a.start.X, a.end.X).CompareTo(System.Math.Min(b.start.X, b.end.X)));
|
||||
System.Array.Sort(
|
||||
edges,
|
||||
(a, b) =>
|
||||
System
|
||||
.Math.Min(a.start.X, a.end.X)
|
||||
.CompareTo(System.Math.Min(b.start.X, b.end.X))
|
||||
);
|
||||
}
|
||||
|
||||
private static bool TryGetCurveParams(Entity entity, out double cx, out double cy, out double r)
|
||||
private static bool TryGetCurveParams(
|
||||
Entity entity,
|
||||
out double cx,
|
||||
out double cy,
|
||||
out double r
|
||||
)
|
||||
{
|
||||
if (entity is Circle circle)
|
||||
{
|
||||
cx = circle.Center.X; cy = circle.Center.Y; r = circle.Radius;
|
||||
cx = circle.Center.X;
|
||||
cy = circle.Center.Y;
|
||||
r = circle.Radius;
|
||||
return true;
|
||||
}
|
||||
if (entity is Arc arc)
|
||||
{
|
||||
cx = arc.Center.X; cy = arc.Center.Y; r = arc.Radius;
|
||||
cx = arc.Center.X;
|
||||
cy = arc.Center.Y;
|
||||
r = arc.Radius;
|
||||
return true;
|
||||
}
|
||||
cx = cy = r = 0;
|
||||
@@ -850,7 +1081,13 @@ namespace OpenNest.Geometry
|
||||
return new Box(lft, btm, rgt - lft, top - btm);
|
||||
}
|
||||
|
||||
private static bool FindVerticalLimits(Vector pt, Box bounds, List<Box> boxes, out double top, out double btm)
|
||||
private static bool FindVerticalLimits(
|
||||
Vector pt,
|
||||
Box bounds,
|
||||
List<Box> boxes,
|
||||
out double top,
|
||||
out double btm
|
||||
)
|
||||
{
|
||||
top = double.MaxValue;
|
||||
btm = double.MinValue;
|
||||
@@ -868,20 +1105,30 @@ namespace OpenNest.Geometry
|
||||
|
||||
if (top == double.MaxValue)
|
||||
{
|
||||
if (bounds.Top > pt.Y) top = bounds.Top;
|
||||
else return false;
|
||||
if (bounds.Top > pt.Y)
|
||||
top = bounds.Top;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
if (btm == double.MinValue)
|
||||
{
|
||||
if (bounds.Bottom < pt.Y) btm = bounds.Bottom;
|
||||
else return false;
|
||||
if (bounds.Bottom < pt.Y)
|
||||
btm = bounds.Bottom;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool FindHorizontalLimits(Vector pt, Box bounds, List<Box> boxes, out double lft, out double rgt)
|
||||
private static bool FindHorizontalLimits(
|
||||
Vector pt,
|
||||
Box bounds,
|
||||
List<Box> boxes,
|
||||
out double lft,
|
||||
out double rgt
|
||||
)
|
||||
{
|
||||
lft = double.MinValue;
|
||||
rgt = double.MaxValue;
|
||||
@@ -899,14 +1146,18 @@ namespace OpenNest.Geometry
|
||||
|
||||
if (rgt == double.MaxValue)
|
||||
{
|
||||
if (bounds.Right > pt.X) rgt = bounds.Right;
|
||||
else return false;
|
||||
if (bounds.Right > pt.X)
|
||||
rgt = bounds.Right;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lft == double.MinValue)
|
||||
{
|
||||
if (bounds.Left < pt.X) lft = bounds.Left;
|
||||
else return false;
|
||||
if (bounds.Left < pt.X)
|
||||
lft = bounds.Left;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -8,7 +8,11 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
private const int MinPointsForArc = 3;
|
||||
|
||||
public static List<Entity> Convert(List<Vector> points, bool isClosed, double tolerance = 0.001)
|
||||
public static List<Entity> Convert(
|
||||
List<Vector> points,
|
||||
bool isClosed,
|
||||
double tolerance = 0.001
|
||||
)
|
||||
{
|
||||
if (points == null || points.Count < 2)
|
||||
return new List<Entity>();
|
||||
@@ -37,8 +41,12 @@ namespace OpenNest.Geometry
|
||||
return entities;
|
||||
}
|
||||
|
||||
private static ArcFitResult TryFitArc(List<Vector> points, int start,
|
||||
Vector chainedTangent, double tolerance)
|
||||
private static ArcFitResult TryFitArc(
|
||||
List<Vector> points,
|
||||
int start,
|
||||
Vector chainedTangent,
|
||||
double tolerance
|
||||
)
|
||||
{
|
||||
var minEnd = start + MinPointsForArc - 1;
|
||||
if (minEnd >= points.Count)
|
||||
@@ -83,7 +91,8 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
private static (Vector center, double radius, double deviation) FitCircumscribed(
|
||||
List<Vector> points)
|
||||
List<Vector> points
|
||||
)
|
||||
{
|
||||
if (points.Count < 3)
|
||||
return (Vector.Invalid, 0, double.MaxValue);
|
||||
@@ -131,11 +140,16 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
private static (Vector center, double radius, double deviation) FitWithStartTangent(
|
||||
List<Vector> points, Vector tangent) =>
|
||||
ArcFit.FitWithStartTangent(points, tangent);
|
||||
List<Vector> points,
|
||||
Vector tangent
|
||||
) => ArcFit.FitWithStartTangent(points, tangent);
|
||||
|
||||
private static double MaxRadialDeviation(List<Vector> points, double cx, double cy, double radius) =>
|
||||
ArcFit.MaxRadialDeviation(points, cx, cy, radius);
|
||||
private static double MaxRadialDeviation(
|
||||
List<Vector> points,
|
||||
double cx,
|
||||
double cy,
|
||||
double radius
|
||||
) => ArcFit.MaxRadialDeviation(points, cx, cy, radius);
|
||||
|
||||
private static double SumSignedAngles(Vector center, List<Vector> points)
|
||||
{
|
||||
@@ -145,8 +159,10 @@ namespace OpenNest.Geometry
|
||||
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;
|
||||
@@ -160,9 +176,7 @@ namespace OpenNest.Geometry
|
||||
var rx = lastPt.X - center.X;
|
||||
var ry = lastPt.Y - center.Y;
|
||||
|
||||
return totalAngle >= 0
|
||||
? new Vector(-ry, rx)
|
||||
: new Vector(ry, -rx);
|
||||
return totalAngle >= 0 ? new Vector(-ry, rx) : new Vector(ry, -rx);
|
||||
}
|
||||
|
||||
private static Arc CreateArc(Vector center, double radius, List<Vector> points)
|
||||
@@ -174,8 +188,10 @@ namespace OpenNest.Geometry
|
||||
var endAngle = System.Math.Atan2(lastPoint.Y - center.Y, lastPoint.X - center.X);
|
||||
var isReversed = SumSignedAngles(center, points) < 0;
|
||||
|
||||
if (startAngle < 0) startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0) endAngle += Angle.TwoPI;
|
||||
if (startAngle < 0)
|
||||
startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0)
|
||||
endAngle += Angle.TwoPI;
|
||||
|
||||
return new Arc(center, radius, startAngle, endAngle, isReversed);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -31,7 +31,7 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
// Use a simple but effective hash combine.
|
||||
// Use a simple but effective hash combine.
|
||||
// We use a small epsilon-safe rounding if needed, but for uniqueness in HashSet
|
||||
// during a single operation, raw bits or slightly rounded is usually fine.
|
||||
// However, IsEqualTo uses Tolerance.Epsilon, so we should probably round to some precision.
|
||||
|
||||
Reference in New Issue
Block a user