fix(geometry): nest the same on Windows and Linux

Geometry, nesting and import code now call PortableMath instead of
System.Math for Sin, Cos, Tan, Atan, Atan2, Asin and Acos (Core, Engine,
IO, Data; 269 calls in 64 files). A test fails if those projects call the
runtime's trig again.

With the runtime's trig, Windows and Linux disagreed in the last bit at
non-quarter angles and the same job could nest differently: the
learned-angle nests (a cross fixture) placed 23 parts in Strip on Windows
and 28 on Linux. Now every Engine.Tests pin gives the same layout on both
(3600 passing on Linux and on DesktopPC), Linux layouts are unchanged, and
the learned-angle rows are re-pinned to the shared layouts, which pass the
overlap and work-area checks, and run on every platform again.

Fixes Project Memory 518f3b58.
This commit is contained in:
aj committed 2026-11-09 19:18:00 -05:00
1 parent 921d100aa0
commit 49603eddf9
68 files changed
+327 -273

No files matched your search

+2 -2
View File
@@ -62,8 +62,8 @@ namespace OpenNest.Bending
else
{
var angle = StartPoint.AngleTo(EndPoint);
var dx = System.Math.Cos(angle) * etchLength;
var dy = System.Math.Sin(angle) * etchLength;
var dx = PortableMath.Cos(angle) * etchLength;
var dy = PortableMath.Sin(angle) * etchLength;
result.Add(
CreateEtchLine(StartPoint, new Vector(StartPoint.X + dx, StartPoint.Y + dy))
+4 -4
View File
@@ -153,8 +153,8 @@ namespace OpenNest.Breakoffs
}
var arc = (Arc)e;
var angle = ArcAngleAt(arc, local);
return new Vector(arc.Center.X + arc.Radius * System.Math.Cos(angle),
arc.Center.Y + arc.Radius * System.Math.Sin(angle));
return new Vector(arc.Center.X + arc.Radius * PortableMath.Cos(angle),
arc.Center.Y + arc.Radius * PortableMath.Sin(angle));
}
private static Vector TangentOn(Entity e, double local)
@@ -168,8 +168,8 @@ namespace OpenNest.Breakoffs
var arc = (Arc)e;
var angle = ArcAngleAt(arc, local);
return arc.IsReversed
? new Vector(System.Math.Sin(angle), -System.Math.Cos(angle))
: new Vector(-System.Math.Sin(angle), System.Math.Cos(angle));
? new Vector(PortableMath.Sin(angle), -PortableMath.Cos(angle))
: new Vector(-PortableMath.Sin(angle), PortableMath.Cos(angle));
}
private static double ArcAngleAt(Arc arc, double local) =>
+3 -3
View File
@@ -123,7 +123,7 @@ namespace OpenNest.CNC
{
var angle = StartAngle + Sweep * fraction;
var sign = System.Math.Sign(Sweep);
return new Vector(-System.Math.Sin(angle) * sign, System.Math.Cos(angle) * sign);
return new Vector(-PortableMath.Sin(angle) * sign, PortableMath.Cos(angle) * sign);
}
return Length > 0 ? new Vector((End.X - Start.X) / Length, (End.Y - Start.Y) / Length) : Vector.Zero;
}
@@ -137,8 +137,8 @@ namespace OpenNest.CNC
{
var angle = StartAngle + Sweep * fraction;
return new Vector(
Center.X + Radius * System.Math.Cos(angle),
Center.Y + Radius * System.Math.Sin(angle));
Center.X + Radius * PortableMath.Cos(angle),
Center.Y + Radius * PortableMath.Sin(angle));
}
return new Vector(
@@ -5,6 +5,7 @@ using System.Threading;
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Diagnostics;
using OpenNest.Geometry;
using OpenNest.Math;
using OpenNest.Support;
namespace OpenNest.CNC.CuttingPlanning;
@@ -511,8 +512,8 @@ public sealed class PreparedContours
if (entity is Circle circle)
{
for (var angle = 0; angle < 8 && entries.Count < maxEntries; angle++)
Add(i, circle.Center + new Vector(System.Math.Cos(angle * System.Math.PI / 4),
System.Math.Sin(angle * System.Math.PI / 4)) * circle.Radius);
Add(i, circle.Center + new Vector(PortableMath.Cos(angle * System.Math.PI / 4),
PortableMath.Sin(angle * System.Math.PI / 4)) * circle.Radius);
}
else
{
@@ -764,8 +765,8 @@ public sealed class PreparedContours
// The legacy eight compass points, same native construction.
for (var angle = 0; angle < 8; angle++)
fallbacks.Add(Fallback(i, circle.Center + new Vector(
System.Math.Cos(angle * System.Math.PI / 4),
System.Math.Sin(angle * System.Math.PI / 4)) * circle.Radius,
PortableMath.Cos(angle * System.Math.PI / 4),
PortableMath.Sin(angle * System.Math.PI / 4)) * circle.Radius,
AutomaticEntryKind.CircleCompass));
break;
}
@@ -977,7 +978,7 @@ public sealed class PreparedContours
if (ContourCuttingStrategy.ClassifyTurn((before.EndPoint - before.StartPoint).Normalize(),
(after.EndPoint - after.StartPoint).Normalize(), winding) != ContourCuttingStrategy.CornerKind.Convex)
return null;
var reach = arc.Radius * System.Math.Tan(sweep / 2);
var reach = arc.Radius * PortableMath.Tan(sweep / 2);
return reach <= System.Math.Min(before.Length, after.Length) ? reach : null;
bool IsSmooth(Vector vertex, Entity entity) =>
@@ -163,8 +163,8 @@ internal static class SeamOvercut
/// <summary>The angle traveled around <paramref name="center"/> from one point to another, in [0, 2pi).</summary>
private static double Travel(Vector center, Vector from, Vector to, bool clockwise)
{
var start = System.Math.Atan2(from.Y - center.Y, from.X - center.X);
var end = System.Math.Atan2(to.Y - center.Y, to.X - center.X);
var start = PortableMath.Atan2(from.Y - center.Y, from.X - center.X);
var end = PortableMath.Atan2(to.Y - center.Y, to.X - center.X);
var travel = (clockwise ? start - end : end - start) % (2 * System.Math.PI);
return travel < 0 ? travel + 2 * System.Math.PI : travel;
}
@@ -516,8 +516,8 @@ namespace OpenNest.CNC.CuttingStrategy
var outwardAngle = normal - System.Math.PI;
point = new Vector(
circle.Center.X + circle.Radius * System.Math.Cos(outwardAngle),
circle.Center.Y + circle.Radius * System.Math.Sin(outwardAngle)
circle.Center.X + circle.Radius * PortableMath.Cos(outwardAngle),
circle.Center.Y + circle.Radius * PortableMath.Sin(outwardAngle)
);
}
@@ -974,13 +974,13 @@ namespace OpenNest.CNC.CuttingStrategy
var adjacentNormal = ComputeNormal(point, corner.Incoming, contourType, winding);
// Sum unit normals rather than averaging angles (which fails at 0/2Ï€).
// Winding makes this point into the scrap even at reflex corners.
var x = System.Math.Cos(normal) + System.Math.Cos(adjacentNormal);
var y = System.Math.Sin(normal) + System.Math.Sin(adjacentNormal);
var x = PortableMath.Cos(normal) + PortableMath.Cos(adjacentNormal);
var y = PortableMath.Sin(normal) + PortableMath.Sin(adjacentNormal);
if (!double.IsFinite(x) || !double.IsFinite(y)
|| x * x + y * y <= Tolerance.Epsilon * Tolerance.Epsilon)
return null; // Opposing normals at a cusp have no unique bisector.
return Angle.NormalizeRad(System.Math.Atan2(y, x));
return Angle.NormalizeRad(PortableMath.Atan2(y, x));
}
/// <summary>
@@ -1009,12 +1009,12 @@ namespace OpenNest.CNC.CuttingStrategy
|| ClassifyTurn(tangentEnd, tangentStart, winding) != CornerKind.Convex)
return null;
var x = System.Math.Cos(startNormal) + System.Math.Cos(endNormal);
var y = System.Math.Sin(startNormal) + System.Math.Sin(endNormal);
var x = PortableMath.Cos(startNormal) + PortableMath.Cos(endNormal);
var y = PortableMath.Sin(startNormal) + PortableMath.Sin(endNormal);
if (x * x + y * y <= Tolerance.Epsilon * Tolerance.Epsilon)
return null;
var normal = Angle.NormalizeRad(System.Math.Atan2(y, x));
var normal = Angle.NormalizeRad(PortableMath.Atan2(y, x));
var pierce = leadIn.GetPiercePoint(start, normal, turn);
var leadOutEnd = ((LinearMove)leadOut.Generate(end, normal, turn)[0]).EndPoint;
return IsClearStraightLead(shape, start, pierce, clearance)
@@ -1248,7 +1248,7 @@ namespace OpenNest.CNC.CuttingStrategy
var length = pierce.DistanceTo(point);
if (length <= Math.Tolerance.Epsilon)
return leadIn;
var direction = System.Math.Atan2(pierce.Y - point.Y, pierce.X - point.X);
var direction = PortableMath.Atan2(pierce.Y - point.Y, pierce.X - point.X);
return new LineLeadIn
{
Length = length,
@@ -1352,7 +1352,7 @@ namespace OpenNest.CNC.CuttingStrategy
private static double Clearance(Shape hole, Vector point) => hole.ClosestPointTo(point, out _).DistanceTo(point);
private static Vector Inward(double normal) => new(System.Math.Cos(normal), System.Math.Sin(normal));
private static Vector Inward(double normal) => new(PortableMath.Cos(normal), PortableMath.Sin(normal));
/// <summary>
/// A straight lead-in from <paramref name="pierce"/> to <paramref name="point"/>, with the
@@ -1360,7 +1360,7 @@ namespace OpenNest.CNC.CuttingStrategy
/// </summary>
private static LeadIn StraightLeadIn(Vector point, Vector pierce, out double normal)
{
normal = System.Math.Atan2(pierce.Y - point.Y, pierce.X - point.X);
normal = PortableMath.Atan2(pierce.Y - point.Y, pierce.X - point.X);
return new LineLeadIn { Length = pierce.DistanceTo(point), ApproachAngle = 90 };
}
@@ -1397,7 +1397,7 @@ namespace OpenNest.CNC.CuttingStrategy
if (shape.Entities is [Circle round] && round.Radius > tabSize / 2)
{
var entry = round.Center.AngleTo(center);
var gap = 2 * System.Math.Asin(tabSize / (2 * round.Radius));
var gap = 2 * PortableMath.Asin(tabSize / (2 * round.Radius));
var reversed = round.Rotation == RotationType.CW;
var arc = new Arc(round.Center, round.Radius, entry, reversed ? entry + gap : entry - gap, reversed)
{
@@ -1577,7 +1577,7 @@ namespace OpenNest.CNC.CuttingStrategy
path = (Shape)cut.Clone();
path.Reverse();
}
var normal = Angle.NormalizeRad(System.Math.Atan2(scrap.Y, scrap.X));
var normal = Angle.NormalizeRad(PortableMath.Atan2(scrap.Y, scrap.X));
program.Codes.AddRange(leadIn.Generate(entry, normal, fromEnd ? RotationType.CW : RotationType.CCW));
program.Codes.AddRange(ConvertShapeToMoves(path, entry, LayerType.Break));
}
@@ -1782,9 +1782,9 @@ namespace OpenNest.CNC.CuttingStrategy
private static bool OnArc(Vector center, Vector start, Vector end, Vector point, RotationType rotation)
{
var a0 = System.Math.Atan2(start.Y - center.Y, start.X - center.X);
var a1 = System.Math.Atan2(end.Y - center.Y, end.X - center.X);
var ap = System.Math.Atan2(point.Y - center.Y, point.X - center.X);
var a0 = PortableMath.Atan2(start.Y - center.Y, start.X - center.X);
var a1 = PortableMath.Atan2(end.Y - center.Y, end.X - center.X);
var ap = PortableMath.Atan2(point.Y - center.Y, point.X - center.X);
var sign = rotation == RotationType.CCW ? 1 : -1;
var sweep = Angle.NormalizeRad(sign * (a1 - a0));
if (sweep <= Tolerance.Epsilon)
@@ -1841,8 +1841,8 @@ namespace OpenNest.CNC.CuttingStrategy
{
var angle = atEnd ? arc.EndAngle : arc.StartAngle;
return arc.IsReversed
? new Vector(System.Math.Sin(angle), -System.Math.Cos(angle))
: new Vector(-System.Math.Sin(angle), System.Math.Cos(angle));
? new Vector(PortableMath.Sin(angle), -PortableMath.Cos(angle))
: new Vector(-PortableMath.Sin(angle), PortableMath.Cos(angle));
}
var line = (Line)entity;
return line.EndPoint - line.StartPoint;
@@ -46,8 +46,8 @@ namespace OpenNest.CNC.CuttingStrategy
var angle = contourNormalAngle + (winding == RotationType.CCW ? shortfall : -shortfall);
var center = ArcCenter(contourStartPoint, contourNormalAngle);
return new Vector(
center.X + Radius * System.Math.Cos(angle),
center.Y + Radius * System.Math.Sin(angle)
center.X + Radius * PortableMath.Cos(angle),
center.Y + Radius * PortableMath.Sin(angle)
);
}
@@ -55,8 +55,8 @@ namespace OpenNest.CNC.CuttingStrategy
new ArcLeadIn { Radius = Radius * factor, SweepAngle = SweepAngle };
private Vector ArcCenter(Vector contourStartPoint, double contourNormalAngle) => new Vector(
contourStartPoint.X + Radius * System.Math.Cos(contourNormalAngle),
contourStartPoint.Y + Radius * System.Math.Sin(contourNormalAngle)
contourStartPoint.X + Radius * PortableMath.Cos(contourNormalAngle),
contourStartPoint.Y + Radius * PortableMath.Sin(contourNormalAngle)
);
}
}
@@ -28,22 +28,22 @@ namespace OpenNest.CNC.CuttingStrategy
public static LineArcApproach Create(Vector contourStartPoint, double contourNormalAngle,
double arcRadius, double approachAngle, double lineLength, RotationType winding)
{
var arcCenterX = contourStartPoint.X + arcRadius * System.Math.Cos(contourNormalAngle);
var arcCenterY = contourStartPoint.Y + arcRadius * System.Math.Sin(contourNormalAngle);
var arcCenterX = contourStartPoint.X + arcRadius * PortableMath.Cos(contourNormalAngle);
var arcCenterY = contourStartPoint.Y + arcRadius * PortableMath.Sin(contourNormalAngle);
// The approach angle is measured back from the normal against the turn, so the arc
// runs the short way round to the contour point whichever way it turns.
var approach = Angle.ToRadians(approachAngle);
var lineAngle = contourNormalAngle + (winding == RotationType.CCW ? approach : -approach);
var arcStartX = arcCenterX + arcRadius * System.Math.Cos(lineAngle);
var arcStartY = arcCenterY + arcRadius * System.Math.Sin(lineAngle);
var arcStartX = arcCenterX + arcRadius * PortableMath.Cos(lineAngle);
var arcStartY = arcCenterY + arcRadius * PortableMath.Sin(lineAngle);
return new LineArcApproach(
new Vector(arcCenterX, arcCenterY),
new Vector(arcStartX, arcStartY),
new Vector(
arcStartX + lineLength * System.Math.Cos(lineAngle),
arcStartY + lineLength * System.Math.Sin(lineAngle)));
arcStartX + lineLength * PortableMath.Cos(lineAngle),
arcStartY + lineLength * PortableMath.Sin(lineAngle)));
}
/// <summary>Rapid to the pierce point, then the line and arc on the lead-in layer.</summary>
@@ -29,8 +29,8 @@ namespace OpenNest.CNC.CuttingStrategy
{
var approachAngle = contourNormalAngle - Angle.HalfPI + Angle.ToRadians(ApproachAngle);
return new Vector(
contourStartPoint.X + Length * System.Math.Cos(approachAngle),
contourStartPoint.Y + Length * System.Math.Sin(approachAngle)
contourStartPoint.X + Length * PortableMath.Cos(approachAngle),
contourStartPoint.Y + Length * PortableMath.Sin(approachAngle)
);
}
@@ -21,8 +21,8 @@ namespace OpenNest.CNC.CuttingStrategy
var secondAngle = contourNormalAngle - Angle.HalfPI + Angle.ToRadians(ApproachAngle1);
var midPoint = new Vector(
contourStartPoint.X + Length2 * System.Math.Cos(secondAngle),
contourStartPoint.Y + Length2 * System.Math.Sin(secondAngle)
contourStartPoint.X + Length2 * PortableMath.Cos(secondAngle),
contourStartPoint.Y + Length2 * PortableMath.Sin(secondAngle)
);
return new List<ICode>
@@ -37,13 +37,13 @@ namespace OpenNest.CNC.CuttingStrategy
RotationType winding = RotationType.CW)
{
var secondAngle = contourNormalAngle - Angle.HalfPI + Angle.ToRadians(ApproachAngle1);
var midX = contourStartPoint.X + Length2 * System.Math.Cos(secondAngle);
var midY = contourStartPoint.Y + Length2 * System.Math.Sin(secondAngle);
var midX = contourStartPoint.X + Length2 * PortableMath.Cos(secondAngle);
var midY = contourStartPoint.Y + Length2 * PortableMath.Sin(secondAngle);
var firstAngle = secondAngle + Angle.ToRadians(ApproachAngle2);
return new Vector(
midX + Length1 * System.Math.Cos(firstAngle),
midY + Length1 * System.Math.Sin(firstAngle)
midX + Length1 * PortableMath.Cos(firstAngle),
midY + Length1 * PortableMath.Sin(firstAngle)
);
}
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.CNC.CuttingStrategy
{
@@ -17,16 +18,16 @@ namespace OpenNest.CNC.CuttingStrategy
RotationType winding = RotationType.CW
)
{
var arcCenterX = contourEndPoint.X + Radius * System.Math.Cos(contourNormalAngle);
var arcCenterY = contourEndPoint.Y + Radius * System.Math.Sin(contourNormalAngle);
var arcCenterX = contourEndPoint.X + Radius * PortableMath.Cos(contourNormalAngle);
var arcCenterY = contourEndPoint.Y + Radius * PortableMath.Sin(contourNormalAngle);
var arcCenter = new Vector(arcCenterX, arcCenterY);
// The contour point sits opposite the normal from the center; turn a quarter on.
var endAngle = contourNormalAngle
+ (winding == RotationType.CCW ? -System.Math.PI / 2 : System.Math.PI / 2);
var endPoint = new Vector(
arcCenterX + Radius * System.Math.Cos(endAngle),
arcCenterY + Radius * System.Math.Sin(endAngle)
arcCenterX + Radius * PortableMath.Cos(endAngle),
arcCenterY + Radius * PortableMath.Sin(endAngle)
);
return new List<ICode>
@@ -17,8 +17,8 @@ namespace OpenNest.CNC.CuttingStrategy
{
var overcutAngle = contourNormalAngle + Angle.HalfPI - Angle.ToRadians(ApproachAngle);
var endPoint = new Vector(
contourEndPoint.X + Length * System.Math.Cos(overcutAngle),
contourEndPoint.Y + Length * System.Math.Sin(overcutAngle)
contourEndPoint.X + Length * PortableMath.Cos(overcutAngle),
contourEndPoint.Y + Length * PortableMath.Sin(overcutAngle)
);
return new List<ICode> { new LinearMove(endPoint) { Layer = LayerType.Leadout } };
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.CNC.CuttingStrategy
{
@@ -23,8 +24,8 @@ namespace OpenNest.CNC.CuttingStrategy
var scoreAngle = contourNormalAngle + System.Math.PI;
var scoreEnd = new Vector(
tabStartPoint.X + BreakerDepth * System.Math.Cos(scoreAngle),
tabStartPoint.Y + BreakerDepth * System.Math.Sin(scoreAngle)
tabStartPoint.X + BreakerDepth * PortableMath.Cos(scoreAngle),
tabStartPoint.Y + BreakerDepth * PortableMath.Sin(scoreAngle)
);
codes.Add(new LinearMove(scoreEnd));
codes.Add(new RapidMove(tabEndPoint));
+2 -2
View File
@@ -103,8 +103,8 @@ namespace OpenNest.CNC
if (subpgm.Offset.X != 0 || subpgm.Offset.Y != 0)
{
var cos = System.Math.Cos(angle);
var sin = System.Math.Sin(angle);
var cos = PortableMath.Cos(angle);
var sin = PortableMath.Sin(angle);
var dx = subpgm.Offset.X - origin.X;
var dy = subpgm.Offset.Y - origin.Y;
subpgm.Offset = new Geometry.Vector(
+2 -1
View File
@@ -1,6 +1,7 @@
using System.Linq;
using OpenNest.Converters;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest
{
@@ -123,7 +124,7 @@ namespace OpenNest
// keeps offsets lying exactly on an axis (mirror-symmetric parts) away from the
// interval edge so floating-point noise cannot flip the choice.
var halfPi = System.Math.PI / 2.0;
var direction = System.Math.Atan2(dy, dx);
var direction = PortableMath.Atan2(dy, dx);
for (var turns = 0; turns < 4; turns++)
{
var relative = direction + turns * halfPi - (System.Math.PI - PreferenceMargin);
+3 -2
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using OpenNest.CNC;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest
{
@@ -238,8 +239,8 @@ namespace OpenNest
{
var din = (points[k] - points[k - 1]).Normalize();
var dout = (points[k + 1] - points[k]).Normalize();
var turn = System.Math.Atan2(System.Math.Abs(Cross(din, dout)), Dot(din, dout));
snaps[k] = kerf / 2 * System.Math.Tan(turn / 2) + 2 * CommonLineGeometry.GapTolerance;
var turn = PortableMath.Atan2(System.Math.Abs(Cross(din, dout)), Dot(din, dout));
snaps[k] = kerf / 2 * PortableMath.Tan(turn / 2) + 2 * CommonLineGeometry.GapTolerance;
}
for (var k = 0; k + 1 < points.Length; k++)
yield return new Leg(points[k], points[k + 1], snaps[k], snaps[k + 1]);
+4 -3
View File
@@ -4,6 +4,7 @@ using System.Linq;
using OpenNest.CNC;
using OpenNest.Diagnostics;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest
{
@@ -161,7 +162,7 @@ namespace OpenNest
Vector offset, double kerf, double maxGap)
{
var along = chosen.First.Direction;
var minimum = System.Math.Sin(MinCornerTurn);
var minimum = PortableMath.Sin(MinCornerTurn);
var shifted = Shift(moving, offset);
var slides = FacingEdges(anchor, shifted, -maxGap, maxGap)
.Select(facing => (Facing: facing, Across: Dot(along, facing.First.Outward)))
@@ -266,14 +267,14 @@ namespace OpenNest
var din = new Vector((aEnd.X - aOther.X) / aLength, (aEnd.Y - aOther.Y) / aLength);
var dout = new Vector((bOther.X - bEnd.X) / bLength, (bOther.Y - bEnd.Y) / bLength);
var cross = Cross(din, dout);
var turn = System.Math.Atan2(System.Math.Abs(cross), Dot(din, dout));
var turn = PortableMath.Atan2(System.Math.Abs(cross), Dot(din, dout));
if (turn < MinCornerTurn || turn > MaxCornerTurn)
return null;
// aEnd + din·s = bEnd − dout·t: the corner lies s past a's end and t before b's end.
var gap = bEnd - aEnd;
var s = Cross(gap, dout) / cross;
var t = Cross(din, gap) / cross;
var reach = kerf / 2 * System.Math.Tan(turn / 2) + 2 * GapTolerance;
var reach = kerf / 2 * PortableMath.Tan(turn / 2) + 2 * GapTolerance;
if (s < -GapTolerance || t < -GapTolerance || s > reach || t > reach)
return null;
return new Vector(aEnd.X + din.X * s, aEnd.Y + din.Y * s);
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Threading;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.Diagnostics;
@@ -243,8 +244,8 @@ internal static class PostVerificationGeometry
System.Math.Max(Start.X, End.X), System.Math.Max(Start.Y, End.Y));
internal Vector Midpoint => Center is { } center
? new Vector(center.X + Radius * System.Math.Cos(StartAngle + Sweep / 2),
center.Y + Radius * System.Math.Sin(StartAngle + Sweep / 2))
? new Vector(center.X + Radius * PortableMath.Cos(StartAngle + Sweep / 2),
center.Y + Radius * PortableMath.Sin(StartAngle + Sweep / 2))
: (Start + End) * 0.5;
/// <summary>
@@ -287,7 +288,7 @@ internal static class PostVerificationGeometry
internal double DistanceAlong(Vector point) => Center is { } center
? point.DistanceTo(Start) <= Epsilon ? 0
: Travel(System.Math.Atan2(point.Y - center.Y, point.X - center.X)) * Radius
: Travel(PortableMath.Atan2(point.Y - center.Y, point.X - center.X)) * Radius
: Dot(point - Start, (End - Start) * (1 / Length));
/// <summary>Unit direction of travel at the curve's start or end.</summary>
@@ -314,8 +315,8 @@ internal static class PostVerificationGeometry
if (distance <= 0) return Start;
if (distance >= Length) return End;
return Center is { } center
? center + new Vector(System.Math.Cos(StartAngle + System.Math.Sign(Sweep) * distance / Radius),
System.Math.Sin(StartAngle + System.Math.Sign(Sweep) * distance / Radius)) * Radius
? center + new Vector(PortableMath.Cos(StartAngle + System.Math.Sign(Sweep) * distance / Radius),
PortableMath.Sin(StartAngle + System.Math.Sign(Sweep) * distance / Radius)) * Radius
: Start + (End - Start) * (distance / Length);
}
@@ -331,7 +332,7 @@ internal static class PostVerificationGeometry
/// </summary>
internal Curve Until(Vector point) => Center is { } center
? new Curve(Start, point, center, Radius,
System.Math.Sign(Sweep) * Travel(System.Math.Atan2(point.Y - center.Y, point.X - center.X)))
System.Math.Sign(Sweep) * Travel(PortableMath.Atan2(point.Y - center.Y, point.X - center.X)))
: new Curve(Start, point, null, 0, 0);
internal bool Contains(Vector point) => Contains(point, Epsilon);
@@ -524,16 +525,16 @@ internal static class PostVerificationGeometry
if (radius <= Epsilon || !double.IsFinite(radius)
|| System.Math.Abs(radius - end.DistanceTo(c)) > Epsilon * System.Math.Max(1, radius))
throw new ArgumentException("Arc has zero or inconsistent radius.");
var a = System.Math.Atan2(start.Y - c.Y, start.X - c.X);
var b = System.Math.Atan2(end.Y - c.Y, end.X - c.X);
var a = PortableMath.Atan2(start.Y - c.Y, start.X - c.X);
var b = PortableMath.Atan2(end.Y - c.Y, end.X - c.X);
var sweep = start.DistanceTo(end) <= Epsilon ? TwoPi
: Normalize(clockwise ? a - b : b - a);
return new Curve(start, end, c, radius, clockwise ? -sweep : sweep);
}
private double StartAngle => System.Math.Atan2(Start.Y - Center.Value.Y, Start.X - Center.Value.X);
private double StartAngle => PortableMath.Atan2(Start.Y - Center.Value.Y, Start.X - Center.Value.X);
private double Travel(double angle) => Normalize(Sweep < 0 ? StartAngle - angle : angle - StartAngle);
private bool OnArc(Vector point) => Travel(System.Math.Atan2(point.Y - Center.Value.Y,
private bool OnArc(Vector point) => Travel(PortableMath.Atan2(point.Y - Center.Value.Y,
point.X - Center.Value.X)) <= System.Math.Abs(Sweep) + Epsilon / Radius
|| point.DistanceTo(Start) <= Epsilon || point.DistanceTo(End) <= Epsilon;
@@ -637,13 +638,13 @@ internal static class PostVerificationGeometry
{
var a = StartAngle + System.Math.Sign(Sweep) * breaks[i - 1];
var b = StartAngle + System.Math.Sign(Sweep) * breaks[i];
var ya = i == 1 ? Start.Y : center.Y + Radius * System.Math.Sin(a);
var yb = i == breaks.Count - 1 ? End.Y : center.Y + Radius * System.Math.Sin(b);
var ya = i == 1 ? Start.Y : center.Y + Radius * PortableMath.Sin(a);
var yb = i == breaks.Count - 1 ? End.Y : center.Y + Radius * PortableMath.Sin(b);
if ((ya > rayY) == (yb > rayY))
continue;
var dy = point.Y - center.Y;
var dx = System.Math.Sqrt(System.Math.Max(0, Radius * Radius - dy * dy));
var x = center.X + (System.Math.Cos((a + b) / 2) >= 0 ? dx : -dx);
var x = center.X + (PortableMath.Cos((a + b) / 2) >= 0 ? dx : -dx);
if (x > point.X)
inside = !inside;
}
+1 -1
View File
@@ -189,7 +189,7 @@ namespace OpenNest.Dimensions
var angle = arc != null && kind == DimensionKind.Radius
? ClampToSweep(arc, dimension.Angle)
: dimension.Angle;
var direction = new Vector(System.Math.Cos(angle), System.Math.Sin(angle));
var direction = new Vector(PortableMath.Cos(angle), PortableMath.Sin(angle));
var arrow = center + direction * radius;
var far = center - direction * radius;
return new DimensionLayout
+2 -1
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.Dimensions
{
@@ -80,7 +81,7 @@ namespace OpenNest.Dimensions
var dx = point.X - Center.X;
var dy = point.Y - Center.Y;
var distance = System.Math.Sqrt(dx * dx + dy * dy);
var angle = distance > 0 ? System.Math.Atan2(dy, dx) : Angle;
var angle = distance > 0 ? PortableMath.Atan2(dy, dx) : Angle;
if (Arc != null && Kind == DimensionKind.Radius)
angle = DimensionBuilder.ClampToSweep(Arc, angle);
+12 -12
View File
@@ -141,8 +141,8 @@ namespace OpenNest.Geometry
public Vector StartPoint()
{
return new Vector(
Center.X + Radius * System.Math.Cos(StartAngle),
Center.Y + Radius * System.Math.Sin(StartAngle)
Center.X + Radius * PortableMath.Cos(StartAngle),
Center.Y + Radius * PortableMath.Sin(StartAngle)
);
}
@@ -153,8 +153,8 @@ namespace OpenNest.Geometry
public Vector EndPoint()
{
return new Vector(
Center.X + Radius * System.Math.Cos(EndAngle),
Center.Y + Radius * System.Math.Sin(EndAngle)
Center.X + Radius * PortableMath.Cos(EndAngle),
Center.Y + Radius * PortableMath.Sin(EndAngle)
);
}
@@ -165,8 +165,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.X + Radius * PortableMath.Cos(midAngle),
Center.Y + Radius * PortableMath.Sin(midAngle)
);
}
@@ -231,7 +231,7 @@ namespace OpenNest.Geometry
if (tolerance >= Radius)
return 1;
var maxAngle = 2.0 * System.Math.Acos(1.0 - tolerance / Radius);
var maxAngle = 2.0 * PortableMath.Acos(1.0 - tolerance / Radius);
return System.Math.Max(
1,
(int)System.Math.Ceiling(System.Math.Abs(SweepAngle()) / maxAngle)
@@ -250,7 +250,7 @@ namespace OpenNest.Geometry
var r =
circumscribe && segments > 0
? Radius / System.Math.Cos(System.Math.Abs(stepAngle) / 2.0)
? Radius / PortableMath.Cos(System.Math.Abs(stepAngle) / 2.0)
: Radius;
for (int i = 0; i <= segments; ++i)
@@ -259,8 +259,8 @@ namespace OpenNest.Geometry
points.Add(
new Vector(
System.Math.Cos(angle) * r + Center.X,
System.Math.Sin(angle) * r + Center.Y
PortableMath.Cos(angle) * r + Center.X,
PortableMath.Sin(angle) * r + Center.Y
)
);
}
@@ -479,8 +479,8 @@ namespace OpenNest.Geometry
if (Angle.IsBetweenRad(angle, StartAngle, EndAngle, IsReversed))
{
return new Vector(
System.Math.Cos(angle) * Radius + Center.X,
System.Math.Sin(angle) * Radius + Center.Y
PortableMath.Cos(angle) * Radius + Center.X,
PortableMath.Sin(angle) * Radius + Center.Y
);
}
else
+5 -5
View File
@@ -102,8 +102,8 @@ namespace OpenNest.Geometry
return (Vector.Invalid, 0, double.MaxValue);
var halfChord = chordLen / 2;
var radius = halfChord / System.Math.Abs(System.Math.Sin(theta));
var d = -halfChord / System.Math.Tan(theta);
var radius = halfChord / System.Math.Abs(PortableMath.Sin(theta));
var d = -halfChord / PortableMath.Tan(theta);
var cx = (p1.X + pn.X) / 2 + d * -uy;
var cy = (p1.Y + pn.Y) / 2 + d * ux;
@@ -116,7 +116,7 @@ namespace OpenNest.Geometry
var len = System.Math.Sqrt(to.X * to.X + to.Y * to.Y);
if (len < 1e-10)
return 0;
return System.Math.Atan2(ux * to.Y - uy * to.X, ux * to.X + uy * to.Y);
return PortableMath.Atan2(ux * to.Y - uy * to.X, ux * to.X + uy * to.Y);
}
/// <summary>
@@ -128,8 +128,8 @@ namespace OpenNest.Geometry
var total = 0.0;
for (var i = 0; i < points.Count - 1; i++)
{
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 a1 = PortableMath.Atan2(points[i].Y - center.Y, points[i].X - center.X);
var a2 = PortableMath.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;
+6 -6
View File
@@ -126,7 +126,7 @@ namespace OpenNest.Geometry
if (tolerance >= Radius)
return 3;
var maxAngle = 2.0 * System.Math.Acos(1.0 - tolerance / Radius);
var maxAngle = 2.0 * PortableMath.Acos(1.0 - tolerance / Radius);
return System.Math.Max(3, (int)System.Math.Ceiling(Angle.TwoPI / maxAngle));
}
@@ -137,7 +137,7 @@ namespace OpenNest.Geometry
Rotation == RotationType.CW ? -Angle.TwoPI / segments : Angle.TwoPI / segments;
var r =
circumscribe && segments > 0 ? Radius / System.Math.Cos(stepAngle / 2.0) : Radius;
circumscribe && segments > 0 ? Radius / PortableMath.Cos(stepAngle / 2.0) : Radius;
for (int i = 0; i <= segments; ++i)
{
@@ -145,8 +145,8 @@ namespace OpenNest.Geometry
points.Add(
new Vector(
System.Math.Cos(angle) * r + Center.X,
System.Math.Sin(angle) * r + Center.Y
PortableMath.Cos(angle) * r + Center.X,
PortableMath.Sin(angle) * r + Center.Y
)
);
}
@@ -312,8 +312,8 @@ namespace OpenNest.Geometry
var angle = Center.AngleTo(pt);
return new Vector(
System.Math.Cos(angle) * Radius + Center.X,
System.Math.Sin(angle) * Radius + Center.Y
PortableMath.Cos(angle) * Radius + Center.X,
PortableMath.Sin(angle) * Radius + Center.Y
);
}
+7 -7
View File
@@ -299,7 +299,7 @@ namespace OpenNest.Geometry
var sweep = arc.SweepAngle();
var segments = CircumscribedSegments(arc.Radius, sweep, tolerance);
var step = (arc.IsReversed ? -sweep : sweep) / segments;
var r = arc.Radius / System.Math.Cos(System.Math.Abs(step) / 2);
var r = arc.Radius / PortableMath.Cos(System.Math.Abs(step) / 2);
points.Add(arc.StartPoint());
@@ -308,8 +308,8 @@ namespace OpenNest.Geometry
var angle = arc.StartAngle + step * (i + 0.5);
points.Add(
new Vector(
arc.Center.X + r * System.Math.Cos(angle),
arc.Center.Y + r * System.Math.Sin(angle)
arc.Center.X + r * PortableMath.Cos(angle),
arc.Center.Y + r * PortableMath.Sin(angle)
)
);
}
@@ -332,14 +332,14 @@ namespace OpenNest.Geometry
var segments = CircumscribedSegments(circle.Radius, Angle.TwoPI, tolerance);
var step = Angle.TwoPI / segments;
var r = circle.Radius / System.Math.Cos(step / 2);
var r = circle.Radius / PortableMath.Cos(step / 2);
for (var i = 0; i < segments; i++)
{
points.Add(
new Vector(
circle.Center.X + r * System.Math.Cos(step * i),
circle.Center.Y + r * System.Math.Sin(step * i)
circle.Center.X + r * PortableMath.Cos(step * i),
circle.Center.Y + r * PortableMath.Sin(step * i)
)
);
}
@@ -352,7 +352,7 @@ namespace OpenNest.Geometry
/// </summary>
private static int CircumscribedSegments(double radius, double sweep, double tolerance)
{
var maxHalfStep = System.Math.Acos(radius / (radius + tolerance));
var maxHalfStep = PortableMath.Acos(radius / (radius + tolerance));
var segments = (int)System.Math.Ceiling(System.Math.Abs(sweep) / (2 * maxHalfStep));
var quarters = (int)System.Math.Ceiling(System.Math.Abs(sweep) / Angle.HalfPI);
+9 -9
View File
@@ -80,7 +80,7 @@ namespace OpenNest.Geometry
var dirX = dx / length;
var dirY = dy / length;
var tangent = System.Math.Atan2(dirY, dirX);
var tangent = PortableMath.Atan2(dirY, dirX);
var count = System.Math.Max(1, (int)(length / spacing));
var step = length / (count + 1);
@@ -114,8 +114,8 @@ namespace OpenNest.Geometry
if (radius < Tolerance.Epsilon)
return;
var startAngle = System.Math.Atan2(start.Y - center.Y, start.X - center.X);
var endAngle = System.Math.Atan2(end.Y - center.Y, end.X - center.X);
var startAngle = PortableMath.Atan2(start.Y - center.Y, start.X - center.X);
var endAngle = PortableMath.Atan2(end.Y - center.Y, end.X - center.X);
double sweep;
if (rotation == RotationType.CCW)
@@ -147,8 +147,8 @@ namespace OpenNest.Geometry
angle = startAngle - stepAngle * i;
var pt = new Vector(
center.X + radius * System.Math.Cos(angle),
center.Y + radius * System.Math.Sin(angle)
center.X + radius * PortableMath.Cos(angle),
center.Y + radius * PortableMath.Sin(angle)
);
double tangent;
@@ -157,7 +157,7 @@ namespace OpenNest.Geometry
else
tangent = angle - System.Math.PI / 2;
var dir = new Vector(System.Math.Cos(tangent), System.Math.Sin(tangent));
var dir = new Vector(PortableMath.Cos(tangent), PortableMath.Sin(tangent));
var at = radius * System.Math.Abs(stepAngle * i);
output.Add(new ContourSample(pt, dir, tangent, at));
}
@@ -325,7 +325,7 @@ namespace OpenNest.Geometry
var local = segLen > 0 ? (s - segStart) / segLen : 0.0;
var dir = SegmentDirection(a, b);
var pt = new Vector(a.X + (b.X - a.X) * local, a.Y + (b.Y - a.Y) * local);
output.Add(new ContourSample(pt, dir, System.Math.Atan2(dir.Y, dir.X), s));
output.Add(new ContourSample(pt, dir, PortableMath.Atan2(dir.Y, dir.X), s));
}
}
@@ -357,8 +357,8 @@ namespace OpenNest.Geometry
// Same sweep convention as ArcMoves: always in (0, 2*PI], so a full
// circle counts its whole circumference toward the walk's arclength.
var startAngle = System.Math.Atan2(start.Y - center.Y, start.X - center.X);
var endAngle = System.Math.Atan2(end.Y - center.Y, end.X - center.X);
var startAngle = PortableMath.Atan2(start.Y - center.Y, start.X - center.X);
var endAngle = PortableMath.Atan2(end.Y - center.Y, end.X - center.X);
var sweep =
rotation == RotationType.CCW ? endAngle - startAngle : startAngle - endAngle;
if (sweep <= 0)
+1 -1
View File
@@ -786,7 +786,7 @@ namespace OpenNest.Geometry
if (!(covXX > 0) || double.IsNaN(covXY) || double.IsNaN(covXX))
return false;
dTheta = System.Math.Atan2(covXY, covXX);
dTheta = PortableMath.Atan2(covXY, covXX);
if (double.IsNaN(dTheta) || double.IsInfinity(dTheta))
return false;
+14 -14
View File
@@ -17,11 +17,11 @@ namespace OpenNest.Geometry
double t
)
{
var x = semiMajor * System.Math.Cos(t);
var y = semiMinor * System.Math.Sin(t);
var x = semiMajor * PortableMath.Cos(t);
var y = semiMinor * PortableMath.Sin(t);
var cos = System.Math.Cos(rotation);
var sin = System.Math.Sin(rotation);
var cos = PortableMath.Cos(rotation);
var sin = PortableMath.Sin(rotation);
return new Vector(center.X + x * cos - y * sin, center.Y + x * sin + y * cos);
}
@@ -33,11 +33,11 @@ namespace OpenNest.Geometry
double t
)
{
var tx = -semiMajor * System.Math.Sin(t);
var ty = semiMinor * System.Math.Cos(t);
var tx = -semiMajor * PortableMath.Sin(t);
var ty = semiMinor * PortableMath.Cos(t);
var cos = System.Math.Cos(rotation);
var sin = System.Math.Sin(rotation);
var cos = PortableMath.Cos(rotation);
var sin = PortableMath.Sin(rotation);
return new Vector(tx * cos - ty * sin, tx * sin + ty * cos);
}
@@ -51,11 +51,11 @@ namespace OpenNest.Geometry
{
// Inward normal: perpendicular to tangent, pointing toward center of curvature.
// In local coords: N(t) = (-b*cos(t), -a*sin(t))
var nx = -semiMinor * System.Math.Cos(t);
var ny = -semiMajor * System.Math.Sin(t);
var nx = -semiMinor * PortableMath.Cos(t);
var ny = -semiMajor * PortableMath.Sin(t);
var cos = System.Math.Cos(rotation);
var sin = System.Math.Sin(rotation);
var cos = PortableMath.Cos(rotation);
var sin = PortableMath.Sin(rotation);
return new Vector(nx * cos - ny * sin, nx * sin + ny * cos);
}
@@ -326,8 +326,8 @@ namespace OpenNest.Geometry
radius = p0.DistanceTo(cc);
}
var startAngle = System.Math.Atan2(p0.Y - arcCenter.Y, p0.X - arcCenter.X);
var endAngle = System.Math.Atan2(p1.Y - arcCenter.Y, p1.X - arcCenter.X);
var startAngle = PortableMath.Atan2(p0.Y - arcCenter.Y, p0.X - arcCenter.X);
var endAngle = PortableMath.Atan2(p1.Y - arcCenter.Y, p1.X - arcCenter.X);
var points = new List<Vector> { p0, pMid, p1 };
var isReversed = ArcFit.SumSignedAngles(arcCenter, points) < 0;
+1 -1
View File
@@ -148,7 +148,7 @@ namespace OpenNest.Geometry
System.Math.Abs(point.DistanceTo(center) - circle.Radius) <= Tolerance.ChainTolerance;
bool OnOverlap(Vector point)
{
var angle = System.Math.Atan2(point.Y - center.Y, point.X - center.X);
var angle = PortableMath.Atan2(point.Y - center.Y, point.X - center.X);
return spans.Count(s =>
{
var offset = Angle.NormalizeRad(angle - s.Start);
+10 -10
View File
@@ -333,10 +333,10 @@ public class GeometrySimplifier
// Mirroring reverses winding — swap start/end to preserve arc direction
var mirrorStart = NormalizeAngle(
System.Math.Atan2(mirrorEp.Y - mirrorCenter.Y, mirrorEp.X - mirrorCenter.X)
PortableMath.Atan2(mirrorEp.Y - mirrorCenter.Y, mirrorEp.X - mirrorCenter.X)
);
var mirrorEnd = NormalizeAngle(
System.Math.Atan2(mirrorSp.Y - mirrorCenter.Y, mirrorSp.X - mirrorCenter.X)
PortableMath.Atan2(mirrorSp.Y - mirrorCenter.Y, mirrorSp.X - mirrorCenter.X)
);
var result = new Arc(mirrorCenter, arc.Radius, mirrorStart, mirrorEnd, arc.IsReversed);
@@ -653,8 +653,8 @@ public class GeometrySimplifier
/// <summary>The direction of travel along an arc at <paramref name="angle"/>.</summary>
private static Vector ArcTangent(Arc arc, double angle) =>
arc.IsReversed
? new Vector(System.Math.Sin(angle), -System.Math.Cos(angle))
: new Vector(-System.Math.Sin(angle), System.Math.Cos(angle));
? new Vector(PortableMath.Sin(angle), -PortableMath.Cos(angle))
: new Vector(-PortableMath.Sin(angle), PortableMath.Cos(angle));
/// <summary>
/// Computes the tangent direction at the last point of a fitted arc,
@@ -779,10 +779,10 @@ public class GeometrySimplifier
var lastPoint = points[^1];
var startAngle = NormalizeAngle(
System.Math.Atan2(firstPoint.Y - center.Y, firstPoint.X - center.X)
PortableMath.Atan2(firstPoint.Y - center.Y, firstPoint.X - center.X)
);
var endAngle = NormalizeAngle(
System.Math.Atan2(lastPoint.Y - center.Y, lastPoint.X - center.X)
PortableMath.Atan2(lastPoint.Y - center.Y, lastPoint.X - center.X)
);
var isReversed = ArcFit.SumSignedAngles(center, points) < 0;
@@ -815,8 +815,8 @@ public class GeometrySimplifier
bool isReversed
)
{
var startAngle = System.Math.Atan2(points[0].Y - center.Y, points[0].X - center.X);
var endAngle = System.Math.Atan2(points[^1].Y - center.Y, points[^1].X - center.X);
var startAngle = PortableMath.Atan2(points[0].Y - center.Y, points[0].X - center.X);
var endAngle = PortableMath.Atan2(points[^1].Y - center.Y, points[^1].X - center.X);
var sweep = endAngle - startAngle;
if (isReversed)
@@ -838,8 +838,8 @@ public class GeometrySimplifier
{
var t = (double)i / sampleCount;
var angle = startAngle + sweep * t;
var px = center.X + radius * System.Math.Cos(angle);
var py = center.Y + radius * System.Math.Sin(angle);
var px = center.X + radius * PortableMath.Cos(angle);
var py = center.Y + radius * PortableMath.Sin(angle);
var arcPt = new Vector(px, py);
var minDist = double.MaxValue;
+3 -3
View File
@@ -214,7 +214,7 @@ namespace OpenNest.Geometry
{
var m1 = Slope();
var m2 = line.Slope();
return System.Math.Atan(System.Math.Abs((m2 - m1) / (1 + m2 * m1)));
return PortableMath.Atan(System.Math.Abs((m2 - m1) / (1 + m2 * m1)));
}
/// <summary>
@@ -395,8 +395,8 @@ namespace OpenNest.Geometry
{
var angle = OpenNest.Math.Angle.NormalizeRad(Angle() + OpenNest.Math.Angle.HalfPI);
var x = System.Math.Cos(angle) * distance;
var y = System.Math.Sin(angle) * distance;
var x = PortableMath.Cos(angle) * distance;
var y = PortableMath.Sin(angle) * distance;
var pt = side == OffsetSide.Left ? new Vector(x, y) : new Vector(-x, -y);
+2 -2
View File
@@ -146,11 +146,11 @@ namespace OpenNest.Geometry
}
else
{
var angleA = System.Math.Atan2(orderedA[ia].Y, orderedA[ia].X);
var angleA = PortableMath.Atan2(orderedA[ia].Y, orderedA[ia].X);
if (angleA < 0)
angleA += Angle.TwoPI;
var angleB = System.Math.Atan2(orderedB[ib].Y, orderedB[ib].X);
var angleB = PortableMath.Atan2(orderedB[ib].Y, orderedB[ib].X);
if (angleB < 0)
angleB += Angle.TwoPI;
+5 -5
View File
@@ -42,7 +42,7 @@ namespace OpenNest.Geometry
{
var dx = vertices[1].X - vertices[0].X;
var dy = vertices[1].Y - vertices[0].Y;
var angle = System.Math.Atan2(dy, dx);
var angle = PortableMath.Atan2(dy, dx);
var length = System.Math.Sqrt(dx * dx + dy * dy);
return new BoundingRectangleResult(angle, length, 0);
}
@@ -100,7 +100,7 @@ namespace OpenNest.Geometry
if (best == null || area < best.Area - margin)
{
var edgeAngle = System.Math.Atan2(edgeY, edgeX);
var edgeAngle = PortableMath.Atan2(edgeY, edgeX);
best = new BoundingRectangleResult(edgeAngle, width, height);
}
}
@@ -137,7 +137,7 @@ namespace OpenNest.Geometry
if (edgeLen.IsEqualTo(0))
continue;
var edgeAngle = Angle.NormalizeRad(System.Math.Atan2(edgeY, edgeX));
var edgeAngle = Angle.NormalizeRad(PortableMath.Atan2(edgeY, edgeX));
// Each edge produces 4 candidate angles (0, 90, 180, 270 relative to edge)
for (int k = 0; k < 4; k++)
@@ -172,8 +172,8 @@ namespace OpenNest.Geometry
double angle
)
{
var cos = System.Math.Cos(angle);
var sin = System.Math.Sin(angle);
var cos = PortableMath.Cos(angle);
var sin = PortableMath.Sin(angle);
double minU = double.MaxValue,
maxU = double.MinValue;
+3 -2
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using OpenNest.Math;
namespace OpenNest.Geometry
{
@@ -661,8 +662,8 @@ namespace OpenNest.Geometry
private static Vector ArcTangent(Arc arc, double angle)
{
var sin = System.Math.Sin(angle);
var cos = System.Math.Cos(angle);
var sin = PortableMath.Sin(angle);
var cos = PortableMath.Cos(angle);
return arc.IsReversed ? new Vector(sin, -cos) : new Vector(-sin, cos);
}
+1 -1
View File
@@ -176,7 +176,7 @@ namespace OpenNest.Geometry
var arc = (Arc)entity;
var deltaX = weldedPoint.X - arc.Center.X;
var deltaY = weldedPoint.Y - arc.Center.Y;
var angle = System.Math.Atan2(deltaY, deltaX);
var angle = PortableMath.Atan2(deltaY, deltaX);
if (isStart)
arc.StartAngle = angle;
+8 -8
View File
@@ -237,8 +237,8 @@ namespace OpenNest.Geometry
if (IsZero(inTangent) || IsZero(outTangent))
return ContactSide.Ambiguous;
var outAngle = System.Math.Atan2(outTangent.Y, outTangent.X);
var inAngle = System.Math.Atan2(-inTangent.Y, -inTangent.X);
var outAngle = PortableMath.Atan2(outTangent.Y, outTangent.X);
var inAngle = PortableMath.Atan2(-inTangent.Y, -inTangent.X);
var left = materialLeft[loopOf[best]];
start = left ? outAngle : inAngle;
@@ -326,7 +326,7 @@ namespace OpenNest.Geometry
private static Vector ArcTangent(double angle, bool clockwise)
{
var sign = clockwise ? -1.0 : 1.0;
return new Vector(-System.Math.Sin(angle) * sign, System.Math.Cos(angle) * sign);
return new Vector(-PortableMath.Sin(angle) * sign, PortableMath.Cos(angle) * sign);
}
private static Vector Direction(Vector from, Vector to)
@@ -380,7 +380,7 @@ namespace OpenNest.Geometry
var sweep = arc.IsReversed ? -arc.SweepAngle() : arc.SweepAngle();
var r = arc.Radius;
area += Cross(arc.StartPoint(), arc.EndPoint()) / 2;
area += r * r / 2 * (sweep - System.Math.Sin(sweep));
area += r * r / 2 * (sweep - PortableMath.Sin(sweep));
break;
}
}
@@ -432,13 +432,13 @@ namespace OpenNest.Geometry
{
var a = arc.StartAngle + sign * cuts[k - 1];
var b = arc.StartAngle + sign * cuts[k];
var y1 = arc.Center.Y + arc.Radius * System.Math.Sin(a);
var y2 = arc.Center.Y + arc.Radius * System.Math.Sin(b);
var y1 = arc.Center.Y + arc.Radius * PortableMath.Sin(a);
var y2 = arc.Center.Y + arc.Radius * PortableMath.Sin(b);
if ((y1 > point.Y) == (y2 > point.Y))
continue;
var dy = point.Y - arc.Center.Y;
var dx = System.Math.Sqrt(System.Math.Max(0, arc.Radius * arc.Radius - dy * dy));
var x = arc.Center.X + (System.Math.Cos((a + b) / 2) >= 0 ? dx : -dx);
var x = arc.Center.X + (PortableMath.Cos((a + b) / 2) >= 0 ? dx : -dx);
if (point.X < x)
inside = !inside;
}
@@ -752,7 +752,7 @@ namespace OpenNest.Geometry
if (stationarySide == ContactSide.Ambiguous || movingSide == ContactSide.Ambiguous)
return true;
var direction = System.Math.Atan2(dirY, dirX);
var direction = PortableMath.Atan2(dirY, dirX);
var stationaryPieces = Split(stationaryStart, stationaryWidth);
var movingPieces = Split(movingStart + System.Math.PI, movingWidth);
var onBoundary = false;
@@ -10,7 +10,7 @@ namespace OpenNest.Geometry
// A zero-radius curve is a point: its angular range has no geometric meaning.
if (arc == null || radius == 0)
return true;
var angle = Angle.NormalizeRad(System.Math.Atan2(y, x));
var angle = Angle.NormalizeRad(PortableMath.Atan2(y, x));
return Angle.IsBetweenRad(angle, arc.StartAngle, arc.EndAngle, arc.IsReversed);
}
+4 -4
View File
@@ -63,7 +63,7 @@ namespace OpenNest.Geometry
continue;
var hitAngle = Angle.NormalizeRad(
System.Math.Atan2(vy + t * rayY - cy, vx + t * rayX - cx)
PortableMath.Atan2(vy + t * rayY - cy, vx + t * rayX - cx)
);
if (!Angle.IsBetweenRad(hitAngle, arc.StartAngle, arc.EndAngle, arc.IsReversed))
continue;
@@ -281,7 +281,7 @@ namespace OpenNest.Geometry
// The directional distance from an arc point at angle θ to the
// line is t(θ) = [A + r·(ey·cosθ − ex·sinθ)] / det.
// dt/dθ = 0 at θ = atan2(−ex, ey) and θ + π.
var theta1 = Angle.NormalizeRad(System.Math.Atan2(-ex, ey));
var theta1 = Angle.NormalizeRad(PortableMath.Atan2(-ex, ey));
var theta2 = Angle.NormalizeRad(theta1 + System.Math.PI);
for (var k = 0; k < 2; k++)
@@ -291,8 +291,8 @@ namespace OpenNest.Geometry
if (arc != null && !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 qx = cx + r * PortableMath.Cos(theta);
var qy = cy + r * PortableMath.Sin(theta);
RayLine(ref sink, qx, qy, p1x, p1y, p2x, p2y, rayX, rayY, arcMoves);
if (sink.IsDone)
+2 -2
View File
@@ -179,7 +179,7 @@ namespace OpenNest.Geometry
if (t1 > -Tolerance.Epsilon)
{
var hitAngle = Angle.NormalizeRad(
System.Math.Atan2(vy + t1 * dirY - cy, vx + t1 * dirX - cx)
PortableMath.Atan2(vy + t1 * dirY - cy, vx + t1 * dirX - cx)
);
if (Angle.IsBetweenRad(hitAngle, startAngle, endAngle, reversed))
best = t1 > Tolerance.Epsilon ? t1 : 0;
@@ -188,7 +188,7 @@ namespace OpenNest.Geometry
if (t2 > -Tolerance.Epsilon && t2 < best)
{
var hitAngle = Angle.NormalizeRad(
System.Math.Atan2(vy + t2 * dirY - cy, vx + t2 * dirX - cx)
PortableMath.Atan2(vy + t2 * dirY - cy, vx + t2 * dirX - cx)
);
if (Angle.IsBetweenRad(hitAngle, startAngle, endAngle, reversed))
best = t2 > Tolerance.Epsilon ? t2 : 0;
+2 -2
View File
@@ -167,8 +167,8 @@ namespace OpenNest.Geometry
var firstPoint = points[0];
var lastPoint = points[^1];
var startAngle = System.Math.Atan2(firstPoint.Y - center.Y, firstPoint.X - center.X);
var endAngle = System.Math.Atan2(lastPoint.Y - center.Y, lastPoint.X - center.X);
var startAngle = PortableMath.Atan2(firstPoint.Y - center.Y, firstPoint.X - center.X);
var endAngle = PortableMath.Atan2(lastPoint.Y - center.Y, lastPoint.X - center.X);
var isReversed = ArcFit.SumSignedAngles(center, points) < 0;
if (startAngle < 0)
+6 -6
View File
@@ -63,7 +63,7 @@ namespace OpenNest.Geometry
public double Angle()
{
return OpenNest.Math.Angle.NormalizeRad(System.Math.Atan2(Y, X));
return OpenNest.Math.Angle.NormalizeRad(PortableMath.Atan2(Y, X));
}
/// <summary>
@@ -99,7 +99,7 @@ namespace OpenNest.Geometry
var dot = v1.X * v2.X + v1.Y + v2.Y;
var det = v1.X * v2.X - v1.Y + v2.Y;
return System.Math.Atan2(det, dot);
return PortableMath.Atan2(det, dot);
}
public static Vector operator +(Vector pt1, Vector pt2)
@@ -161,8 +161,8 @@ namespace OpenNest.Geometry
{
var v = new Vector();
var cos = System.Math.Cos(angle);
var sin = System.Math.Sin(angle);
var cos = PortableMath.Cos(angle);
var sin = PortableMath.Sin(angle);
v.X = X * cos - Y * sin;
v.Y = X * sin + Y * cos;
@@ -175,8 +175,8 @@ namespace OpenNest.Geometry
var v = new Vector();
var pt = this - origin;
var cos = System.Math.Cos(angle);
var sin = System.Math.Sin(angle);
var cos = PortableMath.Cos(angle);
var sin = PortableMath.Sin(angle);
v.X = pt.X * cos - pt.Y * sin + origin.X;
v.Y = pt.X * sin + pt.Y * cos + origin.Y;
+4 -3
View File
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.Shapes
{
@@ -27,7 +28,7 @@ namespace OpenNest.Shapes
{
var n = Sides < 3 ? 3 : Sides;
var center = Width / 2.0;
var circumRadius = Width / (2.0 * System.Math.Cos(System.Math.PI / n));
var circumRadius = Width / (2.0 * PortableMath.Cos(System.Math.PI / n));
var step = 2.0 * System.Math.PI / n;
var start = System.Math.PI / n;
@@ -36,8 +37,8 @@ namespace OpenNest.Shapes
{
var angle = start + i * step;
vertices[i] = new Vector(
center + circumRadius * System.Math.Cos(angle),
center + circumRadius * System.Math.Sin(angle)
center + circumRadius * PortableMath.Cos(angle),
center + circumRadius * PortableMath.Sin(angle)
);
}
+5 -4
View File
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.Shapes
{
@@ -46,7 +47,7 @@ namespace OpenNest.Shapes
{
ShapeDimension.Linear(nameof(OD), new Vector(-OD / 2, 0), new Vector(OD / 2, 0), ShapeDimension.Up, "Ø"),
ShapeDimension.CircleLeader(nameof(HolePatternDiameter), Vector.Zero, HolePatternDiameter / 2,
new Vector(System.Math.Cos(boltAngle), System.Math.Sin(boltAngle)), "Ø"),
new Vector(PortableMath.Cos(boltAngle), PortableMath.Sin(boltAngle)), "Ø"),
};
// The leader crosses the hole nearest 45 degrees through its center, out that way.
@@ -55,7 +56,7 @@ namespace OpenNest.Shapes
var step = 2.0 * System.Math.PI / HoleCount;
var angle = System.Math.Round(System.Math.PI / 4 / step) * step;
var out45 = new Vector(System.Math.Sqrt(0.5), System.Math.Sqrt(0.5));
var center = new Vector(HolePatternDiameter / 2 * System.Math.Cos(angle), HolePatternDiameter / 2 * System.Math.Sin(angle));
var center = new Vector(HolePatternDiameter / 2 * PortableMath.Cos(angle), HolePatternDiameter / 2 * PortableMath.Sin(angle));
dimensions.Add(ShapeDimension.HoleLeader(nameof(HoleDiameter), center, HoleDiameter / 2, out45, "Ø")
.WithCount(nameof(HoleCount), "holes"));
}
@@ -76,8 +77,8 @@ namespace OpenNest.Shapes
for (var i = 0; i < HoleCount; i++)
{
var angle = i * angleStep;
var cx = boltCircleRadius * System.Math.Cos(angle);
var cy = boltCircleRadius * System.Math.Sin(angle);
var cx = boltCircleRadius * PortableMath.Cos(angle);
var cy = boltCircleRadius * PortableMath.Sin(angle);
entities.Add(new Circle(cx, cy, holeRadius));
}
+3 -2
View File
@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using OpenNest.Converters;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest;
@@ -470,8 +471,8 @@ public static class DrawingSplitter
{
var midAngle = (arc.StartAngle + arc.EndAngle) / 2;
return new Vector(
arc.Center.X + arc.Radius * System.Math.Cos(midAngle),
arc.Center.Y + arc.Radius * System.Math.Sin(midAngle)
arc.Center.X + arc.Radius * PortableMath.Cos(midAngle),
arc.Center.Y + arc.Radius * PortableMath.Sin(midAngle)
);
}
+3 -2
View File
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest;
@@ -25,8 +26,8 @@ public class SpikeGrooveSplit : ISplitFeature
var spikeDepth = parameters.SpikeDepth;
var grooveDepth = parameters.GrooveDepth;
var angleRad = OpenNest.Math.Angle.ToRadians(parameters.SpikeAngle / 2);
var spikeHalfWidth = spikeDepth * System.Math.Tan(angleRad);
var grooveHalfWidth = grooveDepth * System.Math.Tan(angleRad);
var spikeHalfWidth = spikeDepth * PortableMath.Tan(angleRad);
var grooveHalfWidth = grooveDepth * PortableMath.Tan(angleRad);
var isVertical = line.Axis == CutOffAxis.Vertical;
var pos = line.Position;
+4 -3
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.Support
{
@@ -177,7 +178,7 @@ namespace OpenNest.Support
{
var box = BoundingRectangle(outline);
var angle = box.Width >= box.Height ? box.Angle : box.Angle + Math.Angle.HalfPI;
return new Vector(System.Math.Cos(angle), System.Math.Sin(angle));
return new Vector(PortableMath.Cos(angle), PortableMath.Sin(angle));
}
// The minimum-area rectangle over every hull edge. Shapes such as a right isosceles
@@ -211,7 +212,7 @@ namespace OpenNest.Support
minV = System.Math.Min(minV, pv);
maxV = System.Math.Max(maxV, pv);
}
candidates.Add(new BoundingRectangleResult(System.Math.Atan2(edge.Y, edge.X), maxU - minU, maxV - minV));
candidates.Add(new BoundingRectangleResult(PortableMath.Atan2(edge.Y, edge.X), maxU - minU, maxV - minV));
}
if (candidates.Count == 0)
return RotatingCalipers.MinimumBoundingRectangle(outline.ToList());
@@ -262,7 +263,7 @@ namespace OpenNest.Support
var outOf = outline[(i + 1) % count] - outline[i];
var cross = into.X * outOf.Y - into.Y * outOf.X;
var dot = into.X * outOf.X + into.Y * outOf.Y;
turns[i] = System.Math.Atan2(cross, dot) * System.Math.Sign(area);
turns[i] = PortableMath.Atan2(cross, dot) * System.Math.Sign(area);
}
// Start after a vertex that does not turn inward so no run is split by the wrap.
@@ -40,6 +40,10 @@ namespace OpenNest.Engine.Tests.Jobs;
/// Re-captured when the pairs phase began tiling herringbone pairs (<c>HerringbonePatterns</c>):
/// the mixed Strip nest keeps 35 laid out differently, and herringbone pairs won the chevron of
/// the learned-angle nests whatever angles were learned, so a cross replaced it.
/// The learned-angle nests were re-captured when geometry moved to <c>PortableMath</c>: their
/// herringbone-era pins came from Windows, whose runtime trig differs from Linux in the last
/// bit, and the cross nested differently on each (Strip 23 there, 28 here). Both platforms
/// now give these layouts, which pass the overlap and work-area checks.
/// Not pinned: richer mixes that pair parallelogram, chevron or tee parts at oblique angles
/// with other irregular items. Their Fill and Vertical Remnant nests vary between runs, most
/// likely because best-fit pair candidates are gathered in a <c>ConcurrentBag</c> and equal
@@ -47,16 +51,6 @@ namespace OpenNest.Engine.Tests.Jobs;
/// </remarks>
public class FillStackCharacterizationTests
{
/// <summary>A theory that runs only on Windows.</summary>
private sealed class WindowsOnlyTheoryAttribute : TheoryAttribute
{
public WindowsOnlyTheoryAttribute()
{
if (!OperatingSystem.IsWindows())
Skip = "Layout differs off Windows (Project Memory 518f3b58)";
}
}
/// <summary>6x4 block with a 2x2 notch cut into the middle of its top edge.</summary>
private static Program Notched()
{
@@ -309,17 +303,11 @@ public class FillStackCharacterizationTests
AssertPinned(layout, expectedCount, expectedDigest);
}
/// <remarks>
/// Windows-only: these pins were captured on Windows, and Linux lays this nest out
/// differently (Strip places 28, not 23) because the two platforms' trig differs in the last
/// bit and the fill stack turns that into different tie and threshold decisions (Project
/// Memory 518f3b58). Unskip on every platform when that is fixed.
/// </remarks>
[WindowsOnlyTheory]
[InlineData("Fill", 28, "BE363E3A47E9B036")]
[InlineData("Strip", 23, "E4AA82327508D538")]
[InlineData("Vertical Remnant", 28, "DB2409DB39642666")]
[InlineData("Horizontal Remnant", 24, "883BD209AE87E2C5")]
[Theory]
[InlineData("Fill", 28, "58E544673B69F3CB")]
[InlineData("Strip", 28, "BB448C553CFECBD8")]
[InlineData("Vertical Remnant", 28, "E146E6F08C26C375")]
[InlineData("Horizontal Remnant", 24, "5D5B0413007B0B07")]
public void Nest_LearnedAngleItems_PinnedLayout(
string strategy,
int expectedCount,
+1 -1
View File
@@ -213,7 +213,7 @@ namespace OpenNest.Engine.BestFit
if (dx * dx + dy * dy < Tolerance.Epsilon)
continue;
var angle = Angle.NormalizeRad(System.Math.Atan2(dy, dx));
var angle = Angle.NormalizeRad(PortableMath.Atan2(dy, dx));
AddUniqueAngle(hullAngles, angle);
}
+2 -2
View File
@@ -54,8 +54,8 @@ namespace OpenNest.Engine.BestFit
return polygon;
var result = new Polygon();
var cos = System.Math.Cos(angle);
var sin = System.Math.Sin(angle);
var cos = PortableMath.Cos(angle);
var sin = PortableMath.Sin(angle);
foreach (var v in polygon.Vertices)
{
+1 -1
View File
@@ -29,7 +29,7 @@ namespace OpenNest.Engine.Fill
/// Pushes movingParts along an arbitrary angle (radians, 0 = right, π/2 = up).
/// </summary>
public static double Push(List<Part> movingParts, Plate plate, double angle) =>
Push(movingParts, plate, new Vector(System.Math.Cos(angle), System.Math.Sin(angle)));
Push(movingParts, plate, new Vector(PortableMath.Cos(angle), PortableMath.Sin(angle)));
private static double Push(List<Part> movingParts, Plate plate, Vector direction)
{
+1 -1
View File
@@ -143,7 +143,7 @@ namespace OpenNest.Engine.Fill
if (lengthSq < Tolerance.Epsilon)
continue;
var angle = -System.Math.Atan2(dy, dx);
var angle = -PortableMath.Atan2(dy, dx);
if (!edges.Any(e => e.angle.IsEqualTo(angle)))
edges.Add((angle, lengthSq));
+2 -2
View File
@@ -260,8 +260,8 @@ namespace OpenNest.Engine.Fill
private static (double Along, double Cross) Extents(List<Vector> hull, double angle, bool alongX)
{
var cos = System.Math.Cos(angle);
var sin = System.Math.Sin(angle);
var cos = PortableMath.Cos(angle);
var sin = PortableMath.Sin(angle);
double minX = double.MaxValue, maxX = double.MinValue;
double minY = double.MaxValue, maxY = double.MinValue;
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using OpenNest.Math;
namespace OpenNest.Engine.Jobs.CommonLines;
@@ -138,8 +139,8 @@ internal sealed class CommonLinePipelinePrepass : IPipelinePrepass
poses.Add((pose.PartId, pose.X, pose.Y, pose.Rotation));
continue;
}
var sine = System.Math.Sin(pose.Rotation);
var cosine = System.Math.Cos(pose.Rotation);
var sine = PortableMath.Sin(pose.Rotation);
var cosine = PortableMath.Cos(pose.Rotation);
var pair = proxy.Pair;
groups.Add(new NestJobCommonLine(kerf, new[] { poses.Count, poses.Count + 1 }));
poses.Add((proxy.PartId, pose.X, pose.Y, pose.Rotation));
@@ -247,7 +247,7 @@ internal static class CutoutCirclePacker
{
if (p.DistanceTo(0, 0) < Epsilon)
return -1;
var angle = System.Math.Atan2(p.X, -p.Y);
var angle = PortableMath.Atan2(p.X, -p.Y);
return angle < 0 ? angle + 2 * System.Math.PI : angle;
}
}
@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading;
using OpenNest.Engine.CirclePacking;
using OpenNest.Engine.Jobs.Adapters;
using OpenNest.Math;
namespace OpenNest.Engine.Jobs.Cutouts;
@@ -317,8 +318,8 @@ internal sealed class CutoutPipelinePrepass : IPipelinePrepass
private void Expand(Bundle bundle, double x, double y, double rotation,
List<(string PartId, double X, double Y, double Rotation)> poses)
{
var sine = System.Math.Sin(rotation);
var cosine = System.Math.Cos(rotation);
var sine = PortableMath.Sin(rotation);
var cosine = PortableMath.Cos(rotation);
foreach (var local in bundle.LocalInserts)
{
var insertX = x + local.X * cosine - local.Y * sine;
+1 -1
View File
@@ -148,7 +148,7 @@ public sealed class JobPartGeometry
var arc = (Arc)entity;
var angle = arc.StartAngle + (arc.IsReversed ? -1 : 1) * arc.SweepAngle() * value;
return arc.Center
+ new Vector(System.Math.Cos(angle), System.Math.Sin(angle)) * arc.Radius;
+ new Vector(PortableMath.Cos(angle), PortableMath.Sin(angle)) * arc.Radius;
}
void Check(Vector point)
{
@@ -83,16 +83,16 @@ internal sealed class PlateFillPolicy
private static double RotatedWidth(NestItem item, double angle)
{
var boundingBox = item.Drawing.Program.BoundingBox();
var cos = System.Math.Abs(System.Math.Cos(angle));
var sin = System.Math.Abs(System.Math.Sin(angle));
var cos = System.Math.Abs(PortableMath.Cos(angle));
var sin = System.Math.Abs(PortableMath.Sin(angle));
return boundingBox.Length * cos + boundingBox.Width * sin;
}
private static double RotatedHeight(NestItem item, double angle)
{
var boundingBox = item.Drawing.Program.BoundingBox();
var cos = System.Math.Abs(System.Math.Cos(angle));
var sin = System.Math.Abs(System.Math.Sin(angle));
var cos = System.Math.Abs(PortableMath.Cos(angle));
var sin = System.Math.Abs(PortableMath.Sin(angle));
return boundingBox.Width * cos + boundingBox.Length * sin;
}
}
@@ -5,6 +5,7 @@ using System.Threading;
using OpenNest.Engine.Jobs.Adapters;
using OpenNest.Engine.Jobs.CommonLines;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.Engine.Jobs.Placement;
@@ -83,8 +84,8 @@ public static partial class PlateFillService
{
var rotation = placed.Rotation;
var first = Place(item.Drawing, rotation, placed.Location);
var sine = System.Math.Sin(rotation);
var cosine = System.Math.Cos(rotation);
var sine = PortableMath.Sin(rotation);
var cosine = PortableMath.Cos(rotation);
var second = Place(item.Drawing, rotation + CommonLinePair.SecondRotation,
new Vector(placed.Location.X + pair.SecondX * cosine - pair.SecondY * sine,
placed.Location.Y + pair.SecondX * sine + pair.SecondY * cosine));
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using Clipper2Lib;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.Engine.NestingEngines.Irregular;
@@ -83,8 +84,8 @@ internal static class ContactFit
double theta = 0, x = centerX, y = centerY;
Pose At() => new(start.Rotation + theta,
x - (System.Math.Cos(theta) * pivotX - System.Math.Sin(theta) * pivotY),
y - (System.Math.Sin(theta) * pivotX + System.Math.Cos(theta) * pivotY));
x - (PortableMath.Cos(theta) * pivotX - PortableMath.Sin(theta) * pivotY),
y - (PortableMath.Sin(theta) * pivotX + PortableMath.Cos(theta) * pivotY));
Pose? best = null;
var bestWorst = double.PositiveInfinity;
var cap = MaxStep;
@@ -205,8 +206,8 @@ internal static class ContactFit
private static PathD Place(PathD local, double theta, double x, double y)
{
var cos = System.Math.Cos(theta);
var sin = System.Math.Sin(theta);
var cos = PortableMath.Cos(theta);
var sin = PortableMath.Sin(theta);
var result = new PathD(local.Count);
foreach (var p in local)
result.Add(new PointD(cos * p.x - sin * p.y + x, sin * p.x + cos * p.y + y));
@@ -6,6 +6,7 @@ using System.Linq;
using System.Threading;
using Clipper2Lib;
using OpenNest.Engine.Jobs;
using OpenNest.Math;
namespace OpenNest.Engine.NestingEngines.Irregular;
@@ -202,7 +203,7 @@ internal sealed class NoFitCache
private static double EdgeAngle(PointD edge)
{
var angle = System.Math.Atan2(edge.y, edge.x);
var angle = PortableMath.Atan2(edge.y, edge.x);
return angle < 0 ? angle + System.Math.PI * 2 : angle;
}
+6 -5
View File
@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.IO
{
@@ -383,9 +384,9 @@ namespace OpenNest.IO
) / d;
var radius = System.Math.Sqrt((ax - ux) * (ax - ux) + (ay - uy) * (ay - uy));
var a0 = System.Math.Atan2(ay - uy, ax - ux);
var am = System.Math.Atan2(by - uy, bx - ux);
var a1 = System.Math.Atan2(cy - uy, cx - ux);
var a0 = PortableMath.Atan2(ay - uy, ax - ux);
var am = PortableMath.Atan2(by - uy, bx - ux);
var a1 = PortableMath.Atan2(cy - uy, cx - ux);
var ccwSweep = a1 - a0;
while (ccwSweep <= 0)
@@ -403,8 +404,8 @@ namespace OpenNest.IO
var angle = a0 + sweep * t;
output.Add(
new Vector(
ux + radius * System.Math.Cos(angle),
uy + radius * System.Math.Sin(angle)
ux + radius * PortableMath.Cos(angle),
uy + radius * PortableMath.Sin(angle)
)
);
}
+2 -2
View File
@@ -398,9 +398,9 @@ namespace OpenNest.IO
center = new XYZ(center.X + CurPos.X, center.Y + CurPos.Y, 0);
}
var startAngle = System.Math.Atan2(CurPos.Y - center.Y, CurPos.X - center.X);
var startAngle = PortableMath.Atan2(CurPos.Y - center.Y, CurPos.X - center.X);
var endAngle = System.Math.Atan2(endpt.Y - center.Y, endpt.X - center.X);
var endAngle = PortableMath.Atan2(endpt.Y - center.Y, endpt.X - center.X);
if (arc.Rotation == RotationType.CW)
Generic.Swap(ref startAngle, ref endAngle);
+2 -1
View File
@@ -4,6 +4,7 @@ using System.Drawing;
using ACadSharp.Entities;
using CSMath;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.IO
{
@@ -210,7 +211,7 @@ namespace OpenNest.IO
var semiMajor = System.Math.Sqrt(majorAxis.X * majorAxis.X + majorAxis.Y * majorAxis.Y);
var semiMinor = semiMajor * ellipse.RadiusRatio;
var rotation = System.Math.Atan2(majorAxis.Y, majorAxis.X);
var rotation = PortableMath.Atan2(majorAxis.Y, majorAxis.X);
var layer = ellipse.Layer.ToOpenNest();
var color = ellipse.ResolveColor();
+3 -2
View File
@@ -7,6 +7,7 @@ using ACadSharp.Tables;
using CSMath;
using OpenNest.Bending;
using OpenNest.Converters;
using OpenNest.Math;
// Disambiguate Entity — both ACadSharp.Entities and OpenNest.Geometry define it
using GeoEntity = OpenNest.Geometry.Entity;
@@ -174,8 +175,8 @@ namespace OpenNest.IO
else
{
var angle = start.AngleTo(end);
var dx = System.Math.Cos(angle) * EtchLength;
var dy = System.Math.Sin(angle) * EtchLength;
var dx = PortableMath.Cos(angle) * EtchLength;
var dy = PortableMath.Sin(angle) * EtchLength;
doc.Entities.Add(
new ACadSharp.Entities.Line
+10 -10
View File
@@ -703,8 +703,8 @@ namespace OpenNest.IO
return;
}
var cos = System.Math.Cos(rotation);
var sin = System.Math.Sin(rotation);
var cos = PortableMath.Cos(rotation);
var sin = PortableMath.Sin(rotation);
var halfX = (from.X - to.X) / 2;
var halfY = (from.Y - to.Y) / 2;
var x = cos * halfX + sin * halfY;
@@ -731,8 +731,8 @@ namespace OpenNest.IO
sin * centerX + cos * centerY + (from.Y + to.Y) / 2
);
var startAngle = System.Math.Atan2((y - centerY) / ry, (x - centerX) / rx);
var endAngle = System.Math.Atan2((-y - centerY) / ry, (-x - centerX) / rx);
var startAngle = PortableMath.Atan2((y - centerY) / ry, (x - centerX) / rx);
var endAngle = PortableMath.Atan2((-y - centerY) / ry, (-x - centerX) / rx);
var delta = endAngle - startAngle;
if (sweep && delta < 0)
delta += Angle.TwoPI;
@@ -758,17 +758,17 @@ namespace OpenNest.IO
Layer layer
)
{
var cos = System.Math.Cos(rotation);
var sin = System.Math.Sin(rotation);
var cos = PortableMath.Cos(rotation);
var sin = PortableMath.Sin(rotation);
var axisX = t.ApplyLinear(new Vector(rx * cos, rx * sin));
var axisY = t.ApplyLinear(new Vector(-ry * sin, ry * cos));
var offset = 0.5 * System.Math.Atan2(
var offset = 0.5 * PortableMath.Atan2(
2 * (axisX.X * axisY.X + axisX.Y * axisY.Y),
axisX.X * axisX.X + axisX.Y * axisX.Y - axisY.X * axisY.X - axisY.Y * axisY.Y
);
var major = axisX * System.Math.Cos(offset) + axisY * System.Math.Sin(offset);
var minor = axisY * System.Math.Cos(offset) - axisX * System.Math.Sin(offset);
var major = axisX * PortableMath.Cos(offset) + axisY * PortableMath.Sin(offset);
var minor = axisY * PortableMath.Cos(offset) - axisX * PortableMath.Sin(offset);
var semiMajor = Length2(major);
var semiMinor = Length2(minor);
@@ -784,7 +784,7 @@ namespace OpenNest.IO
t.Apply(center),
semiMajor,
semiMinor,
System.Math.Atan2(major.Y, major.X),
PortableMath.Atan2(major.Y, major.X),
System.Math.Min(from, to),
System.Math.Max(from, to),
tolerance
+4 -3
View File
@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.IO
{
@@ -126,12 +127,12 @@ namespace OpenNest.IO
private static SvgTransform Rotation(double degrees)
{
var radians = degrees * System.Math.PI / 180;
var cos = System.Math.Cos(radians);
var sin = System.Math.Sin(radians);
var cos = PortableMath.Cos(radians);
var sin = PortableMath.Sin(radians);
return new SvgTransform(cos, sin, -sin, cos, 0, 0);
}
private static double Tan(double degrees) =>
System.Math.Tan(degrees * System.Math.PI / 180);
PortableMath.Tan(degrees * System.Math.PI / 180);
}
}
@@ -112,11 +112,12 @@ public class BoundingBoxHelperTests
return new Box(l, bo, r - l, t - bo);
}
// Frozen from DiscPacker.Rotate / CutoutCirclePacker.Rotate / DrawingAligner.Rotate.
// Frozen from DiscPacker.Rotate / CutoutCirclePacker.Rotate / DrawingAligner.Rotate,
// trig spelled PortableMath as production now is.
private static Vector FrozenRotate(Vector v, double angle)
{
var sine = System.Math.Sin(angle);
var cosine = System.Math.Cos(angle);
var sine = OpenNest.Math.PortableMath.Sin(angle);
var cosine = OpenNest.Math.PortableMath.Cos(angle);
return new Vector(v.X * cosine - v.Y * sine, v.X * sine + v.Y * cosine);
}
}
@@ -2,7 +2,8 @@
// commit ad5f2a9041ee14d0a85aab00e0c8a9f5082d5b6b, before the axis band filter, used only as
// the differential oracle in SlideBandTests. Mechanical edits only: renamed types, internal
// visibility, calls to unchanged SlideEvents members qualified, the private Eps constant
// spelled Tolerance.Epsilon, and the four-space indentation of the original kept.
// spelled Tolerance.Epsilon, trig spelled PortableMath as production now is, and the
// four-space indentation of the original kept.
#nullable disable
using System.Collections.Generic;
using OpenNest.Geometry;
@@ -54,7 +55,7 @@ namespace OpenNest.Tests.Geometry
// The directional distance from an arc point at angle θ to the
// line is t(θ) = [A + r·(ey·cosθ − ex·sinθ)] / det.
// dt/dθ = 0 at θ = atan2(−ex, ey) and θ + π.
var theta1 = Angle.NormalizeRad(System.Math.Atan2(-ex, ey));
var theta1 = Angle.NormalizeRad(PortableMath.Atan2(-ex, ey));
var theta2 = Angle.NormalizeRad(theta1 + System.Math.PI);
for (var k = 0; k < 2; k++)
@@ -64,8 +65,8 @@ namespace OpenNest.Tests.Geometry
if (arc != null && !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 qx = cx + r * PortableMath.Cos(theta);
var qy = cy + r * PortableMath.Sin(theta);
SlideEvents.RayLine(ref sink, qx, qy, p1x, p1y, p2x, p2y, rayX, rayY, arcMoves);
if (sink.IsDone)
+41
View File
@@ -87,6 +87,47 @@ public class PortableMathTests
Assert.True(double.IsNaN(PortableMath.Atan2(double.NaN, 1)));
}
/// <summary>
/// Geometry and nesting code must not call the runtime's trig: one call is enough for the
/// same job to nest differently on Windows and Linux.
/// </summary>
[Fact]
public void LayoutProjects_UsePortableTrigOnly()
{
var root = CuttingPlanning.PlanningCharacterizationTests.RepositoryRoot();
var runtimeTrig = new System.Text.RegularExpressions.Regex(
@"\bMath\.(Sin|Cos|Tan|Atan2|Atan|Asin|Acos|SinCos)\("
);
var offenders = new List<string>();
var projects = new[] { "OpenNest.Core", "OpenNest.Engine", "OpenNest.IO", "OpenNest.Data" };
foreach (var project in projects)
{
var files = Directory.EnumerateFiles(
Path.Combine(root, project),
"*.cs",
SearchOption.AllDirectories
);
foreach (var file in files)
{
var parts = Path.GetRelativePath(root, file).Split(Path.DirectorySeparatorChar);
var generated = parts.Contains("bin") || parts.Contains("obj");
if (generated || parts[^1] == "PortableMath.cs")
continue;
var lines = File.ReadAllLines(file);
for (var i = 0; i < lines.Length; i++)
{
if (runtimeTrig.IsMatch(lines[i]))
offenders.Add($"{string.Join('/', parts)}:{i + 1}");
}
}
}
Assert.True(
offenders.Count == 0,
"use PortableMath instead:\n" + string.Join("\n", offenders)
);
}
/// <summary>
/// The point of PortableMath: the same bits on every platform. The digest was captured on
/// Linux and must also pass on Windows (Project Memory 518f3b58).