Extract common code into VERTEX_SET mixin

The VERTEX_SET is useful when we need to quickly find elements that are
close to each other.  Extracting to a mixin keeps the code from
diverging between implementations and simplifies that maintenance.
This commit is contained in:
Seth Hillbrand 2024-06-25 12:19:56 -07:00
parent 3c88b1ebc7
commit 4fff28220e
5 changed files with 584 additions and 666 deletions

View File

@ -30,6 +30,7 @@ set( KIMATH_SRCS
src/geometry/shape_rect.cpp src/geometry/shape_rect.cpp
src/geometry/shape_compound.cpp src/geometry/shape_compound.cpp
src/geometry/shape_segment.cpp src/geometry/shape_segment.cpp
src/geometry/vertex_set.cpp
src/math/vector2.cpp src/math/vector2.cpp

View File

@ -1,11 +1,11 @@
/* /*
* This program source code file is part of KiCad, a free EDA CAD application. * This program source code file is part of KiCad, a free EDA CAD application.
* *
* Modifications Copyright (C) 2018-2024 KiCad Developers * Copyright (C) 2018 KiCad Developers, see AUTHORS.TXT for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2 * as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version. * of the License, or (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
@ -15,8 +15,8 @@
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here: * along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * http://www.gnu.org/licenses/gpl-3.0.html
* or you may search the http://www.gnu.org website for the version 2 license, * or you may search the http://www.gnu.org website for the version 3 license,
* or you may write to the Free Software Foundation, Inc., * or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
* *
@ -54,16 +54,18 @@
#include <clipper.hpp> #include <clipper.hpp>
#include <geometry/shape_line_chain.h> #include <geometry/shape_line_chain.h>
#include <geometry/shape_poly_set.h> #include <geometry/shape_poly_set.h>
#include <geometry/vertex_set.h>
#include <math/box2.h> #include <math/box2.h>
#include <math/vector2d.h> #include <math/vector2d.h>
#include <wx/log.h> #include <wx/log.h>
#define TRIANGULATE_TRACE "triangulate" #define TRIANGULATE_TRACE "triangulate"
class POLYGON_TRIANGULATION class POLYGON_TRIANGULATION : public VERTEX_SET
{ {
public: public:
POLYGON_TRIANGULATION( SHAPE_POLY_SET::TRIANGULATED_POLYGON& aResult ) : POLYGON_TRIANGULATION( SHAPE_POLY_SET::TRIANGULATED_POLYGON& aResult ) :
VERTEX_SET( ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel ),
m_vertices_original_size( 0 ), m_result( aResult ) m_vertices_original_size( 0 ), m_result( aResult )
{}; {};
@ -81,6 +83,9 @@ public:
/// therefore cannot be polygons /// therefore cannot be polygons
VERTEX* firstVertex = createList( aPoly ); VERTEX* firstVertex = createList( aPoly );
for( const VECTOR2I& pt : aPoly.CPoints() )
m_result.AddVertex( pt );
if( !firstVertex || firstVertex->prev == firstVertex->next ) if( !firstVertex || firstVertex->prev == firstVertex->next )
return true; return true;
@ -114,228 +119,6 @@ public:
} }
private: private:
struct VERTEX
{
VERTEX( size_t aIndex, double aX, double aY, POLYGON_TRIANGULATION* aParent ) :
i( aIndex ),
x( aX ),
y( aY ),
parent( aParent )
{
}
VERTEX& operator=( const VERTEX& ) = delete;
VERTEX& operator=( VERTEX&& ) = delete;
bool operator==( const VERTEX& rhs ) const
{
return this->x == rhs.x && this->y == rhs.y;
}
bool operator!=( const VERTEX& rhs ) const { return !( *this == rhs ); }
/**
* Split the referenced polygon between the reference point and
* vertex b, assuming they are in the same polygon. Notes that while we
* create a new vertex pointer for the linked list, we maintain the same
* vertex index value from the original polygon. In this way, we have
* two polygons that both share the same vertices.
*
* @return the newly created vertex in the polygon that does not include the
* reference vertex.
*/
VERTEX* split( VERTEX* b )
{
parent->m_vertices.emplace_back( i, x, y, parent );
VERTEX* a2 = &parent->m_vertices.back();
parent->m_vertices.emplace_back( b->i, b->x, b->y, parent );
VERTEX* b2 = &parent->m_vertices.back();
VERTEX* an = next;
VERTEX* bp = b->prev;
next = b;
b->prev = this;
a2->next = an;
an->prev = a2;
b2->next = a2;
a2->prev = b2;
bp->next = b2;
b2->prev = bp;
return b2;
}
/**
* Remove the node from the linked list and z-ordered linked list.
*/
void remove()
{
next->prev = prev;
prev->next = next;
if( prevZ )
prevZ->nextZ = nextZ;
if( nextZ )
nextZ->prevZ = prevZ;
next = nullptr;
prev = nullptr;
nextZ = nullptr;
prevZ = nullptr;
}
void updateOrder()
{
if( !z )
z = parent->zOrder( x, y );
}
/**
* After inserting or changing nodes, this function should be called to
* remove duplicate vertices and ensure z-ordering is correct.
*/
void updateList()
{
VERTEX* p = next;
while( p != this )
{
/**
* Remove duplicates
*/
if( *p == *p->next )
{
p = p->prev;
p->next->remove();
if( p == p->next )
break;
}
p->updateOrder();
p = p->next;
};
updateOrder();
zSort();
}
/**
* Sort all vertices in this vertex's list by their Morton code.
*/
void zSort()
{
std::deque<VERTEX*> queue;
queue.push_back( this );
for( auto p = next; p && p != this; p = p->next )
queue.push_back( p );
std::sort( queue.begin(), queue.end(), []( const VERTEX* a, const VERTEX* b )
{
if( a->z != b->z )
return a->z < b->z;
if( a->x != b->x )
return a->x < b->x;
if( a->y != b->y )
return a->y < b->y;
return a->i < b->i;
} );
VERTEX* prev_elem = nullptr;
for( auto elem : queue )
{
if( prev_elem )
prev_elem->nextZ = elem;
elem->prevZ = prev_elem;
prev_elem = elem;
}
prev_elem->nextZ = nullptr;
}
/**
* Check to see if triangle surrounds our current vertex
*/
bool inTriangle( const VERTEX& a, const VERTEX& b, const VERTEX& c )
{
return ( c.x - x ) * ( a.y - y ) - ( a.x - x ) * ( c.y - y ) >= 0
&& ( a.x - x ) * ( b.y - y ) - ( b.x - x ) * ( a.y - y ) >= 0
&& ( b.x - x ) * ( c.y - y ) - ( c.x - x ) * ( b.y - y ) >= 0;
}
/**
* Returns the signed area of the polygon connected to the current vertex,
* optionally ending at a specified vertex.
*/
double area( const VERTEX* aEnd = nullptr ) const
{
const VERTEX* p = this;
double a = 0.0;
do
{
a += ( p->x + p->next->x ) * ( p->next->y - p->y );
p = p->next;
} while( p != this && p != aEnd );
if( p != this )
a += ( p->x + aEnd->x ) * ( aEnd->y - p->y );
return a / 2;
}
const size_t i;
double x;
double y;
POLYGON_TRIANGULATION* parent;
// previous and next vertices nodes in a polygon ring
VERTEX* prev = nullptr;
VERTEX* next = nullptr;
// z-order curve value
int32_t z = 0;
// previous and next nodes in z-order
VERTEX* prevZ = nullptr;
VERTEX* nextZ = nullptr;
};
/**
* Calculate the Morton code of the Vertex
* http://www.graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN
*
*/
int32_t zOrder( const double aX, const double aY ) const
{
int32_t x = static_cast<int32_t>( 32767.0 * ( aX - m_bbox.GetX() ) / m_bbox.GetWidth() );
int32_t y = static_cast<int32_t>( 32767.0 * ( aY - m_bbox.GetY() ) / m_bbox.GetHeight() );
x = ( x | ( x << 8 ) ) & 0x00FF00FF;
x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
x = ( x | ( x << 2 ) ) & 0x33333333;
x = ( x | ( x << 1 ) ) & 0x55555555;
y = ( y | ( y << 8 ) ) & 0x00FF00FF;
y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;
y = ( y | ( y << 2 ) ) & 0x33333333;
y = ( y | ( y << 1 ) ) & 0x55555555;
return x | ( y << 1 );
}
/** /**
* Outputs a list of vertices that have not yet been triangulated. * Outputs a list of vertices that have not yet been triangulated.
@ -510,57 +293,6 @@ private:
return retval; return retval;
} }
/**
* Take a #SHAPE_LINE_CHAIN and links each point into a circular, doubly-linked list.
*/
VERTEX* createList( const SHAPE_LINE_CHAIN& points )
{
wxLogTrace( TRIANGULATE_TRACE, "Creating list from %d points", points.PointCount() );
VERTEX* tail = nullptr;
double sum = 0.0;
// Check for winding order
for( int i = 0; i < points.PointCount(); i++ )
{
VECTOR2D p1 = points.CPoint( i );
VECTOR2D p2 = points.CPoint( i + 1 );
sum += ( ( p2.x - p1.x ) * ( p2.y + p1.y ) );
}
VECTOR2I last_pt{ std::numeric_limits<int>::max(), std::numeric_limits<int>::max() };
VECTOR2I::extended_type sq_dist = ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel;
sq_dist *= sq_dist;
auto addVertex = [&]( int i )
{
const VECTOR2I& pt = points.CPoint( i );
VECTOR2I diff = pt - last_pt;
if( diff.SquaredEuclideanNorm() > sq_dist )
{
tail = insertVertex( pt, tail );
last_pt = pt;
}
};
if( sum > 0.0 )
{
for( int i = points.PointCount() - 1; i >= 0; i-- )
addVertex( i );
}
else
{
for( int i = 0; i < points.PointCount(); i++ )
addVertex( i );
}
if( tail && ( *tail == *tail->next ) )
tail->next->remove();
return tail;
}
/** /**
* Walk through a circular linked list starting at \a aPoint. * Walk through a circular linked list starting at \a aPoint.
* *
@ -823,7 +555,7 @@ private:
{ {
double x = a->x * ( 1.0 - step * i ) + b->x * ( step * i ); double x = a->x * ( 1.0 - step * i ) + b->x * ( step * i );
double y = a->y * ( 1.0 - step * i ) + b->y * ( step * i ); double y = a->y * ( 1.0 - step * i ) + b->y * ( step * i );
last = insertVertex( VECTOR2I( x, y ), last ); last = insertTriVertex( VECTOR2I( x, y ), last );
} }
} }
@ -956,14 +688,6 @@ private:
} }
/**
* Return the twice the signed area of the triangle formed by vertices p, q, and r.
*/
double area( const VERTEX* p, const VERTEX* q, const VERTEX* r ) const
{
return ( q->y - p->y ) * ( r->x - q->x ) - ( q->x - p->x ) * ( r->y - q->y );
}
constexpr int sign( double aVal ) const constexpr int sign( double aVal ) const
{ {
@ -1035,76 +759,19 @@ private:
return false; return false;
} }
/**
* Check whether the segment from vertex a -> vertex b is inside the polygon
* around the immediate area of vertex a.
*
* We don't define the exact area over which the segment is inside but it is guaranteed to
* be inside the polygon immediately adjacent to vertex a.
*
* @return true if the segment from a->b is inside a's polygon next to vertex a.
*/
bool locallyInside( const VERTEX* a, const VERTEX* b ) const
{
if( area( a->prev, a, a->next ) < 0 )
return area( a, b, a->next ) >= 0 && area( a, a->prev, b ) >= 0;
else
return area( a, b, a->prev ) < 0 || area( a, a->next, b ) < 0;
}
/**
* Check to see if the segment halfway point between a and b is inside the polygon
*/
bool middleInside( const VERTEX* a, const VERTEX* b ) const
{
const VERTEX* p = a;
bool inside = false;
double px = ( a->x + b->x ) / 2;
double py = ( a->y + b->y ) / 2;
do
{
if( ( ( p->y > py ) != ( p->next->y > py ) )
&& ( px < ( p->next->x - p->x ) * ( py - p->y ) / ( p->next->y - p->y ) + p->x ) )
inside = !inside;
p = p->next;
} while( p != a );
return inside;
}
/** /**
* Create an entry in the vertices lookup and optionally inserts the newly created vertex * Create an entry in the vertices lookup and optionally inserts the newly created vertex
* into an existing linked list. * into an existing linked list.
* *
* @return a pointer to the newly created vertex. * @return a pointer to the newly created vertex.
*/ */
VERTEX* insertVertex( const VECTOR2I& pt, VERTEX* last ) VERTEX* insertTriVertex( const VECTOR2I& pt, VERTEX* last )
{ {
m_result.AddVertex( pt ); m_result.AddVertex( pt );
m_vertices.emplace_back( m_result.GetVertexCount() - 1, pt.x, pt.y, this ); return insertVertex( m_result.GetVertexCount() - 1, pt, nullptr );
VERTEX* p = &m_vertices.back();
if( !last )
{
p->prev = p;
p->next = p;
}
else
{
p->next = last->next;
p->prev = last;
last->next->prev = p;
last->next = p;
}
return p;
} }
private: private:
BOX2I m_bbox;
std::deque<VERTEX> m_vertices;
size_t m_vertices_original_size; size_t m_vertices_original_size;
SHAPE_POLY_SET::TRIANGULATED_POLYGON& m_result; SHAPE_POLY_SET::TRIANGULATED_POLYGON& m_result;
}; };

