@ \section{Spatial Subdivision for the SNC structure} From the requirements specification defined for the point location and intersection testing processes on the Nef polyhedra, we now that the spatial subdivision structure should provide the following services: \begin{itemize} \item Give the set of objects $O$ around a point $p$. \item Give the set of objects $O$, in bundles, in the neighborhood of a ray $r$. \end{itemize} Meeting the first requirement, the interface needed is straightforward. However, for accomplishing the second requirement a more complex interface is required in order to provide a internal-attributes free service, so the user does not need to have knowledge about the operation of the ray shooting process. A simple way to achieve these goal is to define a iterator over the sets of objects that belong to the cell that the ray is currently intersecting. The class interface is defined then in the following way: <>= #ifndef K3_TREE_H #define K3_TREE_H #include #include #include #include #include #include #undef _DEBUG #define _DEBUG 503 #include CGAL_BEGIN_NAMESPACE template void sort_triangle_by_lexicographically_smaller_vertex (const Triangle_3& t, int& a, int& b, int& c) { typedef typename Triangle_3::R Kernel; a = 0; for( int i = 1; i < 3; ++i) { if( compare_xyz( t[a], t[i]) == SMALLER) a = i; } b = (a + 1) % 3; c = (b + 1) % 3; if( compare_xyz( t[b], t[c]) == LARGER) std::swap( b, c); return; } template struct Compare_triangle_3 { typedef typename Triangle_3::R Kernel; bool operator()( const Triangle_3& t1, const Triangle_3& t2) { int v1[3], v2[3]; sort_triangle_by_lexicographically_smaller_vertex ( t1, v1[0], v1[1], v1[2]); sort_triangle_by_lexicographically_smaller_vertex ( t2, v2[0], v2[1], v2[2]); for( int i = 0; i < 3; ++i) { Comparison_result c = compare_xyz( t1[v1[i]], t2[v2[i]]); if( c == SMALLER) return true; else if( c == LARGER) return false; } return false; // the two triangles are equivalent } }; template class K3_tree { public: class Objects_along_ray; class Objects_around_segment; friend class Objects_along_ray; friend class Objects_around_segment; public: <> private: Traits traits; Node* root; int max_depth; Bounding_box_3 bounding_box; public: K3_tree( const Object_list& objects) { <> <> root = build_kdtree( objects, 0); } const Object_list& objects_around_point( const Point_3& p) const { return locate( p, root); } Objects_along_ray objects_along_ray( const Ray_3& r) const { return Objects_along_ray( *this, r); } Object_list objects_around_segment( const Segment_3& s) const { Object_list O; <> return O; } bool is_point_on_cell( const Point_3& p, const typename Objects_around_segment::Iterator& target) const { return is_point_on_cell( p, target.get_node(), root); } template class BBox_updater { SNCd D; Bounding_box_3 b; public: BBox_updater(const SNCd& sncd) : D(sncd), b(0,0,0,0,0,0) {} void pre_visit(const Node* n) {} void post_visit(const Node* n) { typename Object_list::const_iterator o; for( o = n->objects().begin(); o != n->objects().end(); ++o) { Vertex_handle v; if( assign( v, *o)) { Point_3 p(D.point(v)); b = b + Bounding_box_3(p.x(), p.y(), p.z(), p.x(), p.y(), p.z()); } } } Bounding_box_3 box() const{ return b; } }; template void visit_k3tree(const Node* current, Visitor& V) const { V.pre_visit(current); if(current->left() != 0) { visit_k3tree(current->left(), V); visit_k3tree(current->right(), V); } V.post_visit(current); } void transform(const Aff_transformation_3& t) { // TODO: Bounding box must be updated/transformed, too if(root != 0) root->transform(t); Explorer D; BBox_updater bbup(D); visit_k3tree(root, bbup); bounding_box = bbup.box(); } public: class Objects_around_segment { public: class Iterator; protected: Traits traits; Node *root_node; Segment_3 segment; bool initialized; public: Objects_around_segment() : initialized(false) {} Objects_around_segment( const K3_tree& k, const Segment_3& s) : root_node(k.root), segment(s), initialized(true) { TRACEN("Objects_around_segment: input segment: "< Candidate; protected: std::deque S; const Node* node; Traits traits; CGAL_assertion_code( Segment_3 prev_segment); CGAL_assertion_code( bool first_segment); public: Iterator() : node(0) {} Iterator( const Node* root, const Segment_3& s) { CGAL_assertion_code( first_segment = true); S.push_front( Candidate( root, s)); ++(*this); // place the interator in the first intersected cell } Iterator( const Self& i) : S(i.S), node(i.node) {} const Object_list& operator*() const { CGAL_assertion( node != 0); return node->objects(); } Self& operator++() { <> return *this; } bool operator==(const Self& i) const { return (node == i.node); } bool operator!=(const Self& i) const { return !(*this == i); } private: const Node* get_node() const { CGAL_assertion( node != 0); return node; } <> }; }; class Objects_along_ray : public Objects_around_segment { typedef Objects_around_segment Base; public: Objects_along_ray( const K3_tree& k, const Ray_3& r) { TRACEN("Objects_along_ray: input ray: "< ( b.xmin(), b.ymin(), b.zmin()); // We compute the intersection between a plane with normal vector in the minus x direction and located at the minimum point of the bounding box, and the input ray. When the ray does not intersect the bounding volume, there won't be any object hit, so it is safe to construct a segment that simply lay in the unbounded side of the bounding box. This approach is taken instead of somehow (efficiently) report that there was no hit object, in order to mantain a clear interface with the Iterator class. Plane_3 pl_on_minus_x( pt_on_minus_x_plane, Vector_3( -1, 0, 0)); Object o = traits.intersect_object()( pl_on_minus_x, r); if( !assign( q, o) || pl_on_minus_x.has_on(p)) q = r.source() + Vector_3( -1, 0, 0); else q = normalized(q); Base::initialize( k, Segment_3( p, q)); } }; <> <> <> private: <> <> }; CGAL_END_NAMESPACE #endif // K3_TREE_H @ Since all objects on the model are incident to vertices, are they who determine the complexity of the model. The maximum depth of the spatial subdivision tree is then set as a function of the number of vertices in the model. <>= size_type n_vertices = 0; for( Object_const_iterator o = objects.begin(); o != objects.end(); ++o) { Vertex_handle v; if( assign( v, *o)) n_vertices++; } frexp( (double) n_vertices, &max_depth); @ The bounding box of the input set of objects is used in two moments. First, during the construction of the spatial subdivision, where the bounding box is recursivelly divided in halves defining each one a node on the tree, and second, during the ray shooting where the bounding box is used to clip the infinite rays. <>= // TODO: in the presence of a infimaximal bounding box, the bounding box does not have to be computed Objects_bbox objects_bbox = traits.objects_bbox_object(); bounding_box = objects_bbox(objects); //TRACEN("bounding box:"<>= typedef typename Traits::Infimaximal_box Infimaximal_box; typedef typename Traits::Vertex_handle Vertex_handle; typedef typename Traits::Halfedge_handle Halfedge_handle; typedef typename Traits::Halffacet_handle Halffacet_handle; typedef typename Traits::Halffacet_triangle_handle Halffacet_triangle_handle; typedef typename Traits::Explorer Explorer; typedef typename Traits::Object_handle Object_handle; typedef typename Traits::Object_list Object_list; typedef typename Object_list::const_iterator Object_const_iterator; typedef typename Object_list::iterator Object_iterator; typedef typename Object_list::size_type size_type; typedef typename Traits::Point_3 Point_3; typedef typename Traits::Segment_3 Segment_3; typedef typename Traits::Ray_3 Ray_3; typedef typename Traits::Vector_3 Vector_3; typedef typename Traits::Plane_3 Plane_3; typedef typename Traits::Triangle_3 Triangle_3; typedef typename Traits::Aff_transformation_3 Aff_transformation_3; typedef typename Traits::Bounding_box_3 Bounding_box_3; typedef typename Traits::Side_of_plane Side_of_plane; typedef typename Traits::Objects_bbox Objects_bbox; typedef typename Traits::Kernel Kernel; typedef typename Kernel::FT FT; typedef typename Kernel::RT RT; <> @ \subsection{Construction} During the construction of a Kd-tree, the space is consecutively divided into two halfspaces, switching each time between $X$, $Y$ and $Z$ parallel planes, and distributig the set of objects into the halfspaces they intersect, until no further division is possible. <>= template Node* build_kdtree( const Object_list& O, Depth depth, Node* parent=0, int non_efective_splits=0) { CGAL_precondition( depth >= 0); // CGAL_precondition( O.size() > 0); TRACEN( "build_kdtree: "<= O.size()); bool non_efective_split = ((O1.size() == O.size()) || (O2.size() == O.size())); if( non_efective_split) non_efective_splits++; else non_efective_splits = 0; if( non_efective_splits > 2) { TRACEN("build_kdtree: non efective splits reached maximum"); return new Node( parent, 0, 0, Plane_3(), O); } Node *node = new Node( parent, 0, 0, partition_plane, Object_list()); node->left_node = build_kdtree( O1, depth + 1, node, non_efective_splits); node->right_node = build_kdtree( O2, depth + 1, node, non_efective_splits); return node; } @ When there is only one object in a cell, the node corresponds to a leaf since no more divisions could be performed. And also, taken from the PM Octree definition, the division of a cell should end when there is only one vertex contained in the cell, in order to avoid infinite cell divisions trying to separated the incident objects to the vertex. The subdivision also should finish when the maximum depth of the tree is reached. <>= template bool can_set_be_divided( const Object_list& O, Depth depth) { if( O.size() < 2) return false; if( depth >= max_depth) return false; size_t number_of_vertices = 0; typename Object_list::const_iterator o; for( o = O.begin(); o != O.end(); o++) { Vertex_handle v; if( assign( v, *o)) { number_of_vertices++; if( number_of_vertices > 1) break; } } return (number_of_vertices > 1); } @ If the partition plane is known, it is easy to classify the objects into two categories, one for the objects lying in the positive side of the plane, and the other for the objects on the negative side, by calling the side-of-plane predicate provided in by the traits class. All such objects that intersect the partition plane are included in both sets. In order to make the kdtree structure consistent with the spatial distribution, the orientation of the all partition planes used during the build process must be uniform. Having the partition plane, it is neccesary first to decide if the plane actually divides the set of objects in two distinct parts, and if it is not the case, the spliting process stops, and the set of objects is stored in a leaf node of the tree. <>= template bool classify_objects( const Object_list& O, Plane_3 partition_plane, int depth, OutputIterator o1, OutputIterator o2) { size_t on_oriented_boundary = 0; typename Object_list::const_iterator o; Side_of_plane sop; for( o = O.begin(); o != O.end(); ++o) { Oriented_side side = sop( partition_plane, *o); if( side == ON_NEGATIVE_SIDE || side == ON_ORIENTED_BOUNDARY) { *o1 = *o; ++o1; } if( side == ON_POSITIVE_SIDE || side == ON_ORIENTED_BOUNDARY) { *o2 = *o; ++o2; } if( side == ON_ORIENTED_BOUNDARY) on_oriented_boundary++; } return (on_oriented_boundary != O.size()); } @ Now, it remains to define the process that computes the partition plane. There are many strategies proposed for obtaining such plane. But, since the construction time of the kdtree must stay as low as is possible, the most inexpensive strategy has to be choosen. So, the middle point of the bounding box enclosing the set of objects is taken for placing the plane. <>= template < typename Vertex, typename Explorer, typename Coordinate> class Vertex_smaller_than { public: Vertex_smaller_than(Coordinate c) : coord(c) { CGAL_assertion( c >= 0 && c <=2); } bool operator()( const Vertex& v1, const Vertex& v2) { return( D.point(v1)[coord] < D.point(v2)[coord]); } private: Coordinate coord; Explorer D; }; template Plane_3 construct_splitting_plane( const Object_list& O, Depth depth) { typedef typename std::vector Vertex_list; CGAL_precondition( depth >= 0); Vertex_list vertices; for( typename Object_list::const_iterator o = O.begin(); o != O.end(); ++o) { Vertex_handle v; if( assign( v, *o)) vertices.push_back(v); } CGAL_assertion( vertices.size() > 1); std::sort( vertices.begin(), vertices.end(), Vertex_smaller_than(depth%3)); typename Vertex_list::size_type i, n = vertices.size(); typename Vertex_list::const_iterator median; for( i = 0, median = vertices.begin(); i < ((n+1)/2)-1; ++i, ++median) CGAL_assertion( median != vertices.end()); Explorer D; Point_3 p(D.point(*median)); switch( depth % 3) { case 0: return Plane_3( p, Vector_3( 1, 0, 0)); break; case 1: return Plane_3( p, Vector_3( 0, 1, 0)); break; case 2: return Plane_3( p, Vector_3( 0, 0, 1)); break; } CGAL_assertion_msg( 0, "never reached"); return Plane_3(); } @ Finally, the tree structure holding the k3 tree has to be defined. It corresponds to a binary tree, where each child node represents a half of the space bounded by the parent. Also, when the node is a leaf, the objects bounded by it are stored in an object list attached to the node. <>= class Node { friend class K3_tree; public: Node( Node* p, Node* l, Node* r, Plane_3 pl, const Object_list& O) : parent_node(p), left_node(l), right_node(r), splitting_plane(pl), object_list(O) {} bool is_leaf() const { CGAL_assertion( (left_node != 0 && right_node != 0) || (left_node == 0 && right_node == 0)); return (left_node == 0 && right_node == 0); } const Node* parent() const { return parent_node; } const Node* left() const { return left_node; } const Node* right() const { return right_node; } const Plane_3& plane() const { return splitting_plane; } const Object_list& objects() const { return object_list; } void transform(const Aff_transformation_3& t) { if(left_node != 0) { CGAL_assertion(right_node != 0); left_node->transform(t); right_node->transform(t); splitting_plane = splitting_plane.transform(t); } } <> <> private: Node* parent_node; Node* left_node; Node* right_node; Plane_3 splitting_plane; Object_list object_list; }; @ During the simplification process of an SNC structure, some vertices could be deleted, as well as some halfedges, halffacets and volumes could be merged, and, since the spatial subdivision structure is constructed before this simplification process in order to be available for the volumes construction process, which requires to perform ray shooting for finding the nesting structure of shells, some of the objects stored in the node of the spatial subdivision tree could become invalid. For correcting this situation, a update method is provided. This method takes a map of vertices, edges and facets, that specifies whether a face is still in the SNC structure or was removed, and so must be removed from the list of objects stored on the tree nodes. <>= bool update( Unique_hash_map& V, Unique_hash_map& E, Unique_hash_map& F) { return update( root, V, E, F); } bool update( Node* node, Unique_hash_map& V, Unique_hash_map& E, Unique_hash_map& F) { CGAL_assertion( node != 0); if( node->is_leaf()) { bool updated = false; Object_list* O = &node->object_list; typename Object_list::iterator onext, o = O->begin(); while( o != O->end()) { onext = o; onext++; Vertex_handle v; Halfedge_handle e; Halffacet_handle f; if( assign( v, *o)) { if( !V[v]) { O->erase(o); updated = true; } } else if( assign( e, *o)) { if( !E[e]) { O->erase(o); updated = true; } } else if( assign( f, *o)) { if( !F[f]) { O->erase(o); updated = true; } } else CGAL_assertion_msg( 0, "wrong handle"); o = onext; } return updated; } // TODO: protect the code below from optimizations! bool left_updated = update( node->left_node, V, E, F); TRACEN("k3_tree::update(): left node updated? "<right_node, V, E, F); TRACEN("k3_tree::update(): right node updated? "<>= ~K3_tree() { TRACEN("~K3_tree: deleting root..."); delete root; } <>= ~Node() { TRACEN("~Node: deleting node..."); if( !is_leaf()) { delete left_node; delete right_node; } } @ \subsection{Point location} In a kd tree, the point location process consists in a traversal of the tree structure, recursing on each node with the left or right child node depending on the side of the plane where the point is located, and reporting the objects contained on the leaf when one it is reached. When the point lies on a partition plane, only one of the child nodes has to be visited or reported since all the objects intersecting the partition plane are stored in both incident nodes. The choose of which node to visit in such cases is arbritary. <>= const Node *locate_cell_containing( const Point_3& p, const Node* node) const { CGAL_precondition( node != 0); if( node->is_leaf()) return node; else { Oriented_side side = node->plane().oriented_side(p); if( side == ON_NEGATIVE_SIDE || side == ON_ORIENTED_BOUNDARY) return locate_cell_containing( p, node->left()); else { // side == ON_POSITIVE_SIDE CGAL_assertion( side == ON_POSITIVE_SIDE); return locate_cell_containing( p, node->right()); } } } const Object_list& locate( const Point_3& p, const Node* node) const { CGAL_precondition( node != 0); return locate_cell_containing( p, node)->objects(); } bool is_point_on_cell( const Point_3& p, const Node* target, const Node* current) const { CGAL_precondition( target != 0 && current != 0); if( current->is_leaf()) return (current == target); Oriented_side side = current->plane().oriented_side(p); if( side == ON_NEGATIVE_SIDE) return is_point_on_cell( p, target, current->left()); else if( side == ON_POSITIVE_SIDE) return is_point_on_cell( p, target, current->right()); CGAL_assertion( side == ON_ORIENTED_BOUNDARY); return (is_point_on_cell( p, target, current->left()) || is_point_on_cell( p, target, current->right())); } @ \subsection{Ray tracing} As it was stated before, the interface for ray tracing on the k3 tree structure will consist in an interator that goes over the sets of objects in the cells intersected by the ray, in order of proximity to the origin of the ray. <>= if( S.empty()) node = 0; // end of the iterator else { while( !S.empty()) { const Node* n = S.front().first; Ray_3 r = S.front().second; CGAL_assertion( !r.is_degenerate()); S.pop_front(); if( n->is_leaf()) { #ifndef NDEBUG if( first_ray) { first_ray = false; TRACEN("operator++: prev_ray=(none), ray="<plane() << ", point: "<plane().point()); TRACEN("find next intersected cell: segment: "<plane(), src_side, tgt_side); if( src_side == ON_ORIENTED_BOUNDARY && tgt_side == ON_ORIENTED_BOUNDARY) src_side = tgt_side = ON_NEGATIVE_SIDE; else if( src_side == ON_ORIENTED_BOUNDARY) src_side = tgt_side; else if( tgt_side == ON_ORIENTED_BOUNDARY) tgt_side = src_side; if( src_side == tgt_side) S.push_front( Candidate( get_child_by_side( n, src_side), r)); else { Ray_3 r1, r2; divide_ray_by_plane( r, n->plane(), r1, r2); S.push_front( Candidate( get_child_by_side( n, tgt_side), r2)); S.push_front( Candidate( get_child_by_side( n, src_side), r1)); } } } } @ For determining which cells are interesected by the ray, a recursive approach is followed. On each node of the three beggining on the root, the division plane associated to the node is used for clipping the ray. If the ray does not intersect the division plane, only the nodes in the side of the plane where the ray lays must be checked. When the ray intersects the plane, the nodes on the side where the ray's source lays must be checked at first, and after, the nodes in the other side of the plane. There are two special cases that must be handled explicitly. The first one, when the ray lays completely on the division plane is treated as if one side of the plane is closed and the other one is open, so the ray only intersects the cells in one side of the plane, for instance, the negative. Since all objects intersecting the plane are stored in the nodes on both sides, no possible candidate objects for intersection are discarded. The second special case occurs when only the ray's source belongs to the division plane. In this case, only the cells on the side that the ray crosses should be taken, for instance, the cells in the side of the plane where any point belonging to the ray belongs to. <>= void get_side_of( const Ray_3& ray, const Plane_3& plane, Oriented_side& src_side, Oriented_side& tgt_side) { Object o = traits.intersect_object()( plane, ray); Point_3 p; Ray_3 r; if( assign( r, o)) { src_side = tgt_side = ON_ORIENTED_BOUNDARY; } else if( assign( p, o)) { p = normalized(p); if( p == ray.source()) { src_side = ON_ORIENTED_BOUNDARY; tgt_side = plane.oriented_side( ray.point(1)); CGAL_assertion( tgt_side != ON_ORIENTED_BOUNDARY); } else { src_side = plane.oriented_side( ray.source()); CGAL_assertion( src_side != ON_ORIENTED_BOUNDARY); tgt_side = (src_side == ON_NEGATIVE_SIDE ? ON_POSITIVE_SIDE : ON_NEGATIVE_SIDE); CGAL_assertion( tgt_side != src_side); } } else { src_side = tgt_side = plane.oriented_side( ray.source()); } } inline const Node* get_child_by_side( const Node* node, Oriented_side side) { CGAL_assertion( node != 0); CGAL_assertion( side != ON_ORIENTED_BOUNDARY); if( side == ON_NEGATIVE_SIDE) { return node->left(); } CGAL_assertion( side == ON_POSITIVE_SIDE); return node->right(); } void divide_ray_by_plane( const Ray_3& r, const Plane_3& pl, Ray_3& r1, Ray_3& r2) { Object o = traits.intersect_object()( pl, r); Point_3 ip; CGAL_assertion( assign( ip, o)); assign( ip, o); ip = normalized(ip); r1 = Ray_3( r.source(), r.direction()); r2 = Ray_3( ip, r.direction()); } <>= inline const Node* get_child_by_side( const Node* node, Oriented_side side) { CGAL_assertion( node != NULL); CGAL_assertion( side != ON_ORIENTED_BOUNDARY); if( side == ON_NEGATIVE_SIDE) { return node->left(); } CGAL_assertion( side == ON_POSITIVE_SIDE); return node->right(); } void divide_segment_by_plane( Segment_3 s, Plane_3 pl, Segment_3& s1, Segment_3& s2) { Object o = traits.intersect_object()( pl, s); Point_3 ip; CGAL_assertion( assign( ip, o)); assign( ip, o); ip = normalized(ip); s1 = Segment_3( s.source(), ip); s2 = Segment_3( ip, s.target()); CGAL_assertion( s1.target() == s2.source()); CGAL_assertion( s1.direction() == s.direction()); CGAL_assertion( s2.direction() == s.direction()); } <>= #ifdef CODE_DOES_NOT_WORK_WITH_BOTH_KERNELS_AT_THE_SAME_TIME template friend std::ostream& operator<< (std::ostream& os, const K3_tree& k3_tree) { os << (const Node*)k3_tree.root; // no default conversion to const Node*? return os; } #endif std::string dump_object_list( const Object_list& O, int level = 0) { std::stringstream os; typename Object_list::size_type v_count = 0, e_count = 0, f_count = 0, t_count = 0; typename Object_list::const_iterator o; for( o = O.begin(); o != O.end(); ++o) { Explorer D; Vertex_handle v; Halfedge_handle e; Halffacet_handle f; if( assign( v, *o)) { if( level) os << D.point(v) << std::endl; v_count++; } else if( assign( e, *o)) { if( level) os << D.segment(e) << std::endl; e_count++; } else if( assign( f, *o)) { if( level) os << "facet" << std::endl; f_count++; } else { Halffacet_triangle_handle t; if( assign( t, *o)) { if( level) os << "triangle" << std::endl; t_count++; } else CGAL_assertion_msg( 0, "wrong handle"); } } os << v_count << "v " << e_count << "e " << f_count << "f " << t_count << "t "; return os.str(); } <>= friend std::ostream& operator<< (std::ostream& os, const Node* node) { CGAL_assertion( node != 0); if( node->is_leaf()) os << node->objects().size(); else { if ( collinear( s2.source(), s2.target(), s1.source()) && collinear( s2.source(), s2.target(), s1.point(1)) ) // the segments are collinear return false; os << " ( "; if( !node->left()) os << '-'; else os << node->left(); os << " , "; if( !node->right()) os << '-'; else os << node->right(); os << " ) "; } return os; } @ \subsubsection{Segment intersecting} During the boolean operations computation it is necessary to find out all the intersections between the edges on one structure and all edges and facets in the other one. Naively, all the edges and facets in the latter should be tested, but, using a spatial subdivision structure, it is possible to cut down the number of necessary intersection tests by taking as intersection candidates only the objects in the spatial cell where the supporting line segment of every single edge is located. Since the objects intersecting the division planes of the spatial subdivision are stored in both subspaces, it is necessary to guarantee that every object in the output candidates list is reported only once. <>= Objects_around_segment objects( *this, s); Unique_hash_map< Vertex_handle, bool> v_mark(false); Unique_hash_map< Halfedge_handle, bool> e_mark(false); Unique_hash_map< Halffacet_handle, bool> f_mark(false); std::map< Triangle_3, bool, Compare_triangle_3 > t_mark; for( typename Objects_around_segment::Iterator oar = objects.begin(); oar != objects.end(); ++oar) { for( typename Object_list::const_iterator o = (*oar).begin(); o != (*oar).end(); ++o) { // TODO: implement operator->(...) Vertex_handle v; Halfedge_handle e; Halffacet_handle f; if( assign( v, *o)) { if( !v_mark[v]) { O.push_back(*o); v_mark[v] = true; } } else if( assign( e, *o)) { if( !e_mark [e]) { O.push_back(*o); e_mark[e] = true; } } else if( assign( f, *o)) { if( !f_mark[f]) { O.push_back(*o); f_mark[f] = true; } } else { Halffacet_triangle_handle t; if( assign( t, *o)) { Triangle_3 tr = t.get_triangle(); if( !t_mark[tr]) { O.push_back(*o); t_mark[tr] = true; } } else CGAL_assertion_msg( 0, "wrong handle"); } } } <>= if( S.empty()) node = 0; // end of the iterator else { while( !S.empty()) { const Node* n = S.front().first; Segment_3 s = S.front().second; S.pop_front(); if( n->is_leaf()) { #ifndef NDEBUG if( first_segment) { first_segment = false; TRACEN("operator++: prev_segment=(none), segment="<plane() << ", point: "<plane().point()); Oriented_side src_side = n->plane().oriented_side(s.source()); Oriented_side tgt_side = n->plane().oriented_side(s.target()); if( src_side == ON_ORIENTED_BOUNDARY && tgt_side == ON_ORIENTED_BOUNDARY) src_side = tgt_side = ON_NEGATIVE_SIDE; else if( src_side == ON_ORIENTED_BOUNDARY) src_side = tgt_side; else if( tgt_side == ON_ORIENTED_BOUNDARY) tgt_side = src_side; if( src_side == tgt_side) S.push_front( Candidate( get_child_by_side( n, src_side), s)); else { Segment_3 s1, s2; divide_segment_by_plane( s, n->plane(), s1, s2); S.push_front( Candidate( get_child_by_side( n, tgt_side), s2)); // cell on target pushed first S.push_front( Candidate( get_child_by_side( n, src_side), s1)); } } } } @ \subsection{K3 Tree Traits class for the SNC structure} <>= #ifndef SNC_K3_TREE_TRAITS_H #define SNC_K3_TREE_TRAITS_H #include #include #define CGAL_for_each( i, C) for( i = C.begin(); i != C.end(); ++i) CGAL_BEGIN_NAMESPACE template class Side_of_plane { public: typedef typename SNCstructure::Vertex_handle Vertex_handle; typedef typename SNCstructure::Halfedge_handle Halfedge_handle; typedef typename SNCstructure::Halffacet_handle Halffacet_handle; typedef typename SNCstructure::Halffacet_triangle_handle Halffacet_triangle_handle; typedef typename SNCstructure::Object_handle Object_handle; typedef typename SNCstructure::Halffacet_cycle_iterator Halffacet_cycle_iterator; typedef typename SNCstructure::SHalfedge_around_facet_circulator SHalfedge_around_facet_circulator; typedef typename SNCstructure::SHalfedge_handle SHalfedge_handle; typedef typename SNCstructure::Kernel Kernel; typedef typename Kernel::Point_3 Point_3; typedef typename Kernel::Segment_3 Segment_3; typedef typename Kernel::Plane_3 Plane_3; typedef typename Kernel::Triangle_3 Triangle_3; typedef typename Kernel::Vector_3 Vector_3; typedef typename Kernel::FT FT; typedef typename Kernel::RT RT; Oriented_side operator()( const Plane_3& pl, Object_handle o); Oriented_side operator()( const Plane_3& pl, Vertex_handle v); Oriented_side operator()( const Plane_3& pl, Halfedge_handle e); Oriented_side operator()( const Plane_3& pl, Halffacet_handle f); Oriented_side operator()( const Plane_3& pl, Halffacet_triangle_handle f); typedef typename SNCstructure::SNC_decorator SNC_decorator; SNC_decorator D; Unique_hash_map OnSideMap; }; template class Objects_bbox { public: typedef typename SNCstructure::Vertex_handle Vertex_handle; typedef typename SNCstructure::Halfedge_handle Halfedge_handle; typedef typename SNCstructure::Halffacet_handle Halffacet_handle; typedef typename SNCstructure::Halffacet_triangle_handle Halffacet_triangle_handle; typedef typename SNCstructure::Object_handle Object_handle; typedef typename SNCstructure::Object_list Object_list; typedef typename SNCstructure::Halffacet_cycle_iterator Halffacet_cycle_iterator; typedef typename SNCstructure::SHalfedge_around_facet_circulator SHalfedge_around_facet_circulator; typedef typename SNCstructure::SHalfedge_handle SHalfedge_handle; typedef typename SNCstructure::Kernel Kernel; typedef typename Kernel::Plane_3 Plane_3; typedef typename Kernel::Segment_3 Segment_3; typedef typename Kernel::Point_3 Point_3; typedef typename Kernel::Triangle_3 Triangle_3; typedef typename Kernel::RT RT; typedef typename Kernel::FT FT; typedef Bounding_box_3 Bounding_box_3; virtual Bounding_box_3 operator()(const Object_list& o) const; virtual Bounding_box_3 operator()(Object_handle o) const; virtual Bounding_box_3 operator()(Vertex_handle v) const; virtual Bounding_box_3 operator()(Halfedge_handle e) const; virtual Bounding_box_3 operator()(Halffacet_handle f) const; virtual Bounding_box_3 operator()(Halffacet_triangle_handle f) const; typedef typename SNCstructure::SNC_decorator SNC_decorator; SNC_decorator D; }; template class SNC_k3_tree_traits { public: typedef typename SNCstructure::Kernel Kernel; typedef SNCstructure SNC_structure; typedef typename SNCstructure::Infi_box Infimaximal_box; typedef typename SNCstructure::Vertex_handle Vertex_handle; typedef typename SNCstructure::Halfedge_handle Halfedge_handle; typedef typename SNCstructure::Halffacet_handle Halffacet_handle; typedef typename SNCstructure::Halffacet_triangle_handle Halffacet_triangle_handle; typedef typename SNCstructure::SNC_decorator Explorer; typedef typename SNCstructure::Object_handle Object_handle; typedef typename SNCstructure::Object_list Object_list; typedef typename Kernel::Point_3 Point_3; typedef typename Kernel::Segment_3 Segment_3; typedef typename Kernel::Ray_3 Ray_3; typedef typename Kernel::Vector_3 Vector_3; typedef typename Kernel::Plane_3 Plane_3; typedef typename Kernel::Triangle_3 Triangle_3; typedef typename Kernel::Aff_transformation_3 Aff_transformation_3; typedef typename Kernel::RT RT; typedef typename Kernel::FT FT; typedef Bounding_box_3 Bounding_box_3; typedef typename Kernel::Intersect_3 Intersect; typedef Objects_bbox Objects_bbox; typedef Side_of_plane Side_of_plane; Intersect intersect_object() const { return Intersect(); } Side_of_plane side_of_plane_object() const { return Side_of_plane(); } Objects_bbox objects_bbox_object() const { return Objects_bbox(); } }; template Oriented_side Side_of_plane::operator() ( const Plane_3& pl, Object_handle o) { Vertex_handle v; Halfedge_handle e; Halffacet_handle f; if( assign( v, o)) return (*this)( pl, v); else if( assign( e, o)) return (*this)( pl, e); else if( assign( f, o)) return (*this)( pl, f); else { Halffacet_triangle_handle t; if( assign( t, o)) return (*this)( pl, t); else CGAL_assertion_msg( 0, "wrong handle"); } return Oriented_side(); // never reached } template Oriented_side Side_of_plane::operator() ( const Plane_3& pl, Vertex_handle v) { if(!OnSideMap.is_defined(v)) OnSideMap[v] = pl.oriented_side(D.point(v)); return OnSideMap[v]; } /* An edge is considered intersecting a plane if its endpoints lie on the plane or if they lie on diferent sides. Partial tangency is not considered as intersection, due the fact that a lower dimensional face (the vertex) should be already reported as an object intersecting the plane. */ template Oriented_side Side_of_plane::operator() ( const Plane_3& pl, Halfedge_handle e) { if(!OnSideMap.is_defined(D.source(e))) OnSideMap[D.source(e)] = pl.oriented_side(D.point(D.source(e))); if(!OnSideMap.is_defined(D.target(e))) OnSideMap[D.target(e)] = pl.oriented_side(D.point(D.target(e))); Oriented_side src_side = OnSideMap[D.source(e)]; Oriented_side tgt_side = OnSideMap[D.target(e)]; if( src_side == tgt_side) return src_side; if( src_side == ON_ORIENTED_BOUNDARY) return tgt_side; if( tgt_side == ON_ORIENTED_BOUNDARY) return src_side; return ON_ORIENTED_BOUNDARY; } template Oriented_side Side_of_plane::operator() ( const Plane_3& pl, Halffacet_triangle_handle t) { bool on_positive_side = false, on_negative_side = false; Triangle_3 tr(t.get_triangle()); for( int i = 0; i < 3; ++i) { Oriented_side side = pl.oriented_side(tr[i]); if( side == ON_POSITIVE_SIDE) on_positive_side = true; else if( side == ON_NEGATIVE_SIDE) on_negative_side = true; } if( on_positive_side && on_negative_side) return ON_ORIENTED_BOUNDARY; if( !on_positive_side && !on_negative_side) return ON_ORIENTED_BOUNDARY; if( on_positive_side) { CGAL_assertion( !on_negative_side); return ON_POSITIVE_SIDE; } CGAL_assertion( on_negative_side); return ON_NEGATIVE_SIDE; } /* As for the edges, if a facet is tanget to the plane it is not considered as a interesection since lower dimensional faces, like the edges and vertices where the tangency occurrs, should be reported as the objects intersecting the plane. So, an intersection is reported if all vertices of the facet lie on plane, for which it is only necessary to check three vertices, or if the facet has vertices on both sides of the plane, so the intersection is known as far as two vertices located on different sides of the plane. */ template Oriented_side Side_of_plane::operator() ( const Plane_3& pl, Halffacet_handle f) { CGAL_assertion( std::distance( f->facet_cycles_begin(), f->facet_cycles_end()) > 0); Halffacet_cycle_iterator fc(f->facet_cycles_begin()); SHalfedge_handle e; CGAL_assertion( assign( e, fc)); assign( e, fc); SHalfedge_around_facet_circulator sc(e), send(sc); //CGAL_assertion( iterator_distance( sc, send) >= 3); // TODO: facet with 2 vertices was found, is it possible? Oriented_side facet_side; do { if(!OnSideMap.is_defined(D.vertex(sc))) OnSideMap[D.vertex(sc)] = pl.oriented_side(D.point(D.vertex(sc))); facet_side = OnSideMap[D.vertex(sc)]; ++sc; } while( facet_side == ON_ORIENTED_BOUNDARY && sc != send); if( facet_side == ON_ORIENTED_BOUNDARY) return ON_ORIENTED_BOUNDARY; CGAL_assertion( facet_side != ON_ORIENTED_BOUNDARY); while( sc != send) { if(!OnSideMap.is_defined(D.vertex(sc))) OnSideMap[D.vertex(sc)] = pl.oriented_side(D.point(D.vertex(sc))); Oriented_side point_side = OnSideMap[D.vertex(sc)]; ++sc; if( point_side == ON_ORIENTED_BOUNDARY) continue; if( point_side != facet_side) return ON_ORIENTED_BOUNDARY; } return facet_side; } template Bounding_box_3 Objects_bbox::operator() ( const Object_list& O) const { CGAL_assertion( O.size() >= 0); Bounding_box_3 b(0,0,0,0,0,0); typename Object_list::const_iterator o; for( o = O.begin(); o != O.end(); ++o) { Vertex_handle v; if( assign( v, *o)) b = b + (*this)(v); } return b; } template Bounding_box_3 Objects_bbox::operator() (Object_handle o) const { Vertex_handle v; Halfedge_handle e; Halffacet_handle f; if( assign( v, o)) return operator()(v); else if( assign( e, o)) return operator()(e); else if( assign( f, o)) return operator()(f); else { Halffacet_triangle_handle t; if( assign( t, o)) return operator()(t); else CGAL_assertion_msg( 0, "wrong handle"); } return Bounding_box_3(); // never reached } template Bounding_box_3 Objects_bbox::operator() (Vertex_handle v) const { Point_3 p(D.point(v)); return Bounding_box_3( p.x(), p.y(), p.z(), p.x(), p.y(), p.z()); } template Bounding_box_3 Objects_bbox::operator() (Halfedge_handle e) const { return (operator()(D.vertex(e)) + operator()(D.vertex(D.twin(e)))); } template Bounding_box_3 Objects_bbox::operator() (Halffacet_triangle_handle t) const { Bounding_box_3 bbox(0,0,0,0,0,0); Triangle_3 tr(t.get_triangle()); for( int i = 0; i < 3; ++i) { Point_3 p(tr[i]); bbox = bbox + Bounding_box_3( p.x(), p.y(), p.z(), p.x(), p.y(), p.z()); } return bbox; } template Bounding_box_3 Objects_bbox::operator() (Halffacet_handle f) const { // TODO CGAL_assertion( f->facet_cycles_begin() != Halffacet_cycle_iterator()); Halffacet_cycle_iterator fc(f->facet_cycles_begin()); SHalfedge_handle e; CGAL_assertion( assign( e, fc)); assign( e, fc); SHalfedge_around_facet_circulator sc(e), send(sc); CGAL_assertion( !is_empty_range( sc, send)); Bounding_box_3 b(operator()(D.vertex(sc))); sc++; while( sc != send) { b = b + operator()(D.vertex(sc)); sc++; } return b; } CGAL_END_NAMESPACE #endif // SNC_K3_TREE_TRAITS_H @ \subsection{Test program} <>= //#define CGAL_NEF3_VISUALIZOR #include #include #include #include #include #include #include #include #include #include #include #undef _DEBUG #define _DEBUG 157 #include typedef CGAL::Gmpz NT; typedef CGAL::Extended_homogeneous_3 Kernel; typedef Kernel::Point_3 Point_3; typedef Kernel::Segment_3 Segment_3; typedef CGAL::Polyhedron_3 Polyhedron; typedef CGAL::SNC_items SNC_items; typedef CGAL::SNC_structure SNC_structure; typedef CGAL::Nef_polyhedron_3 Nef_polyhedron; typedef CGAL::SNC_point_locator Point_locator; typedef SNC_structure::Vertex_handle Vertex_handle; typedef SNC_structure::Halfedge_handle Halfedge_handle; typedef SNC_structure::Halffacet_handle Halffacet_handle; typedef SNC_structure::Volume_handle Volume_handle; typedef SNC_structure::Object_handle Object_handle; void error( int n) { std::cerr << "syntax: program file1.off file2.off result.nef " "{naive,subdivision} {union,int,diff,symdiff} debugthread" << std::endl; exit(n); } // syntax: demo file1.off file2.off result.nef strategy operation debug int main( int argc, char** argv) { if( argc != 7) error(1); std::ifstream file1(argv[1]), file2(argv[2]); if( !file1 || !file2) error(2); std::ofstream result(argv[3]); if( !result) error(2); char *strategy = argv[4]; if( strcmp( strategy, "naive") && strcmp( strategy, "subdivision")) error(3); char *boolop = argv[5]; if( strcmp( boolop, "union") && strcmp( boolop, "int") && strcmp( boolop, "diff") && strcmp( boolop, "symmdiff")) error(3); int dthread = atoi(argv[6]); if( dthread < 0) error(4); CGAL::set_pretty_mode(std::cerr); SETDTHREAD(dthread); Point_locator *pl; if( strcmp( strategy, "subdivision") == 0) pl = new CGAL::SNC_point_locator_by_spatial_subdivision; else if( strcmp( strategy, "naive") == 0) pl = new CGAL::SNC_point_locator_naive; else CGAL_assertion_msg( 0, "wrong strategy"); CGAL::Timer constr_time, boolop_time; TRACEN("constructing models..."); Polyhedron Pp, Qp; file1 >> Pp; file2 >> Qp; CGAL_assertion( Pp.size_of_vertices() > 0 && Qp.size_of_vertices()); constr_time.start(); Nef_polyhedron P(Pp, pl->clone()), Q(Qp, pl->clone()); constr_time.stop(); TRACEN("operating models..."); Nef_polyhedron PQ(Nef_polyhedron::EMPTY); // wasted time creating the infimaximal box boolop_time.start(); if( strcmp( boolop, "union") == 0) PQ = P+Q; else if( strcmp( boolop, "int") == 0) PQ = P*Q; else if( strcmp( boolop, "diff") == 0) PQ = P-Q; else if( strcmp( boolop, "symmdiff") == 0) PQ = P^Q; else CGAL_assertion_msg( 0, "wrong boolean operation"); boolop_time.stop(); TRACEN("writing output..."); result << PQ; TRACEN("nef3 construction: " << constr_time.time()); TRACEN("boolean operation: " << boolop_time.time()); return 0; }