From 1d6caa9a35e8c21e20f445f800d66e6df83d165d Mon Sep 17 00:00:00 2001
From: John Beard <john.j.beard@gmail.com>
Date: Wed, 20 Sep 2023 00:55:59 +0100
Subject: [PATCH] Fix winding of FP_SHAPE arcs

Implement "0" versions of some arc calculations and fix some
initialisation bugs in the handling of FP_SHAPE arcs. Also
set m_endsSwapped.

Fixes: https://gitlab.com/kicad/code/kicad/-/issues/15694
---
 pcbnew/fp_shape.cpp | 68 ++++++++++++++++++++++++++++++++++++++++-----
 pcbnew/fp_shape.h   |  6 ++++
 2 files changed, 67 insertions(+), 7 deletions(-)

diff --git a/pcbnew/fp_shape.cpp b/pcbnew/fp_shape.cpp
index 50a165a25b..6b1a5f0784 100644
--- a/pcbnew/fp_shape.cpp
+++ b/pcbnew/fp_shape.cpp
@@ -176,16 +176,42 @@ void FP_SHAPE::SetCenter0( const VECTOR2I& aCenter )
 }
 
 
+void FP_SHAPE::CalcArcAngles0( EDA_ANGLE& aStartAngle0, EDA_ANGLE& aEndAngle0 ) const {
+    VECTOR2D startRadial( GetStart0() - GetCenter0() );
+    VECTOR2D endRadial( GetEnd0() - GetCenter0() );
+
+    aStartAngle0 = EDA_ANGLE( startRadial );
+    aEndAngle0 = EDA_ANGLE( endRadial );
+
+    if( aEndAngle0 == aStartAngle0 )
+        aEndAngle0 = aStartAngle0 + ANGLE_360;   // ring, not null
+
+    while( aEndAngle0 < aStartAngle0 )
+        aEndAngle0 += ANGLE_360;
+}
+
+
+EDA_ANGLE FP_SHAPE::GetArcAngle0() const
+{
+    EDA_ANGLE startAngle0;
+    EDA_ANGLE endAngle0;
+
+    CalcArcAngles0( startAngle0, endAngle0 );
+
+    return endAngle0 - startAngle0;
+}
+
+
 VECTOR2I FP_SHAPE::GetArcMid0() const
 {
     // If none of the input data have changed since we loaded the arc,
     // keep the original mid point data to minimize churn
-    if( m_arcMidData_0.start == m_start && m_arcMidData_0.end == m_end
-            && m_arcMidData_0.center == m_arcCenter )
+    if( m_arcMidData_0.start == m_start0 && m_arcMidData_0.end == m_end0
+            && m_arcMidData_0.center == m_arcCenter0 )
         return m_arcMidData_0.mid;
 
     VECTOR2I mid0 = m_start0;
-    RotatePoint( mid0, m_arcCenter0, -GetArcAngle() / 2.0 );
+    RotatePoint( mid0, m_arcCenter0, -GetArcAngle0() / 2.0 );
     return mid0;
 }
 
@@ -198,21 +224,49 @@ void FP_SHAPE::SetArcAngleAndEnd0( const EDA_ANGLE& aAngle, bool aCheckNegativeA
     RotatePoint( m_end0, m_arcCenter0, -angle.Normalize720() );
 
     if( aCheckNegativeAngle && aAngle < ANGLE_0 )
+    {
         std::swap( m_start0, m_end0 );
+        m_endsSwapped = true;
+    }
+}
+
+
+void FP_SHAPE::SetCachedArcData0( const VECTOR2I& aStart0, const VECTOR2I& aMid0,
+                                  const VECTOR2I& aEnd0, const VECTOR2I& aCenter0 )
+{
+    m_arcMidData_0.start = aStart0;
+    m_arcMidData_0.end = aEnd0;
+    m_arcMidData_0.center = aCenter0;
+    m_arcMidData_0.mid = aMid0;
 }
 
 
 void FP_SHAPE::SetArcGeometry0( const VECTOR2I& aStart0, const VECTOR2I& aMid0,
                                 const VECTOR2I& aEnd0 )
 {
+    m_arcMidData_0 = {};
     m_start0 = aStart0;
     m_end0 = aEnd0;
     m_arcCenter0 = CalcArcCenter( aStart0, aMid0, aEnd0 );
+    const VECTOR2I new_mid = GetArcMid0();
 
-    m_arcMidData_0.center = m_arcCenter0;
-    m_arcMidData_0.end = m_end0;
-    m_arcMidData_0.mid = aMid0;
-    m_arcMidData_0.start = m_start0;
+    m_endsSwapped = false;
+
+    SetCachedArcData0( aStart0, aMid0, aEnd0, m_arcCenter0 );
+
+    /*
+     * If the input winding doesn't match our internal winding, the calculated midpoint will end
+     * up on the other side of the arc.  In this case, we need to flip the start/end points and
+     * flag this change for the system.
+     */
+    VECTOR2D dist( new_mid - aMid0 );
+    VECTOR2D dist2( new_mid - m_arcCenter0 );
+
+    if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
+    {
+        std::swap( m_start0, m_end0 );
+        m_endsSwapped = true;
+    }
 }
 
 
diff --git a/pcbnew/fp_shape.h b/pcbnew/fp_shape.h
index 171e543663..71c9e39a89 100644
--- a/pcbnew/fp_shape.h
+++ b/pcbnew/fp_shape.h
@@ -105,6 +105,12 @@ public:
 
     VECTOR2I GetArcMid0() const;
 
+    void CalcArcAngles0( EDA_ANGLE& aStartAngle, EDA_ANGLE& aEndAngle ) const;
+    EDA_ANGLE GetArcAngle0() const;
+
+    void SetCachedArcData0( const VECTOR2I& aStart0, const VECTOR2I& aMid0, const VECTOR2I& aEnd0,
+                            const VECTOR2I& aCenter0 );
+
     /**
      * Set relative coordinates from draw coordinates.
      * Call in only when the geometry or the footprint is modified and therefore the relative