diff options
Diffstat (limited to 'include/geometry')
-rw-r--r-- | include/geometry/rtree.h | 1905 | ||||
-rw-r--r-- | include/geometry/seg.h | 384 | ||||
-rw-r--r-- | include/geometry/shape.h | 171 | ||||
-rw-r--r-- | include/geometry/shape_circle.h | 103 | ||||
-rw-r--r-- | include/geometry/shape_convex.h | 189 | ||||
-rw-r--r-- | include/geometry/shape_file_io.h | 69 | ||||
-rw-r--r-- | include/geometry/shape_index.h | 363 | ||||
-rw-r--r-- | include/geometry/shape_index_list.h | 291 | ||||
-rw-r--r-- | include/geometry/shape_line_chain.h | 598 | ||||
-rw-r--r-- | include/geometry/shape_poly_set.h | 374 | ||||
-rw-r--r-- | include/geometry/shape_rect.h | 168 | ||||
-rw-r--r-- | include/geometry/shape_segment.h | 101 |
12 files changed, 4716 insertions, 0 deletions
diff --git a/include/geometry/rtree.h b/include/geometry/rtree.h new file mode 100644 index 0000000..35ccadb --- /dev/null +++ b/include/geometry/rtree.h @@ -0,0 +1,1905 @@ +//TITLE +// +// R-TREES: A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING +// +//DESCRIPTION +// +// A C++ templated version of the RTree algorithm. +// For more information please read the comments in RTree.h +// +//AUTHORS +// +// * 1983 Original algorithm and test code by Antonin Guttman and Michael Stonebraker, UC Berkely +// * 1994 ANCI C ported from original test code by Melinda Green - melinda@superliminal.com +// * 1995 Sphere volume fix for degeneracy problem submitted by Paul Brook +// * 2004 Templated C++ port by Greg Douglas +// * 2013 CERN (www.cern.ch) +// +//LICENSE: +// +// Entirely free for all uses. Enjoy! + +#ifndef RTREE_H +#define RTREE_H + +// NOTE This file compiles under MSVC 6 SP5 and MSVC .Net 2003 it may not work on other compilers without modification. + +// NOTE These next few lines may be win32 specific, you may need to modify them to compile on other platform +#include <stdio.h> +#include <math.h> +#include <assert.h> +#include <stdlib.h> + +#define ASSERT assert // RTree uses ASSERT( condition ) +#ifndef rMin + #define rMin std::min +#endif // rMin +#ifndef rMax + #define rMax std::max +#endif // rMax + +// +// RTree.h +// + +#define RTREE_TEMPLATE template <class DATATYPE, class ELEMTYPE, int NUMDIMS, \ + class ELEMTYPEREAL, int TMAXNODES, int TMINNODES> +#define RTREE_SEARCH_TEMPLATE template <class DATATYPE, class ELEMTYPE, int NUMDIMS, \ + class ELEMTYPEREAL, int TMAXNODES, int TMINNODES, class VISITOR> +#define RTREE_QUAL RTree<DATATYPE, ELEMTYPE, NUMDIMS, ELEMTYPEREAL, TMAXNODES, \ + TMINNODES> +#define RTREE_SEARCH_QUAL RTree<DATATYPE, ELEMTYPE, NUMDIMS, ELEMTYPEREAL, TMAXNODES, \ + TMINNODES, VISITOR> + +#define RTREE_DONT_USE_MEMPOOLS // This version does not contain a fixed memory allocator, fill in lines with EXAMPLE to implement one. +#define RTREE_USE_SPHERICAL_VOLUME // Better split classification, may be slower on some systems + +// Fwd decl +class RTFileStream; // File I/O helper class, look below for implementation and notes. + + +/// \class RTree +/// Implementation of RTree, a multidimensional bounding rectangle tree. +/// Example usage: For a 3-dimensional tree use RTree<Object*, float, 3> myTree; +/// +/// This modified, templated C++ version by Greg Douglas at Auran (http://www.auran.com) +/// +/// DATATYPE Referenced data, should be int, void*, obj* etc. no larger than sizeof<void*> and simple type +/// ELEMTYPE Type of element such as int or float +/// NUMDIMS Number of dimensions such as 2 or 3 +/// ELEMTYPEREAL Type of element that allows fractional and large values such as float or double, for use in volume calcs +/// +/// NOTES: Inserting and removing data requires the knowledge of its constant Minimal Bounding Rectangle. +/// This version uses new/delete for nodes, I recommend using a fixed size allocator for efficiency. +/// Instead of using a callback function for returned results, I recommend and efficient pre-sized, grow-only memory +/// array similar to MFC CArray or STL Vector for returning search query result. +/// +template <class DATATYPE, class ELEMTYPE, int NUMDIMS, + class ELEMTYPEREAL = ELEMTYPE, int TMAXNODES = 8, int TMINNODES = TMAXNODES / 2> +class RTree +{ +protected: + + struct Node; // Fwd decl. Used by other internal structs and iterator +public: + + // These constant must be declared after Branch and before Node struct + // Stuck up here for MSVC 6 compiler. NSVC .NET 2003 is much happier. + enum { + MAXNODES = TMAXNODES, ///< Max elements in node + MINNODES = TMINNODES, ///< Min elements in node + }; + + struct Statistics { + int maxDepth; + int avgDepth; + + int maxNodeLoad; + int avgNodeLoad; + int totalItems; + }; + +public: + + RTree(); + virtual ~RTree(); + + /// Insert entry + /// \param a_min Min of bounding rect + /// \param a_max Max of bounding rect + /// \param a_dataId Positive Id of data. Maybe zero, but negative numbers not allowed. + void Insert( const ELEMTYPE a_min[NUMDIMS], + const ELEMTYPE a_max[NUMDIMS], + const DATATYPE& a_dataId ); + + /// Remove entry + /// \param a_min Min of bounding rect + /// \param a_max Max of bounding rect + /// \param a_dataId Positive Id of data. Maybe zero, but negative numbers not allowed. + void Remove( const ELEMTYPE a_min[NUMDIMS], + const ELEMTYPE a_max[NUMDIMS], + const DATATYPE& a_dataId ); + + /// Find all within search rectangle + /// \param a_min Min of search bounding rect + /// \param a_max Max of search bounding rect + /// \param a_resultCallback Callback function to return result. Callback should return 'true' to continue searching + /// \param a_context User context to pass as parameter to a_resultCallback + /// \return Returns the number of entries found + int Search( const ELEMTYPE a_min[NUMDIMS], + const ELEMTYPE a_max[NUMDIMS], + bool a_resultCallback( DATATYPE a_data, void* a_context ), + void* a_context ); + + template <class VISITOR> + int Search( const ELEMTYPE a_min[NUMDIMS], const ELEMTYPE a_max[NUMDIMS], VISITOR& a_visitor ) + { + #ifdef _DEBUG + + for( int index = 0; index<NUMDIMS; ++index ) + { + ASSERT( a_min[index] <= a_max[index] ); + } + + #endif // _DEBUG + + Rect rect; + + for( int axis = 0; axis<NUMDIMS; ++axis ) + { + rect.m_min[axis] = a_min[axis]; + rect.m_max[axis] = a_max[axis]; + } + + + // NOTE: May want to return search result another way, perhaps returning the number of found elements here. + int cnt; + + Search( m_root, &rect, a_visitor, cnt ); + + return cnt; + } + + /// Calculate Statistics + + Statistics CalcStats(); + + /// Remove all entries from tree + void RemoveAll(); + + /// Count the data elements in this container. This is slow as no internal counter is maintained. + int Count(); + + /// Load tree contents from file + bool Load( const char* a_fileName ); + + /// Load tree contents from stream + bool Load( RTFileStream& a_stream ); + + + /// Save tree contents to file + bool Save( const char* a_fileName ); + + /// Save tree contents to stream + bool Save( RTFileStream& a_stream ); + + /// Find the nearest neighbor of a specific point. + /// It uses the MINDIST method, simplifying the one from "R-Trees: Theory and Applications" by Yannis Manolopoulos et al. + /// The bounding rectangle is used to calculate the distance to the DATATYPE. + /// \param a_point point to start the search + /// \return Returns the DATATYPE located closest to a_point, 0 if the tree is empty. + DATATYPE NearestNeighbor( const ELEMTYPE a_point[NUMDIMS] ); + + /// Find the nearest neighbor of a specific point. + /// It uses the MINDIST method, simplifying the one from "R-Trees: Theory and Applications" by Yannis Manolopoulos et al. + /// It receives a callback function to calculate the distance to a DATATYPE object, instead of using the bounding rectangle. + /// \param a_point point to start the search + /// \param a_squareDistanceCallback function that performs the square distance calculation for the selected DATATYPE. + /// \param a_squareDistance Pointer in which the square distance to the nearest neighbour will be returned. + /// \return Returns the DATATYPE located closest to a_point, 0 if the tree is empty. + DATATYPE NearestNeighbor( const ELEMTYPE a_point[NUMDIMS], + ELEMTYPE a_squareDistanceCallback( const ELEMTYPE a_point[NUMDIMS], DATATYPE a_data ), + ELEMTYPE* a_squareDistance ); + + /// Iterator is not remove safe. + class Iterator + { + private: + + enum { MAX_STACK = 32 }; // Max stack size. Allows almost n^32 where n is number of branches in node + + struct StackElement + { + Node* m_node; + int m_branchIndex; + }; + public: + + Iterator() { Init(); } + + ~Iterator() { } + + /// Is iterator invalid + bool IsNull() { return m_tos <= 0; } + + /// Is iterator pointing to valid data + bool IsNotNull() { return m_tos > 0; } + + /// Access the current data element. Caller must be sure iterator is not NULL first. + DATATYPE& operator*() + { + ASSERT( IsNotNull() ); + StackElement& curTos = m_stack[m_tos - 1]; + return curTos.m_node->m_branch[curTos.m_branchIndex].m_data; + } + + /// Access the current data element. Caller must be sure iterator is not NULL first. + const DATATYPE& operator*() const + { + ASSERT( IsNotNull() ); + StackElement& curTos = m_stack[m_tos - 1]; + return curTos.m_node->m_branch[curTos.m_branchIndex].m_data; + } + + /// Find the next data element + bool operator++() { return FindNextData(); } + + /// Get the bounds for this node + void GetBounds( ELEMTYPE a_min[NUMDIMS], ELEMTYPE a_max[NUMDIMS] ) + { + ASSERT( IsNotNull() ); + StackElement& curTos = m_stack[m_tos - 1]; + Branch& curBranch = curTos.m_node->m_branch[curTos.m_branchIndex]; + + for( int index = 0; index < NUMDIMS; ++index ) + { + a_min[index] = curBranch.m_rect.m_min[index]; + a_max[index] = curBranch.m_rect.m_max[index]; + } + } + + private: + + /// Reset iterator + void Init() { m_tos = 0; } + + /// Find the next data element in the tree (For internal use only) + bool FindNextData() + { + for( ; ; ) + { + if( m_tos <= 0 ) + { + return false; + } + + StackElement curTos = Pop(); // Copy stack top cause it may change as we use it + + if( curTos.m_node->IsLeaf() ) + { + // Keep walking through data while we can + if( curTos.m_branchIndex + 1 < curTos.m_node->m_count ) + { + // There is more data, just point to the next one + Push( curTos.m_node, curTos.m_branchIndex + 1 ); + return true; + } + + // No more data, so it will fall back to previous level + } + else + { + if( curTos.m_branchIndex + 1 < curTos.m_node->m_count ) + { + // Push sibling on for future tree walk + // This is the 'fall back' node when we finish with the current level + Push( curTos.m_node, curTos.m_branchIndex + 1 ); + } + + // Since cur node is not a leaf, push first of next level to get deeper into the tree + Node* nextLevelnode = curTos.m_node->m_branch[curTos.m_branchIndex].m_child; + Push( nextLevelnode, 0 ); + + // If we pushed on a new leaf, exit as the data is ready at TOS + if( nextLevelnode->IsLeaf() ) + { + return true; + } + } + } + } + + /// Push node and branch onto iteration stack (For internal use only) + void Push( Node* a_node, int a_branchIndex ) + { + m_stack[m_tos].m_node = a_node; + m_stack[m_tos].m_branchIndex = a_branchIndex; + ++m_tos; + ASSERT( m_tos <= MAX_STACK ); + } + + /// Pop element off iteration stack (For internal use only) + StackElement& Pop() + { + ASSERT( m_tos > 0 ); + --m_tos; + return m_stack[m_tos]; + } + + StackElement m_stack[MAX_STACK]; ///< Stack as we are doing iteration instead of recursion + int m_tos; ///< Top Of Stack index + + friend class RTree; // Allow hiding of non-public functions while allowing manipulation by logical owner + }; + + + /// Get 'first' for iteration + void GetFirst( Iterator& a_it ) + { + a_it.Init(); + Node* first = m_root; + + while( first ) + { + if( first->IsInternalNode() && first->m_count > 1 ) + { + a_it.Push( first, 1 ); // Descend sibling branch later + } + else if( first->IsLeaf() ) + { + if( first->m_count ) + { + a_it.Push( first, 0 ); + } + + break; + } + + first = first->m_branch[0].m_child; + } + } + + /// Get Next for iteration + void GetNext( Iterator& a_it ) { ++a_it; } + + /// Is iterator NULL, or at end? + bool IsNull( Iterator& a_it ) { return a_it.IsNull(); } + + /// Get object at iterator position + DATATYPE& GetAt( Iterator& a_it ) { return *a_it; } +protected: + + /// Minimal bounding rectangle (n-dimensional) + struct Rect + { + ELEMTYPE m_min[NUMDIMS]; ///< Min dimensions of bounding box + ELEMTYPE m_max[NUMDIMS]; ///< Max dimensions of bounding box + }; + + /// May be data or may be another subtree + /// The parents level determines this. + /// If the parents level is 0, then this is data + struct Branch + { + Rect m_rect; ///< Bounds + union + { + Node* m_child; ///< Child node + DATATYPE m_data; ///< Data Id or Ptr + }; + }; + + /// Node for each branch level + struct Node + { + bool IsInternalNode() { return m_level > 0; } // Not a leaf, but a internal node + bool IsLeaf() { return m_level == 0; } // A leaf, contains data + + int m_count; ///< Count + int m_level; ///< Leaf is zero, others positive + Branch m_branch[MAXNODES]; ///< Branch + }; + + /// A link list of nodes for reinsertion after a delete operation + struct ListNode + { + ListNode* m_next; ///< Next in list + Node* m_node; ///< Node + }; + + /// Variables for finding a split partition + struct PartitionVars + { + int m_partition[MAXNODES + 1]; + int m_total; + int m_minFill; + int m_taken[MAXNODES + 1]; + int m_count[2]; + Rect m_cover[2]; + ELEMTYPEREAL m_area[2]; + + Branch m_branchBuf[MAXNODES + 1]; + int m_branchCount; + Rect m_coverSplit; + ELEMTYPEREAL m_coverSplitArea; + }; + + /// Data structure used for Nearest Neighbor search implementation + struct NNNode + { + Branch m_branch; + ELEMTYPE minDist; + bool isLeaf; + }; + + Node* AllocNode(); + void FreeNode( Node* a_node ); + void InitNode( Node* a_node ); + void InitRect( Rect* a_rect ); + bool InsertRectRec( Rect* a_rect, + const DATATYPE& a_id, + Node* a_node, + Node** a_newNode, + int a_level ); + bool InsertRect( Rect* a_rect, const DATATYPE& a_id, Node** a_root, int a_level ); + Rect NodeCover( Node* a_node ); + bool AddBranch( Branch* a_branch, Node* a_node, Node** a_newNode ); + void DisconnectBranch( Node* a_node, int a_index ); + int PickBranch( Rect* a_rect, Node* a_node ); + Rect CombineRect( Rect* a_rectA, Rect* a_rectB ); + void SplitNode( Node* a_node, Branch* a_branch, Node** a_newNode ); + ELEMTYPEREAL RectSphericalVolume( Rect* a_rect ); + ELEMTYPEREAL RectVolume( Rect* a_rect ); + ELEMTYPEREAL CalcRectVolume( Rect* a_rect ); + void GetBranches( Node* a_node, Branch* a_branch, PartitionVars* a_parVars ); + void ChoosePartition( PartitionVars* a_parVars, int a_minFill ); + void LoadNodes( Node* a_nodeA, Node* a_nodeB, PartitionVars* a_parVars ); + void InitParVars( PartitionVars* a_parVars, int a_maxRects, int a_minFill ); + void PickSeeds( PartitionVars* a_parVars ); + void Classify( int a_index, int a_group, PartitionVars* a_parVars ); + bool RemoveRect( Rect* a_rect, const DATATYPE& a_id, Node** a_root ); + bool RemoveRectRec( Rect* a_rect, + const DATATYPE& a_id, + Node* a_node, + ListNode** a_listNode ); + ListNode* AllocListNode(); + void FreeListNode( ListNode* a_listNode ); + bool Overlap( Rect* a_rectA, Rect* a_rectB ); + void ReInsert( Node* a_node, ListNode** a_listNode ); + ELEMTYPE MinDist( const ELEMTYPE a_point[NUMDIMS], Rect* a_rect ); + void InsertNNListSorted( std::vector<NNNode*>* nodeList, NNNode* newNode ); + + bool Search( Node * a_node, Rect * a_rect, int& a_foundCount, bool a_resultCallback( + DATATYPE a_data, + void* a_context ), void* a_context ); + + template <class VISITOR> + bool Search( Node* a_node, Rect* a_rect, VISITOR& a_visitor, int& a_foundCount ) + { + ASSERT( a_node ); + ASSERT( a_node->m_level >= 0 ); + ASSERT( a_rect ); + + if( a_node->IsInternalNode() ) // This is an internal node in the tree + { + for( int index = 0; index < a_node->m_count; ++index ) + { + if( Overlap( a_rect, &a_node->m_branch[index].m_rect ) ) + { + if( !Search( a_node->m_branch[index].m_child, a_rect, a_visitor, a_foundCount ) ) + { + return false; // Don't continue searching + } + } + } + } + else // This is a leaf node + { + for( int index = 0; index < a_node->m_count; ++index ) + { + if( Overlap( a_rect, &a_node->m_branch[index].m_rect ) ) + { + DATATYPE& id = a_node->m_branch[index].m_data; + + if( !a_visitor( id ) ) + return false; + + a_foundCount++; + } + } + } + + return true; // Continue searching + } + + void RemoveAllRec( Node* a_node ); + void Reset(); + void CountRec( Node* a_node, int& a_count ); + + bool SaveRec( Node* a_node, RTFileStream& a_stream ); + bool LoadRec( Node* a_node, RTFileStream& a_stream ); + + Node* m_root; ///< Root of tree + ELEMTYPEREAL m_unitSphereVolume; ///< Unit sphere constant for required number of dimensions +}; + + +// Because there is not stream support, this is a quick and dirty file I/O helper. +// Users will likely replace its usage with a Stream implementation from their favorite API. +class RTFileStream +{ + FILE* m_file; +public: + + + RTFileStream() + { + m_file = NULL; + } + + ~RTFileStream() + { + Close(); + } + + bool OpenRead( const char* a_fileName ) + { + m_file = fopen( a_fileName, "rb" ); + + if( !m_file ) + { + return false; + } + + return true; + } + + bool OpenWrite( const char* a_fileName ) + { + m_file = fopen( a_fileName, "wb" ); + + if( !m_file ) + { + return false; + } + + return true; + } + + void Close() + { + if( m_file ) + { + fclose( m_file ); + m_file = NULL; + } + } + + template <typename TYPE> + size_t Write( const TYPE& a_value ) + { + ASSERT( m_file ); + return fwrite( (void*) &a_value, sizeof(a_value), 1, m_file ); + } + + template <typename TYPE> + size_t WriteArray( const TYPE* a_array, int a_count ) + { + ASSERT( m_file ); + return fwrite( (void*) a_array, sizeof(TYPE) * a_count, 1, m_file ); + } + + template <typename TYPE> + size_t Read( TYPE& a_value ) + { + ASSERT( m_file ); + return fread( (void*) &a_value, sizeof(a_value), 1, m_file ); + } + + template <typename TYPE> + size_t ReadArray( TYPE* a_array, int a_count ) + { + ASSERT( m_file ); + return fread( (void*) a_array, sizeof(TYPE) * a_count, 1, m_file ); + } +}; + + +RTREE_TEMPLATE RTREE_QUAL::RTree() +{ + ASSERT( MAXNODES > MINNODES ); + ASSERT( MINNODES > 0 ); + + + // We only support machine word size simple data type eg. integer index or object pointer. + // Since we are storing as union with non data branch + ASSERT( sizeof(DATATYPE) == sizeof(void*) || sizeof(DATATYPE) == sizeof(int) ); + + // Precomputed volumes of the unit spheres for the first few dimensions + const float UNIT_SPHERE_VOLUMES[] = + { + 0.000000f, 2.000000f, 3.141593f, // Dimension 0,1,2 + 4.188790f, 4.934802f, 5.263789f, // Dimension 3,4,5 + 5.167713f, 4.724766f, 4.058712f, // Dimension 6,7,8 + 3.298509f, 2.550164f, 1.884104f, // Dimension 9,10,11 + 1.335263f, 0.910629f, 0.599265f, // Dimension 12,13,14 + 0.381443f, 0.235331f, 0.140981f, // Dimension 15,16,17 + 0.082146f, 0.046622f, 0.025807f, // Dimension 18,19,20 + }; + + m_root = AllocNode(); + m_root->m_level = 0; + m_unitSphereVolume = (ELEMTYPEREAL) UNIT_SPHERE_VOLUMES[NUMDIMS]; +} + + +RTREE_TEMPLATE +RTREE_QUAL::~RTree() { + Reset(); // Free, or reset node memory +} + + +RTREE_TEMPLATE +void RTREE_QUAL::Insert( const ELEMTYPE a_min[NUMDIMS], + const ELEMTYPE a_max[NUMDIMS], + const DATATYPE& a_dataId ) +{ +#ifdef _DEBUG + + for( int index = 0; index<NUMDIMS; ++index ) + { + ASSERT( a_min[index] <= a_max[index] ); + } + +#endif // _DEBUG + + Rect rect; + + for( int axis = 0; axis<NUMDIMS; ++axis ) + { + rect.m_min[axis] = a_min[axis]; + rect.m_max[axis] = a_max[axis]; + } + + InsertRect( &rect, a_dataId, &m_root, 0 ); +} + + +RTREE_TEMPLATE +void RTREE_QUAL::Remove( const ELEMTYPE a_min[NUMDIMS], + const ELEMTYPE a_max[NUMDIMS], + const DATATYPE& a_dataId ) +{ +#ifdef _DEBUG + + for( int index = 0; index<NUMDIMS; ++index ) + { + ASSERT( a_min[index] <= a_max[index] ); + } + +#endif // _DEBUG + + Rect rect; + + for( int axis = 0; axis<NUMDIMS; ++axis ) + { + rect.m_min[axis] = a_min[axis]; + rect.m_max[axis] = a_max[axis]; + } + + RemoveRect( &rect, a_dataId, &m_root ); +} + + +RTREE_TEMPLATE +int RTREE_QUAL::Search( const ELEMTYPE a_min[NUMDIMS], + const ELEMTYPE a_max[NUMDIMS], + bool a_resultCallback( DATATYPE a_data, void* a_context ), + void* a_context ) +{ +#ifdef _DEBUG + + for( int index = 0; index<NUMDIMS; ++index ) + { + ASSERT( a_min[index] <= a_max[index] ); + } + +#endif // _DEBUG + + Rect rect; + + for( int axis = 0; axis<NUMDIMS; ++axis ) + { + rect.m_min[axis] = a_min[axis]; + rect.m_max[axis] = a_max[axis]; + } + + // NOTE: May want to return search result another way, perhaps returning the number of found elements here. + + int foundCount = 0; + Search( m_root, &rect, foundCount, a_resultCallback, a_context ); + + return foundCount; +} + + +RTREE_TEMPLATE +DATATYPE RTREE_QUAL::NearestNeighbor( const ELEMTYPE a_point[NUMDIMS] ) +{ + return this->NearestNeighbor( a_point, 0, 0 ); +} + + +RTREE_TEMPLATE +DATATYPE RTREE_QUAL::NearestNeighbor( const ELEMTYPE a_point[NUMDIMS], + ELEMTYPE a_squareDistanceCallback( const ELEMTYPE a_point[NUMDIMS], DATATYPE a_data ), + ELEMTYPE* a_squareDistance ) +{ + typedef typename std::vector<NNNode*>::iterator iterator; + std::vector<NNNode*> nodeList; + Node* node = m_root; + NNNode* closestNode = 0; + while( !closestNode || !closestNode->isLeaf ) + { + //check every node on this level + for( int index = 0; index < node->m_count; ++index ) + { + NNNode* newNode = new NNNode; + newNode->isLeaf = node->IsLeaf(); + newNode->m_branch = node->m_branch[index]; + if( newNode->isLeaf && a_squareDistanceCallback ) + newNode->minDist = a_squareDistanceCallback( a_point, newNode->m_branch.m_data ); + else + newNode->minDist = this->MinDist( a_point, &(node->m_branch[index].m_rect) ); + + //TODO: a custom list could be more efficient than a vector + this->InsertNNListSorted( &nodeList, newNode ); + } + if( nodeList.size() == 0 ) + { + return 0; + } + closestNode = nodeList.back(); + node = closestNode->m_branch.m_child; + nodeList.pop_back(); + free(closestNode); + } + + // free memory used for remaining NNNodes in nodeList + for( iterator iter = nodeList.begin(); iter != nodeList.end(); ++iter ) + { + NNNode* node = *iter; + free(node); + } + + *a_squareDistance = closestNode->minDist; + return closestNode->m_branch.m_data; +} + + +RTREE_TEMPLATE +int RTREE_QUAL::Count() +{ + int count = 0; + + CountRec( m_root, count ); + + return count; +} + + +RTREE_TEMPLATE +void RTREE_QUAL::CountRec( Node* a_node, int& a_count ) +{ + if( a_node->IsInternalNode() ) // not a leaf node + { + for( int index = 0; index < a_node->m_count; ++index ) + { + CountRec( a_node->m_branch[index].m_child, a_count ); + } + } + else // A leaf node + { + a_count += a_node->m_count; + } +} + + +RTREE_TEMPLATE +bool RTREE_QUAL::Load( const char* a_fileName ) +{ + RemoveAll(); // Clear existing tree + + RTFileStream stream; + + if( !stream.OpenRead( a_fileName ) ) + { + return false; + } + + bool result = Load( stream ); + + stream.Close(); + + return result; +}; + + +RTREE_TEMPLATE +bool RTREE_QUAL::Load( RTFileStream& a_stream ) +{ + // Write some kind of header + int _dataFileId = ('R' << 0) | ('T' << 8) | ('R' << 16) | ('E' << 24); + int _dataSize = sizeof(DATATYPE); + int _dataNumDims = NUMDIMS; + int _dataElemSize = sizeof(ELEMTYPE); + int _dataElemRealSize = sizeof(ELEMTYPEREAL); + int _dataMaxNodes = TMAXNODES; + int _dataMinNodes = TMINNODES; + + int dataFileId = 0; + int dataSize = 0; + int dataNumDims = 0; + int dataElemSize = 0; + int dataElemRealSize = 0; + int dataMaxNodes = 0; + int dataMinNodes = 0; + + a_stream.Read( dataFileId ); + a_stream.Read( dataSize ); + a_stream.Read( dataNumDims ); + a_stream.Read( dataElemSize ); + a_stream.Read( dataElemRealSize ); + a_stream.Read( dataMaxNodes ); + a_stream.Read( dataMinNodes ); + + bool result = false; + + // Test if header was valid and compatible + if( (dataFileId == _dataFileId) + && (dataSize == _dataSize) + && (dataNumDims == _dataNumDims) + && (dataElemSize == _dataElemSize) + && (dataElemRealSize == _dataElemRealSize) + && (dataMaxNodes == _dataMaxNodes) + && (dataMinNodes == _dataMinNodes) + ) + { + // Recursively load tree + result = LoadRec( m_root, a_stream ); + } + + return result; +} + + +RTREE_TEMPLATE +bool RTREE_QUAL::LoadRec( Node* a_node, RTFileStream& a_stream ) +{ + a_stream.Read( a_node->m_level ); + a_stream.Read( a_node->m_count ); + + if( a_node->IsInternalNode() ) // not a leaf node + { + for( int index = 0; index < a_node->m_count; ++index ) + { + Branch* curBranch = &a_node->m_branch[index]; + + a_stream.ReadArray( curBranch->m_rect.m_min, NUMDIMS ); + a_stream.ReadArray( curBranch->m_rect.m_max, NUMDIMS ); + + curBranch->m_child = AllocNode(); + LoadRec( curBranch->m_child, a_stream ); + } + } + else // A leaf node + { + for( int index = 0; index < a_node->m_count; ++index ) + { + Branch* curBranch = &a_node->m_branch[index]; + + a_stream.ReadArray( curBranch->m_rect.m_min, NUMDIMS ); + a_stream.ReadArray( curBranch->m_rect.m_max, NUMDIMS ); + + a_stream.Read( curBranch->m_data ); + } + } + + return true; // Should do more error checking on I/O operations +} + + +RTREE_TEMPLATE +bool RTREE_QUAL::Save( const char* a_fileName ) +{ + RTFileStream stream; + + if( !stream.OpenWrite( a_fileName ) ) + { + return false; + } + + bool result = Save( stream ); + + stream.Close(); + + return result; +} + + +RTREE_TEMPLATE +bool RTREE_QUAL::Save( RTFileStream& a_stream ) +{ + // Write some kind of header + int dataFileId = ('R' << 0) | ('T' << 8) | ('R' << 16) | ('E' << 24); + int dataSize = sizeof(DATATYPE); + int dataNumDims = NUMDIMS; + int dataElemSize = sizeof(ELEMTYPE); + int dataElemRealSize = sizeof(ELEMTYPEREAL); + int dataMaxNodes = TMAXNODES; + int dataMinNodes = TMINNODES; + + a_stream.Write( dataFileId ); + a_stream.Write( dataSize ); + a_stream.Write( dataNumDims ); + a_stream.Write( dataElemSize ); + a_stream.Write( dataElemRealSize ); + a_stream.Write( dataMaxNodes ); + a_stream.Write( dataMinNodes ); + + // Recursively save tree + bool result = SaveRec( m_root, a_stream ); + + return result; +} + + +RTREE_TEMPLATE +bool RTREE_QUAL::SaveRec( Node* a_node, RTFileStream& a_stream ) +{ + a_stream.Write( a_node->m_level ); + a_stream.Write( a_node->m_count ); + + if( a_node->IsInternalNode() ) // not a leaf node + { + for( int index = 0; index < a_node->m_count; ++index ) + { + Branch* curBranch = &a_node->m_branch[index]; + + a_stream.WriteArray( curBranch->m_rect.m_min, NUMDIMS ); + a_stream.WriteArray( curBranch->m_rect.m_max, NUMDIMS ); + + SaveRec( curBranch->m_child, a_stream ); + } + } + else // A leaf node + { + for( int index = 0; index < a_node->m_count; ++index ) + { + Branch* curBranch = &a_node->m_branch[index]; + + a_stream.WriteArray( curBranch->m_rect.m_min, NUMDIMS ); + a_stream.WriteArray( curBranch->m_rect.m_max, NUMDIMS ); + + a_stream.Write( curBranch->m_data ); + } + } + + return true; // Should do more error checking on I/O operations +} + + +RTREE_TEMPLATE +void RTREE_QUAL::RemoveAll() +{ + // Delete all existing nodes + Reset(); + + m_root = AllocNode(); + m_root->m_level = 0; +} + + +RTREE_TEMPLATE +void RTREE_QUAL::Reset() +{ +#ifdef RTREE_DONT_USE_MEMPOOLS + // Delete all existing nodes + RemoveAllRec( m_root ); +#else // RTREE_DONT_USE_MEMPOOLS + // Just reset memory pools. We are not using complex types + // EXAMPLE +#endif // RTREE_DONT_USE_MEMPOOLS +} + + +RTREE_TEMPLATE +void RTREE_QUAL::RemoveAllRec( Node* a_node ) +{ + ASSERT( a_node ); + ASSERT( a_node->m_level >= 0 ); + + if( a_node->IsInternalNode() ) // This is an internal node in the tree + { + for( int index = 0; index < a_node->m_count; ++index ) + { + RemoveAllRec( a_node->m_branch[index].m_child ); + } + } + + FreeNode( a_node ); +} + + +RTREE_TEMPLATE +typename RTREE_QUAL::Node* RTREE_QUAL::AllocNode() +{ + Node* newNode; + +#ifdef RTREE_DONT_USE_MEMPOOLS + newNode = new Node; +#else // RTREE_DONT_USE_MEMPOOLS + // EXAMPLE +#endif // RTREE_DONT_USE_MEMPOOLS + InitNode( newNode ); + return newNode; +} + + +RTREE_TEMPLATE +void RTREE_QUAL::FreeNode( Node* a_node ) +{ + ASSERT( a_node ); + +#ifdef RTREE_DONT_USE_MEMPOOLS + delete a_node; +#else // RTREE_DONT_USE_MEMPOOLS + // EXAMPLE +#endif // RTREE_DONT_USE_MEMPOOLS +} + + +// Allocate space for a node in the list used in DeletRect to +// store Nodes that are too empty. +RTREE_TEMPLATE +typename RTREE_QUAL::ListNode* RTREE_QUAL::AllocListNode() +{ +#ifdef RTREE_DONT_USE_MEMPOOLS + return new ListNode; +#else // RTREE_DONT_USE_MEMPOOLS + // EXAMPLE +#endif // RTREE_DONT_USE_MEMPOOLS +} + + +RTREE_TEMPLATE +void RTREE_QUAL::FreeListNode( ListNode* a_listNode ) +{ +#ifdef RTREE_DONT_USE_MEMPOOLS + delete a_listNode; +#else // RTREE_DONT_USE_MEMPOOLS + // EXAMPLE +#endif // RTREE_DONT_USE_MEMPOOLS +} + + +RTREE_TEMPLATE +void RTREE_QUAL::InitNode( Node* a_node ) +{ + a_node->m_count = 0; + a_node->m_level = -1; +} + + +RTREE_TEMPLATE +void RTREE_QUAL::InitRect( Rect* a_rect ) +{ + for( int index = 0; index < NUMDIMS; ++index ) + { + a_rect->m_min[index] = (ELEMTYPE) 0; + a_rect->m_max[index] = (ELEMTYPE) 0; + } +} + + +// Inserts a new data rectangle into the index structure. +// Recursively descends tree, propagates splits back up. +// Returns 0 if node was not split. Old node updated. +// If node was split, returns 1 and sets the pointer pointed to by +// new_node to point to the new node. Old node updated to become one of two. +// The level argument specifies the number of steps up from the leaf +// level to insert; e.g. a data rectangle goes in at level = 0. +RTREE_TEMPLATE +bool RTREE_QUAL::InsertRectRec( Rect* a_rect, + const DATATYPE& a_id, + Node* a_node, + Node** a_newNode, + int a_level ) +{ + ASSERT( a_rect && a_node && a_newNode ); + ASSERT( a_level >= 0 && a_level <= a_node->m_level ); + + int index; + Branch branch; + Node* otherNode; + + // Still above level for insertion, go down tree recursively + if( a_node->m_level > a_level ) + { + index = PickBranch( a_rect, a_node ); + + if( !InsertRectRec( a_rect, a_id, a_node->m_branch[index].m_child, &otherNode, a_level ) ) + { + // Child was not split + a_node->m_branch[index].m_rect = + CombineRect( a_rect, &(a_node->m_branch[index].m_rect) ); + return false; + } + else // Child was split + { + a_node->m_branch[index].m_rect = NodeCover( a_node->m_branch[index].m_child ); + branch.m_child = otherNode; + branch.m_rect = NodeCover( otherNode ); + return AddBranch( &branch, a_node, a_newNode ); + } + } + else if( a_node->m_level == a_level ) // Have reached level for insertion. Add rect, split if necessary + { + branch.m_rect = *a_rect; + branch.m_child = (Node*) a_id; + // Child field of leaves contains id of data record + return AddBranch( &branch, a_node, a_newNode ); + } + else + { + // Should never occur + ASSERT( 0 ); + return false; + } +} + + +// Insert a data rectangle into an index structure. +// InsertRect provides for splitting the root; +// returns 1 if root was split, 0 if it was not. +// The level argument specifies the number of steps up from the leaf +// level to insert; e.g. a data rectangle goes in at level = 0. +// InsertRect2 does the recursion. +// +RTREE_TEMPLATE +bool RTREE_QUAL::InsertRect( Rect* a_rect, const DATATYPE& a_id, Node** a_root, int a_level ) +{ + ASSERT( a_rect && a_root ); + ASSERT( a_level >= 0 && a_level <= (*a_root)->m_level ); +#ifdef _DEBUG + + for( int index = 0; index < NUMDIMS; ++index ) + { + ASSERT( a_rect->m_min[index] <= a_rect->m_max[index] ); + } + +#endif // _DEBUG + + Node* newRoot; + Node* newNode; + Branch branch; + + if( InsertRectRec( a_rect, a_id, *a_root, &newNode, a_level ) ) // Root split + { + newRoot = AllocNode(); // Grow tree taller and new root + newRoot->m_level = (*a_root)->m_level + 1; + branch.m_rect = NodeCover( *a_root ); + branch.m_child = *a_root; + AddBranch( &branch, newRoot, NULL ); + branch.m_rect = NodeCover( newNode ); + branch.m_child = newNode; + AddBranch( &branch, newRoot, NULL ); + *a_root = newRoot; + return true; + } + + return false; +} + + +// Find the smallest rectangle that includes all rectangles in branches of a node. +RTREE_TEMPLATE +typename RTREE_QUAL::Rect RTREE_QUAL::NodeCover( Node* a_node ) +{ + ASSERT( a_node ); + + int firstTime = true; + Rect rect; + InitRect( &rect ); + + for( int index = 0; index < a_node->m_count; ++index ) + { + if( firstTime ) + { + rect = a_node->m_branch[index].m_rect; + firstTime = false; + } + else + { + rect = CombineRect( &rect, &(a_node->m_branch[index].m_rect) ); + } + } + + return rect; +} + + +// Add a branch to a node. Split the node if necessary. +// Returns 0 if node not split. Old node updated. +// Returns 1 if node split, sets *new_node to address of new node. +// Old node updated, becomes one of two. +RTREE_TEMPLATE +bool RTREE_QUAL::AddBranch( Branch* a_branch, Node* a_node, Node** a_newNode ) +{ + ASSERT( a_branch ); + ASSERT( a_node ); + + if( a_node->m_count < MAXNODES ) // Split won't be necessary + { + a_node->m_branch[a_node->m_count] = *a_branch; + ++a_node->m_count; + + return false; + } + else + { + ASSERT( a_newNode ); + + SplitNode( a_node, a_branch, a_newNode ); + return true; + } +} + + +// Disconnect a dependent node. +// Caller must return (or stop using iteration index) after this as count has changed +RTREE_TEMPLATE +void RTREE_QUAL::DisconnectBranch( Node* a_node, int a_index ) +{ + ASSERT( a_node && (a_index >= 0) && (a_index < MAXNODES) ); + ASSERT( a_node->m_count > 0 ); + + // Remove element by swapping with the last element to prevent gaps in array + a_node->m_branch[a_index] = a_node->m_branch[a_node->m_count - 1]; + + --a_node->m_count; +} + + +// Pick a branch. Pick the one that will need the smallest increase +// in area to accomodate the new rectangle. This will result in the +// least total area for the covering rectangles in the current node. +// In case of a tie, pick the one which was smaller before, to get +// the best resolution when searching. +RTREE_TEMPLATE +int RTREE_QUAL::PickBranch( Rect* a_rect, Node* a_node ) +{ + ASSERT( a_rect && a_node ); + + bool firstTime = true; + ELEMTYPEREAL increase; + ELEMTYPEREAL bestIncr = (ELEMTYPEREAL) -1; + ELEMTYPEREAL area; + ELEMTYPEREAL bestArea = 0; + int best = 0; + Rect tempRect; + + for( int index = 0; index < a_node->m_count; ++index ) + { + Rect* curRect = &a_node->m_branch[index].m_rect; + area = CalcRectVolume( curRect ); + tempRect = CombineRect( a_rect, curRect ); + increase = CalcRectVolume( &tempRect ) - area; + + if( (increase < bestIncr) || firstTime ) + { + best = index; + bestArea = area; + bestIncr = increase; + firstTime = false; + } + else if( (increase == bestIncr) && (area < bestArea) ) + { + best = index; + bestArea = area; + bestIncr = increase; + } + } + + return best; +} + + +// Combine two rectangles into larger one containing both +RTREE_TEMPLATE +typename RTREE_QUAL::Rect RTREE_QUAL::CombineRect( Rect* a_rectA, Rect* a_rectB ) +{ + ASSERT( a_rectA && a_rectB ); + + Rect newRect; + + for( int index = 0; index < NUMDIMS; ++index ) + { + newRect.m_min[index] = rMin( a_rectA->m_min[index], a_rectB->m_min[index] ); + newRect.m_max[index] = rMax( a_rectA->m_max[index], a_rectB->m_max[index] ); + } + + return newRect; +} + + +// Split a node. +// Divides the nodes branches and the extra one between two nodes. +// Old node is one of the new ones, and one really new one is created. +// Tries more than one method for choosing a partition, uses best result. +RTREE_TEMPLATE +void RTREE_QUAL::SplitNode( Node* a_node, Branch* a_branch, Node** a_newNode ) +{ + ASSERT( a_node ); + ASSERT( a_branch ); + + // Could just use local here, but member or external is faster since it is reused + PartitionVars localVars; + PartitionVars* parVars = &localVars; + int level; + + // Load all the branches into a buffer, initialize old node + level = a_node->m_level; + GetBranches( a_node, a_branch, parVars ); + + // Find partition + ChoosePartition( parVars, MINNODES ); + + // Put branches from buffer into 2 nodes according to chosen partition + *a_newNode = AllocNode(); + (*a_newNode)->m_level = a_node->m_level = level; + LoadNodes( a_node, *a_newNode, parVars ); + + ASSERT( (a_node->m_count + (*a_newNode)->m_count) == parVars->m_total ); +} + + +// Calculate the n-dimensional volume of a rectangle +RTREE_TEMPLATE +ELEMTYPEREAL RTREE_QUAL::RectVolume( Rect* a_rect ) +{ + ASSERT( a_rect ); + + ELEMTYPEREAL volume = (ELEMTYPEREAL) 1; + + for( int index = 0; index<NUMDIMS; ++index ) + { + volume *= a_rect->m_max[index] - a_rect->m_min[index]; + } + + ASSERT( volume >= (ELEMTYPEREAL) 0 ); + + return volume; +} + + +// The exact volume of the bounding sphere for the given Rect +RTREE_TEMPLATE +ELEMTYPEREAL RTREE_QUAL::RectSphericalVolume( Rect* a_rect ) +{ + ASSERT( a_rect ); + + ELEMTYPEREAL sumOfSquares = (ELEMTYPEREAL) 0; + ELEMTYPEREAL radius; + + for( int index = 0; index < NUMDIMS; ++index ) + { + ELEMTYPEREAL halfExtent = + ( (ELEMTYPEREAL) a_rect->m_max[index] - (ELEMTYPEREAL) a_rect->m_min[index] ) * 0.5f; + sumOfSquares += halfExtent * halfExtent; + } + + radius = (ELEMTYPEREAL) sqrt( sumOfSquares ); + + // Pow maybe slow, so test for common dims like 2,3 and just use x*x, x*x*x. + if( NUMDIMS == 3 ) + { + return radius * radius * radius * m_unitSphereVolume; + } + else if( NUMDIMS == 2 ) + { + return radius * radius * m_unitSphereVolume; + } + else + { + return (ELEMTYPEREAL) (pow( radius, NUMDIMS ) * m_unitSphereVolume); + } +} + + +// Use one of the methods to calculate retangle volume +RTREE_TEMPLATE +ELEMTYPEREAL RTREE_QUAL::CalcRectVolume( Rect* a_rect ) +{ +#ifdef RTREE_USE_SPHERICAL_VOLUME + return RectSphericalVolume( a_rect ); // Slower but helps certain merge cases +#else // RTREE_USE_SPHERICAL_VOLUME + return RectVolume( a_rect ); // Faster but can cause poor merges +#endif // RTREE_USE_SPHERICAL_VOLUME +} + + +// Load branch buffer with branches from full node plus the extra branch. +RTREE_TEMPLATE +void RTREE_QUAL::GetBranches( Node* a_node, Branch* a_branch, PartitionVars* a_parVars ) +{ + ASSERT( a_node ); + ASSERT( a_branch ); + + ASSERT( a_node->m_count == MAXNODES ); + + // Load the branch buffer + for( int index = 0; index < MAXNODES; ++index ) + { + a_parVars->m_branchBuf[index] = a_node->m_branch[index]; + } + + a_parVars->m_branchBuf[MAXNODES] = *a_branch; + a_parVars->m_branchCount = MAXNODES + 1; + + // Calculate rect containing all in the set + a_parVars->m_coverSplit = a_parVars->m_branchBuf[0].m_rect; + + for( int index = 1; index < MAXNODES + 1; ++index ) + { + a_parVars->m_coverSplit = + CombineRect( &a_parVars->m_coverSplit, &a_parVars->m_branchBuf[index].m_rect ); + } + + a_parVars->m_coverSplitArea = CalcRectVolume( &a_parVars->m_coverSplit ); + + InitNode( a_node ); +} + + +// Method #0 for choosing a partition: +// As the seeds for the two groups, pick the two rects that would waste the +// most area if covered by a single rectangle, i.e. evidently the worst pair +// to have in the same group. +// Of the remaining, one at a time is chosen to be put in one of the two groups. +// The one chosen is the one with the greatest difference in area expansion +// depending on which group - the rect most strongly attracted to one group +// and repelled from the other. +// If one group gets too full (more would force other group to violate min +// fill requirement) then other group gets the rest. +// These last are the ones that can go in either group most easily. +RTREE_TEMPLATE +void RTREE_QUAL::ChoosePartition( PartitionVars* a_parVars, int a_minFill ) +{ + ASSERT( a_parVars ); + + ELEMTYPEREAL biggestDiff; + int group, chosen = 0, betterGroup = 0; + + InitParVars( a_parVars, a_parVars->m_branchCount, a_minFill ); + PickSeeds( a_parVars ); + + while( ( (a_parVars->m_count[0] + a_parVars->m_count[1]) < a_parVars->m_total ) + && ( a_parVars->m_count[0] < (a_parVars->m_total - a_parVars->m_minFill) ) + && ( a_parVars->m_count[1] < (a_parVars->m_total - a_parVars->m_minFill) ) ) + { + biggestDiff = (ELEMTYPEREAL) -1; + + for( int index = 0; index<a_parVars->m_total; ++index ) + { + if( !a_parVars->m_taken[index] ) + { + Rect* curRect = &a_parVars->m_branchBuf[index].m_rect; + Rect rect0 = CombineRect( curRect, &a_parVars->m_cover[0] ); + Rect rect1 = CombineRect( curRect, &a_parVars->m_cover[1] ); + ELEMTYPEREAL growth0 = CalcRectVolume( &rect0 ) - a_parVars->m_area[0]; + ELEMTYPEREAL growth1 = CalcRectVolume( &rect1 ) - a_parVars->m_area[1]; + ELEMTYPEREAL diff = growth1 - growth0; + + if( diff >= 0 ) + { + group = 0; + } + else + { + group = 1; + diff = -diff; + } + + if( diff > biggestDiff ) + { + biggestDiff = diff; + chosen = index; + betterGroup = group; + } + else if( (diff == biggestDiff) + && (a_parVars->m_count[group] < a_parVars->m_count[betterGroup]) ) + { + chosen = index; + betterGroup = group; + } + } + } + + Classify( chosen, betterGroup, a_parVars ); + } + + // If one group too full, put remaining rects in the other + if( (a_parVars->m_count[0] + a_parVars->m_count[1]) < a_parVars->m_total ) + { + if( a_parVars->m_count[0] >= a_parVars->m_total - a_parVars->m_minFill ) + { + group = 1; + } + else + { + group = 0; + } + + for( int index = 0; index<a_parVars->m_total; ++index ) + { + if( !a_parVars->m_taken[index] ) + { + Classify( index, group, a_parVars ); + } + } + } + + ASSERT( (a_parVars->m_count[0] + a_parVars->m_count[1]) == a_parVars->m_total ); + ASSERT( (a_parVars->m_count[0] >= a_parVars->m_minFill) + && (a_parVars->m_count[1] >= a_parVars->m_minFill) ); +} + + +// Copy branches from the buffer into two nodes according to the partition. +RTREE_TEMPLATE +void RTREE_QUAL::LoadNodes( Node* a_nodeA, Node* a_nodeB, PartitionVars* a_parVars ) +{ + ASSERT( a_nodeA ); + ASSERT( a_nodeB ); + ASSERT( a_parVars ); + + for( int index = 0; index < a_parVars->m_total; ++index ) + { + ASSERT( a_parVars->m_partition[index] == 0 || a_parVars->m_partition[index] == 1 ); + + if( a_parVars->m_partition[index] == 0 ) + { + AddBranch( &a_parVars->m_branchBuf[index], a_nodeA, NULL ); + } + else if( a_parVars->m_partition[index] == 1 ) + { + AddBranch( &a_parVars->m_branchBuf[index], a_nodeB, NULL ); + } + } +} + + +// Initialize a PartitionVars structure. +RTREE_TEMPLATE +void RTREE_QUAL::InitParVars( PartitionVars* a_parVars, int a_maxRects, int a_minFill ) +{ + ASSERT( a_parVars ); + + a_parVars->m_count[0] = a_parVars->m_count[1] = 0; + a_parVars->m_area[0] = a_parVars->m_area[1] = (ELEMTYPEREAL) 0; + a_parVars->m_total = a_maxRects; + a_parVars->m_minFill = a_minFill; + + for( int index = 0; index < a_maxRects; ++index ) + { + a_parVars->m_taken[index] = false; + a_parVars->m_partition[index] = -1; + } +} + + +RTREE_TEMPLATE +void RTREE_QUAL::PickSeeds( PartitionVars* a_parVars ) +{ + int seed0 = 0, seed1 = 0; + ELEMTYPEREAL worst, waste; + ELEMTYPEREAL area[MAXNODES + 1]; + + for( int index = 0; index<a_parVars->m_total; ++index ) + { + area[index] = CalcRectVolume( &a_parVars->m_branchBuf[index].m_rect ); + } + + worst = -a_parVars->m_coverSplitArea - 1; + + for( int indexA = 0; indexA < a_parVars->m_total - 1; ++indexA ) + { + for( int indexB = indexA + 1; indexB < a_parVars->m_total; ++indexB ) + { + Rect oneRect = CombineRect( &a_parVars->m_branchBuf[indexA].m_rect, + &a_parVars->m_branchBuf[indexB].m_rect ); + waste = CalcRectVolume( &oneRect ) - area[indexA] - area[indexB]; + + if( waste >= worst ) + { + worst = waste; + seed0 = indexA; + seed1 = indexB; + } + } + } + + Classify( seed0, 0, a_parVars ); + Classify( seed1, 1, a_parVars ); +} + + +// Put a branch in one of the groups. +RTREE_TEMPLATE +void RTREE_QUAL::Classify( int a_index, int a_group, PartitionVars* a_parVars ) +{ + ASSERT( a_parVars ); + ASSERT( !a_parVars->m_taken[a_index] ); + + a_parVars->m_partition[a_index] = a_group; + a_parVars->m_taken[a_index] = true; + + if( a_parVars->m_count[a_group] == 0 ) + { + a_parVars->m_cover[a_group] = a_parVars->m_branchBuf[a_index].m_rect; + } + else + { + a_parVars->m_cover[a_group] = CombineRect( &a_parVars->m_branchBuf[a_index].m_rect, + &a_parVars->m_cover[a_group] ); + } + + a_parVars->m_area[a_group] = CalcRectVolume( &a_parVars->m_cover[a_group] ); + ++a_parVars->m_count[a_group]; +} + + +// Delete a data rectangle from an index structure. +// Pass in a pointer to a Rect, the tid of the record, ptr to ptr to root node. +// Returns 1 if record not found, 0 if success. +// RemoveRect provides for eliminating the root. +RTREE_TEMPLATE +bool RTREE_QUAL::RemoveRect( Rect* a_rect, const DATATYPE& a_id, Node** a_root ) +{ + ASSERT( a_rect && a_root ); + ASSERT( *a_root ); + + Node* tempNode; + ListNode* reInsertList = NULL; + + if( !RemoveRectRec( a_rect, a_id, *a_root, &reInsertList ) ) + { + // Found and deleted a data item + // Reinsert any branches from eliminated nodes + while( reInsertList ) + { + tempNode = reInsertList->m_node; + + for( int index = 0; index < tempNode->m_count; ++index ) + { + InsertRect( &(tempNode->m_branch[index].m_rect), + tempNode->m_branch[index].m_data, + a_root, + tempNode->m_level ); + } + + ListNode* remLNode = reInsertList; + reInsertList = reInsertList->m_next; + + FreeNode( remLNode->m_node ); + FreeListNode( remLNode ); + } + + // Check for redundant root (not leaf, 1 child) and eliminate + if( (*a_root)->m_count == 1 && (*a_root)->IsInternalNode() ) + { + tempNode = (*a_root)->m_branch[0].m_child; + + ASSERT( tempNode ); + FreeNode( *a_root ); + *a_root = tempNode; + } + + return false; + } + else + { + return true; + } +} + + +// Delete a rectangle from non-root part of an index structure. +// Called by RemoveRect. Descends tree recursively, +// merges branches on the way back up. +// Returns 1 if record not found, 0 if success. +RTREE_TEMPLATE +bool RTREE_QUAL::RemoveRectRec( Rect* a_rect, + const DATATYPE& a_id, + Node* a_node, + ListNode** a_listNode ) +{ + ASSERT( a_rect && a_node && a_listNode ); + ASSERT( a_node->m_level >= 0 ); + + if( a_node->IsInternalNode() ) // not a leaf node + { + for( int index = 0; index < a_node->m_count; ++index ) + { + if( Overlap( a_rect, &(a_node->m_branch[index].m_rect) ) ) + { + if( !RemoveRectRec( a_rect, a_id, a_node->m_branch[index].m_child, a_listNode ) ) + { + if( a_node->m_branch[index].m_child->m_count >= MINNODES ) + { + // child removed, just resize parent rect + a_node->m_branch[index].m_rect = + NodeCover( a_node->m_branch[index].m_child ); + } + else + { + // child removed, not enough entries in node, eliminate node + ReInsert( a_node->m_branch[index].m_child, a_listNode ); + DisconnectBranch( a_node, index ); // Must return after this call as count has changed + } + + return false; + } + } + } + + return true; + } + else // A leaf node + { + for( int index = 0; index < a_node->m_count; ++index ) + { + if( a_node->m_branch[index].m_child == (Node*) a_id ) + { + DisconnectBranch( a_node, index ); // Must return after this call as count has changed + return false; + } + } + + return true; + } +} + + +// Decide whether two rectangles overlap. +RTREE_TEMPLATE +bool RTREE_QUAL::Overlap( Rect* a_rectA, Rect* a_rectB ) +{ + ASSERT( a_rectA && a_rectB ); + + for( int index = 0; index < NUMDIMS; ++index ) + { + if( a_rectA->m_min[index] > a_rectB->m_max[index] + || a_rectB->m_min[index] > a_rectA->m_max[index] ) + { + return false; + } + } + + return true; +} + + +// Add a node to the reinsertion list. All its branches will later +// be reinserted into the index structure. +RTREE_TEMPLATE +void RTREE_QUAL::ReInsert( Node* a_node, ListNode** a_listNode ) +{ + ListNode* newListNode; + + newListNode = AllocListNode(); + newListNode->m_node = a_node; + newListNode->m_next = *a_listNode; + *a_listNode = newListNode; +} + + +// Search in an index tree or subtree for all data retangles that overlap the argument rectangle. +RTREE_TEMPLATE +bool RTREE_QUAL::Search( Node* a_node, Rect* a_rect, int& a_foundCount, bool a_resultCallback( + DATATYPE a_data, + void* a_context ), void* a_context ) +{ + ASSERT( a_node ); + ASSERT( a_node->m_level >= 0 ); + ASSERT( a_rect ); + + if( a_node->IsInternalNode() ) // This is an internal node in the tree + { + for( int index = 0; index < a_node->m_count; ++index ) + { + if( Overlap( a_rect, &a_node->m_branch[index].m_rect ) ) + { + if( !Search( a_node->m_branch[index].m_child, a_rect, a_foundCount, + a_resultCallback, a_context ) ) + { + return false; // Don't continue searching + } + } + } + } + else // This is a leaf node + { + for( int index = 0; index < a_node->m_count; ++index ) + { + if( Overlap( a_rect, &a_node->m_branch[index].m_rect ) ) + { + DATATYPE& id = a_node->m_branch[index].m_data; + + // NOTE: There are different ways to return results. Here's where to modify + if( &a_resultCallback ) + { + ++a_foundCount; + + if( !a_resultCallback( id, a_context ) ) + { + return false; // Don't continue searching + } + } + } + } + } + + return true; // Continue searching +} + + +//calculate the minimum distance between a point and a rectangle as defined by Manolopoulos et al. +//it uses the square distance to avoid the use of ELEMTYPEREAL values, which are slower. +RTREE_TEMPLATE +ELEMTYPE RTREE_QUAL::MinDist( const ELEMTYPE a_point[NUMDIMS], Rect* a_rect ) +{ + ELEMTYPE *q, *s, *t; + q = (ELEMTYPE*) a_point; + s = a_rect->m_min; + t = a_rect->m_max; + int minDist = 0; + for( int index = 0; index < NUMDIMS; index++ ) + { + int r = q[index]; + if( q[index] < s[index] ) + { + r = s[index]; + } + else if( q[index] >t[index] ) + { + r = t[index]; + } + int addend = q[index] - r; + minDist += addend * addend; + } + return minDist; +} + + +//insert a NNNode in a list sorted by its minDist (desc.) +RTREE_TEMPLATE +void RTREE_QUAL::InsertNNListSorted( std::vector<NNNode*>* nodeList, NNNode* newNode ) +{ + typedef typename std::vector<NNNode*>::iterator iterator; + iterator iter = nodeList->begin(); + while( iter != nodeList->end() && (*iter)->minDist > newNode->minDist ) + { + ++iter; + } + nodeList->insert(iter, newNode); +} + + +#undef RTREE_TEMPLATE +#undef RTREE_QUAL +#undef RTREE_SEARCH_TEMPLATE +#undef RTREE_SEARCH_QUAL + +#endif // RTREE_H diff --git a/include/geometry/seg.h b/include/geometry/seg.h new file mode 100644 index 0000000..164457b --- /dev/null +++ b/include/geometry/seg.h @@ -0,0 +1,384 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 + */ + +#ifndef __SEG_H +#define __SEG_H + +#include <cstdio> +#include <climits> + +#include <math/vector2d.h> + +#include <boost/optional/optional.hpp> + +typedef boost::optional<VECTOR2I> OPT_VECTOR2I; + +class SEG +{ +private: + typedef VECTOR2I::extended_type ecoord; + +public: + friend inline std::ostream& operator<<( std::ostream& aStream, const SEG& aSeg ); + + /* Start and the of the segment. Public, to make access simpler. These are references + * to an object the segment belongs to (e.g. a line chain) or references to locally stored + * points (m_a, m_b). + */ + VECTOR2I A; + VECTOR2I B; + + /** Default constructor + * Creates an empty (0, 0) segment, locally-referenced + */ + SEG() + { + m_index = -1; + } + + /** + * Constructor + * Creates a segment between (aX1, aY1) and (aX2, aY2), locally referenced + */ + SEG( int aX1, int aY1, int aX2, int aY2 ) : + A ( VECTOR2I( aX1, aY1 ) ), + B ( VECTOR2I( aX2, aY2 ) ) + { + m_index = -1; + } + + /** + * Constructor + * Creates a segment between (aA) and (aB), locally referenced + */ + SEG( const VECTOR2I& aA, const VECTOR2I& aB ) : A( aA ), B( aB ) + { + m_index = -1; + } + + /** + * Constructor + * Creates a segment between (aA) and (aB), referenced to a multi-segment shape + * @param aA reference to the start point in the parent shape + * @param aB reference to the end point in the parent shape + * @param aIndex index of the segment within the parent shape + */ + SEG( const VECTOR2I& aA, const VECTOR2I& aB, int aIndex ) : A( aA ), B( aB ) + { + m_index = aIndex; + } + + /** + * Copy constructor + */ + SEG( const SEG& aSeg ) : A( aSeg.A ), B( aSeg.B ), m_index( aSeg.m_index ) + { + } + + SEG& operator=( const SEG& aSeg ) + { + A = aSeg.A; + B = aSeg.B; + m_index = aSeg.m_index; + + return *this; + } + + /** + * Function LineProject() + * + * Computes the perpendicular projection point of aP on a line passing through + * ends of the segment. + * @param aP point to project + * @return projected point + */ + VECTOR2I LineProject( const VECTOR2I& aP ) const; + + /** + * Function Side() + * + * Determines on which side of directed line passing via segment ends point aP lies. + * @param aP point to determine the orientation wrs to self + * @return: < 0: left, 0 : on the line, > 0 : right + */ + int Side( const VECTOR2I& aP ) const + { + const ecoord det = ( B - A ).Cross( aP - A ); + + return det < 0 ? -1 : ( det > 0 ? 1 : 0 ); + } + + /** + * Function LineDistance() + * + * Returns the closest Euclidean distance between point aP and the line defined by + * the ends of segment (this). + * @param aDetermineSide: when true, the sign of the returned value indicates + * the side of the line at which we are (negative = left) + * @return the distance + */ + int LineDistance( const VECTOR2I& aP, bool aDetermineSide = false ) const; + + /** + * Function NearestPoint() + * + * Computes a point on the segment (this) that is closest to point aP. + * @return: nearest point + */ + const VECTOR2I NearestPoint( const VECTOR2I &aP ) const; + + /** + * Function Intersect() + * + * Computes intersection point of segment (this) with segment aSeg. + * @param aSeg: segment to intersect with + * @param aIgnoreEndpoints: don't treat corner cases (i.e. end of one segment touching the + * other) as intersections. + * @param aLines: treat segments as infinite lines + * @return intersection point, if exists + */ + OPT_VECTOR2I Intersect( const SEG& aSeg, bool aIgnoreEndpoints = false, + bool aLines = false ) const; + + /** + * Function IntersectLines() + * + * Computes the intersection point of lines passing through ends of (this) and aSeg + * @param aSeg segment defining the line to intersect with + * @return intersection point, if exists + */ + OPT_VECTOR2I IntersectLines( const SEG& aSeg ) const + { + return Intersect( aSeg, false, true ); + } + + bool Collide( const SEG& aSeg, int aClearance ) const; + + ecoord SquaredDistance( const SEG& aSeg ) const; + + /** + * Function Distance() + * + * Computes minimum Euclidean distance to segment aSeg. + * @param aSeg other segment + * @return minimum distance + */ + int Distance( const SEG& aSeg ) const + { + return sqrt( SquaredDistance( aSeg ) ); + } + + ecoord SquaredDistance( const VECTOR2I& aP ) const + { + return ( NearestPoint( aP ) - aP ).SquaredEuclideanNorm(); + } + + /** + * Function Distance() + * + * Computes minimum Euclidean distance to point aP. + * @param aP the point + * @return minimum distance + */ + int Distance( const VECTOR2I& aP ) const + { + return sqrt( SquaredDistance( aP ) ); + } + + void CanonicalCoefs( ecoord& qA, ecoord& qB, ecoord& qC ) const + { + qA = A.y - B.y; + qB = B.x - A.x; + qC = -qA * A.x - qB * A.y; + } + + /** + * Function Collinear() + * + * Checks if segment aSeg lies on the same line as (this). + * @param aSeg the segment to chech colinearity with + * @return true, when segments are collinear. + */ + bool Collinear( const SEG& aSeg ) const + { + ecoord qa, qb, qc; + CanonicalCoefs( qa, qb, qc ); + + ecoord d1 = std::abs( aSeg.A.x * qa + aSeg.A.y * qb + qc ); + ecoord d2 = std::abs( aSeg.B.x * qa + aSeg.B.y * qb + qc ); + + return ( d1 <= 1 && d2 <= 1 ); + } + + bool ApproxCollinear( const SEG& aSeg ) const + { + ecoord p, q, r; + CanonicalCoefs( p, q, r ); + + ecoord dist1 = ( p * aSeg.A.x + q * aSeg.A.y + r ) / sqrt( p * p + q * q ); + ecoord dist2 = ( p * aSeg.B.x + q * aSeg.B.y + r ) / sqrt( p * p + q * q ); + + return std::abs( dist1 ) <= 1 && std::abs( dist2 ) <= 1; + } + + bool ApproxParallel ( const SEG& aSeg ) const + { + ecoord p, q, r; + CanonicalCoefs( p, q, r ); + + ecoord dist1 = ( p * aSeg.A.x + q * aSeg.A.y + r ) / sqrt( p * p + q * q ); + ecoord dist2 = ( p * aSeg.B.x + q * aSeg.B.y + r ) / sqrt( p * p + q * q ); + + return std::abs( dist1 - dist2 ) <= 1; + } + + + bool Overlaps( const SEG& aSeg ) const + { + if( aSeg.A == aSeg.B ) // single point corner case + { + if( A == aSeg.A || B == aSeg.A ) + return false; + + return Contains( aSeg.A ); + } + + if( !Collinear( aSeg ) ) + return false; + + if( Contains( aSeg.A ) || Contains( aSeg.B ) ) + return true; + if( aSeg.Contains( A ) || aSeg.Contains( B ) ) + return true; + + return false; + } + + /** + * Function Length() + * + * Returns the length (this) + * @return length + */ + int Length() const + { + return ( A - B ).EuclideanNorm(); + } + + ecoord SquaredLength() const + { + return ( A - B ).SquaredEuclideanNorm(); + } + + ecoord TCoef( const VECTOR2I& aP ) const; + + /** + * Function Index() + * + * Return the index of this segment in its parent shape (applicable only to non-local segments) + * @return index value + */ + int Index() const + { + return m_index; + } + + bool Contains( const VECTOR2I& aP ) const; + + bool PointCloserThan( const VECTOR2I& aP, int aDist ) const; + + void Reverse() + { + std::swap( A, B ); + } + +private: + bool ccw( const VECTOR2I& aA, const VECTOR2I& aB, const VECTOR2I &aC ) const; + + ///> index withing the parent shape (used when m_is_local == false) + int m_index; +}; + +inline VECTOR2I SEG::LineProject( const VECTOR2I& aP ) const +{ + VECTOR2I d = B - A; + ecoord l_squared = d.Dot( d ); + + if( l_squared == 0 ) + return A; + + ecoord t = d.Dot( aP - A ); + + int xp = rescale( t, (ecoord)d.x, l_squared ); + int yp = rescale( t, (ecoord)d.y, l_squared ); + + return A + VECTOR2I( xp, yp ); +} + +inline int SEG::LineDistance( const VECTOR2I& aP, bool aDetermineSide ) const +{ + ecoord p = A.y - B.y; + ecoord q = B.x - A.x; + ecoord r = -p * A.x - q * A.y; + + ecoord dist = ( p * aP.x + q * aP.y + r ) / sqrt( p * p + q * q ); + + return aDetermineSide ? dist : std::abs( dist ); +} + +inline SEG::ecoord SEG::TCoef( const VECTOR2I& aP ) const +{ + VECTOR2I d = B - A; + return d.Dot( aP - A); +} + +inline const VECTOR2I SEG::NearestPoint( const VECTOR2I& aP ) const +{ + VECTOR2I d = B - A; + ecoord l_squared = d.Dot( d ); + + if( l_squared == 0 ) + return A; + + ecoord t = d.Dot( aP - A ); + + if( t < 0 ) + return A; + else if( t > l_squared ) + return B; + + int xp = rescale( t, (ecoord)d.x, l_squared ); + int yp = rescale( t, (ecoord)d.y, l_squared ); + + return A + VECTOR2I( xp, yp ); +} + +inline std::ostream& operator<<( std::ostream& aStream, const SEG& aSeg ) +{ + aStream << "[ " << aSeg.A << " - " << aSeg.B << " ]"; + + return aStream; +} + +#endif // __SEG_H diff --git a/include/geometry/shape.h b/include/geometry/shape.h new file mode 100644 index 0000000..300de61 --- /dev/null +++ b/include/geometry/shape.h @@ -0,0 +1,171 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 + */ + +#ifndef __SHAPE_H +#define __SHAPE_H + +#include <string> +#include <sstream> + +#include <math/vector2d.h> +#include <math/box2.h> + +#include <geometry/seg.h> + +/** + * Enum SHAPE_TYPE + * Lists all supported shapes + */ + +enum SHAPE_TYPE +{ + SH_RECT = 0, ///> axis-aligned rectangle + SH_SEGMENT, ///> line segment + SH_LINE_CHAIN, ///> line chain (polyline) + SH_CIRCLE, ///> circle + SH_CONVEX, ///> convex polygon + SH_POLY_SET, ///> any polygon (with holes, etc.) + SH_COMPOUND ///> compound shape, consisting of multiple simple shapes +}; + +/** + * Class SHAPE + * + * Represents an abstract shape on 2D plane. + */ +class SHAPE +{ +protected: + typedef VECTOR2I::extended_type ecoord; + +public: + /** + * Constructor + * + * Creates an empty shape of type aType + */ + + SHAPE( SHAPE_TYPE aType ) : m_type( aType ) + {} + + // Destructor + virtual ~SHAPE() + {} + + /** + * Function Type() + * + * Returns the type of the shape. + * @retval the type + */ + SHAPE_TYPE Type() const + { + return m_type; + } + + /** + * Function Clone() + * + * Returns a dynamically allocated copy of the shape + * @retval copy of the shape + */ + virtual SHAPE* Clone() const + { + assert( false ); + return NULL; + }; + + /** + * Function Collide() + * + * Checks if the boundary of shape (this) lies closer to the point aP than aClearance, + * indicating a collision. + * @return true, if there is a collision. + */ + virtual bool Collide( const VECTOR2I& aP, int aClearance = 0 ) const + { + return Collide( SEG( aP, aP ), aClearance ); + } + + /** + * Function Collide() + * + * Checks if the boundary of shape (this) lies closer to the shape aShape than aClearance, + * indicating a collision. + * @param aShape shape to check collision against + * @param aClearance minimum clearance + * @param aMTV minimum translation vector + * @return true, if there is a collision. + */ + virtual bool Collide( const SHAPE* aShape, int aClearance, VECTOR2I& aMTV ) const; + virtual bool Collide( const SHAPE* aShape, int aClearance = 0 ) const; + + /** + * Function Collide() + * + * Checks if the boundary of shape (this) lies closer to the segment aSeg than aClearance, + * indicating a collision. + * @return true, if there is a collision. + */ + virtual bool Collide( const SEG& aSeg, int aClearance = 0 ) const = 0; + + /** + * Function BBox() + * + * Computes a bounding box of the shape, with a margin of aClearance + * a collision. + * @param aClearance how much the bounding box is expanded wrs to the minimum enclosing rectangle + * for the shape. + * @return the bounding box. + */ + virtual const BOX2I BBox( int aClearance = 0 ) const = 0; + + /** + * Function Centre() + * + * Computes a center-of-mass of the shape + * @return the center-of-mass point + */ + virtual VECTOR2I Centre() const + { + return BBox( 0 ).Centre(); // if nothing better is available.... + } + + virtual void Move ( const VECTOR2I& aVector ) = 0; + + virtual bool IsSolid() const = 0; + + virtual bool Parse( std::stringstream& aStream ); + + virtual const std::string Format( ) const; + +protected: + ///> type of our shape + SHAPE_TYPE m_type; +}; + +bool CollideShapes( const SHAPE* aA, const SHAPE* aB, int aClearance, + bool aNeedMTV, VECTOR2I& aMTV ); + +#endif // __SHAPE_H diff --git a/include/geometry/shape_circle.h b/include/geometry/shape_circle.h new file mode 100644 index 0000000..a915ef9 --- /dev/null +++ b/include/geometry/shape_circle.h @@ -0,0 +1,103 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 + */ + +#ifndef __SHAPE_CIRCLE_H +#define __SHAPE_CIRCLE_H + +#include "shape.h" + +class SHAPE_CIRCLE : public SHAPE +{ +public: + SHAPE_CIRCLE() : + SHAPE( SH_CIRCLE ), m_radius( 0 ) + {} + + SHAPE_CIRCLE( const VECTOR2I& aCenter, int aRadius ) : + SHAPE( SH_CIRCLE ), m_radius( aRadius ), m_center( aCenter ) + {} + + SHAPE_CIRCLE( const SHAPE_CIRCLE& aOther ) : + SHAPE( SH_CIRCLE ), + m_radius( aOther.m_radius ), + m_center( aOther.m_center ) + {}; + + ~SHAPE_CIRCLE() + {} + + SHAPE* Clone() const + { + return new SHAPE_CIRCLE( *this ); + } + + const BOX2I BBox( int aClearance = 0 ) const + { + const VECTOR2I rc( m_radius + aClearance, m_radius + aClearance ); + + return BOX2I( m_center - rc, rc * 2 ); + } + + bool Collide( const SEG& aSeg, int aClearance = 0 ) const + { + int rc = aClearance + m_radius; + + return aSeg.Distance( m_center ) < rc; + } + + void SetRadius( int aRadius ) + { + m_radius = aRadius; + } + + void SetCenter( const VECTOR2I& aCenter ) + { + m_center = aCenter; + } + + int GetRadius() const + { + return m_radius; + } + + const VECTOR2I GetCenter() const + { + return m_center; + } + + void Move( const VECTOR2I& aVector ) + { + m_center += aVector; + } + + bool IsSolid() const + { + return true; + } +private: + int m_radius; + VECTOR2I m_center; +}; + +#endif diff --git a/include/geometry/shape_convex.h b/include/geometry/shape_convex.h new file mode 100644 index 0000000..308a1b3 --- /dev/null +++ b/include/geometry/shape_convex.h @@ -0,0 +1,189 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2015 Kicad Developers, see change_log.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 + */ + +#ifndef __SHAPE_CONVEX_H +#define __SHAPE_CONVEX_H + +#include <vector> + +#include <geometry/shape.h> +#include <geometry/seg.h> +#include <geometry/shape_line_chain.h> + +/** + * Class SHAPE_CONVEX + * + * Represents a convex polygon consisting of a zero-thickness closed chain of + * connected line segments. + * + * Internally the vertices are held in a SHAPE_LINE_CHAIN, please note that + * there is a "virtual" line segment between the last and first vertex. + */ +class SHAPE_CONVEX : public SHAPE +{ +public: + /** + * Constructor + * Creates an empty polygon + */ + SHAPE_CONVEX() : + SHAPE( SH_CONVEX ) + { + m_points.SetClosed( true ); + } + + SHAPE_CONVEX( const SHAPE_CONVEX& aOther ) : + SHAPE( SH_CONVEX ), m_points( aOther.m_points ) + {} + + SHAPE* Clone() const + { + return new SHAPE_CONVEX( *this ); + } + + /** + * Function Clear() + * Removes all points from the polygon. + */ + void Clear() + { + m_points.Clear(); + } + + /// @copydoc SHAPE::BBox() + const BOX2I BBox( int aClearance = 0 ) const + { + return m_points.BBox( aClearance ); + } + + /** + * Function PointCount() + * + * Returns the number of points (vertices) in this polygon + * @return number of points + */ + int PointCount() const + { + return m_points.PointCount(); + } + + /** + * Function Point() + * + * Returns a reference to a given point in the polygon. Negative indices + * count from the end of the point list, e.g. -1 means "last point", -2 + * means "second to last point" and so on. + * @param aIndex index of the point + * @return reference to the point + */ + VECTOR2I& Point( int aIndex ) + { + return m_points.Point( aIndex ); + } + + /** + * Function CPoint() + * + * Returns a const reference to a given point in the polygon. Negative + * indices count from the end of the point list, e.g. -1 means "last + * point", -2 means "second to last point" and so on. + * @param aIndex index of the point + * @return const reference to the point + */ + const VECTOR2I& CPoint( int aIndex ) const + { + return m_points.CPoint( aIndex ); + } + + /** + * Function CDPoint() + * + * Returns a given point as a vector with elements of type double. + * + * @param aIndex index of the point + * @return the point with elements of type double + */ + const VECTOR2D CDPoint( int aIndex ) const + { + const VECTOR2I& v = CPoint( aIndex ); + return VECTOR2D( v.x, v.y ); + } + + /** + * Function Vertices() + * + * Returns the list of vertices defining this convex polygon. + * + * @return the list of vertices defining this convex polygon + */ + const SHAPE_LINE_CHAIN& Vertices() const + { + return m_points; + } + + /** + * Function Append() + * + * Appends a new point at the end of the polygon. + * @param aX is X coordinate of the new point + * @param aY is Y coordinate of the new point + */ + void Append( int aX, int aY ) + { + VECTOR2I v( aX, aY ); + Append( v ); + } + + /** + * Function Append() + * + * Appends a new point at the end of the polygon. + * @param aP the new point + */ + void Append( const VECTOR2I& aP ) + { + m_points.Append( aP ); + } + + /// @copydoc SHAPE::Collide() + bool Collide( const SEG& aSeg, int aClearance = 0 ) const + { + return m_points.Collide( aSeg, aClearance ); + } + + void Move( const VECTOR2I& aVector ) + { + m_points.Move( aVector ); + } + + bool IsSolid() const + { + return true; + } + +private: + // vertices + SHAPE_LINE_CHAIN m_points; +}; + +#endif // __SHAPE_CONVEX_H diff --git a/include/geometry/shape_file_io.h b/include/geometry/shape_file_io.h new file mode 100644 index 0000000..78784d7 --- /dev/null +++ b/include/geometry/shape_file_io.h @@ -0,0 +1,69 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2015 CERN + * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 + */ + + +#ifndef __SHAPE_FILE_IO_H +#define __SHAPE_FILE_IO_H + +#include <cstdio> + +class SHAPE; + +/** + * Class SHAPE_FILE_IO + * + * Helper class for saving/loading shapes from a file. + */ +class SHAPE_FILE_IO +{ + public: + enum IO_MODE + { + IOM_READ = 0, + IOM_APPEND, + IOM_WRITE + }; + + SHAPE_FILE_IO( const std::string& aFilename, IO_MODE aMode = IOM_READ ); + ~SHAPE_FILE_IO(); + + void BeginGroup( const std::string aName = "<noname>"); + void EndGroup(); + + SHAPE* Read(); + + void Write( const SHAPE* aShape, const std::string aName = "<noname>" ); + + void Write( const SHAPE& aShape, const std::string aName = "<noname>" ) + { + Write( &aShape, aName ); + } + + private: + FILE* m_file; + bool m_groupActive; + IO_MODE m_mode; +}; + +#endif diff --git a/include/geometry/shape_index.h b/include/geometry/shape_index.h new file mode 100644 index 0000000..94703ce --- /dev/null +++ b/include/geometry/shape_index.h @@ -0,0 +1,363 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Jacobo Aragunde Pérez + * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 + */ + +#ifndef __SHAPE_INDEX_H +#define __SHAPE_INDEX_H + +#include <vector> +#include <geometry/shape.h> +#include <geometry/rtree.h> + + +/** + * shapeFunctor template function + * + * It is used by SHAPE_INDEX to get a SHAPE* from another type. + * By default relies on T::GetShape() method, should be specialized if the T object + * doesn't allow that method. + * @param aItem generic T object + * @return a SHAPE* object equivalent to object. + */ +template <class T> +static const SHAPE* shapeFunctor( T aItem ) +{ + return aItem->Shape(); +} + +/** + * boundingBox template method + * + * It is used by SHAPE_INDEX to get the bounding box of a generic T object. + * By default relies on T::BBox() method, should be specialized if the T object + * doesn't allow that method. + * @param aObject generic T object + * @return a BOX2I object containing the bounding box of the T object. + */ +template <class T> +BOX2I boundingBox( T aObject ) +{ + return shapeFunctor( aObject )->BBox(); +} + +/** + * acceptVisitor template method + * + * It is used by SHAPE_INDEX to implement Accept(). + * By default relies on V::operation() redefinition, should be specialized if V class + * doesn't have its () operation defined to accept T objects. + * @param aObject generic T object + * @param aVisitor V visitor object + */ +template <class T, class V> +void acceptVisitor( T aObject, V aVisitor ) +{ + aVisitor( aObject ); +} + +/** + * collide template method + * + * It is used by SHAPE_INDEX to implement Query(). + * By default relies on T::Collide(U) method, should be specialized if the T object + * doesn't allow that method. + * @param aObject generic T object + * @param aAnotherObject generic U object + * @param aMinDistance minimum collision distance + * @return if object and anotherObject collide + */ +template <class T, class U> +bool collide( T aObject, U aAnotherObject, int aMinDistance ) +{ + return shapeFunctor( aObject )->Collide( aAnotherObject, aMinDistance ); +} + +template <class T, class V> +bool queryCallback( T aShape, void* aContext ) +{ + V* visitor = (V*) aContext; + + acceptVisitor<T, V>( aShape, *visitor ); + + return true; +} + +template <class T = SHAPE*> +class SHAPE_INDEX +{ + public: + class Iterator + { + private: + typedef typename RTree<T, int, 2, float>::Iterator RTreeIterator; + RTreeIterator iterator; + + /** + * Function Init() + * + * Setup the internal tree iterator. + * @param aTree pointer to a RTREE object + */ + void Init( RTree<T, int, 2, float>* aTree ) + { + aTree->GetFirst( iterator ); + } + + public: + /** + * Iterator constructor + * + * Creates an iterator for the index object + * @param aIndex SHAPE_INDEX object to iterate + */ + Iterator( SHAPE_INDEX* aIndex ) + { + Init( aIndex->m_tree ); + } + + /** + * Operator * (prefix) + * + * Returns the next data element. + */ + T operator*() + { + return *iterator; + } + + /** + * Operator ++ (prefix) + * + * Shifts the iterator to the next element. + */ + bool operator++() + { + return ++iterator; + } + + /** + * Operator ++ (postfix) + * + * Shifts the iterator to the next element. + */ + bool operator++( int ) + { + return ++iterator; + } + + /** + * Function IsNull() + * + * Checks if the iterator has reached the end. + * @return true if it is in an invalid position (data finished) + */ + bool IsNull() + { + return iterator.IsNull(); + } + + /** + * Function IsNotNull() + * + * Checks if the iterator has not reached the end. + * @return true if it is in an valid position (data not finished) + */ + bool IsNotNull() + { + return iterator.IsNotNull(); + } + + /** + * Function Next() + * + * Returns the current element of the iterator and moves to the next + * position. + * @return SHAPE object pointed by the iterator before moving to the next position. + */ + T Next() + { + T object = *iterator; + ++iterator; + + return object; + } + }; + + SHAPE_INDEX(); + + ~SHAPE_INDEX(); + + /** + * Function Add() + * + * Adds a SHAPE to the index. + * @param aShape is the new SHAPE. + */ + void Add( T aShape ); + + /** + * Function Remove() + * + * Removes a SHAPE to the index. + * @param aShape is the new SHAPE. + */ + void Remove( T aShape ); + + /** + * Function RemoveAll() + * + * Removes all the contents of the index. + */ + void RemoveAll(); + + /** + * Function Accept() + * + * Accepts a visitor for every SHAPE object contained in this INDEX. + * @param aVisitor Visitor object to be run + */ + template <class V> + void Accept( V aVisitor ) + { + Iterator iter = this->Begin(); + + while( !iter.IsNull() ) + { + T shape = *iter; + acceptVisitor( shape, aVisitor ); + iter++; + } + } + + /** + * Function Reindex() + * + * Rebuilds the index. This should be used if the geometry of the objects + * contained by the index has changed. + */ + void Reindex(); + + /** + * Function Query() + * + * Runs a callback on every SHAPE object contained in the bounding box of (shape). + * @param aShape shape to search against + * @param aMinDistance distance threshold + * @param aVisitor object to be invoked on every object contained in the search area. + */ + template <class V> + int Query( const SHAPE *aShape, int aMinDistance, V& aVisitor, bool aExact ) + { + BOX2I box = aShape->BBox(); + box.Inflate( aMinDistance ); + + int min[2] = { box.GetX(), box.GetY() }; + int max[2] = { box.GetRight(), box.GetBottom() }; + + return this->m_tree->Search( min, max, aVisitor ); + } + + /** + * Function Begin() + * + * Creates an iterator for the current index object + * @return iterator + */ + Iterator Begin(); + + private: + RTree<T, int, 2, float>* m_tree; +}; + +/* + * Class members implementation + */ + +template <class T> +SHAPE_INDEX<T>::SHAPE_INDEX() +{ + this->m_tree = new RTree<T, int, 2, float>(); +} + +template <class T> +SHAPE_INDEX<T>::~SHAPE_INDEX() +{ + delete this->m_tree; +} + +template <class T> +void SHAPE_INDEX<T>::Add( T aShape ) +{ + BOX2I box = boundingBox( aShape ); + int min[2] = { box.GetX(), box.GetY() }; + int max[2] = { box.GetRight(), box.GetBottom() }; + + this->m_tree->Insert( min, max, aShape ); +} + +template <class T> +void SHAPE_INDEX<T>::Remove( T aShape ) +{ + BOX2I box = boundingBox( aShape ); + int min[2] = { box.GetX(), box.GetY() }; + int max[2] = { box.GetRight(), box.GetBottom() }; + + this->m_tree->Remove( min, max, aShape ); +} + +template <class T> +void SHAPE_INDEX<T>::RemoveAll() +{ + this->m_tree->RemoveAll(); +} + +template <class T> +void SHAPE_INDEX<T>::Reindex() +{ + RTree<T, int, 2, float>* newTree; + newTree = new RTree<T, int, 2, float>(); + + Iterator iter = this->Begin(); + + while( !iter.IsNull() ) + { + T shape = *iter; + BOX2I box = boundingBox( shape ); + int min[2] = { box.GetX(), box.GetY() }; + int max[2] = { box.GetRight(), box.GetBottom() }; + newTree->Insert( min, max, shape ); + iter++; + } + + delete this->m_tree; + this->m_tree = newTree; +} + +template <class T> +typename SHAPE_INDEX<T>::Iterator SHAPE_INDEX<T>::Begin() +{ + return Iterator( this ); +} + +#endif diff --git a/include/geometry/shape_index_list.h b/include/geometry/shape_index_list.h new file mode 100644 index 0000000..194ae5f --- /dev/null +++ b/include/geometry/shape_index_list.h @@ -0,0 +1,291 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 + */ + +#ifndef __SHAPE_INDEX_LIST_H +#define __SHAPE_INDEX_LIST_H + +#include <boost/unordered_map.hpp> + +template <class T> +const SHAPE* defaultShapeFunctor( const T aItem ) +{ + return aItem->Shape(); +} + +template <class T, const SHAPE* (ShapeFunctor) (const T) = defaultShapeFunctor<T> > +class SHAPE_INDEX_LIST +{ + struct SHAPE_ENTRY + { + SHAPE_ENTRY( T aParent ) + { + shape = ShapeFunctor( aParent ); + bbox = shape->BBox( 0 ); + parent = aParent; + } + + ~SHAPE_ENTRY() + { + } + + T parent; + const SHAPE* shape; + BOX2I bbox; + }; + + typedef std::vector<SHAPE_ENTRY> SHAPE_VEC; + typedef typename std::vector<SHAPE_ENTRY>::iterator SHAPE_VEC_ITER; + +public: + // "Normal" iterator interface, for STL algorithms. + class iterator + { + public: + iterator() + {} + + iterator( SHAPE_VEC_ITER aCurrent ) : + m_current( aCurrent ) + {} + + iterator( const iterator& aB ) : + m_current( aB.m_current ) + {} + + T operator*() const + { + return (*m_current).parent; + } + + void operator++() + { + ++m_current; + } + + iterator& operator++( int aDummy ) + { + ++m_current; + return *this; + } + + bool operator==( const iterator& aRhs ) const + { + return m_current == aRhs.m_current; + } + + bool operator!=( const iterator& aRhs ) const + { + return m_current != aRhs.m_current; + } + + const iterator& operator=( const iterator& aRhs ) + { + m_current = aRhs.m_current; + return *this; + } + + private: + SHAPE_VEC_ITER m_current; + }; + + // "Query" iterator, for iterating over a set of spatially matching shapes. + class query_iterator + { + public: + query_iterator() + { + } + + query_iterator( SHAPE_VEC_ITER aCurrent, SHAPE_VEC_ITER aEnd, SHAPE* aShape, + int aMinDistance, bool aExact ) : + m_end( aEnd ), + m_current( aCurrent ), + m_shape( aShape ), + m_minDistance( aMinDistance ), + m_exact( aExact ) + { + if( aShape ) + { + m_refBBox = aShape->BBox(); + next(); + } + } + + query_iterator( const query_iterator& aB ) : + m_end( aB.m_end ), + m_current( aB.m_current ), + m_shape( aB.m_shape ), + m_minDistance( aB.m_minDistance ), + m_exact( aB.m_exact ), + m_refBBox( aB.m_refBBox ) + { + } + + T operator*() const + { + return (*m_current).parent; + } + + query_iterator& operator++() + { + ++m_current; + next(); + return *this; + } + + query_iterator& operator++( int aDummy ) + { + ++m_current; + next(); + return *this; + } + + bool operator==( const query_iterator& aRhs ) const + { + return m_current == aRhs.m_current; + } + + bool operator!=( const query_iterator& aRhs ) const + { + return m_current != aRhs.m_current; + } + + const query_iterator& operator=( const query_iterator& aRhs ) + { + m_end = aRhs.m_end; + m_current = aRhs.m_current; + m_shape = aRhs.m_shape; + m_minDistance = aRhs.m_minDistance; + m_exact = aRhs.m_exact; + m_refBBox = aRhs.m_refBBox; + return *this; + } + + private: + void next() + { + while( m_current != m_end ) + { + if( m_refBBox.Distance( m_current->bbox ) <= m_minDistance ) + { + if( !m_exact || m_current->shape->Collide( m_shape, m_minDistance ) ) + return; + } + + ++m_current; + } + } + + SHAPE_VEC_ITER m_end; + SHAPE_VEC_ITER m_current; + BOX2I m_refBBox; + bool m_exact; + SHAPE* m_shape; + int m_minDistance; + }; + + void Add( T aItem ) + { + SHAPE_ENTRY s( aItem ); + + m_shapes.push_back( s ); + } + + void Remove( const T aItem ) + { + SHAPE_VEC_ITER i; + + for( i = m_shapes.begin(); i != m_shapes.end(); ++i ) + { + if( i->parent == aItem ) + break; + } + + if( i == m_shapes.end() ) + return; + + m_shapes.erase( i ); + } + + int Size() const + { + return m_shapes.size(); + } + + template <class Visitor> + int Query( const SHAPE* aShape, int aMinDistance, Visitor& aV, bool aExact = true ) // const + { + SHAPE_VEC_ITER i; + int n = 0; + VECTOR2I::extended_type minDistSq = (VECTOR2I::extended_type) aMinDistance * aMinDistance; + + BOX2I refBBox = aShape->BBox(); + + for( i = m_shapes.begin(); i != m_shapes.end(); ++i ) + { + if( refBBox.SquaredDistance( i->bbox ) <= minDistSq ) + { + if( !aExact || i->shape->Collide( aShape, aMinDistance ) ) + { + n++; + + if( !aV( i->parent ) ) + return n; + } + } + } + + return n; + } + + void Clear() + { + m_shapes.clear(); + } + + query_iterator qbegin( SHAPE* aShape, int aMinDistance, bool aExact ) + { + return query_iterator( m_shapes.begin(), m_shapes.end(), aShape, aMinDistance, aExact ); + } + + const query_iterator qend() + { + return query_iterator( m_shapes.end(), m_shapes.end(), NULL, 0, false ); + } + + iterator begin() + { + return iterator( m_shapes.begin() ); + } + + iterator end() + { + return iterator( m_shapes.end() ); + } + +private: + SHAPE_VEC m_shapes; +}; + +#endif diff --git a/include/geometry/shape_line_chain.h b/include/geometry/shape_line_chain.h new file mode 100644 index 0000000..ae2bf5e --- /dev/null +++ b/include/geometry/shape_line_chain.h @@ -0,0 +1,598 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 + */ + +#ifndef __SHAPE_LINE_CHAIN +#define __SHAPE_LINE_CHAIN + +#include <vector> +#include <sstream> + +#include <boost/optional.hpp> + +#include <math/vector2d.h> +#include <geometry/shape.h> +#include <geometry/seg.h> + +/** + * Class SHAPE_LINE_CHAIN + * + * Represents a polyline (an zero-thickness chain of connected line segments). + * I purposedly didn't name it "polyline" to avoid confusion with the existing CPolyLine + * class in pcbnew. + * + * SHAPE_LINE_CHAIN class shall not be used for polygons! + */ +class SHAPE_LINE_CHAIN : public SHAPE +{ +private: + typedef std::vector<VECTOR2I>::iterator point_iter; + typedef std::vector<VECTOR2I>::const_iterator point_citer; + +public: + /** + * Struct INTERSECTION + * + * Represents an intersection between two line segments + */ + struct INTERSECTION + { + /// segment belonging from the (this) argument of Intersect() + SEG our; + /// segment belonging from the aOther argument of Intersect() + SEG their; + /// point of intersection between our and their. + VECTOR2I p; + }; + + typedef std::vector<INTERSECTION> INTERSECTIONS; + + /** + * Constructor + * Initializes an empty line chain. + */ + SHAPE_LINE_CHAIN() : + SHAPE( SH_LINE_CHAIN ), m_closed( false ) + {} + + /** + * Copy Constructor + */ + SHAPE_LINE_CHAIN( const SHAPE_LINE_CHAIN& aShape ) : + SHAPE( SH_LINE_CHAIN ), m_points( aShape.m_points ), m_closed( aShape.m_closed ) + {} + + /** + * Constructor + * Initializes a 2-point line chain (a single segment) + */ + SHAPE_LINE_CHAIN( const VECTOR2I& aA, const VECTOR2I& aB ) : + SHAPE( SH_LINE_CHAIN ), m_closed( false ) + { + m_points.resize( 2 ); + m_points[0] = aA; + m_points[1] = aB; + } + + SHAPE_LINE_CHAIN( const VECTOR2I& aA, const VECTOR2I& aB, const VECTOR2I& aC ) : + SHAPE( SH_LINE_CHAIN ), m_closed( false ) + { + m_points.resize( 3 ); + m_points[0] = aA; + m_points[1] = aB; + m_points[2] = aC; + } + + SHAPE_LINE_CHAIN( const VECTOR2I& aA, const VECTOR2I& aB, const VECTOR2I& aC, const VECTOR2I& aD ) : + SHAPE( SH_LINE_CHAIN ), m_closed( false ) + { + m_points.resize( 4 ); + m_points[0] = aA; + m_points[1] = aB; + m_points[2] = aC; + m_points[3] = aD; + } + + + SHAPE_LINE_CHAIN( const VECTOR2I* aV, int aCount ) : + SHAPE( SH_LINE_CHAIN ), + m_closed( false ) + { + m_points.resize( aCount ); + + for( int i = 0; i < aCount; i++ ) + m_points[i] = *aV++; + } + + ~SHAPE_LINE_CHAIN() + {} + + SHAPE* Clone() const; + + /** + * Function Clear() + * Removes all points from the line chain. + */ + void Clear() + { + m_points.clear(); + m_closed = false; + } + + /** + * Function SetClosed() + * + * Marks the line chain as closed (i.e. with a segment connecting the last point with + * the first point). + * @param aClosed: whether the line chain is to be closed or not. + */ + void SetClosed( bool aClosed ) + { + m_closed = aClosed; + } + + /** + * Function IsClosed() + * + * @return aClosed: true, when our line is closed. + */ + bool IsClosed() const + { + return m_closed; + } + + /** + * Function SegmentCount() + * + * Returns number of segments in this line chain. + * @return number of segments + */ + int SegmentCount() const + { + int c = m_points.size() - 1; + if( m_closed ) + c++; + + return std::max( 0, c ); + } + + /** + * Function PointCount() + * + * Returns the number of points (vertices) in this line chain + * @return number of points + */ + int PointCount() const + { + return m_points.size(); + } + + /** + * Function Segment() + * + * Returns a segment referencing to the segment (index) in the line chain. + * Modifying ends of the returned segment will modify corresponding points in the line chain. + * @param aIndex: index of the segment in the line chain. Negative values are counted from + * the end (i.e. -1 means the last segment in the line chain) + * @return SEG referenced to given segment in the line chain + */ + SEG Segment( int aIndex ) + { + if( aIndex < 0 ) + aIndex += SegmentCount(); + + if( aIndex == (int)( m_points.size() - 1 ) && m_closed ) + return SEG( m_points[aIndex], m_points[0], aIndex ); + else + return SEG( m_points[aIndex], m_points[aIndex + 1], aIndex ); + } + + /** + * Function CSegment() + * + * Returns a read-only segment referencing to the segment (index) in the line chain. + * @param aIndex: index of the segment in the line chain. Negative values are counted from + * the end (i.e. -1 means the last segment in the line chain) + * @return SEG referenced to given segment in the line chain + */ + const SEG CSegment( int aIndex ) const + { + if( aIndex < 0 ) + aIndex += SegmentCount(); + + if( aIndex == (int)( m_points.size() - 1 ) && m_closed ) + return SEG( const_cast<VECTOR2I&>( m_points[aIndex] ), + const_cast<VECTOR2I&>( m_points[0] ), aIndex ); + else + return SEG( const_cast<VECTOR2I&>( m_points[aIndex] ), + const_cast<VECTOR2I&>( m_points[aIndex + 1] ), aIndex ); + } + + /** + * Function Point() + * + * Returns a reference to a given point in the line chain. + * @param aIndex index of the point + * @return reference to the point + */ + VECTOR2I& Point( int aIndex ) + { + if( aIndex < 0 ) + aIndex += PointCount(); + + return m_points[aIndex]; + } + + /** + * Function CPoint() + * + * Returns a const reference to a given point in the line chain. + * @param aIndex index of the point + * @return const reference to the point + */ + const VECTOR2I& CPoint( int aIndex ) const + { + if( aIndex < 0 ) + aIndex += PointCount(); + else if( aIndex >= PointCount() ) + aIndex -= PointCount(); + + return m_points[aIndex]; + } + + /// @copydoc SHAPE::BBox() + const BOX2I BBox( int aClearance = 0 ) const + { + BOX2I bbox; + bbox.Compute( m_points ); + + if( aClearance != 0 ) + bbox.Inflate( aClearance ); + + return bbox; + } + + /** + * Function Collide() + * + * Checks if point aP lies closer to us than aClearance. + * @param aP the point to check for collisions with + * @param aClearance minimum distance that does not qualify as a collision. + * @return true, when a collision has been found + */ + bool Collide( const VECTOR2I& aP, int aClearance = 0 ) const; + + /** + * Function Collide() + * + * Checks if segment aSeg lies closer to us than aClearance. + * @param aSeg the segment to check for collisions with + * @param aClearance minimum distance that does not qualify as a collision. + * @return true, when a collision has been found + */ + bool Collide( const SEG& aSeg, int aClearance = 0 ) const; + + /** + * Function Distance() + * + * Computes the minimum distance between the line chain and a point aP. + * @param aP the point + * @return minimum distance. + */ + int Distance( const VECTOR2I& aP ) const; + + /** + * Function Reverse() + * + * Reverses point order in the line chain. + * @return line chain with reversed point order (original A-B-C-D: returned D-C-B-A) + */ + const SHAPE_LINE_CHAIN Reverse() const; + + /** + * Function Length() + * + * Returns length of the line chain in Euclidean metric. + * @return length of the line chain + */ + int Length() const; + + /** + * Function Append() + * + * Appends a new point at the end of the line chain. + * @param aX is X coordinate of the new point + * @param aY is Y coordinate of the new point + */ + void Append( int aX, int aY, bool aAllowDuplication = false ) + { + VECTOR2I v( aX, aY ); + Append( v, aAllowDuplication ); + } + + /** + * Function Append() + * + * Appends a new point at the end of the line chain. + * @param aP the new point + */ + void Append( const VECTOR2I& aP, bool aAllowDuplication = false ) + { + if( m_points.size() == 0 ) + m_bbox = BOX2I( aP, VECTOR2I( 0, 0 ) ); + + if( m_points.size() == 0 || aAllowDuplication || CPoint( -1 ) != aP ) + { + m_points.push_back( aP ); + m_bbox.Merge( aP ); + } + } + + /** + * Function Append() + * + * Appends another line chain at the end. + * @param aOtherLine the line chain to be appended. + */ + void Append( const SHAPE_LINE_CHAIN& aOtherLine ) + { + if( aOtherLine.PointCount() == 0 ) + return; + + else if( PointCount() == 0 || aOtherLine.CPoint( 0 ) != CPoint( -1 ) ) + { + const VECTOR2I p = aOtherLine.CPoint( 0 ); + m_points.push_back( p ); + m_bbox.Merge( p ); + } + + for( int i = 1; i < aOtherLine.PointCount(); i++ ) + { + const VECTOR2I p = aOtherLine.CPoint( i ); + m_points.push_back( p ); + m_bbox.Merge( p ); + } + } + + void Insert( int aVertex, const VECTOR2I& aP ) + { + m_points.insert( m_points.begin() + aVertex, aP ); + } + + /** + * Function Replace() + * + * Replaces points with indices in range [start_index, end_index] with a single + * point aP. + * @param aStartIndex start of the point range to be replaced (inclusive) + * @param aEndIndex end of the point range to be replaced (inclusive) + * @param aP replacement point + */ + void Replace( int aStartIndex, int aEndIndex, const VECTOR2I& aP ); + + /** + * Function Replace() + * + * Replaces points with indices in range [start_index, end_index] with the points from + * line chain aLine. + * @param aStartIndex start of the point range to be replaced (inclusive) + * @param aEndIndex end of the point range to be replaced (inclusive) + * @param aLine replacement line chain. + */ + void Replace( int aStartIndex, int aEndIndex, const SHAPE_LINE_CHAIN& aLine ); + + /** + * Function Remove() + * + * Removes the range of points [start_index, end_index] from the line chain. + * @param aStartIndex start of the point range to be replaced (inclusive) + * @param aEndIndex end of the point range to be replaced (inclusive) + */ + void Remove( int aStartIndex, int aEndIndex ); + + /** + * Function Split() + * + * Inserts the point aP belonging to one of the our segments, splitting the adjacent + * segment in two. + * @param aP the point to be inserted + * @return index of the newly inserted point (or a negative value if aP does not lie on + * our line) + */ + int Split( const VECTOR2I& aP ); + + /** + * Function Find() + * + * Searches for point aP. + * @param aP the point to be looked for + * @return index of the correspoinding point in the line chain or negative when not found. + */ + int Find( const VECTOR2I& aP ) const; + + /** + * Function FindSegment() + * + * Searches for segment containing point aP. + * @param aP the point to be looked for + * @return index of the correspoinding segment in the line chain or negative when not found. + */ + int FindSegment( const VECTOR2I& aP ) const; + + /** + * Function Slice() + * + * Returns a subset of this line chain containing the [start_index, end_index] range of points. + * @param aStartIndex start of the point range to be returned (inclusive) + * @param aEndIndex end of the point range to be returned (inclusive) + * @return cut line chain. + */ + const SHAPE_LINE_CHAIN Slice( int aStartIndex, int aEndIndex = -1 ) const; + + struct compareOriginDistance + { + compareOriginDistance( const VECTOR2I& aOrigin ): + m_origin( aOrigin ) + {} + + bool operator()( const INTERSECTION& aA, const INTERSECTION& aB ) + { + return ( m_origin - aA.p ).EuclideanNorm() < ( m_origin - aB.p ).EuclideanNorm(); + } + + VECTOR2I m_origin; + }; + + bool Intersects( const SHAPE_LINE_CHAIN& aChain ) const; + + /** + * Function Intersect() + * + * Finds all intersection points between our line chain and the segment aSeg. + * @param aSeg the segment chain to find intersections with + * @param aIp reference to a vector to store found intersections. Intersection points + * are sorted with increasing distances from point aSeg.a. + * @return number of intersections found + */ + int Intersect( const SEG& aSeg, INTERSECTIONS& aIp ) const; + + /** + * Function Intersect() + * + * Finds all intersection points between our line chain and the line chain aChain. + * @param aChain the line chain to find intersections with + * @param aIp reference to a vector to store found intersections. Intersection points + * are sorted with increasing path lengths from the starting point of aChain. + * @return number of intersections found + */ + int Intersect( const SHAPE_LINE_CHAIN& aChain, INTERSECTIONS& aIp ) const; + + /** + * Function PathLength() + * + * Computes the walk path length from the beginning of the line chain and + * the point aP belonging to our line. + * @return: path length in Euclidean metric or negative if aP does not belong to + * the line chain. + */ + int PathLength( const VECTOR2I& aP ) const; + + /** + * Function PointInside() + * + * Checks if point aP lies inside a convex polygon defined by the line chain. For closed + * shapes only. + * @param aP point to check + * @return true if the point is inside the shape (edge is not treated as being inside). + */ + bool PointInside( const VECTOR2I& aP ) const; + + /** + * Function PointOnEdge() + * + * Checks if point aP lies on an edge or vertex of the line chain. + * @param aP point to check + * @return true if the point lies on the edge. + */ + bool PointOnEdge( const VECTOR2I& aP ) const; + + /** + * Function SelfIntersecting() + * + * Checks if the line chain is self-intersecting. + * @return (optional) first found self-intersection point. + */ + const boost::optional<INTERSECTION> SelfIntersecting() const; + + /** + * Function Simplify() + * + * Simplifies the line chain by removing colinear adjacent segments and duplicate vertices. + * @return reference to self. + */ + SHAPE_LINE_CHAIN& Simplify(); + + /** + * Function NearestPoint() + * + * Finds a point on the line chain that is closest to point aP. + * @return the nearest point. + */ + const VECTOR2I NearestPoint( const VECTOR2I& aP ) const; + + /** + * Function NearestPoint() + * + * Finds a point on the line chain that is closest to the line defined + * by the points of segment aSeg, also returns the distance. + * @param aSeg Segment defining the line. + * @param dist reference receiving the distance to the nearest point. + * @return the nearest point. + */ + const VECTOR2I NearestPoint( const SEG& aSeg, int& dist ) const; + + /// @copydoc SHAPE::Format() + const std::string Format() const; + + /// @copydoc SHAPE::Parse() + bool Parse( std::stringstream& aStream ); + + bool operator!=( const SHAPE_LINE_CHAIN& aRhs ) const + { + if( PointCount() != aRhs.PointCount() ) + return true; + + for( int i = 0; i < PointCount(); i++ ) + { + if( CPoint( i ) != aRhs.CPoint( i ) ) + return true; + } + + return false; + } + + bool CompareGeometry( const SHAPE_LINE_CHAIN & aOther ) const; + + void Move( const VECTOR2I& aVector ) + { + for( std::vector<VECTOR2I>::iterator i = m_points.begin(); i != m_points.end(); ++i ) + (*i) += aVector; + } + + bool IsSolid() const + { + return false; + } + +private: + /// array of vertices + std::vector<VECTOR2I> m_points; + + /// is the line chain closed? + bool m_closed; + + /// cached bounding box + BOX2I m_bbox; +}; + +#endif // __SHAPE_LINE_CHAIN diff --git a/include/geometry/shape_poly_set.h b/include/geometry/shape_poly_set.h new file mode 100644 index 0000000..24c4a91 --- /dev/null +++ b/include/geometry/shape_poly_set.h @@ -0,0 +1,374 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2015 CERN + * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 + */ + +#ifndef __SHAPE_POLY_SET_H +#define __SHAPE_POLY_SET_H + +#include <vector> +#include <cstdio> +#include <geometry/shape.h> +#include <geometry/shape_line_chain.h> + +#include "clipper.hpp" + + +/** + * Class SHAPE_POLY_SET + * + * Represents a set of closed polygons. Polygons may be nonconvex, self-intersecting + * and have holes. Provides boolean operations (using Clipper library as the backend). + * + * TODO: add convex partitioning & spatial index + */ +class SHAPE_POLY_SET : public SHAPE +{ + public: + ///> represents a single polygon outline with holes. The first entry is the outline, + ///> the remaining (if any), are the holes + typedef std::vector<SHAPE_LINE_CHAIN> POLYGON; + + /** + * Class ITERATOR_TEMPLATE + * + * Base class for iterating over all vertices in a given SHAPE_POLY_SET + */ + template <class T> + class ITERATOR_TEMPLATE + { + public: + + bool IsEndContour() const + { + return m_currentVertex + 1 == m_poly->CPolygon( m_currentOutline )[0].PointCount(); + } + + bool IsLastContour() const + { + return m_currentOutline == m_lastOutline; + } + + operator bool() const + { + return m_currentOutline <= m_lastOutline; + } + + void Advance() + { + m_currentVertex ++; + + if( m_currentVertex >= m_poly->CPolygon( m_currentOutline )[0].PointCount() ) + { + m_currentVertex = 0; + m_currentOutline++; + } + } + + void operator++( int dummy ) + { + Advance(); + } + + void operator++() + { + Advance(); + } + + T& Get() + { + return m_poly->Polygon( m_currentOutline )[0].Point( m_currentVertex ); + } + + T& operator*() + { + return Get(); + } + + T* operator->() + { + return &Get(); + } + + + private: + friend class SHAPE_POLY_SET; + + SHAPE_POLY_SET* m_poly; + int m_currentOutline; + int m_lastOutline; + int m_currentVertex; + }; + + typedef ITERATOR_TEMPLATE<VECTOR2I> ITERATOR; + typedef ITERATOR_TEMPLATE<const VECTOR2I> CONST_ITERATOR; + + SHAPE_POLY_SET(); + ~SHAPE_POLY_SET(); + + ///> Creates a new empty polygon in the set and returns its index + int NewOutline(); + + ///> Creates a new hole in a given outline + int NewHole( int aOutline = -1 ); + + ///> Adds a new outline to the set and returns its index + int AddOutline( const SHAPE_LINE_CHAIN& aOutline ); + + ///> Adds a new hole to the given outline (default: last) and returns its index + int AddHole( const SHAPE_LINE_CHAIN& aHole, int aOutline = -1 ); + + ///> Appends a vertex at the end of the given outline/hole (default: the last outline) + int Append( int x, int y, int aOutline = -1, int aHole = -1 ); + + ///> Merges polygons from two sets. + void Append( const SHAPE_POLY_SET& aSet ); + + ///> Appends a vertex at the end of the given outline/hole (default: the last outline) + void Append( const VECTOR2I& aP, int aOutline = -1, int aHole = -1 ); + + ///> Returns the index-th vertex in a given hole outline within a given outline + VECTOR2I& Vertex( int index, int aOutline = -1, int aHole = -1 ); + + ///> Returns the index-th vertex in a given hole outline within a given outline + const VECTOR2I& CVertex( int index, int aOutline = -1, int aHole = -1 ) const; + + ///> Returns true if any of the outlines is self-intersecting + bool IsSelfIntersecting(); + + ///> Returns the number of outlines in the set + int OutlineCount() const { return m_polys.size(); } + + ///> Returns the number of vertices in a given outline/hole + int VertexCount( int aOutline = -1, int aHole = -1 ) const; + + ///> Returns the number of holes in a given outline + int HoleCount( int aOutline ) const + { + if( (aOutline > (int)m_polys.size()) || (m_polys[aOutline].size() < 2) ) + return 0; + return m_polys[aOutline].size() - 1; + } + + ///> Returns the reference to aIndex-th outline in the set + SHAPE_LINE_CHAIN& Outline( int aIndex ) + { + return m_polys[aIndex][0]; + } + + ///> Returns the reference to aHole-th hole in the aIndex-th outline + SHAPE_LINE_CHAIN& Hole( int aOutline, int aHole ) + { + return m_polys[aOutline][aHole + 1]; + } + + ///> Returns the aIndex-th subpolygon in the set + POLYGON& Polygon( int aIndex ) + { + return m_polys[aIndex]; + } + + const SHAPE_LINE_CHAIN& COutline( int aIndex ) const + { + return m_polys[aIndex][0]; + } + + const SHAPE_LINE_CHAIN& CHole( int aOutline, int aHole ) const + { + return m_polys[aOutline][aHole + 1]; + } + + const POLYGON& CPolygon( int aIndex ) const + { + return m_polys[aIndex]; + } + + ///> Returns an iterator object, for iterating between aFirst and aLast outline. + ITERATOR Iterate( int aFirst, int aLast ) + { + ITERATOR iter; + + iter.m_poly = this; + iter.m_currentOutline = aFirst; + iter.m_lastOutline = aLast < 0 ? OutlineCount() - 1 : aLast; + iter.m_currentVertex = 0; + + return iter; + } + + ///> Returns an iterator object, for iterating aOutline-th outline + ITERATOR Iterate( int aOutline ) + { + return Iterate( aOutline, aOutline ); + } + + ///> Returns an iterator object, for all outlines in the set (no holes) + ITERATOR Iterate() + { + return Iterate( 0, OutlineCount() - 1 ); + } + + CONST_ITERATOR CIterate( int aFirst, int aLast ) const + { + CONST_ITERATOR iter; + + iter.m_poly = const_cast<SHAPE_POLY_SET*>( this ); + iter.m_currentOutline = aFirst; + iter.m_lastOutline = aLast < 0 ? OutlineCount() - 1 : aLast; + iter.m_currentVertex = 0; + + return iter; + } + + CONST_ITERATOR CIterate( int aOutline ) const + { + return CIterate( aOutline, aOutline ); + } + + CONST_ITERATOR CIterate() const + { + return CIterate( 0, OutlineCount() - 1 ); + } + + + ///> Performs boolean polyset union + ///> For aFastMode meaning, see function booleanOp + void BooleanAdd( const SHAPE_POLY_SET& b, bool aFastMode = false ); + + ///> Performs boolean polyset difference + ///> For aFastMode meaning, see function booleanOp + void BooleanSubtract( const SHAPE_POLY_SET& b, bool aFastMode = false ); + + ///> Performs boolean polyset intersection + ///> For aFastMode meaning, see function booleanOp + void BooleanIntersection( const SHAPE_POLY_SET& b, bool aFastMode = false ); + + ///> Performs boolean polyset union between a and b, store the result in it self + ///> For aFastMode meaning, see function booleanOp + void BooleanAdd( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, bool aFastMode = false ); + + ///> Performs boolean polyset difference between a and b, store the result in it self + ///> For aFastMode meaning, see function booleanOp + void BooleanSubtract( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, bool aFastMode = false ); + + ///> Performs boolean polyset intersection between a and b, store the result in it self + ///> For aFastMode meaning, see function booleanOp + void BooleanIntersection( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, bool aFastMode = false ); + + ///> Performs outline inflation/deflation, using round corners. + void Inflate( int aFactor, int aCircleSegmentsCount ); + + ///> Converts a set of polygons with holes to a singe outline with "slits"/"fractures" connecting the outer ring + ///> to the inner holes + ///> For aFastMode meaning, see function booleanOp + void Fracture( bool aFastMode = false ); + + ///> Converts a set of slitted polygons to a set of polygons with holes + void Unfracture(); + + ///> Returns true if the polygon set has any holes. + bool HasHoles() const; + + ///> Simplifies the polyset (merges overlapping polys, eliminates degeneracy/self-intersections) + ///> For aFastMode meaning, see function booleanOp + void Simplify( bool aFastMode = false); + + /// @copydoc SHAPE::Format() + const std::string Format() const; + + /// @copydoc SHAPE::Parse() + bool Parse( std::stringstream& aStream ); + + /// @copydoc SHAPE::Move() + void Move( const VECTOR2I& aVector ); + + /// @copydoc SHAPE::IsSolid() + bool IsSolid() const + { + return true; + } + + const BOX2I BBox( int aClearance = 0 ) const; + + // fixme: add collision support + bool Collide( const VECTOR2I& aP, int aClearance = 0 ) const { return false; } + bool Collide( const SEG& aSeg, int aClearance = 0 ) const { return false; } + + + ///> Returns true is a given subpolygon contains the point aP. If aSubpolyIndex < 0 (default value), + ///> checks all polygons in the set + bool Contains( const VECTOR2I& aP, int aSubpolyIndex = -1 ) const; + + ///> Returns true if the set is empty (no polygons at all) + bool IsEmpty() const + { + return m_polys.size() == 0; + } + + ///> Removes all outlines & holes (clears) the polygon set. + void RemoveAllContours(); + + ///> Returns total number of vertices stored in the set. + int TotalVertices() const; + + ///> Deletes aIdx-th polygon from the set + void DeletePolygon( int aIdx ); + + private: + + SHAPE_LINE_CHAIN& getContourForCorner( int aCornerId, int& aIndexWithinContour ); + VECTOR2I& vertex( int aCornerId ); + const VECTOR2I& cvertex( int aCornerId ) const; + + + void fractureSingle( POLYGON& paths ); + void importTree( ClipperLib::PolyTree* tree ); + + /** Function booleanOp + * this is the engine to execute all polygon boolean transforms + * (AND, OR, ... and polygon simplification (merging overlaping polygons) + * @param aType is the transform type ( see ClipperLib::ClipType ) + * @param aOtherShape is the SHAPE_LINE_CHAIN to combine with me. + * @param aFastMode is an option to choos if the result is a weak polygon + * or a stricty simple polygon. + * if aFastMode is true the result can be a weak polygon + * if aFastMode is false (default) the result is (theorically) a strictly + * simple polygon, but calculations can be really significantly time consuming + */ + void booleanOp( ClipperLib::ClipType aType, + const SHAPE_POLY_SET& aOtherShape, bool aFastMode = false ); + + void booleanOp( ClipperLib::ClipType aType, + const SHAPE_POLY_SET& aShape, + const SHAPE_POLY_SET& aOtherShape, bool aFastMode = false ); + + bool pointInPolygon( const VECTOR2I& aP, const SHAPE_LINE_CHAIN& aPath ) const; + + const ClipperLib::Path convertToClipper( const SHAPE_LINE_CHAIN& aPath, bool aRequiredOrientation ); + const SHAPE_LINE_CHAIN convertFromClipper( const ClipperLib::Path& aPath ); + + typedef std::vector<POLYGON> Polyset; + + Polyset m_polys; +}; + +#endif diff --git a/include/geometry/shape_rect.h b/include/geometry/shape_rect.h new file mode 100644 index 0000000..47e6b13 --- /dev/null +++ b/include/geometry/shape_rect.h @@ -0,0 +1,168 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 + */ + +#ifndef __SHAPE_RECT_H +#define __SHAPE_RECT_H + +#include <geometry/shape.h> +#include <geometry/shape_line_chain.h> +#include <geometry/shape_circle.h> +#include <geometry/seg.h> + +class SHAPE_RECT : public SHAPE +{ +public: + /** + * Constructor + * Creates an empty (0-sized) rectangle + */ + SHAPE_RECT() : + SHAPE( SH_RECT ), m_w( 0 ), m_h( 0 ) + {} + + /** + * Constructor + * Creates a rectangle defined by top-left corner (aX0, aY0), width aW and height aH. + */ + SHAPE_RECT( int aX0, int aY0, int aW, int aH ) : + SHAPE( SH_RECT ), m_p0( aX0, aY0 ), m_w( aW ), m_h( aH ) + {} + + /** + * Constructor + * Creates a rectangle defined by top-left corner aP0, width aW and height aH. + */ + SHAPE_RECT( const VECTOR2I& aP0, int aW, int aH ) : + SHAPE( SH_RECT ), m_p0( aP0 ), m_w( aW ), m_h( aH ) + {} + + SHAPE_RECT( const SHAPE_RECT& aOther ) : + SHAPE( SH_RECT ), + m_p0( aOther.m_p0 ), + m_w( aOther.m_w ), + m_h( aOther.m_h ) + {}; + + SHAPE* Clone() const + { + return new SHAPE_RECT( *this ); + } + + /// @copydoc SHAPE::BBox() + const BOX2I BBox( int aClearance = 0 ) const + { + BOX2I bbox( VECTOR2I( m_p0.x - aClearance, m_p0.y - aClearance ), + VECTOR2I( m_w + 2 * aClearance, m_h + 2 * aClearance ) ); + //printf("bb : %s\n",bbox.Format().c_str()); + return bbox; + } + + /** + * Function Diagonal() + * + * Returns length of the diagonal of the rectangle + * @return diagonal length + */ + int Diagonal() const + { + return VECTOR2I( m_w, m_h ).EuclideanNorm(); + } + + /// @copydoc SHAPE::Collide() + bool Collide( const SEG& aSeg, int aClearance = 0 ) const; + + /** + * Function GetPosition() + * + * @return top-left corner of the rectangle + */ + const VECTOR2I& GetPosition() const + { + return m_p0; + } + + /** + * Function GetSize() + * + * @return size of the rectangle + */ + const VECTOR2I GetSize() const + { + return VECTOR2I( m_w, m_h ); + } + + /** + * Function GetWidth() + * + * @return width of the rectangle + */ + const int GetWidth() const + { + return m_w; + } + + /** + * Function GetHeight() + * + * @return height of the rectangle + */ + const int GetHeight() const + { + return m_h; + } + + void Move( const VECTOR2I& aVector ) + { + m_p0 += aVector; + } + + bool IsSolid() const + { + return true; + } + + const SHAPE_LINE_CHAIN Outline() const + { + SHAPE_LINE_CHAIN rv; + rv.Append( m_p0 ); + rv.Append( m_p0.x, m_p0.y + m_w ); + rv.Append( m_p0.x + m_h, m_p0.y + m_w ); + rv.Append( m_p0.x + m_h, m_p0.y ); + rv.Append( m_p0 ); + rv.SetClosed( true ); + return rv; + } + +private: + ///> Top-left corner + VECTOR2I m_p0; + + ///> Width + int m_w; + + ///> Height + int m_h; +}; + +#endif // __SHAPE_RECT_H diff --git a/include/geometry/shape_segment.h b/include/geometry/shape_segment.h new file mode 100644 index 0000000..71ed5b1 --- /dev/null +++ b/include/geometry/shape_segment.h @@ -0,0 +1,101 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 + */ + +#ifndef __SHAPE_SEGMENT_H +#define __SHAPE_SEGMENT_H + +#include <geometry/shape.h> +#include <geometry/seg.h> + +class SHAPE_SEGMENT : public SHAPE { + +public: + SHAPE_SEGMENT(): + SHAPE( SH_SEGMENT ), m_width( 0 ) {}; + + SHAPE_SEGMENT( const VECTOR2I& aA, const VECTOR2I& aB, int aWidth = 0 ): + SHAPE( SH_SEGMENT ), m_seg( aA, aB ), m_width( aWidth ) {}; + + SHAPE_SEGMENT( const SEG& aSeg, int aWidth = 0 ): + SHAPE( SH_SEGMENT ), m_seg( aSeg ), m_width( aWidth ) {}; + + ~SHAPE_SEGMENT() {}; + + SHAPE* Clone() const + { + return new SHAPE_SEGMENT( m_seg, m_width ); + } + + const BOX2I BBox( int aClearance = 0 ) const + { + return BOX2I( m_seg.A, m_seg.B - m_seg.A ).Inflate( aClearance + ( m_width + 1 ) / 2 ); + } + + bool Collide( const SEG& aSeg, int aClearance = 0 ) const + { + return m_seg.Distance( aSeg ) < ( m_width + 1 ) / 2 + aClearance; + } + + bool Collide( const VECTOR2I& aP, int aClearance = 0 ) const + { + return m_seg.Distance( aP ) < ( m_width + 1 ) / 2 + aClearance; + } + + void SetSeg( const SEG& aSeg ) + { + m_seg = aSeg; + } + + const SEG& GetSeg() const + { + return m_seg; + } + + void SetWidth( int aWidth ) + { + m_width = aWidth; + } + + int GetWidth() const + { + return m_width; + } + + bool IsSolid() const + { + return true; + } + + void Move( const VECTOR2I& aVector ) + { + m_seg.A += aVector; + m_seg.B += aVector; + } + +private: + SEG m_seg; + int m_width; +}; + +#endif |