kicad/qa/tests/libs/kimath/geometry/test_shape_line_chain.cpp

1089 lines
51 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2019-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 2
* 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, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <geometry/shape_arc.h>
#include <geometry/shape_line_chain.h>
#include <trigo.h>
#include <qa_utils/geometry/geometry.h>
#include <qa_utils/numeric.h>
#include <qa_utils/wx_utils/unit_test_utils.h>
#include "geom_test_utils.h"
/**
* NOTE: Collision of SHAPE_LINE_CHAIN with arcs is tested in test_shape_arc.cpp
*/
struct SLC_CASES
{
SHAPE_LINE_CHAIN Circle1Arc;
SHAPE_LINE_CHAIN Circle2Arcs;
SHAPE_LINE_CHAIN ArcsCoincident;
SHAPE_LINE_CHAIN ArcsCoincidentClosed;
SHAPE_LINE_CHAIN ArcsIndependent;
SHAPE_LINE_CHAIN DuplicateArcs;
SHAPE_LINE_CHAIN ArcsAndSegMixed;
SHAPE_LINE_CHAIN ArcAndPoint;
SHAPE_LINE_CHAIN SegAndArcCoincident;
SHAPE_LINE_CHAIN EmptyChain;
SHAPE_LINE_CHAIN OnePoint;
SHAPE_LINE_CHAIN TwoPoints;
SHAPE_LINE_CHAIN ThreePoints;
SHAPE_ARC ArcCircle; ///< Full Circle arc
SHAPE_ARC Arc0a; ///< First half of a circle
SHAPE_ARC Arc0b; ///< Second half of a circle
SHAPE_ARC Arc1; ///< start coincident with Arc0a end
SHAPE_ARC Arc2; ///< Independent arc
SHAPE_ARC Arc3; ///< Arc with angle >180
SLC_CASES()
{
ArcCircle = SHAPE_ARC( VECTOR2I( 183450000, 128360000 ),
VECTOR2I( 183850000, 128360000 ),
VECTOR2I( 183450000, 128360000 ), 0 );
Arc0a = SHAPE_ARC( VECTOR2I( 183450000, 128360000 ),
VECTOR2I( 183650000, 128560000 ),
VECTOR2I( 183850000, 128360000 ), 0 );
Arc0b = SHAPE_ARC( VECTOR2I( 183850000, 128360000 ),
VECTOR2I( 183650000, 128160000 ),
VECTOR2I( 183450000, 128360000 ), 0 );
Arc1 = SHAPE_ARC( VECTOR2I( 183850000, 128360000 ),
VECTOR2I( 183638550, 128640305 ),
VECTOR2I( 183500000, 129204974 ), 0 );
Arc2 = SHAPE_ARC( VECTOR2I( 283450000, 228360000 ),
VECTOR2I( 283650000, 228560000 ),
VECTOR2I( 283850000, 228360000 ), 0 );
Arc3 = SHAPE_ARC( VECTOR2I( 0, 0 ),
VECTOR2I( 24142136, 10000000 ),
VECTOR2I( 0, 20000000 ), 0 );
Circle1Arc.Append( ArcCircle );
Circle1Arc.SetClosed( true );
Circle2Arcs.Append( Arc0a );
Circle2Arcs.Append( Arc0b );
Circle2Arcs.SetClosed( true );
ArcsCoincident.Append( Arc0a );
ArcsCoincident.Append( Arc1 );
ArcsCoincidentClosed=ArcsCoincident;
ArcsCoincidentClosed.SetClosed( true );
ArcsIndependent.Append( Arc0a );
ArcsIndependent.Append( Arc2 );
DuplicateArcs=ArcsCoincident;
DuplicateArcs.Append( Arc1 ); //should add a segment between end of the chain and new copy of the arc
ArcAndPoint.Append( Arc0a );
ArcAndPoint.Append( VECTOR2I( 233450000, 228360000 ) );
ArcsAndSegMixed = ArcAndPoint;
ArcsAndSegMixed.Append( Arc2 );
OnePoint.Append( VECTOR2I( 233450000, 228360000 ) );
TwoPoints.Append( VECTOR2I( 233450000, 228360000 ) );
TwoPoints.Append( VECTOR2I( 263450000, 258360000 ) );
ThreePoints = TwoPoints;
ThreePoints.Append( VECTOR2I( 263450000, 308360000 ) );
SegAndArcCoincident.Append( VECTOR2I( 0, 20000000 ) );
SegAndArcCoincident.Append( Arc3 );
}
};
BOOST_FIXTURE_TEST_SUITE( TestShapeLineChain, SLC_CASES )
BOOST_AUTO_TEST_CASE( ClipperConstructorCase1 )
{
// Case of an arc followed by a segment
// The clipper path is not in order (on purpose), to simulate the typical return from clipper
ClipperLib::Path pathClipper1 = {
{ { 125663951, 120099260, 24 }, { 125388111, 120170850, 25 }, { 125124975, 120280270, 26 },
{ 124879705, 120425376, 27 }, { 124657110, 120603322, 28 }, { 124461556, 120810617, 29 },
{ 124296876, 121043198, 30 }, { 124166301, 121296503, 31 }, { 124072391, 121565564, 32 },
{ 124016988, 121845106, 33 }, { 124001177, 122129646, 34 }, { 124025270, 122413605, 35 },
{ 124088794, 122691414, 36 }, { 124190502, 122957625, 37 }, { 124328401, 123207018, 38 },
{ 124499787, 123434703, 39 }, { 124598846, 123537154, 40 }, { 127171000, 123786000, 4 },
{ 127287862, 123704439, 5 }, { 127499716, 123513831, 6 }, { 127682866, 123295498, 7 },
{ 127833720, 123053722, 8 }, { 127949321, 122793242, 9 }, { 128027402, 122519168, 10 },
{ 128066430, 122236874, 11 }, { 128065642, 121951896, 12 }, { 128025053, 121669823, 13 },
{ 127945457, 121396185, 14 }, { 127828417, 121136349, 15 }, { 127676227, 120895410, 16 },
{ 127491873, 120678094, 17 }, { 127278968, 120488661, 18 }, { 127041689, 120330827, 19 },
{ 126784688, 120207687, 20 }, { 126513005, 120121655, 21 }, { 126231968, 120074419, 22 },
{ 125947087, 120066905, 23 } }
};
Clipper2Lib::Path64 pathClipper2 = {
{ { 125663951, 120099260, 24 }, { 125388111, 120170850, 25 }, { 125124975, 120280270, 26 },
{ 124879705, 120425376, 27 }, { 124657110, 120603322, 28 }, { 124461556, 120810617, 29 },
{ 124296876, 121043198, 30 }, { 124166301, 121296503, 31 }, { 124072391, 121565564, 32 },
{ 124016988, 121845106, 33 }, { 124001177, 122129646, 34 }, { 124025270, 122413605, 35 },
{ 124088794, 122691414, 36 }, { 124190502, 122957625, 37 }, { 124328401, 123207018, 38 },
{ 124499787, 123434703, 39 }, { 124598846, 123537154, 40 }, { 127171000, 123786000, 4 },
{ 127287862, 123704439, 5 }, { 127499716, 123513831, 6 }, { 127682866, 123295498, 7 },
{ 127833720, 123053722, 8 }, { 127949321, 122793242, 9 }, { 128027402, 122519168, 10 },
{ 128066430, 122236874, 11 }, { 128065642, 121951896, 12 }, { 128025053, 121669823, 13 },
{ 127945457, 121396185, 14 }, { 127828417, 121136349, 15 }, { 127676227, 120895410, 16 },
{ 127491873, 120678094, 17 }, { 127278968, 120488661, 18 }, { 127041689, 120330827, 19 },
{ 126784688, 120207687, 20 }, { 126513005, 120121655, 21 }, { 126231968, 120074419, 22 },
{ 125947087, 120066905, 23 } }
};
std::vector<CLIPPER_Z_VALUE> z_values = {
{ { -1, -1 }, 0 }, { { -1, -1 }, 0 }, { { -1, -1 }, 0 }, { { -1, -1 }, 0 },
{ { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 },
{ { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 },
{ { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 },
{ { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 },
{ { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 },
{ { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 },
{ { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 },
{ { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 },
{ { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 }, { { 0, -1 }, 0 },
{ { 0, -1 }, 0 }
};
std::vector<SHAPE_ARC> arcs = {
SHAPE_ARC( { 127171000, 123786000 }, { 126231718, 120077003 }, { 124598846, 123537154 }, 0 )
};
SHAPE_LINE_CHAIN clipper1chain( pathClipper1, z_values, arcs );
SHAPE_LINE_CHAIN clipper2chain( pathClipper2, z_values, arcs );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( clipper1chain ) );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( clipper2chain ) );
BOOST_CHECK_EQUAL( clipper1chain.PointCount(), 37 );
BOOST_CHECK_EQUAL( clipper2chain.PointCount(), 37 );
BOOST_CHECK_EQUAL( clipper1chain.ArcCount(), 1 );
BOOST_CHECK_EQUAL( clipper2chain.ArcCount(), 1 );
BOOST_CHECK_EQUAL( clipper1chain.ShapeCount(), 2 );
BOOST_CHECK_EQUAL( clipper2chain.ShapeCount(), 2 );
BOOST_CHECK_EQUAL( clipper1chain.IsClosed(), true );
BOOST_CHECK_EQUAL( clipper2chain.IsClosed(), true );
}
BOOST_AUTO_TEST_CASE( ArcToPolyline )
{
SHAPE_LINE_CHAIN base_chain( { VECTOR2I( 0, 0 ), VECTOR2I( 0, 1000 ), VECTOR2I( 1000, 0 ) } );
SHAPE_LINE_CHAIN chain_insert( {
VECTOR2I( 0, 1500 ),
VECTOR2I( 1500, 1500 ),
VECTOR2I( 1500, 0 ),
} );
SHAPE_LINE_CHAIN arc_insert1( SHAPE_ARC( VECTOR2I( 0, -100 ), VECTOR2I( 0, -200 ), ANGLE_180 ) );
SHAPE_LINE_CHAIN arc_insert2( SHAPE_ARC( VECTOR2I( 0, 500 ), VECTOR2I( 0, 400 ), ANGLE_180 ) );
BOOST_CHECK_EQUAL( base_chain.CShapes().size(), base_chain.CPoints().size() );
BOOST_CHECK_EQUAL( arc_insert1.CShapes().size(), arc_insert1.CPoints().size() );
BOOST_CHECK_EQUAL( arc_insert2.CShapes().size(), arc_insert2.CPoints().size() );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( arc_insert1 ) );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( arc_insert2 ) );
base_chain.Insert( 0, SHAPE_ARC( VECTOR2I( 0, -100 ), VECTOR2I( 0, -200 ), ANGLE_180 ) );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
BOOST_CHECK_EQUAL( base_chain.CShapes().size(), base_chain.CPoints().size() );
base_chain.Replace( 0, 2, chain_insert );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
BOOST_CHECK_EQUAL( base_chain.CShapes().size(), base_chain.CPoints().size() );
}
// Similar test to above but with larger coordinates, so we have more than one point per arc
BOOST_AUTO_TEST_CASE( ArcToPolylineLargeCoords )
{
SHAPE_LINE_CHAIN base_chain( { VECTOR2I( 0, 0 ), VECTOR2I( 0, 100000 ), VECTOR2I( 100000, 0 ) } );
SHAPE_LINE_CHAIN chain_insert( {
VECTOR2I( 0, 1500000 ),
VECTOR2I( 1500000, 1500000 ),
VECTOR2I( 1500000, 0 ),
} );
base_chain.Append( SHAPE_ARC( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 100000 ), ANGLE_180 ) );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
BOOST_CHECK_EQUAL( base_chain.PointCount(), 11 );
base_chain.Insert( 9, VECTOR2I( 250000, 0 ) );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
BOOST_CHECK_EQUAL( base_chain.PointCount(), 12 );
BOOST_CHECK_EQUAL( base_chain.ArcCount(), 2 ); // Should have two arcs after the split
base_chain.Replace( 5, 6, chain_insert );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
BOOST_CHECK_EQUAL( base_chain.PointCount(), 13 ); // Adding 3 points, removing 2
BOOST_CHECK_EQUAL( base_chain.ArcCount(), 3 ); // Should have three arcs after the split
base_chain.Replace( 4, 6, VECTOR2I( 550000, 0 ) );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
BOOST_CHECK_EQUAL( base_chain.PointCount(), 11 ); // Adding 1 point, removing 3
BOOST_CHECK_EQUAL( base_chain.ArcCount(), 3 ); // Should still have three arcs
// Test ClearArcs
base_chain.SetClosed( true );
double areaPriorToArcRemoval = base_chain.Area();
base_chain.ClearArcs();
BOOST_CHECK( GEOM_TEST::IsOutlineValid( base_chain ) );
BOOST_CHECK_EQUAL( base_chain.CPoints().size(), base_chain.CShapes().size() );
BOOST_CHECK_EQUAL( base_chain.PointCount(), 11 ); // We should have the same number of points
BOOST_CHECK_EQUAL( base_chain.ArcCount(), 0 ); // All arcs should have been removed
BOOST_CHECK_EQUAL( base_chain.Area(), areaPriorToArcRemoval ); // Area should not have changed
}
// Test that duplicate point gets removed when line is set to be closed and added where required
BOOST_AUTO_TEST_CASE( SetClosedDuplicatePoint )
{
// Test from issue #9843
SHAPE_LINE_CHAIN chain;
chain.Append(
SHAPE_ARC( { -859598, 2559876 }, { -1632771, 1022403 }, { -3170244, 249230 }, 0 ) );
chain.Append(
SHAPE_ARC( { -3170244, -1657832 }, { -292804, -317564 }, { 1047464, 2559876 }, 0 ) );
chain.Append( VECTOR2I( -859598, 2559876 ) ); // add point that is equal to first arc start
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
BOOST_CHECK_EQUAL( chain.PointCount(), 31 );
// CLOSED CHAIN
chain.SetClosed( true );
BOOST_CHECK_EQUAL( chain.CPoints().size(), chain.CShapes().size() );
BOOST_CHECK_EQUAL( chain.PointCount(), 30 ); // (-1) should have removed coincident points
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
// Special case: arc wrapping around to start (e.g. circle)
BOOST_CHECK( GEOM_TEST::IsOutlineValid( Circle2Arcs ) );
BOOST_CHECK_EQUAL( Circle2Arcs.IsClosed(), true );
BOOST_CHECK_EQUAL( Circle2Arcs.PointCount(), 16 );
BOOST_CHECK_EQUAL( Circle2Arcs.IsArcSegment( 15 ), true );
BOOST_CHECK_EQUAL( Circle2Arcs.ShapeCount(), 2 );
Circle2Arcs.SetClosed( false );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( Circle2Arcs ) );
BOOST_CHECK_EQUAL( Circle2Arcs.IsClosed(), false );
BOOST_CHECK_EQUAL( Circle2Arcs.PointCount(), 17 );
BOOST_CHECK_EQUAL( Circle2Arcs.IsArcSegment( 15 ), true );
BOOST_CHECK_EQUAL( Circle2Arcs.IsArcSegment( 16 ), false ); // last point doesn't join up
}
struct CLOSE_TOGGLE_SHAPE_CASE
{
std::string m_ctx_name;
SHAPE_LINE_CHAIN m_chain;
bool m_closed;
int m_shape_count;
int m_point_count;
int m_expected_shape_count;
int m_expected_point_count;
};
static const std::vector<CLOSE_TOGGLE_SHAPE_CASE> close_toggle_shape_cases =
{
{ "Circle1Arc", SLC_CASES().Circle1Arc, true, 1, 15, 1, 16 },
{ "Circle2Arcs", SLC_CASES().Circle2Arcs, true, 2, 16, 2, 17 },
{ "ArcsCoincident", SLC_CASES().ArcsCoincident, false, 2, 14, 3, 14 },
{ "ArcsCoincidentClosed", SLC_CASES().ArcsCoincidentClosed, true, 3, 14, 2, 14 },
{ "ArcsIndependent", SLC_CASES().ArcsIndependent, false, 3, 18, 4, 18 },
// SegAndArcCoincident will remove the segment after SetClosed(true) and SetClosed(false)
// disable test for now
//{ "SegAndArcCoincident", SLC_CASES().SegAndArcCoincident, false, 2, 92, 2, 91 },
{ "DuplicateArcs", SLC_CASES().DuplicateArcs, false, 4, 20, 5, 20 },
{ "ArcAndPoint", SLC_CASES().ArcAndPoint, false, 2, 10, 3, 10 },
{ "ArcsAndSegMixed", SLC_CASES().ArcsAndSegMixed, false, 4, 19, 5, 19 },
{ "OnePoint", SLC_CASES().OnePoint, false, 0, 1, 0, 1 }, // no shapes
{ "TwoPoints", SLC_CASES().TwoPoints, false, 1, 2, 2, 2 }, // there and back
{ "ThreePoints", SLC_CASES().ThreePoints, false, 2, 3, 3, 3 },
};
BOOST_AUTO_TEST_CASE( ToggleClosed )
{
for( const CLOSE_TOGGLE_SHAPE_CASE& c : close_toggle_shape_cases )
{
BOOST_TEST_CONTEXT( c.m_ctx_name )
{
SHAPE_LINE_CHAIN slc_case = c.m_chain; // make a copy to edit
BOOST_CHECK( GEOM_TEST::IsOutlineValid( slc_case ) );
BOOST_CHECK_EQUAL( slc_case.IsClosed(), c.m_closed );
BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_shape_count );
BOOST_CHECK_EQUAL( slc_case.PointCount(), c.m_point_count );
slc_case.SetClosed( !c.m_closed );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( slc_case ) );
BOOST_CHECK_EQUAL( slc_case.IsClosed(), !c.m_closed );
BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_expected_shape_count );
BOOST_CHECK_EQUAL( slc_case.PointCount(), c.m_expected_point_count );
slc_case.SetClosed( c.m_closed ); // toggle back to normal
BOOST_CHECK( GEOM_TEST::IsOutlineValid( slc_case ) );
BOOST_CHECK_EQUAL( slc_case.IsClosed(), c.m_closed );
BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_shape_count );
BOOST_CHECK_EQUAL( slc_case.PointCount(), c.m_point_count );
}
}
}
// Test that duplicate point gets removed when we call simplify
BOOST_AUTO_TEST_CASE( SimplifyDuplicatePoint )
{
SHAPE_LINE_CHAIN chain;
chain.Append( { 100, 100 } );
chain.Append( { 100, 100 }, true ); //duplicate point to simplify
chain.Append( { 200, 100 } );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
BOOST_CHECK_EQUAL( chain.PointCount(), 3 );
chain.Simplify();
BOOST_CHECK_EQUAL( chain.CPoints().size(), chain.CShapes().size() );
BOOST_CHECK_EQUAL( chain.PointCount(), 2 ); // (-1) should have removed coincident points
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
}
// Test that duplicate point gets removed when we call simplify
BOOST_AUTO_TEST_CASE( SimplifyKeepEndPoint )
{
SHAPE_LINE_CHAIN chain;
chain.Append( { 114772424, 90949410 } );
chain.Append( { 114767360, 90947240 } );
chain.Append( { 114772429, 90947228 } );
chain.SetClosed( true );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
BOOST_CHECK_EQUAL( chain.PointCount(), 3 );
chain.Simplify();
BOOST_CHECK_EQUAL( chain.CPoints().size(), chain.CShapes().size() );
BOOST_CHECK_EQUAL( chain.PointCount(), 3 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
}
struct REMOVE_SHAPE_CASE
{
std::string m_ctx_name;
SHAPE_LINE_CHAIN m_chain;
int m_shape_count;
int m_arc_count;
int m_remove_index;
int m_expected_shape_count;
int m_expected_arc_count;
};
static const std::vector<REMOVE_SHAPE_CASE> remove_shape_cases =
{
{ "Circle1Arc - 1st arc - index on start", SLC_CASES().Circle1Arc, 1, 1, 0, 0, 0 },
{ "Circle1Arc - 1st arc - index on mid", SLC_CASES().Circle1Arc, 1, 1, 8, 0, 0 },
{ "Circle1Arc - 1st arc - index on end", SLC_CASES().Circle1Arc, 1, 1, 14, 0, 0 },
{ "Circle1Arc - 1st arc - index on -1", SLC_CASES().Circle1Arc, 1, 1, -1, 0, 0 },
{ "Circle1Arc - invalid index", SLC_CASES().Circle1Arc, 1, 1, 15, 1, 1 },
{ "Circle2Arcs - 1st arc - index on start", SLC_CASES().Circle2Arcs, 2, 2, 0, 2, 1 },
{ "Circle2Arcs - 1st arc - index on mid", SLC_CASES().Circle2Arcs, 2, 2, 3, 2, 1 },
{ "Circle2Arcs - 1st arc - index on end", SLC_CASES().Circle2Arcs, 2, 2, 7, 2, 1 },
{ "Circle2Arcs - 2nd arc - index on start", SLC_CASES().Circle2Arcs, 2, 2, 8, 2, 1 },
{ "Circle2Arcs - 2nd arc - index on mid", SLC_CASES().Circle2Arcs, 2, 2, 11, 2, 1 },
{ "Circle2Arcs - 2nd arc - index on end", SLC_CASES().Circle2Arcs, 2, 2, 15, 2, 1 },
{ "Circle2Arcs - 2nd arc - index on -1", SLC_CASES().Circle2Arcs, 2, 2, -1, 2, 1 },
{ "Circle2Arcs - invalid index", SLC_CASES().Circle2Arcs, 2, 2, 16, 2, 2 },
{ "ArcsCoinc. - 1st arc - idx on start", SLC_CASES().ArcsCoincident, 2, 2, 0, 1, 1 },
{ "ArcsCoinc. - 1st arc - idx on mid", SLC_CASES().ArcsCoincident, 2, 2, 3, 1, 1 },
{ "ArcsCoinc. - 1st arc - idx on end", SLC_CASES().ArcsCoincident, 2, 2, 7, 1, 1 },
{ "ArcsCoinc. - 2nd arc - idx on start", SLC_CASES().ArcsCoincident, 2, 2, 8, 1, 1 },
{ "ArcsCoinc. - 2nd arc - idx on mid", SLC_CASES().ArcsCoincident, 2, 2, 10, 1, 1 },
{ "ArcsCoinc. - 2nd arc - idx on end", SLC_CASES().ArcsCoincident, 2, 2, 13, 1, 1 },
{ "ArcsCoinc. - 2nd arc - idx on -1", SLC_CASES().ArcsCoincident, 2, 2, -1, 1, 1 },
{ "ArcsCoinc. - invalid idx", SLC_CASES().ArcsCoincident, 2, 2, 14, 2, 2 },
{ "ArcsCoinc. - 1st arc - idx on start", SLC_CASES().ArcsCoincident, 2, 2, 0, 1, 1 },
{ "A.Co.Closed - 1st arc - idx on start", SLC_CASES().ArcsCoincidentClosed, 3, 2, 1, 2, 1 },
{ "A.Co.Closed - 1st arc - idx on mid", SLC_CASES().ArcsCoincidentClosed, 3, 2, 3, 2, 1 },
{ "A.Co.Closed - 1st arc - idx on end", SLC_CASES().ArcsCoincidentClosed, 3, 2, 7, 2, 1 },
{ "A.Co.Closed - 2nd arc - idx on start", SLC_CASES().ArcsCoincidentClosed, 3, 2, 8, 2, 1 },
{ "A.Co.Closed - 2nd arc - idx on mid", SLC_CASES().ArcsCoincidentClosed, 3, 2, 10, 2, 1 },
{ "A.Co.Closed - 2nd arc - idx on end", SLC_CASES().ArcsCoincidentClosed, 3, 2, 13, 2, 1 },
{ "A.Co.Closed - 2nd arc - idx on -1", SLC_CASES().ArcsCoincidentClosed, 3, 2, -1, 2, 1 },
{ "A.Co.Closed - invalid idx", SLC_CASES().ArcsCoincidentClosed, 3, 2, 14, 3, 2 },
{ "ArcsIndep. - 1st arc - idx on start", SLC_CASES().ArcsIndependent, 3, 2, 0, 1, 1 },
{ "ArcsIndep. - 1st arc - idx on mid", SLC_CASES().ArcsIndependent, 3, 2, 3, 1, 1 },
{ "ArcsIndep. - 1st arc - idx on end", SLC_CASES().ArcsIndependent, 3, 2, 8, 1, 1 },
{ "ArcsIndep. - 2nd arc - idx on start", SLC_CASES().ArcsIndependent, 3, 2, 9, 1, 1 },
{ "ArcsIndep. - 2nd arc - idx on mid", SLC_CASES().ArcsIndependent, 3, 2, 12, 1, 1 },
{ "ArcsIndep. - 2nd arc - idx on end", SLC_CASES().ArcsIndependent, 3, 2, 17, 1, 1 },
{ "ArcsIndep. - 2nd arc - idx on -1", SLC_CASES().ArcsIndependent, 3, 2, -1, 1, 1 },
{ "ArcsIndep. - invalid idx", SLC_CASES().ArcsIndependent, 3, 2, 18, 3, 2 },
{ "Dup.Arcs - 1st arc - idx on start", SLC_CASES().DuplicateArcs, 4, 3, 0, 3, 2 },
{ "Dup.Arcs - 1st arc - idx on mid", SLC_CASES().DuplicateArcs, 4, 3, 3, 3, 2 },
{ "Dup.Arcs - 1st arc - idx on end", SLC_CASES().DuplicateArcs, 4, 3, 7, 3, 2 },
{ "Dup.Arcs - 2nd arc - idx on start", SLC_CASES().DuplicateArcs, 4, 3, 8, 3, 2 },
{ "Dup.Arcs - 2nd arc - idx on mid", SLC_CASES().DuplicateArcs, 4, 3, 10, 3, 2 },
{ "Dup.Arcs - 2nd arc - idx on end", SLC_CASES().DuplicateArcs, 4, 3, 13, 3, 2 },
{ "Dup.Arcs - 3rd arc - idx on start", SLC_CASES().DuplicateArcs, 4, 3, 14, 2, 2 },
{ "Dup.Arcs - 3rd arc - idx on mid", SLC_CASES().DuplicateArcs, 4, 3, 17, 2, 2 },
{ "Dup.Arcs - 3rd arc - idx on end", SLC_CASES().DuplicateArcs, 4, 3, 19, 2, 2 },
{ "Dup.Arcs - 3rd arc - idx on -1", SLC_CASES().DuplicateArcs, 4, 3, -1, 2, 2 },
{ "Dup.Arcs - invalid idx", SLC_CASES().DuplicateArcs, 4, 3, 20, 4, 3 },
{ "Arcs Mixed - 1st arc - idx on start", SLC_CASES().ArcsAndSegMixed, 4, 2, 0, 2, 1 },
{ "Arcs Mixed - 1st arc - idx on mid", SLC_CASES().ArcsAndSegMixed, 4, 2, 3, 2, 1 },
{ "Arcs Mixed - 1st arc - idx on end", SLC_CASES().ArcsAndSegMixed, 4, 2, 8, 2, 1 },
{ "Arcs Mixed - Straight segment", SLC_CASES().ArcsAndSegMixed, 4, 2, 9, 3, 2 },
{ "Arcs Mixed - 2nd arc - idx on start", SLC_CASES().ArcsAndSegMixed, 4, 2, 10, 2, 1 },
{ "Arcs Mixed - 2nd arc - idx on mid", SLC_CASES().ArcsAndSegMixed, 4, 2, 14, 2, 1 },
{ "Arcs Mixed - 2nd arc - idx on end", SLC_CASES().ArcsAndSegMixed, 4, 2, 18, 2, 1 },
{ "Arcs Mixed - 2nd arc - idx on -1", SLC_CASES().ArcsAndSegMixed, 4, 2, -1, 2, 1 },
{ "Arcs Mixed - invalid idx", SLC_CASES().ArcsAndSegMixed, 4, 2, 19, 4, 2 }
};
BOOST_AUTO_TEST_CASE( RemoveShape )
{
for( const REMOVE_SHAPE_CASE& c : remove_shape_cases )
{
BOOST_TEST_CONTEXT( c.m_ctx_name )
{
SHAPE_LINE_CHAIN slc_case = c.m_chain; // make a copy to edit
BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_shape_count );
BOOST_CHECK_EQUAL( slc_case.ArcCount(), c.m_arc_count );
BOOST_CHECK_EQUAL( GEOM_TEST::IsOutlineValid( slc_case ), true );
slc_case.RemoveShape( c.m_remove_index );
BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_expected_shape_count );
BOOST_CHECK_EQUAL( slc_case.ArcCount(), c.m_expected_arc_count );
BOOST_CHECK_EQUAL( GEOM_TEST::IsOutlineValid( slc_case ), true );
}
}
}
BOOST_AUTO_TEST_CASE( RemoveShapeAfterSimplify )
{
for( const REMOVE_SHAPE_CASE& c : remove_shape_cases )
{
BOOST_TEST_CONTEXT( c.m_ctx_name )
{
SHAPE_LINE_CHAIN slc_case = c.m_chain; // make a copy to edit
BOOST_CHECK_EQUAL( GEOM_TEST::IsOutlineValid( slc_case ), true );
BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_shape_count );
BOOST_CHECK_EQUAL( slc_case.ArcCount(), c.m_arc_count );
slc_case.Simplify();
BOOST_CHECK_EQUAL( GEOM_TEST::IsOutlineValid( slc_case ), true );
BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_shape_count );
BOOST_CHECK_EQUAL( slc_case.ArcCount(), c.m_arc_count );
slc_case.RemoveShape( c.m_remove_index );
BOOST_CHECK_EQUAL( GEOM_TEST::IsOutlineValid( slc_case ), true );
BOOST_CHECK_EQUAL( slc_case.ShapeCount(), c.m_expected_shape_count );
BOOST_CHECK_EQUAL( slc_case.ArcCount(), c.m_expected_arc_count );
}
}
}
BOOST_AUTO_TEST_CASE( ShapeCount )
{
BOOST_CHECK_EQUAL( Circle1Arc.ShapeCount(), 1 );
BOOST_CHECK_EQUAL( Circle2Arcs.ShapeCount(), 2 );
BOOST_CHECK_EQUAL( ArcsCoincident.ShapeCount(), 2 );
BOOST_CHECK_EQUAL( ArcsCoincidentClosed.ShapeCount(), 3 );
BOOST_CHECK_EQUAL( DuplicateArcs.ShapeCount(), 4 );
BOOST_CHECK_EQUAL( ArcAndPoint.ShapeCount(), 2 );
BOOST_CHECK_EQUAL( ArcsAndSegMixed.ShapeCount(), 4 );
BOOST_CHECK_EQUAL( SegAndArcCoincident.ShapeCount(), 2 );
BOOST_CHECK_EQUAL( EmptyChain.ShapeCount(), 0 );
BOOST_CHECK_EQUAL( OnePoint.ShapeCount(), 0 );
BOOST_CHECK_EQUAL( TwoPoints.ShapeCount(), 1 );
BOOST_CHECK_EQUAL( ThreePoints.ShapeCount(), 2 );
}
BOOST_AUTO_TEST_CASE( NextShape )
{
BOOST_CHECK_EQUAL( Circle1Arc.NextShape( 0 ), -1 ); //only one arc
BOOST_CHECK_EQUAL( Circle2Arcs.NextShape( 0 ), 8 ); // next shape "Arc0b"
BOOST_CHECK_EQUAL( Circle2Arcs.NextShape( 8 ), -1 ); //no more shapes (last point joins with first, part of arc)
BOOST_CHECK_EQUAL( ArcsCoincident.NextShape( 0 ), 8 ); // next shape "Arc1"
BOOST_CHECK_EQUAL( ArcsCoincident.NextShape( 8 ), -1 ); //no more shapes
BOOST_CHECK_EQUAL( ArcsCoincidentClosed.NextShape( 0 ), 8 ); // next shape "Arc1"
BOOST_CHECK_EQUAL( ArcsCoincidentClosed.NextShape( 8 ), 13 ); //next shape is hidden segment joining last/first
BOOST_CHECK_EQUAL( ArcsCoincidentClosed.NextShape( 13 ), -1 ); //no more shapes
BOOST_CHECK_EQUAL( ArcsIndependent.NextShape( 0 ), 8 ); // next shape straight seg
BOOST_CHECK_EQUAL( ArcsIndependent.NextShape( 8 ), 9 ); //next shape second arc
BOOST_CHECK_EQUAL( ArcsIndependent.NextShape( 9 ), -1 ); //no more shapes
BOOST_CHECK_EQUAL( DuplicateArcs.NextShape( 0 ), 8 ); // next shape "Arc1"
BOOST_CHECK_EQUAL( DuplicateArcs.NextShape( 8 ), 13 ); // next shape hidden segment joining the 2 duplicate arcs
BOOST_CHECK_EQUAL( DuplicateArcs.NextShape( 13 ), 14 ); // next shape "Arc1" (duplicate)
BOOST_CHECK_EQUAL( DuplicateArcs.NextShape( 14 ), -1 ); //no more shapes
BOOST_CHECK_EQUAL( ArcAndPoint.NextShape( 0 ), 8 ); // next shape straight segment (end of arc->point)
BOOST_CHECK_EQUAL( ArcAndPoint.NextShape( 8 ), -1 ); //no more shapes
BOOST_CHECK_EQUAL( ArcsAndSegMixed.NextShape( 0 ), 8 ); // next shape straight segment (end of arc->point)
BOOST_CHECK_EQUAL( ArcsAndSegMixed.NextShape( 8 ), 9 ); // next shape straight segment (point->begining of arc)
BOOST_CHECK_EQUAL( ArcsAndSegMixed.NextShape( 9 ), 10 ); //next shape second arc
BOOST_CHECK_EQUAL( ArcsAndSegMixed.NextShape( 10 ), -1 ); //no more shapes
BOOST_CHECK_EQUAL( ArcsAndSegMixed.NextShape( 20 ), -1 ); //invalid indices should still work
BOOST_CHECK_EQUAL( ArcsAndSegMixed.NextShape( -50 ), -1 ); //invalid indices should still work
BOOST_CHECK_EQUAL( SegAndArcCoincident.NextShape( 0 ), 1 ); // next shape Arc3
BOOST_CHECK_EQUAL( SegAndArcCoincident.NextShape( 1 ), -1 ); //no more shapes
BOOST_CHECK_EQUAL( EmptyChain.NextShape( 0 ), -1 );
BOOST_CHECK_EQUAL( EmptyChain.NextShape( 1 ), -1 ); //invalid indices should still work
BOOST_CHECK_EQUAL( EmptyChain.NextShape( 2 ), -1 ); //invalid indices should still work
BOOST_CHECK_EQUAL( EmptyChain.NextShape( -2 ), -1 ); //invalid indices should still work
BOOST_CHECK_EQUAL( OnePoint.NextShape( 0 ), -1 );
BOOST_CHECK_EQUAL( OnePoint.NextShape( -1 ), -1 );
BOOST_CHECK_EQUAL( OnePoint.NextShape( 1 ), -1 ); //invalid indices should still work
BOOST_CHECK_EQUAL( OnePoint.NextShape( 2 ), -1 ); //invalid indices should still work
BOOST_CHECK_EQUAL( OnePoint.NextShape( -2 ), -1 ); //invalid indices should still work
BOOST_CHECK_EQUAL( TwoPoints.NextShape( 0 ), -1 );
BOOST_CHECK_EQUAL( TwoPoints.NextShape( 1 ), -1 );
BOOST_CHECK_EQUAL( TwoPoints.NextShape( -1 ), -1 );
BOOST_CHECK_EQUAL( ThreePoints.NextShape( 0 ), 1 );
BOOST_CHECK_EQUAL( ThreePoints.NextShape( 1 ), -1 );
BOOST_CHECK_EQUAL( ThreePoints.NextShape( 2 ), -1 );
BOOST_CHECK_EQUAL( ThreePoints.NextShape( -1 ), -1 );
}
BOOST_AUTO_TEST_CASE( AppendArc )
{
BOOST_TEST_CONTEXT( "Case 1: Arc mid point nearly collinear" )
{
SHAPE_ARC arc( VECTOR2I( 100000, 0 ), VECTOR2I( 0, 2499 ), VECTOR2I( -100000, 0 ), 0 );
SHAPE_LINE_CHAIN chain;
chain.Append( arc, 5000 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
BOOST_CHECK_EQUAL( chain.ArcCount(), 0 );
BOOST_CHECK_EQUAL( chain.PointCount(), 2 );
BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( 100000, 0 ) ); //arc start
BOOST_CHECK_EQUAL( chain.GetPoint( 1 ), VECTOR2I( -100000, 0 ) ); //arc end
BOOST_CHECK_EQUAL( chain.GetPoint( -1 ), VECTOR2I( -100000, 0 ) ); //arc end
}
BOOST_TEST_CONTEXT( "Case 2: Arc = Large Circle" )
{
SHAPE_ARC arc( VECTOR2I( 100000, 0 ), VECTOR2I( 0, 0 ), VECTOR2I( 100000, 0 ), 0 );
SHAPE_LINE_CHAIN chain;
chain.Append( arc, 5000 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
BOOST_CHECK_EQUAL( chain.ArcCount(), 1 );
BOOST_CHECK_EQUAL( chain.PointCount(), 10 );
BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( 100000, 0 ) ); //arc start
BOOST_CHECK_EQUAL( chain.GetPoint( 9 ), VECTOR2I( 100000, 0 ) ); //arc end
BOOST_CHECK_EQUAL( chain.GetPoint( -1 ), VECTOR2I( 100000, 0 ) ); //arc end
}
BOOST_TEST_CONTEXT( "Case 3: Arc = Small Circle (approximate to point)" )
{
SHAPE_ARC arc( VECTOR2I( 2499, 0 ), VECTOR2I( 0, 0 ), VECTOR2I( 2499, 0 ), 0 );
SHAPE_LINE_CHAIN chain;
chain.Append( arc, 5000 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
BOOST_CHECK_EQUAL( chain.ArcCount(), 0 );
BOOST_CHECK_EQUAL( chain.PointCount(), 1 );
BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( 2499, 0 ) ); //arc start
}
BOOST_TEST_CONTEXT( "Case 3: Small Arc (approximate to segment)" )
{
SHAPE_ARC arc( VECTOR2I( 1767, 0 ), VECTOR2I( 2499, 2499 ), VECTOR2I( 0, 1767 ), 0 );
SHAPE_LINE_CHAIN chain;
chain.Append( arc, 5000 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
BOOST_CHECK_EQUAL( chain.ArcCount(), 0 );
BOOST_CHECK_EQUAL( chain.PointCount(), 2 );
BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( 1767, 0 ) ); //arc start
BOOST_CHECK_EQUAL( chain.GetPoint( 1 ), VECTOR2I( 0, 1767 ) ); //arc end
}
BOOST_TEST_CONTEXT( "Case 4: Arc = null arc (all points coincident)" )
{
SHAPE_ARC arc( VECTOR2I( 2499, 0 ), VECTOR2I( 2499, 0 ), VECTOR2I( 2499, 0 ), 0 );
SHAPE_LINE_CHAIN chain;
chain.Append( arc, 5000 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
BOOST_CHECK_EQUAL( chain.ArcCount(), 0 );
BOOST_CHECK_EQUAL( chain.PointCount(), 1 );
BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( 2499, 0 ) ); //arc start
}
BOOST_TEST_CONTEXT( "Case 5: Arc = infinite radius (all points very close)" )
{
SHAPE_ARC arc( VECTOR2I( 2499, 0 ), VECTOR2I( 2500, 0 ), VECTOR2I( 2501, 0 ), 0 );
SHAPE_LINE_CHAIN chain;
chain.Append( arc, 5000 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
BOOST_CHECK_EQUAL( chain.ArcCount(), 0 );
BOOST_CHECK_EQUAL( chain.PointCount(), 2 );
BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( 2499, 0 ) ); //arc start
BOOST_CHECK_EQUAL( chain.GetPoint( 1 ), VECTOR2I( 2501, 0 ) ); //arc end
}
BOOST_TEST_CONTEXT( "Case 6: Arc = large radius (all points very close)" )
{
SHAPE_ARC arc( VECTOR2I( -100000, 0 ), VECTOR2I( 0, 1 ), VECTOR2I( 100000, 0 ), 0 );
SHAPE_LINE_CHAIN chain;
chain.Append( arc, 5000 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
BOOST_CHECK_EQUAL( chain.ArcCount(), 0 );
BOOST_CHECK_EQUAL( chain.PointCount(), 2 );
BOOST_CHECK_EQUAL( chain.GetPoint( 0 ), VECTOR2I( -100000, 0 ) ); //arc start
BOOST_CHECK_EQUAL( chain.GetPoint( 1 ), VECTOR2I( 100000, 0 ) ); //arc end
}
}
// Test special case where the last arc in the chain has a shared point with the first arc
BOOST_AUTO_TEST_CASE( ArcWrappingToStartSharedPoints )
{
// represent a circle with two semicircular arcs
SHAPE_ARC arc1( VECTOR2I( 100000, 0 ), VECTOR2I( 0, 100000 ), VECTOR2I( -100000, 0 ), 0 );
SHAPE_ARC arc2( VECTOR2I( -100000, 0 ), VECTOR2I( 0, -100000 ), VECTOR2I( 100000, 0 ), 0 );
// Start a chain with the two arcs
SHAPE_LINE_CHAIN chain;
chain.Append( arc1 );
chain.Append( arc2 );
BOOST_CHECK_EQUAL( chain.PointCount(), 13 );
//BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
// OPEN CHAIN
// Start of the chain is not yet a shared point, so can't be an arc end either
BOOST_CHECK_EQUAL( chain.IsSharedPt( 0 ), false );
BOOST_CHECK_EQUAL( chain.IsArcEnd( 0 ), false );
BOOST_CHECK_EQUAL( chain.IsArcStart( 0 ), true );
// Index 6 is the shared point between the two arcs in the middle of the chain
BOOST_CHECK_EQUAL( chain.IsSharedPt( 6 ), true );
BOOST_CHECK_EQUAL( chain.IsArcEnd( 6 ), true );
BOOST_CHECK_EQUAL( chain.IsArcStart( 6 ), true );
// End index is not yet a shared point
int endIndex = chain.PointCount() - 1;
BOOST_CHECK_EQUAL( chain.IsSharedPt( endIndex ), false );
BOOST_CHECK_EQUAL( chain.IsArcEnd( endIndex ), true );
BOOST_CHECK_EQUAL( chain.IsArcStart( endIndex ), false );
for( int i = 0; i < chain.PointCount(); i++ )
{
BOOST_CHECK_EQUAL( chain.IsPtOnArc( i ), true ); // all points in the chain are arcs
}
// CLOSED CHAIN
chain.SetClosed( true );
BOOST_CHECK_EQUAL( chain.PointCount(), 12 ); // (-1) should have removed coincident points
//BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
// Start of the chain should be a shared point now, so can't be an arc end either
BOOST_CHECK_EQUAL( chain.IsSharedPt( 0 ), true );
BOOST_CHECK_EQUAL( chain.IsArcEnd( 0 ), true );
BOOST_CHECK_EQUAL( chain.IsArcStart( 0 ), true );
// Index 6 is the shared point between the two arcs in the middle of the chain
BOOST_CHECK_EQUAL( chain.IsSharedPt( 6 ), true );
BOOST_CHECK_EQUAL( chain.IsArcEnd( 6 ), true );
BOOST_CHECK_EQUAL( chain.IsArcStart( 6 ), true );
// End index is in the middle of an arc, so not an end point or shared point
endIndex = chain.PointCount() - 1;
BOOST_CHECK_EQUAL( chain.IsSharedPt( endIndex ), false );
BOOST_CHECK_EQUAL( chain.IsArcEnd( endIndex ), false );
BOOST_CHECK_EQUAL( chain.IsArcStart( endIndex ), false );
}
// Test SHAPE_LINE_CHAIN::Split()
BOOST_AUTO_TEST_CASE( Split )
{
SEG seg1( VECTOR2I( 0, 100000 ), VECTOR2I( 50000, 0 ) );
SEG seg2( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ) );
SHAPE_ARC arc( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ), ANGLE_180 );
// Start a chain with 2 points (seg1)
SHAPE_LINE_CHAIN chain( { seg1.A, seg1.B } );
BOOST_CHECK_EQUAL( chain.PointCount(), 2 );
// Add first arc
chain.Append( arc );
BOOST_CHECK_EQUAL( chain.PointCount(), 9 );
// Add two points (seg2)
chain.Append( seg2.A );
chain.Append( seg2.B );
BOOST_CHECK_EQUAL( chain.PointCount(), 11 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
BOOST_TEST_CONTEXT( "Case 1: Point not in the chain" )
{
SHAPE_LINE_CHAIN chainCopy = chain;
BOOST_CHECK_EQUAL( chainCopy.Split( VECTOR2I( 400000, 0 ) ), -1 );
BOOST_CHECK_EQUAL( chainCopy.PointCount(), chain.PointCount() );
BOOST_CHECK_EQUAL( chainCopy.ArcCount(), chain.ArcCount() );
}
BOOST_TEST_CONTEXT( "Case 2: Point close to start of a segment" )
{
SHAPE_LINE_CHAIN chainCopy = chain;
VECTOR2I splitPoint = seg1.A + VECTOR2I( 5, -10 );
BOOST_CHECK_EQUAL( chainCopy.Split( splitPoint ), 1 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chainCopy ) );
BOOST_CHECK_EQUAL( chainCopy.GetPoint( 1 ), splitPoint );
BOOST_CHECK_EQUAL( chainCopy.PointCount(), chain.PointCount() + 1 ); // new point added
BOOST_CHECK_EQUAL( chainCopy.ArcCount(), chain.ArcCount() );
}
BOOST_TEST_CONTEXT( "Case 3: Point exactly on the segment" )
{
SHAPE_LINE_CHAIN chainCopy = chain;
VECTOR2I splitPoint = seg1.B;
BOOST_CHECK_EQUAL( chainCopy.Split( splitPoint ), 1 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chainCopy ) );
BOOST_CHECK_EQUAL( chainCopy.GetPoint( 1 ), splitPoint );
BOOST_CHECK_EQUAL( chainCopy.PointCount(), chain.PointCount() );
BOOST_CHECK_EQUAL( chainCopy.ArcCount(), chain.ArcCount() );
}
BOOST_TEST_CONTEXT( "Case 4: Point at start of arc" )
{
SHAPE_LINE_CHAIN chainCopy = chain;
VECTOR2I splitPoint = arc.GetP0();
BOOST_CHECK_EQUAL( chainCopy.Split( splitPoint ), 2 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chainCopy ) );
BOOST_CHECK_EQUAL( chainCopy.GetPoint( 2 ), splitPoint );
BOOST_CHECK_EQUAL( chainCopy.PointCount(), chain.PointCount() );
BOOST_CHECK_EQUAL( chainCopy.ArcCount(), chain.ArcCount() );
}
BOOST_TEST_CONTEXT( "Case 5: Point close to start of arc" )
{
SHAPE_LINE_CHAIN chainCopy = chain;
VECTOR2I splitPoint = arc.GetP0() + VECTOR2I( -10, 130 );
BOOST_CHECK_EQUAL( chainCopy.Split( splitPoint ), 3 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chainCopy ) );
BOOST_CHECK_EQUAL( chainCopy.GetPoint( 3 ), splitPoint );
BOOST_CHECK_EQUAL( chainCopy.IsSharedPt( 3 ), true ); // must be a shared point
BOOST_CHECK_EQUAL( chainCopy.PointCount(), chain.PointCount() + 1 ); // new point added
BOOST_CHECK_EQUAL( chainCopy.ArcCount(), chain.ArcCount() + 1 ); // new arc should have been created
}
}
// Test SHAPE_LINE_CHAIN::Slice()
BOOST_AUTO_TEST_CASE( Slice )
{
SEG targetSegment( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ) );
SHAPE_ARC firstArc( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ), ANGLE_180 );
SHAPE_ARC secondArc( VECTOR2I( -200000, -200000 ), VECTOR2I( -300000, -100000 ), -ANGLE_180 );
int tol = SHAPE_ARC::DefaultAccuracyForPCB(); // Tolerance for arc collisions
// Start a chain with 3 points
SHAPE_LINE_CHAIN chain( { VECTOR2I( 0, 0 ), VECTOR2I( 0, 100000 ), VECTOR2I( 100000, 0 ) } );
BOOST_CHECK_EQUAL( chain.PointCount(), 3 );
// Add first arc
chain.Append( firstArc );
BOOST_CHECK_EQUAL( chain.PointCount(), 10 );
// Add two points (target segment)
chain.Append( targetSegment.A );
chain.Append( targetSegment.B );
BOOST_CHECK_EQUAL( chain.PointCount(), 12 );
// Add a second arc
chain.Append( secondArc );
BOOST_CHECK_EQUAL( chain.PointCount(), 20 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
//////////////////////////////////////////////////////////
/// CASE 1: Start at arc endpoint, finish middle of arc /
//////////////////////////////////////////////////////////
BOOST_TEST_CONTEXT( "Case 1: Start at arc endpoint, finish middle of arc" )
{
SHAPE_LINE_CHAIN sliceResult = chain.Slice( 9, 18 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );
BOOST_CHECK_EQUAL( sliceResult.ArcCount(), 1 );
SHAPE_ARC expectedSliceArc0;
expectedSliceArc0.ConstructFromStartEndCenter( secondArc.GetP0(), chain.GetPoint( 18 ),
secondArc.GetCenter(),
secondArc.IsClockwise() );
BOOST_CHECK_EQUAL( sliceResult.Arc( 0 ).GetP0(), expectedSliceArc0.GetP0() ); // equal arc start points
BOOST_CHECK( sliceResult.Arc( 0 ).Collide( expectedSliceArc0.GetArcMid(), tol ) );
BOOST_CHECK( sliceResult.Arc( 0 ).Collide( expectedSliceArc0.GetP1(), tol ) );
BOOST_CHECK_EQUAL( sliceResult.PointCount(), 10 );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ), firstArc.GetP1() ); // equal to arc end
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 1 ), targetSegment.A );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 2 ), targetSegment.B );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 3 ), expectedSliceArc0.GetP0() ); // equal to arc start
BOOST_CHECK_EQUAL( sliceResult.IsArcStart( 3 ), true );
for( int i = 4; i <= 8; i++ )
BOOST_CHECK_EQUAL( sliceResult.IsArcStart( i ), false );
for( int i = 3; i <= 7; i++ )
BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( i ), false );
BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( 9 ), true );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 9 ), expectedSliceArc0.GetP1() ); // equal to arc end
}
//////////////////////////////////////////////////////////////////
/// CASE 2: Start at middle of an arc, finish at arc startpoint /
//////////////////////////////////////////////////////////////////
BOOST_TEST_CONTEXT( "Case 2: Start at middle of an arc, finish at arc startpoint" )
{
SHAPE_LINE_CHAIN sliceResult = chain.Slice( 5, 12 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );
BOOST_CHECK_EQUAL( sliceResult.ArcCount(), 1 );
SHAPE_ARC expectedSliceArc0;
expectedSliceArc0.ConstructFromStartEndCenter( chain.GetPoint( 5 ), firstArc.GetP1(),
firstArc.GetCenter(),
firstArc.IsClockwise() );
BOOST_CHECK_EQUAL( sliceResult.Arc( 0 ).GetP1(),
expectedSliceArc0.GetP1() ); // equal arc end points
BOOST_CHECK( sliceResult.Arc( 0 ).Collide( expectedSliceArc0.GetArcMid(), tol ) );
BOOST_CHECK( sliceResult.Arc( 0 ).Collide( expectedSliceArc0.GetP0(), tol ) );
BOOST_CHECK_EQUAL( sliceResult.PointCount(), 8 );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ),
expectedSliceArc0.GetP0() ); // equal to arc start
BOOST_CHECK_EQUAL( sliceResult.IsArcStart( 0 ), true );
for( int i = 1; i <= 4; i++ )
BOOST_CHECK_EQUAL( sliceResult.IsArcStart( i ), false );
for( int i = 0; i <= 3; i++ )
BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( i ), false );
BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( 4 ), true );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 4 ),
expectedSliceArc0.GetP1() ); // equal to arc end
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 5 ), targetSegment.A );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 6 ), targetSegment.B );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 7 ), secondArc.GetP0() );
}
//////////////////////////////////////////////////////////////////
/// CASE 3: Full arc, nothing else /
//////////////////////////////////////////////////////////////////
BOOST_TEST_CONTEXT( "Case 3: Full arc, nothing else" )
{
SHAPE_LINE_CHAIN sliceResult = chain.Slice( 3, 9 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );
BOOST_CHECK_EQUAL( sliceResult.ArcCount(), 1 );
SHAPE_ARC sliceArc0 = sliceResult.Arc( 0 );
// Equal arc to original inserted arc
BOOST_CHECK_EQUAL( firstArc.GetP1(), sliceArc0.GetP1() );
BOOST_CHECK_EQUAL( firstArc.GetArcMid(), sliceArc0.GetArcMid() );
BOOST_CHECK_EQUAL( firstArc.GetP1(), sliceArc0.GetP1() );
BOOST_CHECK_EQUAL( sliceResult.PointCount(), 7 );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ), sliceArc0.GetP0() ); // equal to arc start
BOOST_CHECK_EQUAL( sliceResult.IsArcStart( 0 ), true );
for( int i = 1; i <= 6; i++ )
BOOST_CHECK_EQUAL( sliceResult.IsArcStart( i ), false );
for( int i = 0; i <= 5; i++ )
BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( i ), false );
BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( 6 ), true );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 6 ), sliceArc0.GetP1() ); // equal to arc end
}
//////////////////////////////////////////////////////////////////
/// CASE 4: Full arc, and straight segments to next arc start /
//////////////////////////////////////////////////////////////////
BOOST_TEST_CONTEXT( "Case 4: Full arc, and straight segments to next arc start" )
{
SHAPE_LINE_CHAIN sliceResult = chain.Slice( 3, 12 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );
BOOST_CHECK_EQUAL( sliceResult.ArcCount(), 1 );
SHAPE_ARC sliceArc0 = sliceResult.Arc( 0 );
// Equal arc to original inserted arc
BOOST_CHECK_EQUAL( firstArc.GetP1(), sliceArc0.GetP1() );
BOOST_CHECK_EQUAL( firstArc.GetArcMid(), sliceArc0.GetArcMid() );
BOOST_CHECK_EQUAL( firstArc.GetP1(), sliceArc0.GetP1() );
BOOST_CHECK_EQUAL( sliceResult.PointCount(), 10 );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ), sliceArc0.GetP0() ); // equal to arc start
BOOST_CHECK_EQUAL( sliceResult.IsArcStart( 0 ), true );
for( int i = 1; i <= 6; i++ )
BOOST_CHECK_EQUAL( sliceResult.IsArcStart( i ), false );
for( int i = 0; i <= 5; i++ )
BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( i ), false );
BOOST_CHECK_EQUAL( sliceResult.IsArcEnd( 6 ), true );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 6 ), sliceArc0.GetP1() ); // equal to arc end
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 7 ), targetSegment.A );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 8 ), targetSegment.B );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 9 ), secondArc.GetP0() );
}
BOOST_TEST_CONTEXT( "Case 5: Chain ends in arc and point" )
{
SHAPE_LINE_CHAIN chainCopy = chain;
chainCopy.Append( VECTOR2I( 400000, 400000 ) );
SHAPE_LINE_CHAIN sliceResult = chainCopy.Slice( 11, -1 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( sliceResult ) );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( -1 ), VECTOR2I( 400000, 400000 ) );
}
BOOST_TEST_CONTEXT( "Case 6: Start to end, chain with one point" )
{
SHAPE_LINE_CHAIN chainCopy = SLC_CASES().OnePoint;
SHAPE_LINE_CHAIN sliceResult = chainCopy.Slice( 0, -1 );
BOOST_CHECK_EQUAL( sliceResult.PointCount(), 1 );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ), VECTOR2I( 233450000, 228360000 ) );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( -1 ), VECTOR2I( 233450000, 228360000 ) ); // Same as index 0
}
BOOST_TEST_CONTEXT( "Case 7: Start to end, chain with two points" )
{
SHAPE_LINE_CHAIN chainCopy = SLC_CASES().TwoPoints;
SHAPE_LINE_CHAIN sliceResult = chainCopy.Slice( 0, -1 );
BOOST_CHECK_EQUAL( sliceResult.PointCount(), 2 );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 0 ), VECTOR2I( 233450000, 228360000 ) );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( 1 ), VECTOR2I( 263450000, 258360000 ) );
BOOST_CHECK_EQUAL( sliceResult.GetPoint( -1 ), VECTOR2I( 263450000, 258360000 ) ); // Same as index 1
}
}
// Test SHAPE_LINE_CHAIN::NearestPoint( VECTOR2I )
BOOST_AUTO_TEST_CASE( NearestPointPt )
{
SEG seg1( VECTOR2I( 0, 100000 ), VECTOR2I( 50000, 0 ) );
SEG seg2( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ) );
SHAPE_ARC arc( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ), ANGLE_180 );
// Start a chain with 2 points (seg1)
SHAPE_LINE_CHAIN chain( { seg1.A, seg1.B } );
BOOST_CHECK_EQUAL( chain.PointCount(), 2 );
// Add first arc
chain.Append( arc );
BOOST_CHECK_EQUAL( chain.PointCount(), 9 );
// Add two points (seg2)
chain.Append( seg2.A );
chain.Append( seg2.B );
BOOST_CHECK_EQUAL( chain.PointCount(), 11 );
BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) );
VECTOR2I ptOnArcCloseToStart( 297553, 31697 ); //should be index 3 in chain
VECTOR2I ptOnArcCloseToEnd( 139709, 82983 ); //should be index 6 in chain
BOOST_CHECK_EQUAL( chain.NearestPoint( ptOnArcCloseToStart, true ), ptOnArcCloseToStart );
BOOST_CHECK_EQUAL( chain.NearestPoint( ptOnArcCloseToStart, false ), arc.GetP0() );
BOOST_CHECK_EQUAL( chain.NearestPoint( ptOnArcCloseToEnd, true ), ptOnArcCloseToEnd );
BOOST_CHECK_EQUAL( chain.NearestPoint( ptOnArcCloseToEnd, false ), arc.GetP1() );
}
// Test SHAPE_LINE_CHAIN::Replace( SHAPE_LINE_CHAIN )
BOOST_AUTO_TEST_CASE( ReplaceChain )
{
BOOST_TEST_INFO( "8949 crash" );
std::vector<VECTOR2I> linePts = {
{ 206000000, 140110000 }, { 192325020, 140110000 }, { 192325020, 113348216 },
{ 192251784, 113274980 }, { 175548216, 113274980 }, { 175474980, 113348216 },
{ 175474980, 136694980 }, { 160774511, 121994511 }, { 160774511, 121693501 },
{ 160086499, 121005489 }, { 159785489, 121005489 }, { 159594511, 120814511 },
{ 160086499, 120814511 }, { 160774511, 120126499 }, { 160774511, 119153501 },
{ 160086499, 118465489 }, { 159113501, 118465489 }, { 158425489, 119153501 },
{ 158425489, 119645489 }, { 157325020, 118545020 }, { 157325020, 101925020 },
{ 208674980, 101925020 }, { 208674980, 145474980 }, { 192325020, 145474980 },
{ 192325020, 140110000 }
};
SHAPE_LINE_CHAIN baseChain( linePts, false );
baseChain.SetWidth( 250000 );
BOOST_CHECK_EQUAL( baseChain.PointCount(), linePts.size() );
SHAPE_LINE_CHAIN replaceChain( { VECTOR2I( 192325020, 140110000 ) }, false );
BOOST_CHECK_EQUAL( replaceChain.PointCount(), 1 );
baseChain.Replace( 1, 23, replaceChain );
BOOST_CHECK_EQUAL( baseChain.PointCount(), linePts.size() - ( 23 - 1 ) );
// Replacing the last point in a chain is special-cased
baseChain.Replace( baseChain.PointCount() - 1, baseChain.PointCount() - 1, VECTOR2I( -1, -1 ) );
BOOST_CHECK_EQUAL( baseChain.CLastPoint(), VECTOR2I( -1, -1 ) );
}
BOOST_AUTO_TEST_SUITE_END()