mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-04-07 18:45:32 +00:00
Break out some geom functions from dimension text adjustment
Put these in a separate header - they don't need to be available whenever VECTOR2 is, but they are generic, reusable functions that can have the corner cases defined and tested.
This commit is contained in:
parent
80abdee90d
commit
5925f9c374
libs/kimath
pcbnew/tools
qa/tests/libs/kimath
@ -31,6 +31,7 @@ set( KIMATH_SRCS
|
||||
src/geometry/shape_rect.cpp
|
||||
src/geometry/shape_compound.cpp
|
||||
src/geometry/shape_segment.cpp
|
||||
src/geometry/vector_utils.cpp
|
||||
src/geometry/vertex_set.cpp
|
||||
|
||||
|
||||
|
118
libs/kimath/include/geometry/vector_utils.h
Normal file
118
libs/kimath/include/geometry/vector_utils.h
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2024 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 <math/vector2d.h>
|
||||
|
||||
class SEG;
|
||||
|
||||
namespace KIGEOM
|
||||
{
|
||||
|
||||
/**
|
||||
* @file vector_utils.h
|
||||
*
|
||||
* Supplemental functions for working with vectors and
|
||||
* objects that interact with vectors.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Determine if a point is in a given direction from another point.
|
||||
*
|
||||
* This returns true if the vector from aFrom to aPoint is within
|
||||
* 90 degrees of aDirection.
|
||||
*
|
||||
* ------> aDirection
|
||||
*
|
||||
* /-- aFrom
|
||||
* O /-- aPoint
|
||||
* O
|
||||
*
|
||||
* If the point is perpendicular to the direction, it is considered
|
||||
* to NOT be in the direction (e.g. both the direction and the
|
||||
* reversed direction would return false).
|
||||
*
|
||||
* @param aPoint The point to test.
|
||||
* @param aDirection The direction vector.
|
||||
* @param aFrom The point to test from.
|
||||
*
|
||||
* @return true if the point is in the direction.
|
||||
*/
|
||||
template <typename T>
|
||||
bool PointIsInDirection( const VECTOR2<T>& aPoint, const VECTOR2<T>& aDirection,
|
||||
const VECTOR2<T>& aFrom )
|
||||
{
|
||||
return ( aPoint - aFrom ).Dot( aDirection ) > 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if a segment's vector is within 90 degrees of a given direction.
|
||||
*/
|
||||
bool SegIsInDirection( const SEG& aSeg, const VECTOR2I& aDirection );
|
||||
|
||||
|
||||
/**
|
||||
* Determine if a point projects onto a segment.
|
||||
*
|
||||
* /--- projects /--- does not project
|
||||
* o o
|
||||
* | |
|
||||
* |<------------>| x
|
||||
* aSeg
|
||||
*/
|
||||
bool PointProjectsOntoSegment( const VECTOR2I& aPoint, const SEG& aSeg );
|
||||
|
||||
|
||||
/**
|
||||
* Get the ratio of the vector to a point from the segment's start,
|
||||
* compared to the segment's length.
|
||||
* /--- aPoint
|
||||
* A<---+-------->B <-- Length L
|
||||
* | |
|
||||
* >|----|<-- Length R
|
||||
*
|
||||
* The point doesn't have to lie on the segment.
|
||||
*/
|
||||
double GetLengthRatioFromStart( const VECTOR2I& aPoint, const SEG& aSeg );
|
||||
|
||||
/**
|
||||
* Get the ratio of the vector to a point projected onto a segment
|
||||
* from the start, relative to the segment's length.
|
||||
*
|
||||
* /--- projects
|
||||
* o
|
||||
* |
|
||||
* A<---+-------->B <-- Length L
|
||||
* | |
|
||||
* >|----|<-- Length R
|
||||
*
|
||||
* The ratio is R / L. IF 0, the point is at A. If 1, the point is at B.
|
||||
* It assumes the point projects onto the segment.
|
||||
*/
|
||||
double GetProjectedPointLengthRatio( const VECTOR2I& aPoint, const SEG& aSeg );
|
||||
|
||||
|
||||
/**
|
||||
* Get the nearest end of a segment to a point.
|
||||
*
|
||||
* If equidistant, the start point is returned.
|
||||
*/
|
||||
const VECTOR2I& GetNearestEndpoint( const SEG& aSeg, const VECTOR2I& aPoint );
|
||||
|
||||
} // namespace KIGEOM
|
67
libs/kimath/src/geometry/vector_utils.cpp
Normal file
67
libs/kimath/src/geometry/vector_utils.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2024 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/vector_utils.h"
|
||||
|
||||
#include <geometry/seg.h>
|
||||
|
||||
using namespace KIGEOM;
|
||||
|
||||
bool KIGEOM::SegIsInDirection( const SEG& aSeg, const VECTOR2I& aDirection )
|
||||
{
|
||||
return PointIsInDirection( aSeg.B, aDirection, aSeg.A );
|
||||
};
|
||||
|
||||
|
||||
bool KIGEOM::PointProjectsOntoSegment( const VECTOR2I& aPoint, const SEG& aSeg )
|
||||
{
|
||||
// SEG::NearestPoint returns the end points if the projection is
|
||||
// outside the segment
|
||||
const VECTOR2I projected = aSeg.NearestPoint( aPoint );
|
||||
return projected != aSeg.A && projected != aSeg.B;
|
||||
}
|
||||
|
||||
|
||||
double KIGEOM::GetLengthRatioFromStart( const VECTOR2I& aPoint, const SEG& aSeg )
|
||||
{
|
||||
const double length = aSeg.Length();
|
||||
const double projected_length = ( aPoint - aSeg.A ).EuclideanNorm();
|
||||
return projected_length / length;
|
||||
}
|
||||
|
||||
|
||||
double KIGEOM::GetProjectedPointLengthRatio( const VECTOR2I& aPoint, const SEG& aSeg )
|
||||
{
|
||||
const VECTOR2I projected = aSeg.NearestPoint( aPoint );
|
||||
|
||||
if( projected == aSeg.A )
|
||||
return 0.0;
|
||||
if( projected == aSeg.B )
|
||||
return 1.0;
|
||||
|
||||
return GetLengthRatioFromStart( projected, aSeg );
|
||||
}
|
||||
|
||||
|
||||
const VECTOR2I& KIGEOM::GetNearestEndpoint( const SEG& aSeg, const VECTOR2I& aPoint )
|
||||
{
|
||||
const double distToCBStart = aSeg.A.Distance( aPoint );
|
||||
const double distToCBEnd = aSeg.B.Distance( aPoint );
|
||||
return ( distToCBStart <= distToCBEnd ) ? aSeg.A : aSeg.B;
|
||||
}
|
@ -33,6 +33,7 @@ using namespace std::placeholders;
|
||||
#include <view/view_controls.h>
|
||||
#include <gal/graphics_abstraction_layer.h>
|
||||
#include <geometry/seg.h>
|
||||
#include <geometry/vector_utils.h>
|
||||
#include <confirm.h>
|
||||
#include <tools/pcb_actions.h>
|
||||
#include <tools/pcb_selection_tool.h>
|
||||
@ -1277,20 +1278,21 @@ private:
|
||||
const EDA_ANGLE rotation = oldAngle - newAngle;
|
||||
|
||||
// There are two modes - when the text is between the crossbar points, and when it's not.
|
||||
if( !textIsOverCrossBar( m_oldCrossBar, m_originalTextPos ) )
|
||||
if( !KIGEOM::PointProjectsOntoSegment( m_originalTextPos, m_oldCrossBar ) )
|
||||
{
|
||||
VECTOR2I rotTextOffsetFromCbCenter = m_originalTextPos - m_oldCrossBar.Center();
|
||||
RotatePoint( rotTextOffsetFromCbCenter, rotation );
|
||||
|
||||
VECTOR2I rotTextOffsetFromCbEnd =
|
||||
getTextOffsetFromCrossbarNearestEnd( m_oldCrossBar, m_originalTextPos );
|
||||
m_originalTextPos
|
||||
- KIGEOM::GetNearestEndpoint( m_oldCrossBar, m_originalTextPos );
|
||||
RotatePoint( rotTextOffsetFromCbEnd, rotation );
|
||||
|
||||
// Which of the two crossbar points is now in the right direction? They could be swapped over now.
|
||||
// If zero-length, doesn't matter, they're the same thing
|
||||
const bool startIsInOffsetDirection =
|
||||
pointIsInDirection( m_dimension.GetCrossbarStart(), rotTextOffsetFromCbCenter,
|
||||
newCrossBar.Center() );
|
||||
KIGEOM::PointIsInDirection( m_dimension.GetCrossbarStart(),
|
||||
rotTextOffsetFromCbCenter, newCrossBar.Center() );
|
||||
|
||||
const VECTOR2I& newCbRefPt = startIsInOffsetDirection ? m_dimension.GetCrossbarStart()
|
||||
: m_dimension.GetCrossbarEnd();
|
||||
@ -1303,8 +1305,8 @@ private:
|
||||
// good place for it. Keep it the same distance from the crossbar line, but rotated as needed.
|
||||
|
||||
const VECTOR2I origTextPointProjected = m_oldCrossBar.NearestPoint( m_originalTextPos );
|
||||
const double oldRatio = ( origTextPointProjected - m_oldCrossBar.A ).EuclideanNorm()
|
||||
/ double( m_oldCrossBar.Length() );
|
||||
const double oldRatio =
|
||||
KIGEOM::GetLengthRatioFromStart( origTextPointProjected, m_oldCrossBar );
|
||||
|
||||
// Perpendicular from the crossbar line to the text position
|
||||
// We need to keep this length constant
|
||||
@ -1315,30 +1317,6 @@ private:
|
||||
return newProjected + rotCbNormalToText;
|
||||
}
|
||||
|
||||
static bool pointIsInDirection( const VECTOR2I& aPoint, const VECTOR2I& aDirection,
|
||||
const VECTOR2I& aFrom )
|
||||
{
|
||||
return ( aPoint - aFrom ).Dot( aDirection ) > 0;
|
||||
};
|
||||
|
||||
static bool textIsOverCrossBar( const SEG& aCrossbar, const VECTOR2I& aTextPos )
|
||||
{
|
||||
const VECTOR2I projected = aCrossbar.NearestPoint( aTextPos );
|
||||
return projected != aCrossbar.A && projected != aCrossbar.B;
|
||||
}
|
||||
|
||||
static VECTOR2I getTextOffsetFromCrossbarNearestEnd( const SEG& aCrossbar,
|
||||
const VECTOR2I& aTextPos )
|
||||
{
|
||||
const int distToCBStart = aCrossbar.A.Distance( aTextPos );
|
||||
const int distToCBEnd = aCrossbar.B.Distance( aTextPos );
|
||||
|
||||
const bool isNearerStart = distToCBStart < distToCBEnd;
|
||||
|
||||
// This is the offset of the text from the nearer crossbar point
|
||||
return aTextPos - ( isNearerStart ? aCrossbar.A : aCrossbar.B );
|
||||
}
|
||||
|
||||
PCB_DIM_ALIGNED& m_dimension;
|
||||
const VECTOR2I m_originalTextPos;
|
||||
const SEG m_oldCrossBar;
|
||||
|
@ -46,6 +46,7 @@ set( QA_KIMATH_SRCS
|
||||
geometry/test_shape_poly_set_iterator.cpp
|
||||
geometry/test_shape_line_chain.cpp
|
||||
geometry/test_shape_line_chain_collision.cpp
|
||||
geometry/test_vector_utils.cpp
|
||||
|
||||
math/test_box2.cpp
|
||||
math/test_matrix3x3.cpp
|
||||
|
106
qa/tests/libs/kimath/geometry/test_vector_utils.cpp
Normal file
106
qa/tests/libs/kimath/geometry/test_vector_utils.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2024 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <qa_utils/wx_utils/unit_test_utils.h>
|
||||
|
||||
#include <geometry/vector_utils.h>
|
||||
#include <geometry/seg.h>
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE( VectorUtils )
|
||||
|
||||
BOOST_AUTO_TEST_CASE( PointIsInDirection )
|
||||
{
|
||||
const VECTOR2I p0( 1000, 1000 );
|
||||
|
||||
const VECTOR2I ne( 100, 100 );
|
||||
const VECTOR2I n( 0, 100 );
|
||||
const VECTOR2I s( 0, -100 );
|
||||
|
||||
// To the east, so not directly inline with ne from p0
|
||||
const VECTOR2I p1_east = p0 + VECTOR2I( 1000, 0 );
|
||||
const VECTOR2I p1_west = p0 - VECTOR2I( 1000, 0 );
|
||||
|
||||
BOOST_TEST( KIGEOM::PointIsInDirection( p1_east, ne, p0 ) );
|
||||
BOOST_TEST( !KIGEOM::PointIsInDirection( p1_west, ne, p0 ) );
|
||||
|
||||
// Test the perpendicular corner case
|
||||
|
||||
// Points on both sides are not in the direction
|
||||
BOOST_TEST( !KIGEOM::PointIsInDirection( p1_east, n, p0 ) );
|
||||
BOOST_TEST( !KIGEOM::PointIsInDirection( p1_west, n, p0 ) );
|
||||
|
||||
// And they're also not in the opposite direction
|
||||
BOOST_TEST( !KIGEOM::PointIsInDirection( p1_east, s, p0 ) );
|
||||
BOOST_TEST( !KIGEOM::PointIsInDirection( p1_west, s, p0 ) );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( ProjectsOntoSeg )
|
||||
{
|
||||
const SEG seg( VECTOR2I( 0, 0 ), VECTOR2I( 100, 0 ) );
|
||||
|
||||
BOOST_TEST( KIGEOM::PointProjectsOntoSegment( VECTOR2I( 50, 1000 ), seg ) );
|
||||
BOOST_TEST( !KIGEOM::PointProjectsOntoSegment( VECTOR2I( 150, 1000 ), seg ) );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( LengthRatio )
|
||||
{
|
||||
const SEG seg( VECTOR2I( 0, 0 ), VECTOR2I( 100, 0 ) );
|
||||
|
||||
BOOST_TEST( KIGEOM::GetLengthRatioFromStart( VECTOR2I( 0, 0 ), seg ) == 0.0 );
|
||||
BOOST_TEST( KIGEOM::GetLengthRatioFromStart( VECTOR2I( 100, 0 ), seg ) == 1.0 );
|
||||
BOOST_TEST( KIGEOM::GetLengthRatioFromStart( VECTOR2I( 50, 0 ), seg ) == 0.5 );
|
||||
|
||||
// Points not on the segment also work
|
||||
BOOST_CHECK_CLOSE( KIGEOM::GetLengthRatioFromStart( VECTOR2I( 0, 100 ), seg ), 1.0, 0.0001 );
|
||||
BOOST_CHECK_CLOSE( KIGEOM::GetLengthRatioFromStart( VECTOR2I( 0, 50 ), seg ), 0.5, 0.0001 );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( NearestEndpoint )
|
||||
{
|
||||
struct PtCase
|
||||
{
|
||||
VECTOR2I Point;
|
||||
bool ExpectedStart;
|
||||
};
|
||||
|
||||
const SEG seg( VECTOR2I( 0, 0 ), VECTOR2I( 100, 0 ) );
|
||||
|
||||
const std::vector<PtCase> cases{
|
||||
{ { -100, 0 }, true },
|
||||
{ { 0, 0 }, true },
|
||||
{ { 0, 50 }, true },
|
||||
// Make sure the tie breaks predictably
|
||||
// Equidistant points -> start
|
||||
{ { 50, 0 }, true },
|
||||
{ { 50, 50 }, true },
|
||||
{ { 50, -50 }, true },
|
||||
// End points
|
||||
{ { 200, 0 }, false },
|
||||
{ { 100, 0 }, false },
|
||||
{ { 100, 50 }, false },
|
||||
};
|
||||
|
||||
for( const PtCase& pc : cases )
|
||||
{
|
||||
BOOST_TEST_INFO( "Point: " << pc.Point );
|
||||
BOOST_TEST( KIGEOM::GetNearestEndpoint( seg, pc.Point )
|
||||
== ( pc.ExpectedStart ? seg.A : seg.B ) );
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
Loading…
Reference in New Issue
Block a user