diff --git a/libs/kimath/include/geometry/shape_arc.h b/libs/kimath/include/geometry/shape_arc.h index 42d25a33f4..9b9ee7f494 100644 --- a/libs/kimath/include/geometry/shape_arc.h +++ b/libs/kimath/include/geometry/shape_arc.h @@ -98,6 +98,41 @@ public: m_pc += aVector; } + /** + * Function Rotate + * rotates the arc by a given angle about a point + * @param aCenter is the rotation center + * @param aAngle rotation angle in radians + */ + void Rotate( double aAngle, const VECTOR2I& aCenter ) + { + m_p0 -= aCenter; + m_pc -= aCenter; + + m_p0.Rotate( aAngle ); + m_pc.Rotate( aAngle ); + + m_pc += aCenter; + m_p0 += aCenter; + } + + void Mirror( bool aX = true, bool aY = false, const VECTOR2I& aVector = { 0, 0 } ) + { + if( aX ) + { + m_p0.x = -m_p0.x + 2 * aVector.x; + m_pc.x = -m_pc.x + 2 * aVector.x; + m_centralAngle = - m_centralAngle; + } + + if( aY ) + { + m_p0.y = -m_p0.y + 2 * aVector.y; + m_pc.y = -m_pc.y + 2 * aVector.y; + m_centralAngle = - m_centralAngle; + } + } + int GetRadius() const; SEG GetChord() const diff --git a/libs/kimath/include/geometry/shape_line_chain.h b/libs/kimath/include/geometry/shape_line_chain.h index e245df7f5b..9707f855e3 100644 --- a/libs/kimath/include/geometry/shape_line_chain.h +++ b/libs/kimath/include/geometry/shape_line_chain.h @@ -37,6 +37,7 @@ #include #include #include +#include #include // for BOX2I #include @@ -85,10 +86,14 @@ public: /** * Copy Constructor */ + SHAPE_LINE_CHAIN( const SHAPE_LINE_CHAIN& aShape ) : SHAPE( SH_LINE_CHAIN ), m_points( aShape.m_points ), + m_shapes( aShape.m_shapes ), + m_arcs( aShape.m_arcs ), m_closed( aShape.m_closed ), + m_bbox( aShape.m_bbox ), m_width( aShape.m_width ) {} @@ -99,18 +104,22 @@ public: for( auto pt : aV ) m_points.emplace_back( pt.x, pt.y ); + + m_shapes = std::vector( aV.size(), ssize_t( SHAPE_IS_PT ) ); } SHAPE_LINE_CHAIN( const std::vector& aV, bool aClosed = false ) : SHAPE( SH_LINE_CHAIN ), m_closed( aClosed ), m_width( 0 ) { m_points = aV; + m_shapes = std::vector( aV.size(), ssize_t( SHAPE_IS_PT ) ); } SHAPE_LINE_CHAIN( const ClipperLib::Path& aPath ) : SHAPE( SH_LINE_CHAIN ), m_closed( true ), m_width( 0 ) { m_points.reserve( aPath.size() ); + m_shapes = std::vector( aPath.size(), ssize_t( SHAPE_IS_PT ) ); for( const auto& point : aPath ) m_points.emplace_back( point.X, point.Y ); @@ -130,6 +139,8 @@ public: void Clear() { m_points.clear(); + m_arcs.clear(); + m_shapes.clear(); m_closed = false; } @@ -252,6 +263,9 @@ public: aIndex -= PointCount(); m_points[aIndex] = aPos; + + if( m_shapes[aIndex] != SHAPE_IS_PT ) + convertArc( m_shapes[aIndex] ); } /** @@ -382,6 +396,7 @@ public: if( m_points.size() == 0 || aAllowDuplication || CPoint( -1 ) != aP ) { m_points.push_back( aP ); + m_shapes.push_back( ssize_t( SHAPE_IS_PT ) ); m_bbox.Merge( aP ); } } @@ -392,30 +407,13 @@ public: * Appends another line chain at the end. * @param aOtherLine the line chain to be appended. */ - void Append( const SHAPE_LINE_CHAIN& aOtherLine ) - { - if( aOtherLine.PointCount() == 0 ) - return; + void Append( const SHAPE_LINE_CHAIN& aOtherLine ); - else if( PointCount() == 0 || aOtherLine.CPoint( 0 ) != CPoint( -1 ) ) - { - const VECTOR2I p = aOtherLine.CPoint( 0 ); - m_points.push_back( p ); - m_bbox.Merge( p ); - } + void Append( const SHAPE_ARC& aArc ); - for( int i = 1; i < aOtherLine.PointCount(); i++ ) - { - const VECTOR2I p = aOtherLine.CPoint( i ); - m_points.push_back( p ); - m_bbox.Merge( p ); - } - } + void Insert( size_t aVertex, const VECTOR2I& aP ); - void Insert( int aVertex, const VECTOR2I& aP ) - { - m_points.insert( m_points.begin() + aVertex, aP ); - } + void Insert( size_t aVertex, const SHAPE_ARC& aArc ); /** * Function Replace() @@ -601,6 +599,13 @@ public: */ SHAPE_LINE_CHAIN& Simplify(); + /** + * Converts an arc to only a point chain by removing the arc and references + * + * @param aArcIndex index of the arc to convert to points + */ + void convertArc( ssize_t aArcIndex ); + /** * Creates a new Clipper path from the SHAPE_LINE_CHAIN in a given orientation * @@ -658,8 +663,11 @@ public: void Move( const VECTOR2I& aVector ) override { - for( std::vector::iterator i = m_points.begin(); i != m_points.end(); ++i ) - (*i) += aVector; + for( auto& pt : m_points ) + pt += aVector; + + for( auto& arc : m_arcs ) + arc.Move( aVector ); } /** @@ -668,17 +676,7 @@ public: * @param aY If true, mirror about the x axis (flip Y coordinate) * @param aRef sets the reference point about which to mirror */ - void Mirror( bool aX = true, bool aY = false, const VECTOR2I& aRef = { 0, 0 } ) - { - for( auto& pt : m_points ) - { - if( aX ) - pt.x = -pt.x + 2 * aRef.x; - - if( aY ) - pt.y = -pt.y + 2 * aRef.y; - } - } + void Mirror( bool aX = true, bool aY = false, const VECTOR2I& aRef = { 0, 0 } ); /** * Function Rotate @@ -701,6 +699,16 @@ private: /// array of vertices std::vector m_points; + /** + * Array of indices that refer to the index of the shape if the point + * is part of a larger shape, e.g. arc or spline. + * If the value is -1, the point is just a point + */ + std::vector m_shapes; + constexpr static ssize_t SHAPE_IS_PT = -1; + + std::vector m_arcs; + /// is the line chain closed? bool m_closed; @@ -714,4 +722,5 @@ private: BOX2I m_bbox; }; + #endif // __SHAPE_LINE_CHAIN diff --git a/libs/kimath/src/geometry/shape_line_chain.cpp b/libs/kimath/src/geometry/shape_line_chain.cpp index d1f960b997..059fe0d0a0 100644 --- a/libs/kimath/src/geometry/shape_line_chain.cpp +++ b/libs/kimath/src/geometry/shape_line_chain.cpp @@ -53,6 +53,29 @@ ClipperLib::Path SHAPE_LINE_CHAIN::convertToClipper( bool aRequiredOrientation ) } +//TODO(SH): Adjust this into two functions: one to convert and one to split the arc into two arcs +void SHAPE_LINE_CHAIN::convertArc( ssize_t aArcIndex ) +{ + if( aArcIndex < 0 ) + aArcIndex += m_arcs.size(); + + if( aArcIndex >= static_cast( m_arcs.size() ) ) + return; + + // Clear the shapes references + for( auto& sh : m_shapes ) + { + if( sh == aArcIndex ) + sh = SHAPE_IS_PT; + + if( sh > aArcIndex ) + --sh; + } + + m_arcs.erase( m_arcs.begin() + aArcIndex ); +} + + bool SHAPE_LINE_CHAIN::Collide( const VECTOR2I& aP, int aClearance ) const { // fixme: ugly! @@ -63,12 +86,15 @@ bool SHAPE_LINE_CHAIN::Collide( const VECTOR2I& aP, int aClearance ) const void SHAPE_LINE_CHAIN::Rotate( double aAngle, const VECTOR2I& aCenter ) { - for( std::vector::iterator i = m_points.begin(); i != m_points.end(); ++i ) + for( auto& pt : m_points ) { - (*i) -= aCenter; - (*i) = (*i).Rotate( aAngle ); - (*i) += aCenter; + pt -= aCenter; + pt = pt.Rotate( aAngle ); + pt += aCenter; } + + for( auto& arc : m_arcs ) + arc.Rotate( aAngle, aCenter ); } @@ -100,6 +126,16 @@ const SHAPE_LINE_CHAIN SHAPE_LINE_CHAIN::Reverse() const SHAPE_LINE_CHAIN a( *this ); reverse( a.m_points.begin(), a.m_points.end() ); + reverse( a.m_shapes.begin(), a.m_shapes.end() ); + reverse( a.m_arcs.begin(), a.m_arcs.end() ); + + for( auto& sh : a.m_shapes ) + { + if( sh != SHAPE_IS_PT ) + sh = a.m_arcs.size() - sh - 1; + } + + a.m_closed = m_closed; return a; @@ -117,6 +153,22 @@ long long int SHAPE_LINE_CHAIN::Length() const } +void SHAPE_LINE_CHAIN::Mirror( bool aX, bool aY, const VECTOR2I& aRef ) +{ + for( auto& pt : m_points ) + { + if( aX ) + pt.x = -pt.x + 2 * aRef.x; + + if( aY ) + pt.y = -pt.y + 2 * aRef.y; + } + + for( auto& arc : m_arcs ) + arc.Mirror( aX, aY, aRef ); +} + + void SHAPE_LINE_CHAIN::Replace( int aStartIndex, int aEndIndex, const VECTOR2I& aP ) { if( aEndIndex < 0 ) @@ -125,6 +177,15 @@ void SHAPE_LINE_CHAIN::Replace( int aStartIndex, int aEndIndex, const VECTOR2I& if( aStartIndex < 0 ) aStartIndex += PointCount(); + aEndIndex = std::min( aEndIndex, PointCount() - 1 ); + + // N.B. This works because convertArc changes m_shapes on the first run + for( int ind = aStartIndex; ind <= aEndIndex; ind++ ) + { + if( m_shapes[ind] != SHAPE_IS_PT ) + convertArc( ind ); + } + if( aStartIndex == aEndIndex ) m_points[aStartIndex] = aP; else @@ -143,6 +204,40 @@ void SHAPE_LINE_CHAIN::Replace( int aStartIndex, int aEndIndex, const SHAPE_LINE if( aStartIndex < 0 ) aStartIndex += PointCount(); + aEndIndex = std::min( aEndIndex, PointCount() - 1 ); + ssize_t arc_index = -1; + + // N.B. This works because convertArc changes m_shapes on the first run + for( int ind = aStartIndex; ind <= aEndIndex; ind++ ) + { + if( m_shapes[ind] != SHAPE_IS_PT ) + { + if( arc_index == -1 ) + arc_index = m_shapes[ind]; + + convertArc( ind ); + } + } + + for( auto remaining_it = m_shapes.erase( m_shapes.begin() + aStartIndex, + m_shapes.begin() + aEndIndex + 1 ); + remaining_it != m_shapes.end(); remaining_it++ ) + { + if( *remaining_it != SHAPE_IS_PT ) + *remaining_it += aLine.m_arcs.size(); + } + + m_shapes.insert( m_shapes.begin() + aStartIndex, aLine.m_shapes.begin(), aLine.m_shapes.end() ); + + for( auto new_it = m_shapes.begin() + aStartIndex; + new_it != m_shapes.begin() + aStartIndex + aLine.m_shapes.size() + 1; new_it++ ) + { + if( *new_it != SHAPE_IS_PT ) + *new_it += arc_index; + } + + m_arcs.insert( m_arcs.begin() + arc_index, aLine.m_arcs.begin(), aLine.m_arcs.end() ); + m_points.erase( m_points.begin() + aStartIndex, m_points.begin() + aEndIndex + 1 ); m_points.insert( m_points.begin() + aStartIndex, aLine.m_points.begin(), aLine.m_points.end() ); } @@ -156,6 +251,26 @@ void SHAPE_LINE_CHAIN::Remove( int aStartIndex, int aEndIndex ) if( aStartIndex < 0 ) aStartIndex += PointCount(); + if( aStartIndex >= PointCount() ) + return; + + aEndIndex = std::min( aEndIndex, PointCount() ); + std::vector extra_arcs; + ssize_t last_arc = -1; + + for( ssize_t i = aStartIndex; i < aEndIndex; i++) + { + if( m_shapes[i] != SHAPE_IS_PT && m_shapes[i] != last_arc ) + extra_arcs.emplace_back( m_shapes[i] ); + } + + // Reverse the sort order to ensure we maintain valid indices + std::sort( extra_arcs.begin(), extra_arcs.end(), std::greater() ); + + for( auto arc : extra_arcs ) + convertArc( arc ); + + m_shapes.erase( m_shapes.begin() + aStartIndex, m_shapes.begin() + aEndIndex + 1 ); m_points.erase( m_points.begin() + aStartIndex, m_points.begin() + aEndIndex + 1 ); } @@ -204,6 +319,7 @@ int SHAPE_LINE_CHAIN::Split( const VECTOR2I& aP ) if( ii >= 0 ) { m_points.insert( m_points.begin() + ii + 1, aP ); + m_shapes.insert( m_shapes.begin() + ii + 1, ssize_t( SHAPE_IS_PT ) ); return ii + 1; } @@ -249,6 +365,81 @@ const SHAPE_LINE_CHAIN SHAPE_LINE_CHAIN::Slice( int aStartIndex, int aEndIndex ) } +void SHAPE_LINE_CHAIN::Append( const SHAPE_LINE_CHAIN& aOtherLine ) +{ + if( aOtherLine.PointCount() == 0 ) + return; + + else if( PointCount() == 0 || aOtherLine.CPoint( 0 ) != CPoint( -1 ) ) + { + const VECTOR2I p = aOtherLine.CPoint( 0 ); + m_points.push_back( p ); + m_shapes.push_back( ssize_t( SHAPE_IS_PT ) ); + m_bbox.Merge( p ); + } + + for( int i = 1; i < aOtherLine.PointCount(); i++ ) + { + const VECTOR2I p = aOtherLine.CPoint( i ); + m_points.push_back( p ); + m_shapes.push_back( ssize_t( SHAPE_IS_PT ) ); + m_bbox.Merge( p ); + } +} + + +void SHAPE_LINE_CHAIN::Append( const SHAPE_ARC& aArc ) +{ + auto& chain = aArc.ConvertToPolyline(); + m_arcs.push_back( aArc ); + + for( auto& pt : chain.CPoints() ) + { + m_points.push_back( pt ); + m_shapes.push_back( m_arcs.size() ); + } +} + + +void SHAPE_LINE_CHAIN::Insert( size_t aVertex, const VECTOR2I& aP ) +{ + if( m_shapes[aVertex] != SHAPE_IS_PT ) + convertArc( aVertex ); + + m_points.insert( m_points.begin() + aVertex, aP ); + m_shapes.insert( m_shapes.begin() + aVertex, ssize_t( SHAPE_IS_PT ) ); +} + + +void SHAPE_LINE_CHAIN::Insert( size_t aVertex, const SHAPE_ARC& aArc ) +{ + if( m_shapes[aVertex] != SHAPE_IS_PT ) + convertArc( aVertex ); + + /// Step 1: Find the position for the new arc in the existing arc vector + size_t arc_pos = m_arcs.size(); + + for( auto arc_it = m_shapes.rbegin() ; + arc_it != m_shapes.rend() + aVertex; + arc_it++ ) + { + if( *arc_it != SHAPE_IS_PT ) + arc_pos = ( *arc_it )++; + } + + m_arcs.insert( m_arcs.begin() + arc_pos, aArc ); + + /// Step 2: Add the arc polyline points to the chain + auto& chain = aArc.ConvertToPolyline(); + m_points.insert( m_points.begin() + aVertex, + chain.CPoints().begin(), chain.CPoints().end() ); + + /// Step 3: Add the vector of indices to the shape vector + std::vector new_points( chain.PointCount(), arc_pos ); + m_shapes.insert( m_shapes.begin() + aVertex, new_points.begin(), new_points.end() ); +} + + struct compareOriginDistance { compareOriginDistance( VECTOR2I& aOrigin ) : @@ -550,6 +741,7 @@ SHAPE_LINE_CHAIN& SHAPE_LINE_CHAIN::Simplify() i = 0; // stage 1: eliminate collinear segments + // TODO(sh): handle arcs Maybe segment-wise? while( i < np - 2 ) { const VECTOR2I p0 = pts_unique[i]; @@ -562,6 +754,7 @@ SHAPE_LINE_CHAIN& SHAPE_LINE_CHAIN::Simplify() n++; m_points.push_back( p0 ); + m_shapes.push_back( ssize_t( SHAPE_IS_PT ) ); if( n > i ) i = n; @@ -569,6 +762,7 @@ SHAPE_LINE_CHAIN& SHAPE_LINE_CHAIN::Simplify() if( n == np ) { m_points.push_back( pts_unique[n - 1] ); + m_shapes.push_back( ssize_t( SHAPE_IS_PT ) ); return *this; } @@ -576,9 +770,13 @@ SHAPE_LINE_CHAIN& SHAPE_LINE_CHAIN::Simplify() } if( np > 1 ) + { m_points.push_back( pts_unique[np - 2] ); + m_shapes.push_back( ssize_t( SHAPE_IS_PT ) ); + } m_points.push_back( pts_unique[np - 1] ); + m_shapes.push_back( ssize_t( SHAPE_IS_PT ) ); return *this; } @@ -648,10 +846,15 @@ const std::string SHAPE_LINE_CHAIN::Format() const { std::stringstream ss; - ss << m_points.size() << " " << ( m_closed ? 1 : 0 ) << " "; + ss << m_points.size() << " " << ( m_closed ? 1 : 0 ) << " " << m_arcs.size() << " "; for( int i = 0; i < PointCount(); i++ ) - ss << m_points[i].x << " " << m_points[i].y << " "; // Format() << " "; + ss << m_points[i].x << " " << m_points[i].y << " " << m_shapes[i]; + + for( size_t i = 0; i < m_arcs.size(); i++ ) + ss << m_arcs[i].GetCenter().x << " " << m_arcs[i].GetCenter().y << " " + << m_arcs[i].GetP0().x << " " << m_arcs[i].GetP0().y << " " + << m_arcs[i].GetCentralAngle(); return ss.str(); } @@ -687,23 +890,46 @@ SHAPE* SHAPE_LINE_CHAIN::Clone() const bool SHAPE_LINE_CHAIN::Parse( std::stringstream& aStream ) { - int n_pts; + size_t n_pts; + size_t n_arcs; m_points.clear(); aStream >> n_pts; // Rough sanity check, just make sure the loop bounds aren't absolutely outlandish - if( n_pts < 0 || n_pts > int( aStream.str().size() ) ) + if( n_pts > aStream.str().size() ) return false; aStream >> m_closed; + aStream >> n_arcs; - for( int i = 0; i < n_pts; i++ ) + if( n_arcs > aStream.str().size() ) + return false; + + for( size_t i = 0; i < n_pts; i++ ) { int x, y; + ssize_t ind; aStream >> x; aStream >> y; m_points.emplace_back( x, y ); + + aStream >> ind; + m_shapes.push_back( ind ); + } + + for( size_t i = 0; i < n_arcs; i++ ) + { + VECTOR2I p0, pc; + double angle; + + aStream >> pc.x; + aStream >> pc.y; + aStream >> p0.x; + aStream >> p0.y; + aStream >> angle; + + m_arcs.emplace_back( pc, p0, angle ); } return true;