View File

@ -0,0 +1,352 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 20218 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, you may find one here:
* http://www.gnu.org/licenses/gpl-3.0.html
* or you may search the http://www.gnu.org website for the version 3 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
* Based on Uniform Plane Subdivision algorithm from Lamot, Marko, and Borut Žalik.
* "A fast polygon triangulation algorithm based on uniform plane subdivision."
* Computers & graphics 27, no. 2 (2003): 239-253.
*
* Code derived from:
* K-3D which is Copyright (c) 2005-2006, Romain Behar, GPL-2, license above
* earcut which is Copyright (c) 2016, Mapbox, ISC
*
* ISC License:
* Permission to use, copy, modify, and/or distribute this software for any purpose
* with or without fee is hereby granted, provided that the above copyright notice
* and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
*
*/
#ifndef VERTEX_SET_H
#define VERTEX_SET_H
#include <algorithm>
#include <deque>
#include <math/box2.h>
#include <geometry/shape_line_chain.h>
class VERTEX;
class VERTEX_SET
{
friend class VERTEX;
public:
VERTEX_SET( int aSimplificationLevel )
{
m_simplificationLevel = aSimplificationLevel * ( VECTOR2I::extended_type ) aSimplificationLevel;
}
~VERTEX_SET() {}
void SetBoundingBox( const BOX2I& aBBox );
/**
* Insert a vertex into the vertex set
* @param aIndex the index of the vertex
* @param pt the point to insert
* @param last the last vertex in the list
* @return the newly inserted vertex
*/
VERTEX* insertVertex( int aIndex, const VECTOR2I& pt, VERTEX* last );
/**
* Create a list of vertices from a line chain
* @param points the line chain to create the list from
* @return the first vertex in the list
*/
VERTEX* createList( const SHAPE_LINE_CHAIN& points );
protected:
/**
* Get the next vertex in the outline, avoiding steiner points
* and points that overlap with splits
* @param aPt the current vertex
* @return the next vertex in the outline
*/
VERTEX* getNextOutlineVertex( const VERTEX* aPt ) const;
/**
* Get the previous vertex in the outline, avoiding steiner points
* and points that overlap with splits
* @param aPt the current vertex
* @return the previous vertex in the outline
*/
VERTEX* getPrevOutlineVertex( const VERTEX* aPt ) const;
/**
* Check whether the segment from vertex a -> vertex b is inside the polygon
* around the immediate area of vertex a.
*
* We don't define the exact area over which the segment is inside but it is guaranteed to
* be inside the polygon immediately adjacent to vertex a.
*
* @return true if the segment from a->b is inside a's polygon next to vertex a.
*/
bool locallyInside( const VERTEX* a, const VERTEX* b ) const;
/**
* Check if the middle of the segment from a to b is inside the polygon
* @param a the first vertex
* @param b the second vertex
* @return true if the point is in the middle of the triangle
*/
bool middleInside( const VERTEX* a, const VERTEX* b ) const;
/**
* Check if two vertices are at the same point
* @param aA the first vertex
* @param aB the second vertex
* @return true if the vertices are at the same point
*/
bool same_point( const VERTEX* aA, const VERTEX* aB ) const;
/**
* Note that while the inputs are doubles, these are scaled
* by the size of the bounding box to fit into a 32-bit Morton code
* @return the Morton code for the point (aX, aY)
*/
int32_t zOrder( const double aX, const double aY ) const;
/**
* @return the area of the triangle defined by the three vertices
*/
double area( const VERTEX* p, const VERTEX* q, const VERTEX* r ) const;
BOX2I m_bbox;
std::deque<VERTEX> m_vertices;
VECTOR2I::extended_type m_simplificationLevel;
};
class VERTEX
{
public:
VERTEX( int aIndex, double aX, double aY, VERTEX_SET* aParent ) :
i( aIndex ),
x( aX ),
y( aY ),
parent( aParent )
{
}
VERTEX& operator=( const VERTEX& ) = delete;
VERTEX& operator=( VERTEX&& ) = delete;
bool operator==( const VERTEX& rhs ) const
{
return this->x == rhs.x && this->y == rhs.y;
}
bool operator!=( const VERTEX& rhs ) const { return !( *this == rhs ); }
/**
* Remove the node from the linked list and z-ordered linked list.
*/
void remove()
{
next->prev = prev;
prev->next = next;
if( prevZ )
prevZ->nextZ = nextZ;
if( nextZ )
nextZ->prevZ = prevZ;
next = nullptr;
prev = nullptr;
nextZ = nullptr;
prevZ = nullptr;
}
/**
* Split the referenced polygon between the reference point and
* vertex b, assuming they are in the same polygon. Notes that while we
* create a new vertex pointer for the linked list, we maintain the same
* vertex index value from the original polygon. In this way, we have
* two polygons that both share the same vertices.
*
* @return the newly created vertex in the polygon that does not include the
* reference vertex.
*/
VERTEX* split( VERTEX* b )
{
parent->m_vertices.emplace_back( i, x, y, parent );
VERTEX* a2 = parent->insertVertex( i, VECTOR2I( x, y ), nullptr );
parent->m_vertices.emplace_back( b->i, b->x, b->y, parent );
VERTEX* b2 = &parent->m_vertices.back();
VERTEX* an = next;
VERTEX* bp = b->prev;
next = b;
b->prev = this;
a2->next = an;
an->prev = a2;
b2->next = a2;
a2->prev = b2;
bp->next = b2;
b2->prev = bp;
return b2;
}
void updateOrder()
{
if( !z )
z = parent->zOrder( x, y );
}
/**
* After inserting or changing nodes, this function should be called to
* remove duplicate vertices and ensure z-ordering is correct.
*/
void updateList()
{
VERTEX* p = next;
while( p != this )
{
/**
* Remove duplicates
*/
if( *p == *p->next )
{
p = p->prev;
p->next->remove();
if( p == p->next )
break;
}
p->updateOrder();
p = p->next;
};
updateOrder();
zSort();
}
/**
* Sort all vertices in this vertex's list by their Morton code.
*/
void zSort()
{
std::deque<VERTEX*> queue;
queue.push_back( this );
for( VERTEX* p = next; p && p != this; p = p->next )
queue.push_back( p );
std::sort( queue.begin(), queue.end(), []( const VERTEX* a, const VERTEX* b )
{
if( a->z != b->z )
return a->z < b->z;
if( a->x != b->x )
return a->x < b->x;
if( a->y != b->y )
return a->y < b->y;
return a->i < b->i;
} );
VERTEX* prev_elem = nullptr;
for( VERTEX* elem : queue )
{
if( prev_elem )
prev_elem->nextZ = elem;
elem->prevZ = prev_elem;
prev_elem = elem;
}
prev_elem->nextZ = nullptr;
}
/**
* Check to see if triangle surrounds our current vertex
*/
bool inTriangle( const VERTEX& a, const VERTEX& b, const VERTEX& c )
{
return ( c.x - x ) * ( a.y - y ) - ( a.x - x ) * ( c.y - y ) >= 0
&& ( a.x - x ) * ( b.y - y ) - ( b.x - x ) * ( a.y - y ) >= 0
&& ( b.x - x ) * ( c.y - y ) - ( c.x - x ) * ( b.y - y ) >= 0;
}
/**
* Returns the signed area of the polygon connected to the current vertex,
* optionally ending at a specified vertex.
*/
double area( const VERTEX* aEnd = nullptr ) const
{
const VERTEX* p = this;
double a = 0.0;
do
{
a += ( p->x + p->next->x ) * ( p->next->y - p->y );
p = p->next;
} while( p != this && p != aEnd );
if( p != this )
a += ( p->x + aEnd->x ) * ( aEnd->y - p->y );
return a / 2;
}
const int i;
const double x;
const double y;
VERTEX_SET* parent;
// previous and next vertices nodes in a polygon ring
VERTEX* prev = nullptr;
VERTEX* next = nullptr;
// z-order curve value
int32_t z = 0;
// previous and next nodes in z-order
VERTEX* prevZ = nullptr;
VERTEX* nextZ = nullptr;
};
#endif // VERTEX_SET_H

