2020-10-19 19:42:06 +00:00
/*
* KiRouter - a push - and - ( sometimes - ) shove PCB router
*
* Copyright ( C ) 2013 - 2020 CERN
* Author : Tomasz Wlostowski < tomasz . wlostowski @ cern . ch >
*
* This program is free software : you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation , either version 3 of the License , or ( at your
* option ) any later version .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License along
* with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "pns_mouse_trail_tracer.h"
# include "pns_router.h"
# include "pns_debug_decorator.h"
namespace PNS {
MOUSE_TRAIL_TRACER : : MOUSE_TRAIL_TRACER ( )
{
m_tolerance = 0 ;
m_disableMouse = false ;
Clear ( ) ;
}
MOUSE_TRAIL_TRACER : : ~ MOUSE_TRAIL_TRACER ( ) { }
void MOUSE_TRAIL_TRACER : : Clear ( )
{
m_forced = false ;
m_manuallyForced = false ;
m_trail . Clear ( ) ;
}
void MOUSE_TRAIL_TRACER : : AddTrailPoint ( const VECTOR2I & aP )
{
if ( m_trail . SegmentCount ( ) = = 0 )
{
m_trail . Append ( aP ) ;
}
else
{
SEG s_new ( m_trail . CPoint ( - 1 ) , aP ) ;
2021-06-06 23:47:42 +00:00
if ( m_trail . SegmentCount ( ) > 2 )
2020-10-19 19:42:06 +00:00
{
2021-06-06 23:47:42 +00:00
SEG : : ecoord limit = ( static_cast < SEG : : ecoord > ( m_tolerance ) * m_tolerance ) ;
2020-10-19 19:42:06 +00:00
2021-06-06 23:47:42 +00:00
for ( int i = 0 ; i < m_trail . SegmentCount ( ) - 2 ; i + + )
2020-10-19 19:42:06 +00:00
{
2021-06-06 23:47:42 +00:00
const SEG & s_trail = m_trail . CSegment ( i ) ;
if ( s_trail . SquaredDistance ( s_new ) < = limit )
{
m_trail = m_trail . Slice ( 0 , i ) ;
break ;
}
2020-10-19 19:42:06 +00:00
}
}
m_trail . Append ( aP ) ;
}
m_trail . Simplify ( ) ;
2021-05-28 22:09:21 +00:00
DEBUG_DECORATOR * dbg = ROUTER : : GetInstance ( ) - > GetInterface ( ) - > GetDebugDecorator ( ) ;
2022-02-26 01:42:16 +00:00
PNS_DBG ( dbg , AddShape , & m_trail , CYAN , 50000 , wxT ( " mt-trail " ) ) ;
2020-10-19 19:42:06 +00:00
}
DIRECTION_45 MOUSE_TRAIL_TRACER : : GetPosture ( const VECTOR2I & aP )
{
// Tuning factor for how good the "fit" of the trail must be to the posture
const double areaRatioThreshold = 1.3 ;
// Tuning factor to minimize flutter
const double areaRatioEpsilon = 0.25 ;
// Minimum distance factor of the trail before the min area test is used to lock the solver
const double minAreaCutoffDistanceFactor = 6 ;
// Adjusts how far away from p0 we get before whatever posture we solved is locked in
2023-01-12 22:35:41 +00:00
const int lockDistanceFactor = 30 ;
2020-10-19 19:42:06 +00:00
// Adjusts how close to p0 we unlock the posture again if one was locked already
2023-01-12 22:35:41 +00:00
const int unlockDistanceFactor = 10 ;
2020-10-19 19:42:06 +00:00
if ( m_trail . PointCount ( ) < 2 | | m_manuallyForced )
{
// If mouse trail detection is enabled; using the last seg direction as a starting point
// will give the best results. Otherwise, just assume that we switch postures every
// segment.
if ( ! m_manuallyForced & & m_lastSegDirection ! = DIRECTION_45 : : UNDEFINED )
m_direction = m_disableMouse ? m_lastSegDirection . Right ( ) : m_lastSegDirection ;
return m_direction ;
}
DEBUG_DECORATOR * dbg = ROUTER : : GetInstance ( ) - > GetInterface ( ) - > GetDebugDecorator ( ) ;
VECTOR2I p0 = m_trail . CPoint ( 0 ) ;
double refLength = SEG ( p0 , aP ) . Length ( ) ;
2021-11-03 02:14:23 +00:00
SHAPE_LINE_CHAIN straight ( DIRECTION_45 ( ) . BuildInitialTrace ( p0 , aP , false ) ) ;
2020-10-19 19:42:06 +00:00
straight . SetClosed ( true ) ;
straight . Append ( m_trail . Reverse ( ) ) ;
straight . Simplify ( ) ;
2021-05-28 22:09:21 +00:00
2022-02-26 01:42:16 +00:00
PNS_DBG ( dbg , AddShape , & straight , m_forced ? BLUE : GREEN , 100000 , wxT ( " mt-straight " ) ) ;
2020-10-19 19:42:06 +00:00
2021-05-09 19:01:47 +00:00
double areaS = straight . Area ( ) ;
2020-10-19 19:42:06 +00:00
2021-11-03 02:14:23 +00:00
SHAPE_LINE_CHAIN diag ( DIRECTION_45 ( ) . BuildInitialTrace ( p0 , aP , true ) ) ;
2020-10-19 19:42:06 +00:00
diag . Append ( m_trail . Reverse ( ) ) ;
diag . SetClosed ( true ) ;
diag . Simplify ( ) ;
2021-05-28 22:09:21 +00:00
2022-02-26 01:42:16 +00:00
PNS_DBG ( dbg , AddShape , & diag , YELLOW , 100000 , wxT ( " mt-diag " ) ) ;
2020-10-19 19:42:06 +00:00
2021-05-09 19:01:47 +00:00
double areaDiag = diag . Area ( ) ;
2020-10-19 19:42:06 +00:00
double ratio = areaS / ( areaDiag + 1.0 ) ;
2023-01-12 22:35:41 +00:00
2020-10-19 19:42:06 +00:00
// heuristic to detect that the user dragged back the cursor to the beginning of the trace
// in this case, we cancel any forced posture and restart the trail
if ( m_forced & & refLength < unlockDistanceFactor * m_tolerance )
{
2021-05-28 22:09:21 +00:00
PNS_DBG ( dbg , Message , " Posture: Unlocked and reset " ) ;
2020-10-19 19:42:06 +00:00
m_forced = false ;
VECTOR2I start = p0 ;
m_trail . Clear ( ) ;
m_trail . Append ( start ) ;
}
bool areaOk = false ;
// Check the actual trail area against the cutoff. This prevents flutter when the trail is
// very close to a straight line.
if ( ! m_forced & & refLength > minAreaCutoffDistanceFactor * m_tolerance )
{
double areaCutoff = m_tolerance * refLength ;
SHAPE_LINE_CHAIN trail ( m_trail ) ;
trail . SetClosed ( true ) ;
2021-05-09 19:01:47 +00:00
if ( trail . Area ( ) > areaCutoff )
2020-10-19 19:42:06 +00:00
areaOk = true ;
2021-12-31 00:04:11 +00:00
}
2020-10-19 19:42:06 +00:00
2023-01-12 22:35:41 +00:00
PNS_DBG ( dbg , Message , wxString : : Format ( " Posture: rl %.0f thr %d tol %d as %.3f area OK %d forced %d \n " , refLength , ( int ) ( unlockDistanceFactor * m_tolerance ) , m_tolerance , ratio , areaOk ? 1 : 0 , m_forced ? 1 : 0 ) ) ;
2020-10-19 19:42:06 +00:00
DIRECTION_45 straightDirection ;
DIRECTION_45 diagDirection ;
DIRECTION_45 newDirection = m_direction ;
straightDirection = DIRECTION_45 ( straight . CSegment ( 0 ) ) ;
diagDirection = DIRECTION_45 ( diag . CSegment ( 0 ) ) ;
if ( ! m_forced & & areaOk & & ratio > areaRatioThreshold + areaRatioEpsilon )
newDirection = diagDirection ;
else if ( ! m_forced & & areaOk & & ratio < ( 1.0 / areaRatioThreshold ) - areaRatioEpsilon )
newDirection = straightDirection ;
else
newDirection = m_direction . IsDiagonal ( ) ? diagDirection : straightDirection ;
if ( ! m_disableMouse & & newDirection ! = m_direction )
{
2021-06-06 23:47:42 +00:00
PNS_DBG ( dbg , Message , wxString : : Format ( " Posture: direction update %s => %s " ,
m_direction . Format ( ) , newDirection . Format ( ) ) ) ;
2020-10-19 19:42:06 +00:00
m_direction = newDirection ;
}
// If we have a last segment, correct the direction relative to it. For segment exit, we want
// to correct to the least obtuse
if ( ! m_manuallyForced & & ! m_disableMouse & & m_lastSegDirection ! = DIRECTION_45 : : UNDEFINED )
{
2021-06-06 23:47:42 +00:00
PNS_DBG ( dbg , Message ,
2022-02-05 02:06:25 +00:00
wxString : : Format ( wxT ( " Posture: checking direction %s against last seg %s " ) ,
2021-06-06 23:47:42 +00:00
m_direction . Format ( ) , m_lastSegDirection . Format ( ) ) ) ;
2020-10-19 19:42:06 +00:00
if ( straightDirection = = m_lastSegDirection )
2021-05-28 22:09:21 +00:00
{
2020-10-19 19:42:06 +00:00
if ( m_direction ! = straightDirection )
2021-05-28 22:09:21 +00:00
{
2022-02-05 02:06:25 +00:00
PNS_DBG ( dbg , Message , wxString : : Format ( wxT ( " Posture: forcing straight => %s " ) ,
2021-06-06 23:47:42 +00:00
straightDirection . Format ( ) ) ) ;
2020-10-19 19:42:06 +00:00
}
m_direction = straightDirection ;
}
else if ( diagDirection = = m_lastSegDirection )
{
2021-12-31 00:04:11 +00:00
if ( m_direction ! = diagDirection )
2020-10-19 19:42:06 +00:00
{
2022-02-05 02:06:25 +00:00
PNS_DBG ( dbg , Message , wxString : : Format ( wxT ( " Posture: forcing diagonal => %s " ) ,
2021-06-06 23:47:42 +00:00
diagDirection . Format ( ) ) ) ;
2020-10-19 19:42:06 +00:00
}
m_direction = diagDirection ;
2021-12-31 00:04:11 +00:00
}
else
{
2020-10-19 19:42:06 +00:00
switch ( m_direction . Angle ( m_lastSegDirection ) )
{
case DIRECTION_45 : : ANG_HALF_FULL :
// Force a better (acute) connection
m_direction = m_direction . IsDiagonal ( ) ? straightDirection : diagDirection ;
2022-02-05 02:06:25 +00:00
PNS_DBG ( dbg , Message , wxString : : Format ( wxT ( " Posture: correcting half full => %s " ) ,
2021-06-06 23:47:42 +00:00
m_direction . Format ( ) ) ) ;
2020-10-19 19:42:06 +00:00
break ;
case DIRECTION_45 : : ANG_ACUTE :
{
// Force a better connection by flipping if possible
DIRECTION_45 candidate = m_direction . IsDiagonal ( ) ? straightDirection
: diagDirection ;
if ( candidate . Angle ( m_lastSegDirection ) = = DIRECTION_45 : : ANG_RIGHT )
{
2022-02-05 02:06:25 +00:00
PNS_DBG ( dbg , Message , wxString : : Format ( wxT ( " Posture: correcting right => %s " ) ,
2021-06-06 23:47:42 +00:00
candidate . Format ( ) ) ) ;
2020-10-19 19:42:06 +00:00
m_direction = candidate ;
2021-05-28 22:09:21 +00:00
}
2020-10-19 19:42:06 +00:00
break ;
2021-05-28 22:09:21 +00:00
}
2020-10-19 19:42:06 +00:00
case DIRECTION_45 : : ANG_RIGHT :
{
// Force a better connection by flipping if possible
DIRECTION_45 candidate = m_direction . IsDiagonal ( ) ? straightDirection
: diagDirection ;
if ( candidate . Angle ( m_lastSegDirection ) = = DIRECTION_45 : : ANG_OBTUSE )
{
2022-02-05 02:06:25 +00:00
PNS_DBG ( dbg , Message , wxString : : Format ( wxT ( " Posture: correcting obtuse => %s " ) ,
2021-06-06 23:47:42 +00:00
candidate . Format ( ) ) ) ;
2020-10-19 19:42:06 +00:00
m_direction = candidate ;
}
break ;
}
default :
break ;
}
}
}
// If we get far away from the initial point, lock in the current solution to prevent flutter
if ( ! m_forced & & refLength > lockDistanceFactor * m_tolerance )
{
2021-05-28 22:09:21 +00:00
PNS_DBG ( dbg , Message , " Posture: solution locked " ) ;
2020-10-19 19:42:06 +00:00
m_forced = true ;
}
return m_direction ;
}
void MOUSE_TRAIL_TRACER : : FlipPosture ( )
{
m_direction = m_direction . Right ( ) ;
m_forced = true ;
m_manuallyForced = true ;
}
2021-02-11 23:05:46 +00:00
VECTOR2I MOUSE_TRAIL_TRACER : : GetTrailLeadVector ( ) const
{
if ( m_trail . PointCount ( ) < 2 )
{
return VECTOR2I ( 0 , 0 ) ;
}
else
{
return m_trail . CPoint ( - 1 ) - m_trail . CPoint ( 0 ) ;
}
}
2020-10-19 19:42:06 +00:00
}