ShapeToPoly: Fix outline/arc intersection for large expansions

Fixes https://gitlab.com/kicad/code/kicad/-/issues/8820
Adds shortcut code path for 90deg corners
Segments are now actually symetrical
Refactored and commented the code
This commit is contained in:
david-beinder 2021-07-22 00:01:09 +02:00 committed by Jeff Young
parent a16b85db67
commit 937e4b1d8c
1 changed files with 62 additions and 49 deletions

View File

@ -254,78 +254,91 @@ void CornerListToPolygon( SHAPE_POLY_SET& outline, std::vector<ROUNDED_CORNER>&
outline.Append( cur.m_position ); outline.Append( cur.m_position );
else else
{ {
VECTOR2I position = cur.m_position; VECTOR2I cornerPosition = cur.m_position;
int radius = cur.m_radius; int endAngle, radius = cur.m_radius;
double cosNum = (double) incoming.x * outgoing.x + (double) incoming.y * outgoing.y; double tanAngle2;
double cosDen = (double) incoming.EuclideanNorm() * outgoing.EuclideanNorm();
double angle = acos( cosNum / cosDen ); if( incoming.x == 0 && outgoing.y == 0 || incoming.y == 0 && outgoing.x == 0 )
double tanAngle2 = tan( ( M_PI - angle ) / 2 ); {
endAngle = 900;
tanAngle2 = 1.0;
}
else
{
double cosNum = (double) incoming.x * outgoing.x + (double) incoming.y * outgoing.y;
double cosDen = (double) incoming.EuclideanNorm() * outgoing.EuclideanNorm();
double angle = acos( cosNum / cosDen );
tanAngle2 = tan( ( M_PI - angle ) / 2 );
endAngle = RAD2DECIDEG( angle );
}
if( aInflate ) if( aInflate )
{ {
radius += aInflate; radius += aInflate;
position += incoming.Resize( aInflate / tanAngle2 ) cornerPosition += incoming.Resize( aInflate / tanAngle2 )
+ incoming.Perpendicular().Resize( -aInflate ); + incoming.Perpendicular().Resize( -aInflate );
} }
// Ensure 16+ segments per 360° and ensure first & last segment are the same size // Ensure 16+ segments per 360deg and ensure first & last segment are the same size
int numSegs = std::max( 16, GetArcToSegmentCount( radius, aError, 360.0 ) ); int numSegs = std::max( 16, GetArcToSegmentCount( radius, aError, 360.0 ) );
int angDelta = 3600 / numSegs; int angDelta = 3600 / numSegs;
int targetAngle = RAD2DECIDEG( angle ); int lastSegLen = endAngle % angDelta; // or 0 if last seg length is angDelta
int angPos = ( angDelta + ( targetAngle % angDelta ) ) / 2; int angPos = lastSegLen ? ( angDelta + lastSegLen ) / 2 : angDelta;
double centerProjection = radius / tanAngle2; double arcTransitionDistance = radius / tanAngle2;
VECTOR2I arcStart = position - incoming.Resize( centerProjection ); VECTOR2I arcStart = cornerPosition - incoming.Resize( arcTransitionDistance );
VECTOR2I arcEnd = position + outgoing.Resize( centerProjection );
VECTOR2I arcCenter = arcStart + incoming.Perpendicular().Resize( radius ); VECTOR2I arcCenter = arcStart + incoming.Perpendicular().Resize( radius );
VECTOR2I arcEnd, arcStartOrigin;
if( aErrorLoc == ERROR_INSIDE ) if( aErrorLoc == ERROR_INSIDE )
{ {
arcEnd = SEG( cornerPosition, arcCenter ).ReflectPoint( arcStart );
arcStartOrigin = arcStart - arcCenter;
outline.Append( arcStart ); outline.Append( arcStart );
VECTOR2I zeroRef = arcStart - arcCenter;
for( ; angPos < targetAngle; angPos += angDelta )
{
VECTOR2I pt = zeroRef;
RotatePoint( pt, -angPos );
outline.Append( pt + arcCenter );
}
outline.Append( arcEnd );
} }
else else
{ {
// The outer radius should be radius+aError, recalculate because numSegs is clamped // The outer radius should be radius+aError, recalculate because numSegs is clamped
int actualDeltaRadius = CircleToEndSegmentDeltaRadius( radius, numSegs ); int actualDeltaRadius = CircleToEndSegmentDeltaRadius( radius, numSegs );
int radiusExtend = GetCircleToPolyCorrection( actualDeltaRadius ); int radiusExtend = GetCircleToPolyCorrection( actualDeltaRadius );
VECTOR2I arcExStart = arcStart + incoming.Perpendicular().Resize( -radiusExtend ); arcStart += incoming.Perpendicular().Resize( -radiusExtend );
VECTOR2I arcExEnd = arcEnd + outgoing.Perpendicular().Resize( -radiusExtend ); arcStartOrigin = arcStart - arcCenter;
// A larger radius will create "ears", so we intersect the first and last segment // To avoid "ears", we only add segments crossing/within the non-rounded outline
// of the rounded corner with the non-rounded outline // Note: outlineIn is short and must be treated as defining an infinite line
SEG inSeg( position - incoming, position ); SEG outlineIn( cornerPosition - incoming, cornerPosition );
SEG outSeg( position, position + outgoing ); VECTOR2I prevPt = arcStart;
VECTOR2I zeroRef = arcExStart - arcCenter; arcEnd = cornerPosition; // default if no points within the outline are found
VECTOR2I pt = zeroRef;
RotatePoint( pt, -angPos ); while( angPos < endAngle )
pt += arcCenter;
OPT<VECTOR2I> intersect = inSeg.Intersect( SEG( arcExStart, pt ) );
outline.Append( intersect.is_initialized() ? intersect.get() : arcStart );
outline.Append( pt );
angPos += angDelta;
for( ; angPos < targetAngle; angPos += angDelta )
{ {
pt = zeroRef; VECTOR2I pt = arcStartOrigin;
RotatePoint( pt, -angPos ); RotatePoint( pt, -angPos );
pt += arcCenter; pt += arcCenter;
outline.Append( pt ); angPos += angDelta;
}
intersect = outSeg.Intersect( SEG( pt, arcExEnd ) ); if( outlineIn.Side( pt ) > 0 )
outline.Append( intersect.is_initialized() ? intersect.get() : arcEnd ); {
VECTOR2I intersect = outlineIn.IntersectLines( SEG( prevPt, pt ) ).get();
outline.Append( intersect );
outline.Append( pt );
arcEnd = SEG( cornerPosition, arcCenter ).ReflectPoint( intersect );
break;
}
endAngle -= angDelta; // if skipping first, also skip last
prevPt = pt;
}
} }
for( ; angPos < endAngle; angPos += angDelta )
{
VECTOR2I pt = arcStartOrigin;
RotatePoint( pt, -angPos );
outline.Append( pt + arcCenter );
}
outline.Append( arcEnd );
} }
incoming = outgoing; incoming = outgoing;