diff --git a/OpenNest.Core/Bending/Bend.cs b/OpenNest.Core/Bending/Bend.cs index 50c8b582..0c0c7dbc 100644 --- a/OpenNest.Core/Bending/Bend.cs +++ b/OpenNest.Core/Bending/Bend.cs @@ -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)) diff --git a/OpenNest.Core/Breakoffs/BreakoffPath.cs b/OpenNest.Core/Breakoffs/BreakoffPath.cs index 3ea6a718..ca37e9f1 100644 --- a/OpenNest.Core/Breakoffs/BreakoffPath.cs +++ b/OpenNest.Core/Breakoffs/BreakoffPath.cs @@ -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) => diff --git a/OpenNest.Core/CNC/CutSimulation.cs b/OpenNest.Core/CNC/CutSimulation.cs index 0a230d2d..8824eb3a 100644 --- a/OpenNest.Core/CNC/CutSimulation.cs +++ b/OpenNest.Core/CNC/CutSimulation.cs @@ -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( diff --git a/OpenNest.Core/CNC/CuttingPlanning/PreparedContours.cs b/OpenNest.Core/CNC/CuttingPlanning/PreparedContours.cs index cf33876e..38f1bb7b 100644 --- a/OpenNest.Core/CNC/CuttingPlanning/PreparedContours.cs +++ b/OpenNest.Core/CNC/CuttingPlanning/PreparedContours.cs @@ -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) => diff --git a/OpenNest.Core/CNC/CuttingPlanning/SeamOvercut.cs b/OpenNest.Core/CNC/CuttingPlanning/SeamOvercut.cs index 687feb6c..db1b1a86 100644 --- a/OpenNest.Core/CNC/CuttingPlanning/SeamOvercut.cs +++ b/OpenNest.Core/CNC/CuttingPlanning/SeamOvercut.cs @@ -163,8 +163,8 @@ internal static class SeamOvercut /// The angle traveled around from one point to another, in [0, 2pi). 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; } diff --git a/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs b/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs index 84d9eaf5..1aec05c0 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs @@ -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)); } /// @@ -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)); /// /// A straight lead-in from to , with the @@ -1360,7 +1360,7 @@ namespace OpenNest.CNC.CuttingStrategy /// 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; diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs index 63bdc80c..8e8524c2 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs @@ -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) ); } } diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineArcApproach.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineArcApproach.cs index 35e683e3..679049b6 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineArcApproach.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineArcApproach.cs @@ -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))); } /// Rapid to the pierce point, then the line and arc on the lead-in layer. diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLeadIn.cs index 2219dd3c..0d0d4560 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLeadIn.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLeadIn.cs @@ -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) ); } diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLineLeadIn.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLineLeadIn.cs index 0c537dc7..e1aad60e 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLineLeadIn.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadIns/LineLineLeadIn.cs @@ -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 @@ -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) ); } diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/ArcLeadOut.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/ArcLeadOut.cs index 346ee846..1acd1fb7 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/ArcLeadOut.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/ArcLeadOut.cs @@ -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 diff --git a/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/LineLeadOut.cs b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/LineLeadOut.cs index a7bafa6d..1a738422 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/LineLeadOut.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/LeadOuts/LineLeadOut.cs @@ -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 { new LinearMove(endPoint) { Layer = LayerType.Leadout } }; diff --git a/OpenNest.Core/CNC/CuttingStrategy/Tabs/BreakerTab.cs b/OpenNest.Core/CNC/CuttingStrategy/Tabs/BreakerTab.cs index 3f7b986a..e28c881d 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/Tabs/BreakerTab.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/Tabs/BreakerTab.cs @@ -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)); diff --git a/OpenNest.Core/CNC/Program.cs b/OpenNest.Core/CNC/Program.cs index 4d36d64e..3bed0436 100644 --- a/OpenNest.Core/CNC/Program.cs +++ b/OpenNest.Core/CNC/Program.cs @@ -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( diff --git a/OpenNest.Core/CanonicalAngle.cs b/OpenNest.Core/CanonicalAngle.cs index 0fa9733a..22fbf895 100644 --- a/OpenNest.Core/CanonicalAngle.cs +++ b/OpenNest.Core/CanonicalAngle.cs @@ -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); diff --git a/OpenNest.Core/CommonLineCutting.cs b/OpenNest.Core/CommonLineCutting.cs index df623d95..42e9b873 100644 --- a/OpenNest.Core/CommonLineCutting.cs +++ b/OpenNest.Core/CommonLineCutting.cs @@ -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]); diff --git a/OpenNest.Core/CommonLineGeometry.cs b/OpenNest.Core/CommonLineGeometry.cs index f56add66..72644178 100644 --- a/OpenNest.Core/CommonLineGeometry.cs +++ b/OpenNest.Core/CommonLineGeometry.cs @@ -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); diff --git a/OpenNest.Core/Diagnostics/PostVerificationGeometry.cs b/OpenNest.Core/Diagnostics/PostVerificationGeometry.cs index acde64d9..9f468316 100644 --- a/OpenNest.Core/Diagnostics/PostVerificationGeometry.cs +++ b/OpenNest.Core/Diagnostics/PostVerificationGeometry.cs @@ -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; /// @@ -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)); /// Unit direction of travel at the curve's start or end. @@ -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 /// 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; } diff --git a/OpenNest.Core/Dimensions/DimensionBuilder.cs b/OpenNest.Core/Dimensions/DimensionBuilder.cs index a379b36f..fab53d49 100644 --- a/OpenNest.Core/Dimensions/DimensionBuilder.cs +++ b/OpenNest.Core/Dimensions/DimensionBuilder.cs @@ -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 diff --git a/OpenNest.Core/Dimensions/DimensionLayout.cs b/OpenNest.Core/Dimensions/DimensionLayout.cs index 809c2b74..e0efaa3d 100644 --- a/OpenNest.Core/Dimensions/DimensionLayout.cs +++ b/OpenNest.Core/Dimensions/DimensionLayout.cs @@ -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); diff --git a/OpenNest.Core/Geometry/Arc.cs b/OpenNest.Core/Geometry/Arc.cs index 5a3c4a7a..7849b37b 100644 --- a/OpenNest.Core/Geometry/Arc.cs +++ b/OpenNest.Core/Geometry/Arc.cs @@ -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 diff --git a/OpenNest.Core/Geometry/ArcFit.cs b/OpenNest.Core/Geometry/ArcFit.cs index 1773cb05..b929e10d 100644 --- a/OpenNest.Core/Geometry/ArcFit.cs +++ b/OpenNest.Core/Geometry/ArcFit.cs @@ -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); } /// @@ -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; diff --git a/OpenNest.Core/Geometry/Circle.cs b/OpenNest.Core/Geometry/Circle.cs index 7c890f06..cf1c8e3b 100644 --- a/OpenNest.Core/Geometry/Circle.cs +++ b/OpenNest.Core/Geometry/Circle.cs @@ -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 ); } diff --git a/OpenNest.Core/Geometry/ClipperBridge.cs b/OpenNest.Core/Geometry/ClipperBridge.cs index 137f9cbe..fd772513 100644 --- a/OpenNest.Core/Geometry/ClipperBridge.cs +++ b/OpenNest.Core/Geometry/ClipperBridge.cs @@ -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 /// 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); diff --git a/OpenNest.Core/Geometry/ContourSampler.cs b/OpenNest.Core/Geometry/ContourSampler.cs index a3acce1f..54cda5f2 100644 --- a/OpenNest.Core/Geometry/ContourSampler.cs +++ b/OpenNest.Core/Geometry/ContourSampler.cs @@ -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) diff --git a/OpenNest.Core/Geometry/DrawingAligner.cs b/OpenNest.Core/Geometry/DrawingAligner.cs index f17750a3..7ad15a52 100644 --- a/OpenNest.Core/Geometry/DrawingAligner.cs +++ b/OpenNest.Core/Geometry/DrawingAligner.cs @@ -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; diff --git a/OpenNest.Core/Geometry/EllipseConverter.cs b/OpenNest.Core/Geometry/EllipseConverter.cs index 6d621060..eb256bcc 100644 --- a/OpenNest.Core/Geometry/EllipseConverter.cs +++ b/OpenNest.Core/Geometry/EllipseConverter.cs @@ -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 { p0, pMid, p1 }; var isReversed = ArcFit.SumSignedAngles(arcCenter, points) < 0; diff --git a/OpenNest.Core/Geometry/GeometryOptimizer.cs b/OpenNest.Core/Geometry/GeometryOptimizer.cs index e90953c2..8e685ebe 100644 --- a/OpenNest.Core/Geometry/GeometryOptimizer.cs +++ b/OpenNest.Core/Geometry/GeometryOptimizer.cs @@ -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); diff --git a/OpenNest.Core/Geometry/GeometrySimplifier.cs b/OpenNest.Core/Geometry/GeometrySimplifier.cs index 2e8b8fd4..3ed03be0 100644 --- a/OpenNest.Core/Geometry/GeometrySimplifier.cs +++ b/OpenNest.Core/Geometry/GeometrySimplifier.cs @@ -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 /// The direction of travel along an arc at . 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)); /// /// 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; diff --git a/OpenNest.Core/Geometry/Line.cs b/OpenNest.Core/Geometry/Line.cs index 1fb4ef82..69372099 100644 --- a/OpenNest.Core/Geometry/Line.cs +++ b/OpenNest.Core/Geometry/Line.cs @@ -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))); } /// @@ -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); diff --git a/OpenNest.Core/Geometry/NoFitPolygon.cs b/OpenNest.Core/Geometry/NoFitPolygon.cs index 680aa23c..af580f1f 100644 --- a/OpenNest.Core/Geometry/NoFitPolygon.cs +++ b/OpenNest.Core/Geometry/NoFitPolygon.cs @@ -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; diff --git a/OpenNest.Core/Geometry/RotatingCalipers.cs b/OpenNest.Core/Geometry/RotatingCalipers.cs index 550748ef..04e70861 100644 --- a/OpenNest.Core/Geometry/RotatingCalipers.cs +++ b/OpenNest.Core/Geometry/RotatingCalipers.cs @@ -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; diff --git a/OpenNest.Core/Geometry/Shape.cs b/OpenNest.Core/Geometry/Shape.cs index 52201344..2f028f8d 100644 --- a/OpenNest.Core/Geometry/Shape.cs +++ b/OpenNest.Core/Geometry/Shape.cs @@ -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); } diff --git a/OpenNest.Core/Geometry/ShapeBuilder.cs b/OpenNest.Core/Geometry/ShapeBuilder.cs index 06647e83..4be66d83 100644 --- a/OpenNest.Core/Geometry/ShapeBuilder.cs +++ b/OpenNest.Core/Geometry/ShapeBuilder.cs @@ -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; diff --git a/OpenNest.Core/Geometry/SlideContact.cs b/OpenNest.Core/Geometry/SlideContact.cs index 0007dfad..0aed4ccd 100644 --- a/OpenNest.Core/Geometry/SlideContact.cs +++ b/OpenNest.Core/Geometry/SlideContact.cs @@ -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; diff --git a/OpenNest.Core/Geometry/SlideCurvePrimitives.cs b/OpenNest.Core/Geometry/SlideCurvePrimitives.cs index c8749a99..a3212d97 100644 --- a/OpenNest.Core/Geometry/SlideCurvePrimitives.cs +++ b/OpenNest.Core/Geometry/SlideCurvePrimitives.cs @@ -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); } diff --git a/OpenNest.Core/Geometry/SlideEvents.cs b/OpenNest.Core/Geometry/SlideEvents.cs index 4b3890ea..1f1f6fc1 100644 --- a/OpenNest.Core/Geometry/SlideEvents.cs +++ b/OpenNest.Core/Geometry/SlideEvents.cs @@ -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) diff --git a/OpenNest.Core/Geometry/SpatialQuery.cs b/OpenNest.Core/Geometry/SpatialQuery.cs index 5c1e1d81..81b937b0 100644 --- a/OpenNest.Core/Geometry/SpatialQuery.cs +++ b/OpenNest.Core/Geometry/SpatialQuery.cs @@ -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; diff --git a/OpenNest.Core/Geometry/SplineConverter.cs b/OpenNest.Core/Geometry/SplineConverter.cs index fc282173..073240a0 100644 --- a/OpenNest.Core/Geometry/SplineConverter.cs +++ b/OpenNest.Core/Geometry/SplineConverter.cs @@ -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) diff --git a/OpenNest.Core/Geometry/Vector.cs b/OpenNest.Core/Geometry/Vector.cs index 285a7fc3..4b55f596 100644 --- a/OpenNest.Core/Geometry/Vector.cs +++ b/OpenNest.Core/Geometry/Vector.cs @@ -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)); } /// @@ -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; diff --git a/OpenNest.Core/Shapes/NgonShape.cs b/OpenNest.Core/Shapes/NgonShape.cs index 326cb47d..a659cedd 100644 --- a/OpenNest.Core/Shapes/NgonShape.cs +++ b/OpenNest.Core/Shapes/NgonShape.cs @@ -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) ); } diff --git a/OpenNest.Core/Shapes/PipeFlangeShape.cs b/OpenNest.Core/Shapes/PipeFlangeShape.cs index 9b3a6651..c2ead366 100644 --- a/OpenNest.Core/Shapes/PipeFlangeShape.cs +++ b/OpenNest.Core/Shapes/PipeFlangeShape.cs @@ -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)); } diff --git a/OpenNest.Core/Splitting/DrawingSplitter.cs b/OpenNest.Core/Splitting/DrawingSplitter.cs index d6eccd45..e2545101 100644 --- a/OpenNest.Core/Splitting/DrawingSplitter.cs +++ b/OpenNest.Core/Splitting/DrawingSplitter.cs @@ -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) ); } diff --git a/OpenNest.Core/Splitting/SpikeGrooveSplit.cs b/OpenNest.Core/Splitting/SpikeGrooveSplit.cs index 10895816..0996bec6 100644 --- a/OpenNest.Core/Splitting/SpikeGrooveSplit.cs +++ b/OpenNest.Core/Splitting/SpikeGrooveSplit.cs @@ -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; diff --git a/OpenNest.Core/Support/SupportAnalyzer.cs b/OpenNest.Core/Support/SupportAnalyzer.cs index 1a320cb2..25d063ed 100644 --- a/OpenNest.Core/Support/SupportAnalyzer.cs +++ b/OpenNest.Core/Support/SupportAnalyzer.cs @@ -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. diff --git a/OpenNest.Engine.Tests/Jobs/FillStackCharacterizationTests.cs b/OpenNest.Engine.Tests/Jobs/FillStackCharacterizationTests.cs index fb7093e8..5df21f85 100644 --- a/OpenNest.Engine.Tests/Jobs/FillStackCharacterizationTests.cs +++ b/OpenNest.Engine.Tests/Jobs/FillStackCharacterizationTests.cs @@ -40,6 +40,10 @@ namespace OpenNest.Engine.Tests.Jobs; /// Re-captured when the pairs phase began tiling herringbone pairs (HerringbonePatterns): /// 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 PortableMath: 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 ConcurrentBag and equal @@ -47,16 +51,6 @@ namespace OpenNest.Engine.Tests.Jobs; /// public class FillStackCharacterizationTests { - /// A theory that runs only on Windows. - private sealed class WindowsOnlyTheoryAttribute : TheoryAttribute - { - public WindowsOnlyTheoryAttribute() - { - if (!OperatingSystem.IsWindows()) - Skip = "Layout differs off Windows (Project Memory 518f3b58)"; - } - } - /// 6x4 block with a 2x2 notch cut into the middle of its top edge. private static Program Notched() { @@ -309,17 +303,11 @@ public class FillStackCharacterizationTests AssertPinned(layout, expectedCount, expectedDigest); } - /// - /// 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. - /// - [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, diff --git a/OpenNest.Engine/BestFit/BestFitFinder.cs b/OpenNest.Engine/BestFit/BestFitFinder.cs index 29da0a74..b4b189bc 100644 --- a/OpenNest.Engine/BestFit/BestFitFinder.cs +++ b/OpenNest.Engine/BestFit/BestFitFinder.cs @@ -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); } diff --git a/OpenNest.Engine/BestFit/PolygonHelper.cs b/OpenNest.Engine/BestFit/PolygonHelper.cs index bcdfb957..79d934aa 100644 --- a/OpenNest.Engine/BestFit/PolygonHelper.cs +++ b/OpenNest.Engine/BestFit/PolygonHelper.cs @@ -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) { diff --git a/OpenNest.Engine/Fill/Compactor.cs b/OpenNest.Engine/Fill/Compactor.cs index 6fe4f8ef..eafa83aa 100644 --- a/OpenNest.Engine/Fill/Compactor.cs +++ b/OpenNest.Engine/Fill/Compactor.cs @@ -29,7 +29,7 @@ namespace OpenNest.Engine.Fill /// Pushes movingParts along an arbitrary angle (radians, 0 = right, π/2 = up). /// public static double Push(List 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 movingParts, Plate plate, Vector direction) { diff --git a/OpenNest.Engine/Fill/RotationAnalysis.cs b/OpenNest.Engine/Fill/RotationAnalysis.cs index cab26610..f638d0d3 100644 --- a/OpenNest.Engine/Fill/RotationAnalysis.cs +++ b/OpenNest.Engine/Fill/RotationAnalysis.cs @@ -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)); diff --git a/OpenNest.Engine/Fill/TiltedRemnantFiller.cs b/OpenNest.Engine/Fill/TiltedRemnantFiller.cs index 36c6f595..6a617b96 100644 --- a/OpenNest.Engine/Fill/TiltedRemnantFiller.cs +++ b/OpenNest.Engine/Fill/TiltedRemnantFiller.cs @@ -260,8 +260,8 @@ namespace OpenNest.Engine.Fill private static (double Along, double Cross) Extents(List 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; diff --git a/OpenNest.Engine/Jobs/CommonLines/CommonLinePipelinePrepass.cs b/OpenNest.Engine/Jobs/CommonLines/CommonLinePipelinePrepass.cs index b3c7e0be..82d0a804 100644 --- a/OpenNest.Engine/Jobs/CommonLines/CommonLinePipelinePrepass.cs +++ b/OpenNest.Engine/Jobs/CommonLines/CommonLinePipelinePrepass.cs @@ -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)); diff --git a/OpenNest.Engine/Jobs/Cutouts/CutoutCirclePacker.cs b/OpenNest.Engine/Jobs/Cutouts/CutoutCirclePacker.cs index e3ee077e..5ada3821 100644 --- a/OpenNest.Engine/Jobs/Cutouts/CutoutCirclePacker.cs +++ b/OpenNest.Engine/Jobs/Cutouts/CutoutCirclePacker.cs @@ -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; } } diff --git a/OpenNest.Engine/Jobs/Cutouts/CutoutPipelinePrepass.cs b/OpenNest.Engine/Jobs/Cutouts/CutoutPipelinePrepass.cs index 18370aaf..a564b0d9 100644 --- a/OpenNest.Engine/Jobs/Cutouts/CutoutPipelinePrepass.cs +++ b/OpenNest.Engine/Jobs/Cutouts/CutoutPipelinePrepass.cs @@ -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; diff --git a/OpenNest.Engine/Jobs/JobPartGeometry.cs b/OpenNest.Engine/Jobs/JobPartGeometry.cs index af1e18db..b590d70e 100644 --- a/OpenNest.Engine/Jobs/JobPartGeometry.cs +++ b/OpenNest.Engine/Jobs/JobPartGeometry.cs @@ -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) { diff --git a/OpenNest.Engine/Jobs/Placement/Fillers/PlateFillPolicy.cs b/OpenNest.Engine/Jobs/Placement/Fillers/PlateFillPolicy.cs index 2d874574..da95c520 100644 --- a/OpenNest.Engine/Jobs/Placement/Fillers/PlateFillPolicy.cs +++ b/OpenNest.Engine/Jobs/Placement/Fillers/PlateFillPolicy.cs @@ -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; } } diff --git a/OpenNest.Engine/Jobs/Placement/PlateFillService.CommonLines.cs b/OpenNest.Engine/Jobs/Placement/PlateFillService.CommonLines.cs index f5bd6c3c..7d3d5f94 100644 --- a/OpenNest.Engine/Jobs/Placement/PlateFillService.CommonLines.cs +++ b/OpenNest.Engine/Jobs/Placement/PlateFillService.CommonLines.cs @@ -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)); diff --git a/OpenNest.Engine/NestingEngines/Irregular/ContactFit.cs b/OpenNest.Engine/NestingEngines/Irregular/ContactFit.cs index 1c83bf75..81718eb7 100644 --- a/OpenNest.Engine/NestingEngines/Irregular/ContactFit.cs +++ b/OpenNest.Engine/NestingEngines/Irregular/ContactFit.cs @@ -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)); diff --git a/OpenNest.Engine/NestingEngines/Irregular/NoFitCache.cs b/OpenNest.Engine/NestingEngines/Irregular/NoFitCache.cs index 6afc291f..bc0785fe 100644 --- a/OpenNest.Engine/NestingEngines/Irregular/NoFitCache.cs +++ b/OpenNest.Engine/NestingEngines/Irregular/NoFitCache.cs @@ -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; } diff --git a/OpenNest.IO/ChrFont.cs b/OpenNest.IO/ChrFont.cs index 3fa8bfdd..2f12f1ca 100644 --- a/OpenNest.IO/ChrFont.cs +++ b/OpenNest.IO/ChrFont.cs @@ -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) ) ); } diff --git a/OpenNest.IO/Dxf.cs b/OpenNest.IO/Dxf.cs index 844c8dc5..b454956f 100644 --- a/OpenNest.IO/Dxf.cs +++ b/OpenNest.IO/Dxf.cs @@ -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); diff --git a/OpenNest.IO/Extensions.cs b/OpenNest.IO/Extensions.cs index c4773ebf..1b8f8e2b 100644 --- a/OpenNest.IO/Extensions.cs +++ b/OpenNest.IO/Extensions.cs @@ -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(); diff --git a/OpenNest.IO/SplitDxfWriter.cs b/OpenNest.IO/SplitDxfWriter.cs index 842df35e..78ebf58d 100644 --- a/OpenNest.IO/SplitDxfWriter.cs +++ b/OpenNest.IO/SplitDxfWriter.cs @@ -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 diff --git a/OpenNest.IO/SvgReader.cs b/OpenNest.IO/SvgReader.cs index 2f2c6512..fc88d65d 100644 --- a/OpenNest.IO/SvgReader.cs +++ b/OpenNest.IO/SvgReader.cs @@ -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 diff --git a/OpenNest.IO/SvgTransform.cs b/OpenNest.IO/SvgTransform.cs index c8d21c1d..944b5eb7 100644 --- a/OpenNest.IO/SvgTransform.cs +++ b/OpenNest.IO/SvgTransform.cs @@ -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); } } diff --git a/OpenNest.Tests/Geometry/BoundingBoxHelperTests.cs b/OpenNest.Tests/Geometry/BoundingBoxHelperTests.cs index f618459b..619e9685 100644 --- a/OpenNest.Tests/Geometry/BoundingBoxHelperTests.cs +++ b/OpenNest.Tests/Geometry/BoundingBoxHelperTests.cs @@ -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); } } diff --git a/OpenNest.Tests/Geometry/LegacyEntitySlideEvents.cs b/OpenNest.Tests/Geometry/LegacyEntitySlideEvents.cs index 62744b00..5236f225 100644 --- a/OpenNest.Tests/Geometry/LegacyEntitySlideEvents.cs +++ b/OpenNest.Tests/Geometry/LegacyEntitySlideEvents.cs @@ -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) diff --git a/OpenNest.Tests/Math/PortableMathTests.cs b/OpenNest.Tests/Math/PortableMathTests.cs index 57ad8611..b337db8e 100644 --- a/OpenNest.Tests/Math/PortableMathTests.cs +++ b/OpenNest.Tests/Math/PortableMathTests.cs @@ -87,6 +87,47 @@ public class PortableMathTests Assert.True(double.IsNaN(PortableMath.Atan2(double.NaN, 1))); } + /// + /// 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. + /// + [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(); + 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) + ); + } + /// /// 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).