From 49603eddf9c26e848807dd58bcd829810188cb9c Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 9 Nov 2026 19:08:16 -0500 Subject: [PATCH] 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. --- OpenNest.Core/Bending/Bend.cs | 4 +- OpenNest.Core/Breakoffs/BreakoffPath.cs | 8 ++-- OpenNest.Core/CNC/CutSimulation.cs | 6 +-- .../CNC/CuttingPlanning/PreparedContours.cs | 11 ++--- .../CNC/CuttingPlanning/SeamOvercut.cs | 4 +- .../CuttingStrategy/ContourCuttingStrategy.cs | 36 ++++++++-------- .../CNC/CuttingStrategy/LeadIns/ArcLeadIn.cs | 8 ++-- .../LeadIns/LineArcApproach.cs | 12 +++--- .../CNC/CuttingStrategy/LeadIns/LineLeadIn.cs | 4 +- .../CuttingStrategy/LeadIns/LineLineLeadIn.cs | 12 +++--- .../CuttingStrategy/LeadOuts/ArcLeadOut.cs | 9 ++-- .../CuttingStrategy/LeadOuts/LineLeadOut.cs | 4 +- .../CNC/CuttingStrategy/Tabs/BreakerTab.cs | 5 ++- OpenNest.Core/CNC/Program.cs | 4 +- OpenNest.Core/CanonicalAngle.cs | 3 +- OpenNest.Core/CommonLineCutting.cs | 5 ++- OpenNest.Core/CommonLineGeometry.cs | 7 ++-- .../Diagnostics/PostVerificationGeometry.cs | 27 ++++++------ OpenNest.Core/Dimensions/DimensionBuilder.cs | 2 +- OpenNest.Core/Dimensions/DimensionLayout.cs | 3 +- OpenNest.Core/Geometry/Arc.cs | 24 +++++------ OpenNest.Core/Geometry/ArcFit.cs | 10 ++--- OpenNest.Core/Geometry/Circle.cs | 12 +++--- OpenNest.Core/Geometry/ClipperBridge.cs | 14 +++---- OpenNest.Core/Geometry/ContourSampler.cs | 18 ++++---- OpenNest.Core/Geometry/DrawingAligner.cs | 2 +- OpenNest.Core/Geometry/EllipseConverter.cs | 28 ++++++------- OpenNest.Core/Geometry/GeometryOptimizer.cs | 2 +- OpenNest.Core/Geometry/GeometrySimplifier.cs | 20 ++++----- OpenNest.Core/Geometry/Line.cs | 6 +-- OpenNest.Core/Geometry/NoFitPolygon.cs | 4 +- OpenNest.Core/Geometry/RotatingCalipers.cs | 10 ++--- OpenNest.Core/Geometry/Shape.cs | 5 ++- OpenNest.Core/Geometry/ShapeBuilder.cs | 2 +- OpenNest.Core/Geometry/SlideContact.cs | 16 ++++---- .../Geometry/SlideCurvePrimitives.cs | 2 +- OpenNest.Core/Geometry/SlideEvents.cs | 8 ++-- OpenNest.Core/Geometry/SpatialQuery.cs | 4 +- OpenNest.Core/Geometry/SplineConverter.cs | 4 +- OpenNest.Core/Geometry/Vector.cs | 12 +++--- OpenNest.Core/Shapes/NgonShape.cs | 7 ++-- OpenNest.Core/Shapes/PipeFlangeShape.cs | 9 ++-- OpenNest.Core/Splitting/DrawingSplitter.cs | 5 ++- OpenNest.Core/Splitting/SpikeGrooveSplit.cs | 5 ++- OpenNest.Core/Support/SupportAnalyzer.cs | 7 ++-- .../Jobs/FillStackCharacterizationTests.cs | 30 ++++---------- OpenNest.Engine/BestFit/BestFitFinder.cs | 2 +- OpenNest.Engine/BestFit/PolygonHelper.cs | 4 +- OpenNest.Engine/Fill/Compactor.cs | 2 +- OpenNest.Engine/Fill/RotationAnalysis.cs | 2 +- OpenNest.Engine/Fill/TiltedRemnantFiller.cs | 4 +- .../CommonLines/CommonLinePipelinePrepass.cs | 5 ++- .../Jobs/Cutouts/CutoutCirclePacker.cs | 2 +- .../Jobs/Cutouts/CutoutPipelinePrepass.cs | 5 ++- OpenNest.Engine/Jobs/JobPartGeometry.cs | 2 +- .../Jobs/Placement/Fillers/PlateFillPolicy.cs | 8 ++-- .../Placement/PlateFillService.CommonLines.cs | 5 ++- .../NestingEngines/Irregular/ContactFit.cs | 9 ++-- .../NestingEngines/Irregular/NoFitCache.cs | 3 +- OpenNest.IO/ChrFont.cs | 11 ++--- OpenNest.IO/Dxf.cs | 4 +- OpenNest.IO/Extensions.cs | 3 +- OpenNest.IO/SplitDxfWriter.cs | 5 ++- OpenNest.IO/SvgReader.cs | 20 ++++----- OpenNest.IO/SvgTransform.cs | 7 ++-- .../Geometry/BoundingBoxHelperTests.cs | 7 ++-- .../Geometry/LegacyEntitySlideEvents.cs | 9 ++-- OpenNest.Tests/Math/PortableMathTests.cs | 41 +++++++++++++++++++ 68 files changed, 327 insertions(+), 273 deletions(-) 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).