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();
|
|
|
|
|
2021-06-06 23:47:42 +00:00
|
|
|
PNS_DBG( dbg, AddLine, m_trail, CYAN, 50000, "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
|
|
|
|
const int lockDistanceFactor = 25;
|
|
|
|
|
|
|
|
// Adjusts how close to p0 we unlock the posture again if one was locked already
|
|
|
|
const int unlockDistanceFactor = 4;
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
PNS_DBG( dbg, AddLine, straight, m_forced ? BLUE : GREEN, 100000, "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
|
|
|
|
|
|
|
PNS_DBG( dbg, AddLine, diag, YELLOW, 100000, "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 );
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
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,
|
|
|
|
wxString::Format( "Posture: checking direction %s against last seg %s",
|
|
|
|
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
|
|
|
{
|
2021-06-06 23:47:42 +00:00
|
|
|
PNS_DBG( dbg, Message, wxString::Format( "Posture: forcing straight => %s",
|
|
|
|
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
|
|
|
{
|
2021-06-06 23:47:42 +00:00
|
|
|
PNS_DBG( dbg, Message, wxString::Format( "Posture: forcing diagonal => %s",
|
|
|
|
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;
|
2021-06-06 23:47:42 +00:00
|
|
|
PNS_DBG( dbg, Message, wxString::Format( "Posture: correcting half full => %s",
|
|
|
|
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 )
|
|
|
|
{
|
2021-06-06 23:47:42 +00:00
|
|
|
PNS_DBG( dbg, Message, wxString::Format( "Posture: correcting right => %s",
|
|
|
|
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 )
|
|
|
|
{
|
2021-06-06 23:47:42 +00:00
|
|
|
PNS_DBG( dbg, Message, wxString::Format( "Posture: correcting obtuse => %s",
|
|
|
|
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
|
|
|
}
|
|
|
|
|