CHANGED: Dragging a curved track starts an interactive resizing of it, keeping start an end points tangent
This commit is contained in:
parent
25956152c6
commit
2b5c1bae97
|
@ -4,6 +4,7 @@ set( KIMATH_SRCS
|
|||
src/md5_hash.cpp
|
||||
src/trigo.cpp
|
||||
|
||||
src/geometry/circle.cpp
|
||||
src/geometry/convex_hull.cpp
|
||||
src/geometry/direction_45.cpp
|
||||
src/geometry/geometry_utils.cpp
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2021 Roberto Fernandez Bautista <roberto.fer.bau@gmail.com>
|
||||
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __CIRCLE_H
|
||||
#define __CIRCLE_H
|
||||
|
||||
#include <math/vector2d.h> // for VECTOR2I
|
||||
#include <vector> // for std::vector
|
||||
|
||||
class SEG;
|
||||
|
||||
/**
|
||||
* Class Circle
|
||||
* Represents basic circle geometry with utility geometry functions.
|
||||
*/
|
||||
class CIRCLE
|
||||
{
|
||||
public:
|
||||
int Radius; ///< Public to make access simpler
|
||||
VECTOR2I Center; ///< Public to make access simpler
|
||||
|
||||
CIRCLE();
|
||||
|
||||
CIRCLE( const VECTOR2I& aCenter, int aRadius );
|
||||
|
||||
CIRCLE( const CIRCLE& aOther );
|
||||
|
||||
/**
|
||||
* Constructs this circle such that it is tangent to the given lines and passes through the
|
||||
* given point. There are two possible solutions, controlled by aAlternateSolution.
|
||||
*
|
||||
* When aAlternateSolution is false, find the best solution that can be used to fillet both
|
||||
* lines (i.e. choose the most likely quadrant and find the solution with smallest arc angle
|
||||
* between the tangent points on the lines)
|
||||
*
|
||||
* @param aLineA is the first tangent line. Treated as an infinite line except for the purpose
|
||||
* of selecting the solution to return.
|
||||
* @param aLineB is the second tangent line. Treated as an infinite line except for the purpose
|
||||
* of selecting the solution to return.
|
||||
* @param aP is the point to pass through
|
||||
* @param aAlternateSolution If true, returns the other solution.
|
||||
* @return *this
|
||||
*/
|
||||
CIRCLE& ConstructFromTanTanPt( const SEG& aLineA, const SEG& aLineB, const VECTOR2I& aP,
|
||||
bool aAlternateSolution = false );
|
||||
|
||||
/**
|
||||
* Function NearestPoint()
|
||||
*
|
||||
* Computes the point on the circumference of the circle that is the closest to aP.
|
||||
*
|
||||
* In other words: finds the intersection point of this circle and a line that passes through
|
||||
* both this circle's center and aP.
|
||||
*
|
||||
* @param aP
|
||||
* @return nearest point to aP
|
||||
*/
|
||||
VECTOR2I NearestPoint( const VECTOR2I& aP ) const;
|
||||
|
||||
/**
|
||||
* Function Intersect()
|
||||
*
|
||||
* Computes the intersection points between this circle and aCircle.
|
||||
*
|
||||
* @param aCircle The other circle to intersect with this.
|
||||
* @return std::vector containing:
|
||||
* - 0 elements if the circles do not intersect
|
||||
* - 1 element if the circles are tangent
|
||||
* - 2 elements if the circles intersect
|
||||
*/
|
||||
std::vector<VECTOR2I> Intersect( const CIRCLE& aCircle ) const;
|
||||
|
||||
/**
|
||||
* Function Intersect()
|
||||
*
|
||||
* Computes the intersection points between this circle and aLine.
|
||||
*
|
||||
* @param aLine The line to intersect with this circle (end points ignored)
|
||||
* @return std::vector containing:
|
||||
* - 0 elements if there is no intersection
|
||||
* - 1 element if the line is tangent to the circle
|
||||
* - 2 elements if the line intersects the circle
|
||||
*/
|
||||
std::vector<VECTOR2I> Intersect( const SEG& aLine ) const;
|
||||
};
|
||||
|
||||
#endif // __CIRCLE_H
|
||||
|
|
@ -200,6 +200,24 @@ public:
|
|||
return Intersect( aSeg, false, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Function PerpendicularSeg()
|
||||
*
|
||||
* Computes a segment perpendicular to this one, passing through point aP
|
||||
* @param aP Point through which the new segment will pass
|
||||
* @return SEG perpendicular to this passing through point aP
|
||||
*/
|
||||
SEG PerpendicularSeg( const VECTOR2I& aP ) const;
|
||||
|
||||
/**
|
||||
* Function ParallelSeg()
|
||||
*
|
||||
* Computes a segment parallel to this one, passing through point aP
|
||||
* @param aP Point through which the new segment will pass
|
||||
* @return SEG parallel to this passing through point aP
|
||||
*/
|
||||
SEG ParallelSeg( const VECTOR2I& aP ) const;
|
||||
|
||||
bool Collide( const SEG& aSeg, int aClearance, int* aActual = nullptr ) const;
|
||||
|
||||
ecoord SquaredDistance( const SEG& aSeg ) const;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#define __SHAPE_CIRCLE_H
|
||||
|
||||
#include <geometry/shape.h>
|
||||
#include <geometry/circle.h>
|
||||
#include <math/box2.h>
|
||||
#include <math/vector2d.h>
|
||||
|
||||
|
@ -36,19 +37,17 @@ class SHAPE_CIRCLE : public SHAPE
|
|||
public:
|
||||
SHAPE_CIRCLE() :
|
||||
SHAPE( SH_CIRCLE ),
|
||||
m_radius( 0 )
|
||||
m_circle()
|
||||
{}
|
||||
|
||||
SHAPE_CIRCLE( const VECTOR2I& aCenter, int aRadius ) :
|
||||
SHAPE( SH_CIRCLE ),
|
||||
m_radius( aRadius ),
|
||||
m_center( aCenter )
|
||||
m_circle( aCenter, aRadius )
|
||||
{}
|
||||
|
||||
SHAPE_CIRCLE( const SHAPE_CIRCLE& aOther ) :
|
||||
SHAPE( SH_CIRCLE ),
|
||||
m_radius( aOther.m_radius ),
|
||||
m_center( aOther.m_center )
|
||||
m_circle( aOther.m_circle )
|
||||
{};
|
||||
|
||||
~SHAPE_CIRCLE()
|
||||
|
@ -63,17 +62,17 @@ public:
|
|||
|
||||
const BOX2I BBox( int aClearance = 0 ) const override
|
||||
{
|
||||
const VECTOR2I rc( m_radius + aClearance, m_radius + aClearance );
|
||||
const VECTOR2I rc( m_circle.Radius + aClearance, m_circle.Radius + aClearance );
|
||||
|
||||
return BOX2I( m_center - rc, rc * 2 );
|
||||
return BOX2I( m_circle.Center - rc, rc * 2 );
|
||||
}
|
||||
|
||||
bool Collide( const SEG& aSeg, int aClearance = 0, int* aActual = nullptr,
|
||||
VECTOR2I* aLocation = nullptr ) const override
|
||||
{
|
||||
int minDist = aClearance + m_radius;
|
||||
VECTOR2I pn = aSeg.NearestPoint( m_center );
|
||||
ecoord dist_sq = ( pn - m_center ).SquaredEuclideanNorm();
|
||||
int minDist = aClearance + m_circle.Radius;
|
||||
VECTOR2I pn = aSeg.NearestPoint( m_circle.Center );
|
||||
ecoord dist_sq = ( pn - m_circle.Center ).SquaredEuclideanNorm();
|
||||
|
||||
if( dist_sq == 0 || dist_sq < SEG::Square( minDist ) )
|
||||
{
|
||||
|
@ -81,7 +80,7 @@ public:
|
|||
*aLocation = pn;
|
||||
|
||||
if( aActual )
|
||||
*aActual = std::max( 0, (int) sqrt( dist_sq ) - m_radius );
|
||||
*aActual = std::max( 0, (int) sqrt( dist_sq ) - m_circle.Radius );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -91,34 +90,39 @@ public:
|
|||
|
||||
void SetRadius( int aRadius )
|
||||
{
|
||||
m_radius = aRadius;
|
||||
m_circle.Radius = aRadius;
|
||||
}
|
||||
|
||||
void SetCenter( const VECTOR2I& aCenter )
|
||||
{
|
||||
m_center = aCenter;
|
||||
m_circle.Center = aCenter;
|
||||
}
|
||||
|
||||
int GetRadius() const
|
||||
{
|
||||
return m_radius;
|
||||
return m_circle.Radius;
|
||||
}
|
||||
|
||||
const VECTOR2I GetCenter() const
|
||||
{
|
||||
return m_center;
|
||||
return m_circle.Center;
|
||||
}
|
||||
|
||||
const CIRCLE GetCircle() const
|
||||
{
|
||||
return m_circle;
|
||||
}
|
||||
|
||||
void Move( const VECTOR2I& aVector ) override
|
||||
{
|
||||
m_center += aVector;
|
||||
m_circle.Center += aVector;
|
||||
}
|
||||
|
||||
void Rotate( double aAngle, const VECTOR2I& aCenter = { 0, 0 } ) override
|
||||
{
|
||||
m_center -= aCenter;
|
||||
m_center = m_center.Rotate( aAngle );
|
||||
m_center += aCenter;
|
||||
m_circle.Center -= aCenter;
|
||||
m_circle.Center = m_circle.Center.Rotate( aAngle );
|
||||
m_circle.Center += aCenter;
|
||||
}
|
||||
|
||||
bool IsSolid() const override
|
||||
|
@ -127,8 +131,7 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
int m_radius;
|
||||
VECTOR2I m_center;
|
||||
CIRCLE m_circle;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -118,6 +118,20 @@ const wxPoint GetArcCenter( VECTOR2I aStart, VECTOR2I aEnd, double aAngle );
|
|||
*/
|
||||
double GetArcAngle( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd );
|
||||
|
||||
/**
|
||||
* Returns the middle point of an arc, half-way between aStart and aEnd. There are two possible
|
||||
* solutions which can be found by toggling aMinArcAngle. The behaviour is undefined for
|
||||
* semicircles (i.e. 180 degree arcs).
|
||||
*
|
||||
* @param aStart The starting point of the arc (for calculating the radius)
|
||||
* @param aEnd The end point of the arc (for determining the arc angle)
|
||||
* @param aCenter The center point of the arc
|
||||
* @param aMinArcAngle If true, returns the point that results in the smallest arc angle.
|
||||
* @return The middle point of the arc
|
||||
*/
|
||||
const VECTOR2I GetArcMid( const VECTOR2I& aStart, const VECTOR2I& aEnd, const VECTOR2I& aCenter,
|
||||
bool aMinArcAngle = true );
|
||||
|
||||
/* Return the arc tangent of 0.1 degrees coord vector dx, dy
|
||||
* between -1800 and 1800
|
||||
* Equivalent to atan2 (but faster for calculations if
|
||||
|
|
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2021 Roberto Fernandez Bautista <roberto.fer.bau@gmail.com>
|
||||
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
|
||||
*
|
||||
* 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 <geometry/circle.h>
|
||||
#include <geometry/seg.h>
|
||||
#include <geometry/shape_arc.h> // for MIN_PRECISION_IU
|
||||
#include <math/util.h> // for KiROUND
|
||||
#include <math/vector2d.h> // for VECTOR2I
|
||||
#include <math.h> // for sqrt
|
||||
#include <trigo.h> // for GetArcMid
|
||||
|
||||
|
||||
CIRCLE::CIRCLE()
|
||||
{
|
||||
Radius = 0;
|
||||
}
|
||||
|
||||
|
||||
CIRCLE::CIRCLE( const VECTOR2I& aCenter, int aRadius )
|
||||
{
|
||||
Center = aCenter;
|
||||
Radius = aRadius;
|
||||
}
|
||||
|
||||
|
||||
CIRCLE::CIRCLE( const CIRCLE& aOther )
|
||||
{
|
||||
Center = aOther.Center;
|
||||
Radius = aOther.Radius;
|
||||
}
|
||||
|
||||
|
||||
CIRCLE& CIRCLE::ConstructFromTanTanPt( const SEG& aLineA, const SEG& aLineB, const VECTOR2I& aP,
|
||||
bool aAlternateSolution )
|
||||
{
|
||||
//fixme: There might be more efficient / accurate solution than using geometrical constructs
|
||||
|
||||
SEG anglebisector;
|
||||
VECTOR2I intersectPoint;
|
||||
|
||||
auto furthestFromIntersect =
|
||||
[&]( VECTOR2I aPt1, VECTOR2I aPt2 ) -> VECTOR2I&
|
||||
{
|
||||
if( ( aPt1 - intersectPoint ).EuclideanNorm()
|
||||
> ( aPt2 - intersectPoint ).EuclideanNorm() )
|
||||
{
|
||||
return aPt1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return aPt2;
|
||||
}
|
||||
};
|
||||
|
||||
auto closestToIntersect =
|
||||
[&]( VECTOR2I aPt1, VECTOR2I aPt2 ) -> VECTOR2I&
|
||||
{
|
||||
if( ( aPt1 - intersectPoint ).EuclideanNorm()
|
||||
<= ( aPt2 - intersectPoint ).EuclideanNorm() )
|
||||
{
|
||||
return aPt1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return aPt2;
|
||||
}
|
||||
};
|
||||
|
||||
if( aLineA.ApproxParallel( aLineB ) )
|
||||
{
|
||||
// Special case, no intersection point between the two lines
|
||||
// The center will be in the line equidistant between the two given lines
|
||||
// The radius will be half the distance between the two lines
|
||||
// The possible centers can be found by intersection
|
||||
|
||||
SEG perpendicularAtoB( aLineA.A, aLineB.LineProject( aLineA.A ) );
|
||||
VECTOR2I midPt = perpendicularAtoB.Center();
|
||||
Radius = ( midPt - aLineA.A ).EuclideanNorm();
|
||||
|
||||
anglebisector = aLineA.ParallelSeg( midPt );
|
||||
|
||||
Center = aP; // use this circle as a construction to find the actual centers
|
||||
std::vector<VECTOR2I> possibleCenters = Intersect( anglebisector );
|
||||
|
||||
wxCHECK_MSG( possibleCenters.size() > 0, *this, "No solutions exist!" );
|
||||
intersectPoint = aLineA.A; // just for the purpose of deciding which solution to return
|
||||
|
||||
if( aAlternateSolution )
|
||||
Center = closestToIntersect( possibleCenters.front(), possibleCenters.back() );
|
||||
else
|
||||
Center = furthestFromIntersect( possibleCenters.front(), possibleCenters.back() );
|
||||
}
|
||||
else
|
||||
{
|
||||
// General case, using homothety.
|
||||
// All circles inscribed in the same angle are homothetic with center at the intersection
|
||||
// In this code, the prefix "h" denotes "the homothetic image"
|
||||
OPT_VECTOR2I intersectCalc = aLineA.IntersectLines( aLineB );
|
||||
wxCHECK_MSG( intersectCalc, *this, "Lines do not intersect but are not parallel?" );
|
||||
intersectPoint = intersectCalc.get();
|
||||
|
||||
if( aP == intersectPoint )
|
||||
{
|
||||
//Special case: The point is at the intersection of the two lines
|
||||
Center = aP;
|
||||
Radius = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Calculate bisector
|
||||
VECTOR2I& lineApt = furthestFromIntersect( aLineA.A, aLineA.B );
|
||||
VECTOR2I& lineBpt = furthestFromIntersect( aLineB.A, aLineB.B );
|
||||
VECTOR2I bisectorPt = GetArcMid( lineApt, lineBpt, intersectPoint, true );
|
||||
|
||||
anglebisector.A = intersectPoint;
|
||||
anglebisector.B = bisectorPt;
|
||||
|
||||
if( aAlternateSolution && ( aLineA.Contains( aP ) || aLineB.Contains( aP ) ) )
|
||||
anglebisector.PerpendicularSeg( intersectPoint );
|
||||
|
||||
// Create an arbitrary circle that is tangent to both lines
|
||||
CIRCLE hSolution;
|
||||
hSolution.Center = anglebisector.LineProject( aP );
|
||||
hSolution.Radius = aLineA.LineDistance( hSolution.Center );
|
||||
|
||||
// Find the homothetic image of aP in the construction circle (hSolution)
|
||||
SEG throughaP( intersectPoint, aP );
|
||||
std::vector<VECTOR2I> hProjections = hSolution.Intersect( throughaP );
|
||||
wxCHECK_MSG( hProjections.size() > 0, *this, "No solutions exist!" );
|
||||
|
||||
VECTOR2I hSelected;
|
||||
|
||||
if( aAlternateSolution )
|
||||
hSelected = furthestFromIntersect( hProjections.front(), hProjections.back() );
|
||||
else
|
||||
hSelected = closestToIntersect( hProjections.front(), hProjections.back() );
|
||||
|
||||
VECTOR2I hTanLineA = aLineA.LineProject( hSolution.Center );
|
||||
VECTOR2I hTanLineB = aLineB.LineProject( hSolution.Center );
|
||||
|
||||
// To minimise errors, use the furthest away tangent point from aP
|
||||
if( ( hTanLineA - aP ).EuclideanNorm() > ( hTanLineB - aP ).EuclideanNorm() )
|
||||
{
|
||||
// Find the tangent at line A by homothetic inversion
|
||||
SEG hT( hTanLineA, hSelected );
|
||||
OPT_VECTOR2I actTanA = hT.ParallelSeg( aP ).IntersectLines( aLineA );
|
||||
wxCHECK_MSG( actTanA, *this, "No solutions exist!" );
|
||||
|
||||
// Find circle center by perpendicular intersection with the angle bisector
|
||||
SEG perpendicularToTanA = aLineA.PerpendicularSeg( actTanA.get() );
|
||||
OPT_VECTOR2I actCenter = perpendicularToTanA.IntersectLines( anglebisector );
|
||||
wxCHECK_MSG( actCenter, *this, "No solutions exist!" );
|
||||
|
||||
Center = actCenter.get();
|
||||
Radius = aLineA.LineDistance( Center );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find the tangent at line B by inversion
|
||||
SEG hT( hTanLineB, hSelected );
|
||||
OPT_VECTOR2I actTanB = hT.ParallelSeg( aP ).IntersectLines( aLineB );
|
||||
wxCHECK_MSG( actTanB, *this, "No solutions exist!" );
|
||||
|
||||
// Find circle center by perpendicular intersection with the angle bisector
|
||||
SEG perpendicularToTanB = aLineB.PerpendicularSeg( actTanB.get() );
|
||||
OPT_VECTOR2I actCenter = perpendicularToTanB.IntersectLines( anglebisector );
|
||||
wxCHECK_MSG( actCenter, *this, "No solutions exist!" );
|
||||
|
||||
Center = actCenter.get();
|
||||
Radius = aLineB.LineDistance( Center );
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
VECTOR2I CIRCLE::NearestPoint( const VECTOR2I& aP ) const
|
||||
{
|
||||
VECTOR2I vec = aP - Center;
|
||||
|
||||
// Handle special case where aP is equal to this circle's center
|
||||
if( vec.x == 0 && vec.y == 0 )
|
||||
vec.x = 1; // Arbitrary, to ensure the return value is always on the circumference
|
||||
|
||||
return vec.Resize( Radius ) + Center;
|
||||
}
|
||||
|
||||
|
||||
std::vector<VECTOR2I> CIRCLE::Intersect( const CIRCLE& aCircle ) const
|
||||
{
|
||||
// Simplify the problem:
|
||||
// Let this circle be centered at (0,0), with radius r1
|
||||
// Let aCircle be centered at (d, 0), with radius r2
|
||||
// (i.e. d is the distance between the two circle centers)
|
||||
//
|
||||
// The equations of the two circles are
|
||||
// (1) x^2 + y^2 = r1^2
|
||||
// (2) (x - d)^2 + y^2 = r2^2
|
||||
//
|
||||
// Combining (1) into (2):
|
||||
// (x - d)^2 + r1^2 - x^2 = r2^2
|
||||
// Expanding:
|
||||
// x^2 - 2*d*x + d^2 + r1^2 - x^2 = r2^2
|
||||
// Rearranging for x:
|
||||
// (3) x = (d^2 + r1^2 - r2^2) / (2 * d)
|
||||
//
|
||||
// Rearranging (1) gives:
|
||||
// (4) y = sqrt(r1^2 - x^2)
|
||||
|
||||
std::vector<VECTOR2I> retval;
|
||||
|
||||
VECTOR2I vecCtoC = aCircle.Center - Center;
|
||||
int64_t d = vecCtoC.EuclideanNorm();
|
||||
int64_t r1 = Radius;
|
||||
int64_t r2 = aCircle.Radius;
|
||||
|
||||
if( d > ( r1 + r2 ) || d == 0 )
|
||||
return retval; //circles do not intersect
|
||||
|
||||
// Equation (3)
|
||||
int64_t x = ( ( d * d ) + ( r1 * r1 ) - ( r2 * r2 ) ) / ( int64_t( 2 ) * d );
|
||||
int64_t r1sqMinusXsq = ( r1 * r1 ) - ( x * x );
|
||||
|
||||
if( r1sqMinusXsq < 0 )
|
||||
return retval; //circles do not intersect
|
||||
|
||||
// Equation (4)
|
||||
int64_t y = KiROUND( sqrt( r1sqMinusXsq ) );
|
||||
|
||||
// Now correct back to original coordinates
|
||||
double rotAngle = vecCtoC.Angle();
|
||||
VECTOR2I solution1( x, y );
|
||||
solution1 = solution1.Rotate( rotAngle );
|
||||
solution1 += Center;
|
||||
retval.push_back( solution1 );
|
||||
|
||||
if( y != 0 )
|
||||
{
|
||||
VECTOR2I solution2( x, -y );
|
||||
solution2 = solution2.Rotate( rotAngle );
|
||||
solution2 += Center;
|
||||
retval.push_back( solution2 );
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
std::vector<VECTOR2I> CIRCLE::Intersect( const SEG& aLine ) const
|
||||
{
|
||||
std::vector<VECTOR2I> retval;
|
||||
|
||||
//
|
||||
// . * .
|
||||
// * *
|
||||
// -----1-------m-------2----
|
||||
// * *
|
||||
// * O *
|
||||
// * *
|
||||
// * *
|
||||
// * *
|
||||
// * *
|
||||
// ' * '
|
||||
// Let O be the center of this circle, 1 and 2 the intersection points of the line
|
||||
// and M be the center of the chord connecting points 1 and 2
|
||||
//
|
||||
// M will be O projected perpendicularly to the line since a chord is always perpendicular
|
||||
// to the radius.
|
||||
//
|
||||
// The distance M1 = M2 can be computed by pythagoras since O1 = O2 = Radius
|
||||
//
|
||||
// O1= O2 = sqrt( Radius^2 - OM^2)
|
||||
//
|
||||
|
||||
VECTOR2I m = aLine.LineProject( Center );
|
||||
int64_t om = ( m - Center ).EuclideanNorm();
|
||||
|
||||
if( om > ( (int64_t) Radius + SHAPE_ARC::MIN_PRECISION_IU ) )
|
||||
{
|
||||
return retval; // does not intersect
|
||||
}
|
||||
else if( om <= ( (int64_t) Radius + SHAPE_ARC::MIN_PRECISION_IU )
|
||||
&& om >= ( (int64_t) Radius - SHAPE_ARC::MIN_PRECISION_IU ) )
|
||||
{
|
||||
retval.push_back( m );
|
||||
return retval; //tangent
|
||||
}
|
||||
|
||||
int64_t radiusSquared = (int64_t) Radius * (int64_t) Radius;
|
||||
int64_t omSquared = om * om;
|
||||
|
||||
int mTo1 = sqrt( radiusSquared - omSquared );
|
||||
|
||||
VECTOR2I mTo1vec = ( aLine.B - aLine.A ).Resize( mTo1 );
|
||||
VECTOR2I mTo2vec = -mTo1vec;
|
||||
|
||||
retval.push_back( mTo1vec + m );
|
||||
retval.push_back( mTo2vec + m );
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
|
@ -130,6 +130,24 @@ OPT_VECTOR2I SEG::Intersect( const SEG& aSeg, bool aIgnoreEndpoints, bool aLines
|
|||
}
|
||||
|
||||
|
||||
SEG SEG::PerpendicularSeg( const VECTOR2I& aP ) const
|
||||
{
|
||||
VECTOR2I slope( B - A );
|
||||
VECTOR2I endPoint = slope.Perpendicular() + aP;
|
||||
|
||||
return SEG( aP, endPoint );
|
||||
}
|
||||
|
||||
|
||||
SEG SEG::ParallelSeg( const VECTOR2I& aP ) const
|
||||
{
|
||||
VECTOR2I slope( B - A );
|
||||
VECTOR2I endPoint = slope + aP;
|
||||
|
||||
return SEG( aP, endPoint );
|
||||
}
|
||||
|
||||
|
||||
bool SEG::ccw( const VECTOR2I& aA, const VECTOR2I& aB, const VECTOR2I& aC ) const
|
||||
{
|
||||
return (ecoord) ( aC.y - aA.y ) * ( aB.x - aA.x ) > (ecoord) ( aB.y - aA.y ) * ( aC.x - aA.x );
|
||||
|
|
|
@ -159,6 +159,26 @@ bool TestSegmentHit( const wxPoint &aRefPoint, wxPoint aStart, wxPoint aEnd, int
|
|||
}
|
||||
|
||||
|
||||
const VECTOR2I GetArcMid( const VECTOR2I& aStart, const VECTOR2I& aEnd, const VECTOR2I& aCenter,
|
||||
bool aMinArcAngle )
|
||||
{
|
||||
VECTOR2I startVector = aStart - aCenter;
|
||||
VECTOR2I endVector = aEnd - aCenter;
|
||||
|
||||
double startAngle = ArcTangente( startVector.y, startVector.x );
|
||||
double endAngle = ArcTangente( endVector.y, endVector.x );
|
||||
double midPointRotAngleDeciDeg = NormalizeAngle180( startAngle - endAngle ) / 2;
|
||||
|
||||
if( !aMinArcAngle )
|
||||
midPointRotAngleDeciDeg += 1800.0;
|
||||
|
||||
VECTOR2I newMid = aStart;
|
||||
RotatePoint( newMid, aCenter, midPointRotAngleDeciDeg );
|
||||
|
||||
return newMid;
|
||||
}
|
||||
|
||||
|
||||
double ArcTangente( int dy, int dx )
|
||||
{
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ using namespace std::placeholders;
|
|||
#include <dialogs/dialog_track_via_properties.h>
|
||||
#include <dialogs/dialog_unit_entry.h>
|
||||
#include <board_commit.h>
|
||||
#include <pcb_target.h>
|
||||
#include <zone_filler.h>
|
||||
|
||||
|
||||
|
@ -269,7 +270,294 @@ int EDIT_TOOL::Drag( const TOOL_EVENT& aEvent )
|
|||
if( selection.Empty() )
|
||||
return 0;
|
||||
|
||||
if( selection.Size() == 1 && selection.Front()->Type() == PCB_ARC_T )
|
||||
{
|
||||
// TODO: This really should be done in PNS to ensure DRC is maintained, but for now
|
||||
// it allows interactive editing of an arc track
|
||||
return DragArcTrack( aEvent );
|
||||
}
|
||||
else
|
||||
{
|
||||
invokeInlineRouter( mode );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
PCB_SELECTION& selection = m_selectionTool->GetSelection();
|
||||
|
||||
if( selection.Size() != 1 || selection.Front()->Type() != PCB_ARC_T )
|
||||
return 0;
|
||||
|
||||
Activate();
|
||||
|
||||
ARC* theArc = static_cast<ARC*>( selection.Front() );
|
||||
m_commit->Modify( theArc );
|
||||
|
||||
KIGFX::VIEW_CONTROLS* controls = getViewControls();
|
||||
|
||||
controls->ShowCursor( true );
|
||||
controls->SetAutoPan( true );
|
||||
bool restore_state = false;
|
||||
|
||||
VECTOR2I arcCenter = theArc->GetCenter();
|
||||
SEG tanStart = SEG( arcCenter, theArc->GetStart() ).PerpendicularSeg( theArc->GetStart() );
|
||||
SEG tanEnd = SEG( arcCenter, theArc->GetEnd() ).PerpendicularSeg( theArc->GetEnd() );
|
||||
|
||||
if( !tanStart.ApproxParallel( tanEnd ) )
|
||||
{
|
||||
//Ensure the tangent segments are in the correct orientation
|
||||
OPT_VECTOR2I tanIntersect = tanStart.IntersectLines( tanEnd );
|
||||
tanStart.A = tanIntersect.get();
|
||||
tanStart.B = theArc->GetStart();
|
||||
tanEnd.A = tanIntersect.get();
|
||||
tanEnd.B = theArc->GetEnd();
|
||||
}
|
||||
|
||||
KICAD_T track_types[] = { PCB_PAD_T, PCB_VIA_T, PCB_TRACE_T, PCB_ARC_T, EOT };
|
||||
|
||||
auto getUniqueConnectedTrack =
|
||||
[&]( const VECTOR2I& aAnchor ) -> TRACK*
|
||||
{
|
||||
auto conn = board()->GetConnectivity();
|
||||
auto itemsOnAnchor = conn->GetConnectedItemsAtAnchor( theArc, aAnchor, track_types );
|
||||
TRACK* retval = nullptr;
|
||||
|
||||
if( itemsOnAnchor.size() == 1 && itemsOnAnchor.front()->Type() == PCB_TRACE_T )
|
||||
{
|
||||
retval = static_cast<TRACK*>( itemsOnAnchor.front() );
|
||||
m_commit->Modify( retval );
|
||||
}
|
||||
else
|
||||
{
|
||||
retval = new TRACK( theArc->GetParent() );
|
||||
retval->SetStart( (wxPoint) aAnchor );
|
||||
retval->SetEnd( (wxPoint) aAnchor );
|
||||
retval->SetNet( theArc->GetNet() );
|
||||
retval->SetLayer( theArc->GetLayer() );
|
||||
retval->SetWidth( theArc->GetWidth() );
|
||||
retval->SetFlags( IS_NEW );
|
||||
getView()->Add( retval );
|
||||
}
|
||||
|
||||
return retval;
|
||||
};
|
||||
|
||||
TRACK* trackOnStart = getUniqueConnectedTrack( theArc->GetStart() );
|
||||
TRACK* trackOnEnd = getUniqueConnectedTrack( theArc->GetEnd() );
|
||||
|
||||
if( !trackOnStart->IsNew() )
|
||||
{
|
||||
tanStart.A = trackOnStart->GetStart();
|
||||
tanStart.B = trackOnStart->GetEnd();
|
||||
}
|
||||
|
||||
if( !trackOnEnd->IsNew() )
|
||||
{
|
||||
tanEnd.A = trackOnEnd->GetStart();
|
||||
tanEnd.B = trackOnEnd->GetEnd();
|
||||
}
|
||||
|
||||
OPT_VECTOR2I optInterectTan = tanStart.IntersectLines( tanEnd );
|
||||
int tanStartSide = tanStart.Side( theArc->GetMid() );
|
||||
int tanEndSide = tanEnd.Side( theArc->GetMid() );
|
||||
|
||||
bool isStartTrackOnStartPt = ( VECTOR2I( theArc->GetStart() ) - tanStart.A ).EuclideanNorm()
|
||||
< ( VECTOR2I( theArc->GetStart() ) - tanStart.B ).EuclideanNorm();
|
||||
|
||||
bool isEndTrackOnStartPt = ( VECTOR2I( theArc->GetStart() ) - tanEnd.A ).EuclideanNorm()
|
||||
< ( VECTOR2I( theArc->GetStart() ) - tanEnd.B ).EuclideanNorm();
|
||||
|
||||
// Calculate constraints
|
||||
CIRCLE maxTangentCircle;
|
||||
VECTOR2I startOther = ( isStartTrackOnStartPt ) ? tanStart.B : tanStart.A;
|
||||
maxTangentCircle.ConstructFromTanTanPt( tanStart, tanEnd, startOther );
|
||||
|
||||
VECTOR2I maxTanPtStart = tanStart.LineProject( maxTangentCircle.Center );
|
||||
VECTOR2I maxTanPtEnd = tanEnd.LineProject( maxTangentCircle.Center );
|
||||
|
||||
if( !tanEnd.Contains( maxTanPtEnd ) )
|
||||
{
|
||||
startOther = ( isEndTrackOnStartPt ) ? tanEnd.B : tanEnd.A;
|
||||
maxTangentCircle.ConstructFromTanTanPt( tanStart, tanEnd, startOther );
|
||||
maxTanPtStart = tanStart.LineProject( maxTangentCircle.Center );
|
||||
maxTanPtEnd = tanEnd.LineProject( maxTangentCircle.Center );
|
||||
}
|
||||
|
||||
SEG constraintSeg( maxTanPtStart, maxTanPtEnd );
|
||||
int constraintSegSide = constraintSeg.Side( theArc->GetMid() );
|
||||
|
||||
while( TOOL_EVENT* evt = Wait() )
|
||||
{
|
||||
m_cursor = controls->GetMousePosition();
|
||||
|
||||
// Constrain cursor
|
||||
// Fix-me: this is a bit ugly but it works
|
||||
if( tanStartSide != tanStart.Side( m_cursor ) )
|
||||
{
|
||||
VECTOR2I projected = tanStart.LineProject( m_cursor );
|
||||
|
||||
if( tanEndSide != tanEnd.Side( projected ) )
|
||||
{
|
||||
m_cursor = tanEnd.LineProject( m_cursor );
|
||||
|
||||
if( tanStartSide != tanStart.Side( m_cursor ) )
|
||||
m_cursor = optInterectTan.get();
|
||||
}
|
||||
else if( constraintSegSide != constraintSeg.Side( projected ) )
|
||||
{
|
||||
m_cursor = constraintSeg.LineProject( m_cursor );
|
||||
|
||||
if( tanStartSide != tanStart.Side( m_cursor ) )
|
||||
m_cursor = maxTanPtStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cursor = projected;
|
||||
}
|
||||
}
|
||||
else if( tanEndSide != tanEnd.Side( m_cursor ) )
|
||||
{
|
||||
VECTOR2I projected = tanEnd.LineProject( m_cursor );
|
||||
|
||||
if( tanStartSide != tanStart.Side( projected ) )
|
||||
{
|
||||
m_cursor = tanStart.LineProject( m_cursor );
|
||||
|
||||
if( tanEndSide != tanEnd.Side( m_cursor ) )
|
||||
m_cursor = optInterectTan.get();
|
||||
}
|
||||
else if( constraintSegSide != constraintSeg.Side( projected ) )
|
||||
{
|
||||
m_cursor = constraintSeg.LineProject( m_cursor );
|
||||
|
||||
if( tanEndSide != tanEnd.Side( m_cursor ) )
|
||||
m_cursor = maxTanPtEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cursor = projected;
|
||||
}
|
||||
}
|
||||
else if( constraintSegSide != constraintSeg.Side( m_cursor ) )
|
||||
{
|
||||
VECTOR2I projected = constraintSeg.LineProject( m_cursor );
|
||||
|
||||
if( tanStartSide != tanStart.Side( projected ) )
|
||||
{
|
||||
m_cursor = tanStart.LineProject( m_cursor );
|
||||
|
||||
if( constraintSegSide != constraintSeg.Side( m_cursor ) )
|
||||
m_cursor = maxTanPtStart;
|
||||
}
|
||||
else if( tanEndSide != tanEnd.Side( projected ) )
|
||||
{
|
||||
m_cursor = tanEnd.LineProject( m_cursor );
|
||||
|
||||
if( constraintSegSide != constraintSeg.Side( m_cursor ) )
|
||||
m_cursor = maxTanPtEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cursor = projected;
|
||||
}
|
||||
}
|
||||
|
||||
if( ( m_cursor - maxTangentCircle.Center ).EuclideanNorm() < maxTangentCircle.Radius )
|
||||
m_cursor = maxTangentCircle.NearestPoint( m_cursor );
|
||||
|
||||
controls->ForceCursorPosition( true, m_cursor );
|
||||
|
||||
// Calculate resulting object coordinates
|
||||
CIRCLE circlehelper;
|
||||
circlehelper.ConstructFromTanTanPt( tanStart, tanEnd, m_cursor );
|
||||
|
||||
VECTOR2I newCenter = circlehelper.Center;
|
||||
VECTOR2I newStart = tanStart.LineProject( newCenter );
|
||||
VECTOR2I newEnd = tanEnd.LineProject( newCenter );
|
||||
VECTOR2I newMid = GetArcMid( newStart, newEnd, newCenter );
|
||||
|
||||
//Update objects
|
||||
theArc->SetStart( (wxPoint) newStart );
|
||||
theArc->SetEnd( (wxPoint) newEnd );
|
||||
theArc->SetMid( (wxPoint) newMid );
|
||||
|
||||
if( isStartTrackOnStartPt )
|
||||
trackOnStart->SetStart( (wxPoint) newStart );
|
||||
else
|
||||
trackOnStart->SetEnd( (wxPoint) newStart );
|
||||
|
||||
if( isEndTrackOnStartPt )
|
||||
trackOnEnd->SetStart( (wxPoint) newEnd );
|
||||
else
|
||||
trackOnEnd->SetEnd( (wxPoint) newEnd );
|
||||
|
||||
//Update view
|
||||
getView()->Update( trackOnStart );
|
||||
getView()->Update( trackOnEnd );
|
||||
getView()->Update( theArc );
|
||||
|
||||
//Handle events
|
||||
if( evt->IsCancelInteractive() || evt->IsActivate() )
|
||||
{
|
||||
restore_state = true; // Canceling the tool means that items have to be restored
|
||||
break; // Finish
|
||||
}
|
||||
else if( evt->IsAction( &ACTIONS::undo ) )
|
||||
{
|
||||
restore_state = true; // Perform undo locally
|
||||
break; // Finish
|
||||
}
|
||||
else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT )
|
||||
|| evt->IsDblClick( BUT_LEFT ) )
|
||||
{
|
||||
break; // finish
|
||||
}
|
||||
}
|
||||
|
||||
// Remove zero length tracks
|
||||
if( theArc->GetStart() == theArc->GetEnd() )
|
||||
m_commit->Remove( theArc );
|
||||
|
||||
auto cleanupTrack =
|
||||
[&]( TRACK* aTrack )
|
||||
{
|
||||
if( aTrack->IsNew() )
|
||||
{
|
||||
getView()->Remove( aTrack );
|
||||
|
||||
if( aTrack->GetStart() == aTrack->GetEnd() )
|
||||
delete aTrack;
|
||||
else
|
||||
m_commit->Add( aTrack );
|
||||
}
|
||||
else if( aTrack->GetStart() == aTrack->GetEnd() )
|
||||
{
|
||||
m_commit->Remove( aTrack );
|
||||
}
|
||||
};
|
||||
|
||||
cleanupTrack( trackOnStart );
|
||||
cleanupTrack( trackOnEnd );
|
||||
|
||||
// Should we commit?
|
||||
if( restore_state )
|
||||
{
|
||||
m_commit->Revert();
|
||||
|
||||
if( trackOnStart->IsNew() )
|
||||
delete trackOnStart;
|
||||
|
||||
if( trackOnEnd->IsNew() )
|
||||
delete trackOnEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_commit->Push( _( "Drag Arc Track" ) );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -82,10 +82,17 @@ public:
|
|||
|
||||
/**
|
||||
* Function Drag()
|
||||
* Invoke the PNS router to drag tracks.
|
||||
* Invoke the PNS router to drag tracks or do an offline resizing of an arc track
|
||||
* if a single arc track is selected
|
||||
*/
|
||||
int Drag( const TOOL_EVENT& aEvent );
|
||||
|
||||
/**
|
||||
* Function DragArcTrack()
|
||||
* Drag-resize an arc (and change end points of connected straight segments)
|
||||
*/
|
||||
int DragArcTrack( const TOOL_EVENT& aEvent );
|
||||
|
||||
/**
|
||||
* Function Properties()
|
||||
* Displays properties window for the selected object.
|
||||
|
|
Loading…
Reference in New Issue