View File

@ -0,0 +1,206 @@
#include <geometry/vertex_set.h>
void VERTEX_SET::SetBoundingBox( const BOX2I& aBBox ) { m_bbox = aBBox; }
/**
* Take a #SHAPE_LINE_CHAIN and links each point into a circular, doubly-linked list.
*/
VERTEX* VERTEX_SET::createList( const SHAPE_LINE_CHAIN& points )
{
VERTEX* tail = nullptr;
double sum = 0.0;
// Check for winding order
for( int i = 0; i < points.PointCount(); i++ )
{
VECTOR2D p1 = points.CPoint( i );
VECTOR2D p2 = points.CPoint( i + 1 );
sum += ( ( p2.x - p1.x ) * ( p2.y + p1.y ) );
}
VECTOR2I last_pt{ std::numeric_limits<int>::max(), std::numeric_limits<int>::max() };
auto addVertex = [&]( int i )
{
const VECTOR2I& pt = points.CPoint( i );
VECTOR2I diff = pt - last_pt;
if( diff.SquaredEuclideanNorm() > m_simplificationLevel )
{
tail = insertVertex( i, pt, tail );
last_pt = pt;
}
};
if( sum > 0.0 )
{
for( int i = points.PointCount() - 1; i >= 0; i-- )
addVertex( i );
}
else
{
for( int i = 0; i < points.PointCount(); i++ )
addVertex( i );
}
if( tail && ( *tail == *tail->next ) )
{
tail->next->remove();
}
return tail;
}
/**
* Calculate the Morton code of the VERTEX
* http://www.graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN
*
*/
int32_t VERTEX_SET::zOrder( const double aX, const double aY ) const
{
int32_t x = static_cast<int32_t>( 32767.0 * ( aX - m_bbox.GetX() ) / m_bbox.GetWidth() );
int32_t y = static_cast<int32_t>( 32767.0 * ( aY - m_bbox.GetY() ) / m_bbox.GetHeight() );
x = ( x | ( x << 8 ) ) & 0x00FF00FF;
x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
x = ( x | ( x << 2 ) ) & 0x33333333;
x = ( x | ( x << 1 ) ) & 0x55555555;
y = ( y | ( y << 8 ) ) & 0x00FF00FF;
y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;
y = ( y | ( y << 2 ) ) & 0x33333333;
y = ( y | ( y << 1 ) ) & 0x55555555;
return x | ( y << 1 );
}
/**
* Return the twice the signed area of the triangle formed by vertices p, q, and r.
*/
double VERTEX_SET::area( const VERTEX* p, const VERTEX* q, const VERTEX* r ) const
{
return ( q->y - p->y ) * ( r->x - q->x ) - ( q->x - p->x ) * ( r->y - q->y );
}
bool VERTEX_SET::same_point( const VERTEX* aA, const VERTEX* aB ) const
{
return aA && aB && aA->x == aB->x && aA->y == aB->y;
}
VERTEX* VERTEX_SET::getNextOutlineVertex( const VERTEX* aPt ) const
{
VERTEX* nz = aPt->nextZ;
VERTEX* pz = aPt->prevZ;
// If we hit a fracture point, we want to continue around the
// edge we are working on and not switch to the pair edge
// However, this will depend on which direction the initial
// fracture hit is. If we find that we skip directly to
// a new fracture point, then we know that we are proceeding
// in the wrong direction from the fracture and should
// fall through to the next point
if( same_point( aPt, nz ) && same_point( aPt->next, nz->prev )
&& aPt->y == aPt->next->y )
{
return nz->next;
}
if( same_point( aPt, pz ) && same_point( aPt->next, pz->prev )
&& aPt->y == aPt->next->y )
{
return pz->next;
}
return aPt->next;
}
VERTEX* VERTEX_SET::getPrevOutlineVertex( const VERTEX* aPt ) const
{
VERTEX* nz = aPt->nextZ;
VERTEX* pz = aPt->prevZ;
// If we hit a fracture point, we want to continue around the
// edge we are working on and not switch to the pair edge
// However, this will depend on which direction the initial
// fracture hit is. If we find that we skip directly to
// a new fracture point, then we know that we are proceeding
// in the wrong direction from the fracture and should
// fall through to the next point
if( same_point( aPt, nz )
&& aPt->y == aPt->prev->y)
{
return nz->prev;
}
if( same_point( aPt, pz )
&& aPt->y == aPt->prev->y )
{
return pz->prev;
}
return aPt->prev;
}
bool VERTEX_SET::locallyInside( const VERTEX* a, const VERTEX* b ) const
{
const VERTEX* an = getNextOutlineVertex( a );
const VERTEX* ap = getPrevOutlineVertex( a );
if( area( ap, a, an ) < 0 )
return area( a, b, an ) >= 0 && area( a, ap, b ) >= 0;
else
return area( a, b, ap ) < 0 || area( a, an, b ) < 0;
}
bool VERTEX_SET::middleInside( const VERTEX* a, const VERTEX* b ) const
{
const VERTEX* p = a;
bool inside = false;
double px = ( a->x + b->x ) / 2;
double py = ( a->y + b->y ) / 2;
do
{
if( ( ( p->y > py ) != ( p->next->y > py ) )
&& ( px < ( p->next->x - p->x ) * ( py - p->y ) / ( p->next->y - p->y ) + p->x ) )
inside = !inside;
p = p->next;
} while( p != a );
return inside;
}
/**
* Create an entry in the vertices lookup and optionally inserts the newly created vertex
* into an existing linked list.
*
* @return a pointer to the newly created vertex.
*/
VERTEX* VERTEX_SET::insertVertex( int aIndex, const VECTOR2I& pt, VERTEX* last )
{
m_vertices.emplace_back( aIndex, pt.x, pt.y, this );
VERTEX* p = &m_vertices.back();
if( !last )
{
p->prev = p;
p->next = p;
}
else
{
p->next = last->next;
p->prev = last;
last->next->prev = p;
last->next = p;
}
return p;
}

View File

@ -40,6 +40,7 @@
#include <footprint.h> #include <footprint.h>
#include <geometry/seg.h> #include <geometry/seg.h>
#include <geometry/shape_poly_set.h> #include <geometry/shape_poly_set.h>
#include <geometry/vertex_set.h>
#include <math/box2.h> #include <math/box2.h>
#include <math/vector2d.h> #include <math/vector2d.h>
#include <pcb_shape.h> #include <pcb_shape.h>
@ -111,10 +112,11 @@ private:
}; };
class POLYGON_TEST class POLYGON_TEST : public VERTEX_SET
{ {
public: public:
POLYGON_TEST( int aLimit ) : POLYGON_TEST( int aLimit ) :
VERTEX_SET( 0 ),
m_limit( aLimit ) m_limit( aLimit )
{ {
}; };
@ -129,12 +131,12 @@ public:
m_vertices.front().updateList(); m_vertices.front().updateList();
Vertex* p = m_vertices.front().next; VERTEX* p = m_vertices.front().next;
std::set<Vertex*> all_hits; std::set<VERTEX*> all_hits;
while( p != &m_vertices.front() ) while( p != &m_vertices.front() )
{ {
Vertex* match = nullptr; VERTEX* match = nullptr;
// Only run the expensive search if we don't already have a match for the point // Only run the expensive search if we don't already have a match for the point
if( ( all_hits.empty() || all_hits.count( p ) == 0 ) && ( match = getKink( p ) ) != nullptr ) if( ( all_hits.empty() || all_hits.count( p ) == 0 ) && ( match = getKink( p ) ) != nullptr )
@ -162,222 +164,6 @@ public:
} }
private:
struct Vertex
{
Vertex( int aIndex, double aX, double aY, POLYGON_TEST* aParent ) :
i( aIndex ),
x( aX ),
y( aY ),
parent( aParent )
{
}
Vertex& operator=( const Vertex& ) = delete;
Vertex& operator=( Vertex&& ) = delete;
bool operator==( const Vertex& rhs ) const
{
return this->x == rhs.x && this->y == rhs.y;
}
bool operator!=( const Vertex& rhs ) const { return !( *this == rhs ); }
/**
* Remove the node from the linked list and z-ordered linked list.
*/
void remove()
{
next->prev = prev;
prev->next = next;
if( prevZ )
prevZ->nextZ = nextZ;
if( nextZ )
nextZ->prevZ = prevZ;
next = nullptr;
prev = nullptr;
nextZ = nullptr;
prevZ = nullptr;
}
void updateOrder()
{
if( !z )
z = parent->zOrder( x, y );
}
/**
* After inserting or changing nodes, this function should be called to
* remove duplicate vertices and ensure z-ordering is correct.
*/
void updateList()
{
Vertex* p = next;
while( p != this )
{
/**
* Remove duplicates
*/
if( *p == *p->next )
{
p = p->prev;
p->next->remove();
if( p == p->next )
break;
}
p->updateOrder();
p = p->next;
};
updateOrder();
zSort();
}
/**
* Sort all vertices in this vertex's list by their Morton code.
*/
void zSort()
{
std::deque<Vertex*> queue;
queue.push_back( this );
for( Vertex* p = next; p && p != this; p = p->next )
queue.push_back( p );
std::sort( queue.begin(), queue.end(), []( const Vertex* a, const Vertex* b )
{
if( a->z != b->z )
return a->z < b->z;
if( a->x != b->x )
return a->x < b->x;
if( a->y != b->y )
return a->y < b->y;
return a->i < b->i;
} );
Vertex* prev_elem = nullptr;
for( Vertex* elem : queue )
{
if( prev_elem )
prev_elem->nextZ = elem;
elem->prevZ = prev_elem;
prev_elem = elem;
}
prev_elem->nextZ = nullptr;
}
const int i;
const double x;
const double y;
POLYGON_TEST* parent;
// previous and next vertices nodes in a polygon ring
Vertex* prev = nullptr;
Vertex* next = nullptr;
// z-order curve value
int32_t z = 0;
// previous and next nodes in z-order
Vertex* prevZ = nullptr;
Vertex* nextZ = nullptr;
};
/**
* Calculate the Morton code of the Vertex
* http://www.graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN
*
*/
int32_t zOrder( const double aX, const double aY ) const
{
int32_t x = static_cast<int32_t>( 32767.0 * ( aX - m_bbox.GetX() ) / m_bbox.GetWidth() );
int32_t y = static_cast<int32_t>( 32767.0 * ( aY - m_bbox.GetY() ) / m_bbox.GetHeight() );
x = ( x | ( x << 8 ) ) & 0x00FF00FF;
x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
x = ( x | ( x << 2 ) ) & 0x33333333;
x = ( x | ( x << 1 ) ) & 0x55555555;
y = ( y | ( y << 8 ) ) & 0x00FF00FF;
y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;
y = ( y | ( y << 2 ) ) & 0x33333333;
y = ( y | ( y << 1 ) ) & 0x55555555;
return x | ( y << 1 );
}
constexpr bool same_point( const Vertex* aA, const Vertex* aB ) const
{
return aA && aB && aA->x == aB->x && aA->y == aB->y;
}
Vertex* getNextOutlineVertex( const Vertex* aPt ) const
{
Vertex* nz = aPt->nextZ;
Vertex* pz = aPt->prevZ;
// If we hit a fracture point, we want to continue around the
// edge we are working on and not switch to the pair edge
// However, this will depend on which direction the initial
// fracture hit is. If we find that we skip directly to
// a new fracture point, then we know that we are proceeding
// in the wrong direction from the fracture and should
// fall through to the next point
if( same_point( aPt, nz ) && same_point( aPt->next, nz->prev )
&& aPt->y == aPt->next->y )
{
return nz->next;
}
if( same_point( aPt, pz ) && same_point( aPt->next, pz->prev )
&& aPt->y == aPt->next->y )
{
return pz->next;
}
return aPt->next;
}
Vertex* getPrevOutlineVertex( const Vertex* aPt ) const
{
Vertex* nz = aPt->nextZ;
Vertex* pz = aPt->prevZ;
// If we hit a fracture point, we want to continue around the
// edge we are working on and not switch to the pair edge
// However, this will depend on which direction the initial
// fracture hit is. If we find that we skip directly to
// a new fracture point, then we know that we are proceeding
// in the wrong direction from the fracture and should
// fall through to the next point
if( same_point( aPt, nz )
&& aPt->y == aPt->prev->y)
{
return nz->prev;
}
if( same_point( aPt, pz )
&& aPt->y == aPt->prev->y )
{
return pz->prev;
}
return aPt->prev;
}
/** /**
* Checks to see if there is a "substantial" protrusion in each polygon produced by the cut from * Checks to see if there is a "substantial" protrusion in each polygon produced by the cut from
* aA to aB. Substantial in this case means that the polygon bulges out to a wider cross-section * aA to aB. Substantial in this case means that the polygon bulges out to a wider cross-section
@ -386,7 +172,7 @@ private:
* @param aB Ending point in the polygon * @param aB Ending point in the polygon
* @return True if the two polygons are both "substantial" * @return True if the two polygons are both "substantial"
*/ */
bool isSubstantial( const Vertex* aA, const Vertex* aB ) const bool isSubstantial( const VERTEX* aA, const VERTEX* aB ) const
{ {
bool x_change = false; bool x_change = false;
bool y_change = false; bool y_change = false;
@ -396,8 +182,8 @@ private:
size_t checked = 0; size_t checked = 0;
size_t total_pts = m_vertices.size(); size_t total_pts = m_vertices.size();
const Vertex* p0 = aA; const VERTEX* p0 = aA;
const Vertex* p = getNextOutlineVertex( p0 ); const VERTEX* p = getNextOutlineVertex( p0 );
while( !same_point( p, aB ) // We've reached the other inflection point while( !same_point( p, aB ) // We've reached the other inflection point
&& !same_point( p, aA ) // We've gone around in a circle && !same_point( p, aA ) // We've gone around in a circle
@ -458,43 +244,8 @@ private:
return ( same_point( p, aA ) || ( x_change && y_change ) ); return ( same_point( p, aA ) || ( x_change && y_change ) );
} }
/**
* Take a #SHAPE_LINE_CHAIN and links each point into a circular, doubly-linked list.
*/
Vertex* createList( const SHAPE_LINE_CHAIN& points )
{
Vertex* tail = nullptr;
double sum = 0.0;
// Check for winding order VERTEX* getKink( VERTEX* aPt ) const
for( int i = 0; i < points.PointCount(); i++ )
{
VECTOR2D p1 = points.CPoint( i );
VECTOR2D p2 = points.CPoint( i + 1 );
sum += ( ( p2.x - p1.x ) * ( p2.y + p1.y ) );
}
if( sum > 0.0 )
{
for( int i = points.PointCount() - 1; i >= 0; i--)
tail = insertVertex( i, points.CPoint( i ), tail );
}
else
{
for( int i = 0; i < points.PointCount(); i++ )
tail = insertVertex( i, points.CPoint( i ), tail );
}
if( tail && ( *tail == *tail->next ) )
{
tail->next->remove();
}
return tail;
}
Vertex* getKink( Vertex* aPt ) const
{ {
// The point needs to be at a concave surface // The point needs to be at a concave surface
if( locallyInside( aPt->prev, aPt->next ) ) if( locallyInside( aPt->prev, aPt->next ) )
@ -506,9 +257,9 @@ private:
const SEG::ecoord limit2 = SEG::Square( m_limit ); const SEG::ecoord limit2 = SEG::Square( m_limit );
// first look for points in increasing z-order // first look for points in increasing z-order
Vertex* p = aPt->nextZ; VERTEX* p = aPt->nextZ;
SEG::ecoord min_dist = std::numeric_limits<SEG::ecoord>::max(); SEG::ecoord min_dist = std::numeric_limits<SEG::ecoord>::max();
Vertex* retval = nullptr; VERTEX* retval = nullptr;
while( p && p->z <= maxZ ) while( p && p->z <= maxZ )
{ {
@ -546,67 +297,8 @@ private:
return retval; return retval;
} }
/**
* Return the twice the signed area of the triangle formed by vertices p, q, and r.
*/
double area( const Vertex* p, const Vertex* q, const Vertex* r ) const
{
return ( q->y - p->y ) * ( r->x - q->x ) - ( q->x - p->x ) * ( r->y - q->y );
}
/**
* Check whether the segment from vertex a -> vertex b is inside the polygon
* around the immediate area of vertex a.
*
* We don't define the exact area over which the segment is inside but it is guaranteed to
* be inside the polygon immediately adjacent to vertex a.
*
* @return true if the segment from a->b is inside a's polygon next to vertex a.
*/
bool locallyInside( const Vertex* a, const Vertex* b ) const
{
const Vertex* an = getNextOutlineVertex( a );
const Vertex* ap = getPrevOutlineVertex( a );
if( area( ap, a, an ) < 0 )
return area( a, b, an ) >= 0 && area( a, ap, b ) >= 0;
else
return area( a, b, ap ) < 0 || area( a, an, b ) < 0;
}
/**
* Create an entry in the vertices lookup and optionally inserts the newly created vertex
* into an existing linked list.
*
* @return a pointer to the newly created vertex.
*/
Vertex* insertVertex( int aIndex, const VECTOR2I& pt, Vertex* last )
{
m_vertices.emplace_back( aIndex, pt.x, pt.y, this );
Vertex* p = &m_vertices.back();
if( !last )
{
p->prev = p;
p->next = p;
}
else
{
p->next = last->next;
p->prev = last;
last->next->prev = p;
last->next = p;
}
return p;
}
private: private:
int m_limit; int m_limit;
BOX2I m_bbox;
std::deque<Vertex> m_vertices;
std::set<std::pair<int, int>> m_hits; std::set<std::pair<int, int>> m_hits;
}; };