%------------------------------------------------------------------------------ %KILLSTART DISS REP %LDEL TRACE.*?\)\; %LDEL CGAL_assertion\(.*?\)\; \documentclass[a4paper]{article} \usepackage{MyLweb,version} \input{defs} \excludeversion{ignoreindiss} \excludeversion{ignore} \includeversion{onlyindiss} \begin{document} \title{Nef Polyhedra in the Plane} \author{Michael Seel} \date{\today} \maketitle \begin{abstract} A planar Nef polyhedron is any set that can be obtained from the open half\-spaces by a finite number of set complement and set intersection operations. The set of Nef polyhedra is closed under the Boolean set operations. We describe a data structure that realizes two-dimensional Nef polyhedra and offers a large set of binary and unary set operations. The underlying set operations are realized by an efficient and complete algorithm for the overlay of two Nef polyhedra. The algorithm is efficient in the sense that its running time is bounded by the size of the inputs plus the size of the output times a logarithmic factor. The algorithm is complete in the sense that it can handle all inputs and requires no general position assumption. \end{abstract} \tableofcontents \newpage %KILLEND REP \section{The Interface Specification} \input manpages/Nef_polyhedron_2.man \input manpages/Topological_explorer.man \input manpages/Explorer.man %KILLEND DISS \section{Motivation} Nef polyhedra are the most general model for rectilinearly bounded subsets of affine space. Their definition is surprisingly simple whereas the operations that are supported without leaving the model are versatile. Nef's model of polyhedra does not impose topological restrictions on the sets that can be modeled like manifold or regularized models do. This implies, of course, that the abstract representation of the underlying theory has to cope with general topological complexity. The main reason for us to offer a data type for Nef polyhedra is that many other models that are standard concepts in the field are covered by Nef's model: \begin{itemize}\parsep0ex plus 0.0ex minus 1.0ex% \itemsep0ex\topsep-0.5ex% \item A \emph{convex polytope} is defined as the convex hull of a nonempty finite set of points. Convex polytopes are thus compact closed and manifold sets. \cite{gruenbaum67} \item An \emph{elementary polyhedron} is defined as the union of a finite number of convex polytopes. \cite{gruenbaum67} \item A \emph{polyhedral set} is defined as the intersection of a finite number of closed half-spaces. Such sets are closed and convex but need not to be compact. \cite{gruenbaum67} \item The set of all points belonging to the simplices of a \emph{simplicial complex} is normally called a (rectilinear) polyhedron. \cite{lefschetz71} \end{itemize} This list shows that a system modeling Nef polyhedra enables a user to calculate in many interesting domains. \section{Previous Work} We cite the main publications from the field and present its development. We will first give an outline, then we go into more details about the notions that are interesting with respect to our research. Other notions are solely linked to the literature. The theory of Nef polyhedra was first published in W. Nef's book ``Beitr{\"a}ge zur Theorie der Polyeder mit Anwendungen in der Computergraphik'' \cite{nef:polybook}. The book presents a mathematically sound theory of a general kind of polyhedra in arbitrary dimension and provides a great intuition about the generality of the elaborated concepts. The algorithmic part does not specialize in dimension. It should be clear that by realizing only a fixed low-dimensional data type the corresponding algorithms and data structures can be streamlined in runtime and space requirements. In the following considerations we concentrate mainly on the presented data structure and the realization of a binary set intersection operation, which poses the most requirements\footnote{The calculation of closure, interior, or complement is much simpler due to the fact that the faces of the input polyhedron are part of the faces of the output polyhedron in these cases.} on the underlying data structures and algorithmic modules. At the workshop on computational geometry in W{\"u}rzburg \cite{lncs333-88}, a refined elaboration of the concepts in Nef's book was given. The paper introduces the later-named \emph{W{\"u}rzburg structure}, which is the set of all low-dimensional\footnote{not full-dimensional} faces of a polyhedron. Each such face is stored in the form of its local pyramid\footnote{Pyramids represent faces.}. Thus, the data structure is essentially a collection of pyramids. Each pyramid is realized by a selective arrangement of hyperplanes. This representation of pyramids is \emph{not lean} in low dimensions and can be improved. Moreover, no incidence relation is coded into the collection. The intersection operation of two polyhedra is defined on top of this structure and is based on two techniques: recursion in dimension and superposition of two local pyramids. Neither space nor runtime bounds are given for the algorithmic description, which, apart from that, is clearly structured. The paper solves some subproblems by introducing a symbolic parameter to obtain a symbolic hyperplane at infinity. However the transfer from the affine to the symbolic objects is part of the algorithmic flow and not encapsulated into a geometric kernel as we proceed in \repdiss{the report \cite{TR:infimaximalframes}}{Chapter \ref{chap Infimaximal Frames}}. H. Bieri's introduction \cite{bieri:nefintro} was a step to market Nef polyhedra to a wider audience. It provides a summary of the book and introduces all notions in a more tutorial-style picture. It also describes a small test system realizing binary set and simple topological operations written in PASCAL based on the so-called \emph{extended W{\"u}rzburg structure}. The predicate ``extended'' stems from the addition of incidence links to the W{\"u}rzburg structure\footnote{However, the extension is based only on an untyped list.}. The article \cite{bieri:booltopolops} describes the realization of simple topological and set operations on top of the W{\"u}rzburg structure. It has an introductory part and an algorithmic part. The key operation of interest, the intersection of two Nef polyhedra, is defined in pseudo code in a dimension-recursive manner (up to dimension three but a possible generalization to higher dimensions is sketched). The main phase of the operation is based on a spatial sweep approach, but the presentation is rather condensed, mainly mathematical, and lacks runtime and space bounds. In fact, the described procedure up to dimension two is quadratic and we will improve this bound to the optimal plane sweep bound (of segment intersection). A follow-up to the previous publication is the article \cite{bieri:twobasicops} that mainly closes open details of algorithmic considerations of the previous articles. Its main impact is the introduction of the \emph{reduced W{\"u}rzburg structure}. H.~Bieri shows that it suffices to store the pyramids of faces that are minimal elements of the incidence relation (a partial order defined via closure relation). As a consequence, this reduces the space requirements of the pyramid collection but requires additional algorithmic processing when collecting all faces. The proposition has a strong impact on the representation of Nef polyhedra when we consider dimension three or higher. In space, the above result implies that the pyramids of all vertices suffice to completely describe a compact polyhedron. The paper \cite{bieri:representationconversion} embeds Nef polyhedra in the field of solid modeling by offering conversion routines between previously defined data structures for Nef polyhedra: the reduced W{\"u}rzburg structure, selective cellular complexes based on hyperplane arrangements, CSG-trees based on half-spaces, and binary space partitions. Again there are no time and space bounds. J.R.~Rossignac and M.A.~O'Connor \cite{ro:SGC} have introduced \emph{Selective Geometric Complexes} (SGC). An SGC consists of a cellular complex (the topological structure) and the corresponding geometric support spaces. Geometrically SGCs use \emph{real algebraic varieties} as the geometric elements that support cells. Such varieties can be decomposed into finite sets of connected smooth manifolds (so-called \emph{extents}). An SGC is therefore a collection of mutually disjoint cells such that (1) each cell is a relatively open subset of an extent, (2) for each cell of the complex its boundary (a set of cells) is also part of the complex, and (3) each cell has a Boolean selection flag. The dimension of a cell is determined by the dimension of its underlying extent. The point set modeled by such a complex is the union of all selected cells. One important concept is the incidence relation on cells. In SGCs it is defined in terms of a \emph{boundary} and a \emph{star} relation stemming from the corresponding concepts of simplicial complexes. Moreover due to the possibly curved geometry of extents, the notion of \emph{neighborhood} (orientation) is introduced to disambiguate degenerate boundary conditions. The description of SGCs is still abstract and leaves room for refinement concerning the realization of the necessary data structure concepts\footnote{A promised follow-up paper never appeared.}. The paper presents the central ideas and abstract definition of SGCs and its notions. It sketches binary operations based on the data type separated into phases. (We use this approach later in our implementation.) On the other hand, the paper omits many concrete considerations of the algorithmic subtasks (boundary evaluations, merging complexes, etc.) including runtime and space complexity. The problem of unbounded structures is not an issue. How do SGCs relate to Nef polyhedra? The support spaces of Nef polyhedra are flats. Therefore, varieties and extents are not separate concepts. Many geometric ambiguities do not occur. The theory of Nef polyhedra as described by Nef and Bieri varies in the way the exterior of Nef polyhedra is modeled. In their later papers, the exterior is a \emph{non-proper} face and thereby the ambient space is completely partitioned. With SGCs the exterior is not a cell. We want to stress the following similarity. Assume we realize SGCs geometrically restricted to flats. Then, the simplification algorithm as part of the algorithmic description of the binary operations on SGCs produces cells that are the connected components of proper Nef faces. \repdiss{}{(The simplification algorithm is described and used in Section \ref{overlay module} of this thesis.)} K.~Dobrindt, K.~Mehlhorn, and M.~Yvinec \cite{dmy:polyhedra} describe an efficient algorithm for the intersection of a convex polyhedron and a Nef polyhedron in three-dimensional space. The presentation uses a \emph{local graph} data structure modeling the local view that we introduce below. The local graph data structure is used as a vehicle to project the three dimensional topological neighborhood (the local pyramid) that defines Nef facets into the surface of a sphere centered at a point of interest. Their idea greatly simplifies the representation of local pyramids in three dimensions and allows a space-efficient representation thereof (linear as opposed to the possible quadratic space of the original proposed arrangements). The corresponding algorithm was not implemented. K.~Mehlhorn and S.~Naeher have introduced the notion of planar \emph{generalized} polygons and implemented them in LEDA \cite[Section 10.8]{ledabook}. A generalized polygon is a point set bounded by possibly several (weakly) simple polygonal chains. This topological restriction implies that generalized polygons are the same as regularized compact Nef polyhedra. V.~Ferrucci \cite{ferrucci:nefreps} presented two implementation efforts to realize a data type modeling Nef polyhedra. His first approach is based on selective simplicial complexes and restricts the model to bounded Nef polyhedra. All simplices are rectilinearly embedded into the affine subspace spanned by their vertices and represent relatively open convex sets. All simplices (including subsimplices) of the complex are selectable by a Boolean flag. The point set of such a simplicial complex is the union of the embeddings of the selected simplices. In this approach, Nef faces are present only implicitly as a union of simplices. The simplicial complex is thus a conforming simplicial subdivision of the bounded Nef polyhedron. In the second part of the paper, Ferrucci proves that binary space partitions can be used to realize general Nef polyhedra. Neither representation provides runtime or space qualification. Several algorithmic descriptions from the above list either assume general position of their inputs to avoid degeneracies or even require what is called regular intersection. In some cases, the generally present robustness problems are tackled by transformations of the underlying coordinate system to avoid degenerate inputs and minimize robustness problems. The possibility of that approach was presented in \cite{nefschmidt90}. Our approach differs from the previous work in several aspects. We elaborate on the planar case which is, of course, easier than the higher-dimensional case but leaves room for optimization via specialization. We solve the problem of degeneracy and robustness. We use standard data structures of our field to represent Nef polyhedra. We generalize an optimized plane sweep framework that can handle all degenerate cases for the binary operations and meet the optimal time and space bounds. One of our main contributions is the introduction of an extended geometric kernel that encapsulates the necessary geometric predicates to run the operations of the data type. In \repdiss{the report \cite{TR:infimaximalframes}}{Chapter \ref{chap Infimaximal Frames}} we show the implementation of the kernel in two flavors, one which is simple to implement and one which is tuned by filter methods and is both robust and fast. Quite some effort is put into the user interface of the software. Our data type offers the user to get her hands on faces by handles and explore the incidence of an object within the geometric structure. Our data type is based on a space-efficient implementation of plane maps. It incorporates an intuitive exploration interface and allows further attribution of the objects. Our data type could be considered a flavor of the extended W{\"u}rzburg structure where we shift the incidence into the center of our attention. Faces are not represented by their pyramids but the pyramids can be inferred from the incidence relation and the extent of any face can be explored via incident lower-dimensional faces at a cost linear in its size. It is our conviction that the original W{\"u}rzburg design is reasonable in higher-dimensions but means a loss of strength in the planar case. We also add functionality. Exploration of a geometric complex needs point location and ray shooting operations to link a user's geometric question into the geometric complex and its incidence relation. Finally our approach unifies the handling of special cases. By the introduction of infimaximal frames (\repdiss{technical report \cite{TR:infimaximalframes}}{Chapter \ref{chap Infimaximal Frames}}) and their integration into the geometric complex we enclose the geometric complex into a symbolic box. Exploration and maintainance of the structures become much easier and more homogeneous as the faces of minimal dimension are always vertices. The latter has interesting consequences as Bieri has shown that the local pyramids of minimal faces describe the polyhedron completely. In this project we realize a data type |Nef_polyhedron_2|. We present a software project clearly separated into different modules responsible for the different aspects of its realization. We will start with the theory, derive an abstract representation, map it to an abstract data type and add the algorithmic components. In Section \ref{nef theory} we present the abstract knowledge about Nef polyhedra and introduce the notions that we use. Afterwards, in Section \ref{nef data structure} we present the necessary software components of our design. We introduce the geometric and topological modules and their interaction. Then, in Section \ref{nef implementation} we present the concrete software design of our main interface data type and describe the interaction of different modules to implement the geometric methods of that data type. \repdiss{To fill the details of the representation we append the three additional implementation projects: the extended geometry in the Chapters \ref{simple extended geometry} and \ref{filtered extended geometry}, the binary operations in Chapter \ref{plane map overlay}, and the point location in Chapter \ref{plane map point location}.}{In Section \ref{nef implementation} we describe the top-level software design, in Section \ref{plane map implementation} we provide some basics about how we integrated the CGAL HDS as our plane map data type. In Section \ref{overlay module} we describe the detailed techniques that implement overlays of segments and plane maps. Afterwards, in Section \ref{generic plane sweep} we present a generic plane sweep framework used as a working horse in the former implementation. Finally in Section \ref{Nef Conclusions} we present runtime results and further applications.} \section{The Theory}\label{nef theory} We start with the formal definition of Nef polyhedra. \begin{deff}[Nef Polyhedra \cite{nef:polybook}]\label{nefpolyhedra} A set $P \subseteq \Rn$ is a \emph{Nef polyhedron} if $P$ is the result of a recursive application of set intersection and set complement starting from open half-spaces. \end{deff} This definition supports the claim that they are the most general framework to handle polyhedral sets. As set union, set difference, and symmetric set difference can be reduced to intersection and complement all these set operations are closed in the model. H.~Bieri later gave alternative definitions for Nef polyhedra and proved their equivalence. \begin{fact}[Bieri \cite{bieri:nefintro}]\label{bieri alternative defs} The original definition is equivalent to any of the following conditions \begin{enumerate}\parsep0ex plus 0.0ex minus 1.0ex% \itemsep0ex\topsep-0.5ex% \item $P$ corresponds to the root of a CSG-tree with closed half-spaces as leaf primitives and intersection, union and difference as internal nodes. \item There exist two finite families $F = \{f_1,\ldots,f_n\}$ and $G = \{g_1,\ldots,g_m\}$ of relatively open subsets of $\Rn$ such that $P = \bigcup_i f_i$ and $\cpl P = \bigcup_j g_j$. \item There exists a set of hyperplanes $H$ such that $P$ is the union of some cells of the arrangement $\mathcal{A}(H)$. \end{enumerate}\end{fact} (1) gives a link to constructive solid geometry. (2) links Nef polyhedra to cellular complexes and (3) to hyperplane arrangements. When studying the original theory the third equivalence is actually the key observation. Many propositions about the point set $P$ can be reduced to an examination of the minimal building blocks of the polyhedron: the cells of the arrangement built by the hyperplanes that define the polyhedron. The elegance of the definition is carried forward to the notion of faces. \begin{deff}[Local pyramids and Faces] Let $K \subseteq \Rn, x \in \Rn$. We call $K$ a \emph{cone with apex} $0$ if $K = \R^+ K$ and cone with apex $x $ if $K = x + \R^+ (K-x)$. A cone that is also a polyhedron is called a \emph{pyramid}. Now let $P \subseteq \Rn$ be a polyhedron and $x \in \Rn$. There is a neighborhood $U_0(x)$ such that the pyramid $Q := x + \R^+ ((P \cap U(x)) - x)$ is the same for all neighborhoods $U(x) \subseteq U_0(x)$. $Q$ is called the \emph{local pyramid} of $P$ in $x$ and is denoted $P^x$. A \emph{face} $s$ of $P$ is then a maximal non-empty subset of $\Rn$ such that all of its points have the same local pyramid $Q$, i.e., $s = \{x\in\Rn : P^x = Q\}$. In this case $Q$ is also denoted $P^s$. The dimensions of a face is the dimension of its affine hull $\dim(s) := \dim(\aff s)$. \end{deff} \displayeps{localview}{The polyhedron $P$ consists of the colored face, the triangle boundary and the vertical segment below the triangle. Some local views of $P$ are: (A) the full plane, (B) the empty set, (C) a radial cake of sectors, (D) a half-space including its boundary.}{6cm} Note that this notion of a face partitions $\Rn$ into faces of different dimension. Faces as defined by Nef do not have to be connected. There are only two full-dimensional faces possible whose local pyramids are the space itself or the empty set. All lower-dimensional faces form the \emph{boundary} of the polyhedron. As usual we call zero-dimensional faces \emph{vertices} and one-dimensional faces \emph{edges}. In the plane, we call the full-dimensional faces \emph{2-faces} or just \emph{faces} when the meaning is clear from the context. \begin{deff}[Incidence] Two faces $s$ and $t$ (in their general sense) are \emph{incident} if $s \subseteq \clos t$. \end{deff} We will treat incidence as a bidirectional relation. We say that $s$ is \emph{downward-incident} to $t$ and conversely that $t$ is \emph{upward-incident} to $s$. We now list some facts about faces. The proofs for these facts can be found in Nef's book \cite{nef:polybook}. We append the chapter and theorem numbers separated by a semicolon. All faces of a polyhedron are polyhedra [6;1.1]. Faces are relatively open sets [6;2]. The linear subspace of all apices of the pyramid associated with a face is the affine hull of the face [6;2]. $x \in P^x$ iff $x \in P$ [3;7]. Let $s$ be a face of the polyhedron $P$ and let $t$ be a face of $s$. Then, $t$ is the union of some faces of $P$ [6;12]. A face of $P$ is either a subset of $P$ or disjoint from $P$ [6;4]. For two faces $s$ and $t$ of $P$ either $s \subseteq \clos(t)$ or $s \cap \clos(t) = \emptyset$ [6;9,10]. Remember that Nef edges and Nef 2-faces are not necessarily connected. Note also that some connected components of edges are not necessarily bounded by a vertex. What do the local pyramids of any point $x$ in the plane look like? We can represent them by the intersection of a small enough neighborhood disc centered at $x$ with its pyramid $P^x$. The disc is partitioned into sectors by radial segments. We assign a mark to the center, to any radial segment, and to the sectors in between the radial segments such that the corresponding point set is marked if it belongs to the local pyramid of $x$. We also call such a disc together with its marks the \emph{local view} of $x$ (cf. Figure \figref{localview}). \begin{lemma}\label{local view properties} The local view of a point $x$ has the following properties: \begin{enumerate} \item the mark of any radial segment is different from one of the two sectors incident to it. \item $x$ is contained in a 2-face of $P$ iff the local view is a disc that contains no radial segments and is marked as the center. \item $x$ is contained in an edge of $P$ iff the local view contains exactly two radial segments that are part of a line through the center; the center and the two radial segments are marked equally but their common mark is different from at least one sector of the disc. \item $x$ is a vertex of $P$ iff the mark of the center is different from at least one radial segment or one sector of the disc and the local view is not that of item 3. \end{enumerate} \end{lemma} \begin{proof} (1) follows from the fact that radial segments are part of the boundary of an open or closed half-plane and a point on this boundary has the local view of that half-plane. (2) refers to the two trivial pyramids: the empty set and the full plane. (3) follows from the fact that edges are part of the boundary of open and closed half-spaces. (4) if all marks are equal then $x$ would have a totally marked or non-marked disk neighborhood. But that's the neighborhood of a 2-face. \end{proof} To determine the local view of a point $x$ with respect to a polyhedron $P$ is called to \emph{qualify} $x$ with respect to $P$. \section{The Data Structure}\label{nef data structure} We want to store Nef polyhedra in an intuitive way and want to use generally known data structures. Faces should be objects whose extents and topological neighborhoods (incidence) can be explored. We shift the focus from pyramids to incidence as compared to the extended W{\"u}rzburg structure. In our approach incidence is the key concept, pyramids can be derived from it. Starting from the last item of Fact \ref{bieri alternative defs} we have to model (parts) of an arrangement of lines in the plane. Moreover, we want to model the Nef faces including their incidences. 2-faces of a polyhedron are in general two-dimensional sets of points bounded by chains of segments that do not have to be simple, can be circularly closed, or open. The latter happens when 2-faces extend to infinity. Then the chain of segments has rays as its first and last elements. A simple line bounding a 2-face can be seen as two oppositely oriented rays starting in the same point. Therefore, concerning the geometry of edges, we need to model straight-line objects, such as segments, rays and lines, which are the result of intersection operations of half-spaces. To simplify the treatment of unbounded structures we use the concept of extended points and infimaximal frames which we have formally introduce in \repdiss{the report \cite{TR:infimaximalframes}}{Chapter \ref{chap Infimaximal Frames}} and which constitutes the geometric layer of our design. Consider an axis-parallel squared box centered at the origin. Any ray is pruned by this box in a so-called non-standard point (corresponding to the ray-tip). The assignment of ray-tips to box segments becomes topologically constant when we grow the framing box above a certain size. The frame is called infimaximal because it is always large enough to enclose all concrete geometric objects like points and segments in its interior that appear in the execution of our algorithms. Extended points are defined to be standard points and the non-standard points corresponding to ray-tips. They allow us to represent segments, rays and lines by a pair of such points and we get rid of the infinite extent of the unbounded structures. Adding such a frame to bound the plane, all unbounded faces become symbolically bounded structures, their boundary becomes a cyclic structure. To realize Nef faces, including their incidence relations, we need a cellular subdivision of the plane. We use a plane map data type (bidirected, embedded graphs). The incidence relations between the objects of a plane map (vertices, edges, and faces) reflect the topology of the local pyramids of the planar Nef polyhedron. \repdiss{}{The plane map concept is presented in the introduction of our notions and is our topological bottom layer.} Most newer textbook recommend the use of doubly connected edge lists (DCEL) or equivalently half edge data structures (HDS) \cite{prep-sham:CG,mmmo:CG} when they discuss the implementation of plane maps. There are already standard implementations of |plane maps| like the CGAL HDS or embedded bidirected graphs in LEDA (similar design). We use an extended version of the CGAL HDS structure as the topological bottom layer of our Nef polyhedra. See the paper of L. Kettner \cite{kettner-polyhedra} for an excellent review of different plane map representations and for the description of the adaptability of the CGAL HDS. To be more flexible we insert a decorator interface that homogeneously defines the functionality offered by the HDS. Geometrically we embed the vertices by means of extended points. Segments, rays, and lines are uniformly treated by the straight-line embedding of edges incident to such vertices. We additionally add one face cycle of edges whose embedding corresponds to an infimaximal frame: \begin{construction}\label{from Nef faces to plane map faces} Consider a Nef polyhedron $P$ and the connected components of the faces of dimension zero to two. Assign plane map objects of corresponding dimension to each component and match the Nef incidence concept with the plane map incidence concept where possible. All objects corresponding to unbounded connected components of Nef edges and unbounded components of Nef 2-faces have incomplete incidence structures: edges are missing end vertices and faces are missing closed face cycles. To cure this, we add an infimaximal frame consisting of four uedges and four corner vertices. For all plane map edges $e$ that correspond to a Nef edge component extending to infinity along a ray $r$ we do the following: if $r$ is pruned by one of the corner vertices on the frame structure link $e$ to that vertex; otherwise $e$ obtains an additional terminating vertex in the relative interior of a uedge $e'$ that is part of the infimaximal frame where $r$ meets the frame. $e'$ is split into two uedges by this vertex insertion. (For Nef edges that represent lines we do this at both ends.) After all such edges are linked to the frame (respecting the embedding such that the adjacency lists are order-preserving), all plane map faces corresponding to an unbounded component of a Nef face are cyclically bounded and their incidence structure can be completed. We call all edges and vertices that are part of the frame and the face outside of the frame that completes the subdivision combinatorially \emph{infimaximal frame objects}. \end{construction} To mark set membership, all objects of the plane map are \emph{selectable}. All objects (vertices, edges, faces) obtain a marker labeling set inclusion or exclusion. The markers allow us to obtain the local pyramids associated with the plane map objects. The local view of a vertex is defined by its own marker, the markers of the edges in its adjacency list and markers of the faces in between these edges representing the neighborhood disc as explained above. The local view of an edge is defined by its mark and the marks of its two incident faces. Finally the mark of a face maps to the trivial local view: the whole plane or the empty set depending on its selection flag. The selection markers of infimaximal frame objects have no geometric meaning and therefore those objects are always kept unselected. As plane maps are implemented by bidirected graphs, the incidence relation between edges and faces is encoded in an oriented fashion. Unoriented edges are implemented as pairs of oppositely directed halfedges where each such halfedge is incident to exactly one face. \repdiss{}{See the implementation description of plane maps in Section \ref{plane map implementation} for more information.} \begin{deff}[Data type]\label{data type definition} A Nef polyhedron $P$ is stored as a selective plane map $(V,E,F)$ according to Construction \ref{from Nef faces to plane map faces}. The objects of the plane map (vertices, edges, and faces) correspond to the connected components of the Nef faces of corresponding dimension and additionally to the infimaximal frame objects. Each vertex $v \in V$ is embedded via an extended point |point(v)|. Each object $o \in V \cup E \cup F$ is contained in $P$ iff the selection mark |mark(o) == true|. \end{deff} The feasibility of this definition can be seen as follows. Interpret $P$ as a set-valued function $\phi$ on (open) half-spaces $H_1,\ldots,H_r$ that are combined by the operations $\cap$ and $\cpl$. Let $h_i$ be the line bounding the half-space $H_i$. Now consider the arrangement built by the lines $\{h_i\}_i$ enclosed in a large enough frame. Interpret the arrangement $A(h_1,\ldots,h_r)$ inside the frame as a collection of relatively open convex cells of dimension 0 to 2. Consider any cell $c$. Any point of $c$ is either in $\phi(H_1,\ldots,H_r)$ or not. Mark all cells correspondingly. It should be clear that $A(h_1,\ldots,h_r)$ can be represented by a plane map as described above. Now start a simplification process. Consider all edges $e$ (besides the ones on the frame box). If $e$ and the two faces incident to it have the same mark, remove the edge and unify the faces (if not equal). Afterwards we iterate over all vertices and check if any vertex incident to two edges that are supported by the same line has the same local view as the points in the relative interior of the two incident edges. If this is the case, we unify the two edges and remove the vertex. Finally, we remove all vertices that are isolated and whose selection mark equals the one of the surrounding face. The final complex is just the data type described. When the simplification iteration terminates all points on vertices and edges have a local view that makes them low-dimensional faces of the Nef polyhedron. The above algorithm is called \emph{simplification} and is described in more detail when considering binary operations. There, we also give a runtime bound. Note that the local view properties of the vertices, edges, and faces of the plane map after the simplification process are a necessary condition of Definition \ref{data type definition}. \begin{lemma} The representation of Definition \ref{data type definition} is unique. \end{lemma} \begin{proof} Assume that there are two different plane maps $M(V,E,F)$ and $M'(V',E',F')$ representing the same Nef polyhedron $P$. If $P$ is the complete plane or the empty set then the plane maps consist of just one face inside the frame box and $F$ and $F'$ and their selection markers have to be equal. So assume otherwise. Then neither $P$ nor $\cpl P$ is empty. The boundary of $P$ and $\cpl P$ consists of vertices and edges. Assume that $M$ and $M'$ have a vertex at a point $x$ but the local views differ. Then, the represented point sets of $M$ and $M'$ differ and thus $P$ cannot be represented by both. If there is a point $x$ where $M$ has a vertex $v$ but $M'$ does not then obviously the local views are different too. Note that edges are terminated by vertices, and thus edges that are in $M$ but not in $M'$ already imply differences in the local view of their end vertices. The same holds when the edges are equal but the selection markers are not. Finally, note that the fact that the $1$-skeleta of $M$ and $M'$ are equal, implies that the faces are as well (they are defined that way). Different selection markers on faces imply different local views in the vertices that are part of their closure. As a consequence $M$ and $M'$ have to be equal. \end{proof} Selective plane maps are the basic structure used to store Nef polyhedra. Remember that the edges and faces of a plane map are the connected components of the Nef edges and Nef 2-faces. For performance reasons we do not maintain the relationship between plane map objects and the corresponding abstract Nef faces, which are defined as collections of the plane map objects with the same implicitly stored local pyramids. The \emph{size} of a Nef polyhedron is the size of its underlying plane map (which is the number of vertices, edges, and faces). For the binary operations we follow an approach as presented by Rossignac et al. \cite{ro:SGC}. In our case the approach is based on a generic plane sweep framework. A binary operation is basically split into three phases: subdivision -- selection -- simplification. The implementation is presented in the module |PM_overlayer|. A unary (topological) operation can be subdivided into two phases: selection and simplification. We shortly present the abstract algorithmic ideas and the runtimes. \begin{description} \item[subdivision] means for two plane maps $P_i (i=0,1)$ to create a plane map $P$ with a minimal number of objects (vertices, edges, faces) such that each object of $P$ is supported by exactly one object of $P_i$ for $i=0,1$. The subdivision is realized by a plane sweep of the objects of the $1$-skeleta of $P_i$ followed by face creation and determination of support. The time is dominated by the time for the sweep phase which is $O(n \log n)$ where $n$ is the size of the resulting subdivision. \item[selection] with respect to the binary set operations means selecting the cells of the subdivision according to the logic of the underlying Boolean operation. With respect to unary topological set operations, selection happens according to the logic of the topological unary operation. Selection is in both cases linear in $n$. \item[simplification] means unification of subsets of cells that have the same local view. This phase has a quasi-linear runtime due to the usage of a union-find data structure. Runtime is $O(n \alpha(kn,n))$\footnote{$\alpha(kn,n)$ is the extremly slow growing inverse of the Ackermann function used in the analysis of union-find data structures.} for some small constant $k$. \end{description} \begin{theorem} The result of a binary set operation (intersection, union, difference, symmetric difference) of two Nef polyhedra $P_0$ and $P_1$ can be calculated in time $O(n \log n)$ where $n$ is the size of the overlay of $P_0$ and $P_1$. The result of a unary set operation (complement, boundary, interior, closure, regularization) can be constructed in time $O( n \alpha(kn,n) )$ where $n$ is the size of the input structure. \end{theorem} The correctness and time bounds of our binary operations are based on three parts: \begin{itemize} \item The Sections \ref{unary operations} and \ref{binary set operations} show the high-level composition of unary and binary set operations decomposed into phases. \item \repdiss{Chapter \ref{plane map overlay}}{Section \ref{overlay module}} provides the algorithmic modules for the subdivision, selection, and simplification phase. The correctness and resource argumentation is purely based on affine concepts and therefore our readers can trust their standard geometric intuition when verifying the correctness of those modules. \item \repdiss{The report \cite{TR:infimaximalframes}}{Chapter \ref{chap Infimaximal Frames}} on the other hand shows that infimaximal frames allow us to use these algorithmic modules together with our extended objects. \end{itemize} The latter two observations together imply the correctness of the above theorem. The runtime lemmata of \repdiss{Chapter \ref{plane map overlay}}{Section \ref{overlay module}} imply the time bounds. Now for our additional functionality like point location and ray shooting queries we need more than just the naked plane map structure described above. Looking at the literature for ray shooting there is the notion of segment walks, which can be done easiest in convex subdivisions of the plane of bounded complexity \cite{mms:qsrayshooting}. Our goal is to refine the basic plane map by such a structure. Note that this structure implies again faces, edges, and vertices, but of a much simpler fashion. However we do not want to lose the original character of the plane map. We might still be interested in the original face cycles. We store the refinement separate from the plane map. One solution to the segment walk problem is to use a constrained Delaunay triangulation of the edges and vertices of the HDS, and use any efficient point location structure for the location of the ray shooting start point. \repdiss{Our approach is presented in Section \ref{PM_point_locator}.}{Point location is ommitted here and can be studied in the full implementation report \cite{TR:nefimplementation}.} \section{Top Level Implementation}\label{nef implementation} \displayeps{General_design}{An UML diagram of the Nef polyhedron module. The main modules are the geometry |Extended_homogenous|, the plane map decorator |PM_decorator|, the two algorithmic modules |PM_overlayer| and |PM_point_locator|.}{12cm} The whole implementation scheme is depicted in Figure \figref{General_design}. The main classes map to the abstract layers described above: geometry, plane maps, binary overlay, and point location. \begin{onlyindiss} In the following, we will mainly concentrate on the realization of the class |Nef_polyhedron_2| and the overlay modules |PM_overlayer| and |Segment_overlay_traits|. The functionality of |PM_decorator| is described by the concept in the appendix. The realization of the extended kernel is a topic of Chapter \ref{chap Infimaximal Frames}. \end{onlyindiss} \subsection{The Polyhedron Class} \begin{ignoreindiss} <>= template class Nef_polyhedron_2; template class Nef_polyhedron_2_rep; template std::ostream& operator<<(std::ostream&, const Nef_polyhedron_2&); template std::istream& operator>>(std::istream&, Nef_polyhedron_2&); @ \end{ignoreindiss} Our data type |Nef_polyhedron_2| is implemented as a smart pointer data type. Content such as a plane map object and a pointer to an optional point location object is stored in the class |Nef_polyhedron_2_rep|. The traits template parameter |T| is specified by the concept |ExtendedKernelTraits_2| as presented on page \pageref{ExtendedKernelTraits_2}. \displayeps{Nef_polyhedron_2}{The smart pointer realization of data type |Nef_polyhedron_2|.}{6cm} Within the scope of |Nef_polyhedron_2_rep| all auxiliary classes are instantiated. The plane map type is based on the CGAL HDS and uses two traits classes |HDS_traits| and |HDS_items|. The former carries attributes; the latter carries the (fixed) models for vertices, edges, and faces. \repdiss{}{Compare the extensions to their standard design in Section \ref{plane map implementation}.} <>= struct HDS_traits { typedef typename T::Point_2 Point; typedef bool Mark; }; typedef CGAL_HALFEDGEDS_DEFAULT Plane_map; typedef CGAL::PM_const_decorator Const_decorator; typedef CGAL::PM_decorator Decorator; typedef CGAL::PM_naive_point_locator Slocator; typedef CGAL::PM_point_locator Locator; typedef CGAL::PM_overlayer Overlayer; @ \begin{ignoreindiss} For Microsoft we use a hardwired Halfedge data structure specially adapted to its weaknesses. <>= struct HDS_traits { typedef typename T::Point_2 Point; typedef bool Mark; }; typedef CGAL::HalfedgeDS_default_MSC Plane_map; typedef CGAL::PM_const_decorator Const_decorator; typedef CGAL::PM_decorator Decorator; typedef CGAL::PM_naive_point_locator Slocator; typedef CGAL::PM_point_locator Locator; typedef CGAL::PM_overlayer Overlayer; <>= template class Nef_polyhedron_2_rep { typedef Nef_polyhedron_2_rep Self; friend class Nef_polyhedron_2; #ifndef CGAL_SIMPLE_HDS <> #else <> #endif //typedef CGAL::PM_transformer Transformer; Plane_map pm_; Locator* pl_; void init_locator() { if ( !pl_ ) pl_ = new Locator(pm_); } void clear_locator() { if ( pl_ ) { delete pl_; pl_=0; } } public: Nef_polyhedron_2_rep() : pm_(), pl_(0) {} Nef_polyhedron_2_rep(const Self& R) : pm_(), pl_(0) {} ~Nef_polyhedron_2_rep() { pm_.clear(); clear_locator(); } }; @ The class |Nef_polyhedron_2| has a static member object |EK| of type |T| which allows us to interface a kernel object. <>= /*{\Moptions print_title=yes }*/ /*{\Manpage {Nef_polyhedron_2}{T}{Nef Polyhedra in the Plane}{N}}*/ /*{\Mdefinition An instance of data type |\Mname| is a subset of the plane that is the result of forming complements and intersections starting from a finite set |H| of half-spaces. |\Mtype| is closed under all binary set operations |intersection|, |union|, |difference|, |complement| and under the topological operations |boundary|, |closure|, and |interior|. The template parameter |T| is specified via an extended kernel concept. |T| must be a model of the concept |ExtendedKernelTraits_2|. }*/ template class Nef_polyhedron_2 : public Handle_for< Nef_polyhedron_2_rep > { public: typedef T Extended_kernel; static T EK; // static extended kernel <> protected: <> public: <> }; // end of Nef_polyhedron_2 template T Nef_polyhedron_2::EK; @ \end{ignoreindiss} In the class |Nef_polyhedron_2| all geometric types are obtained from the geometric traits class |T|. |T| contains affine types that are part of the interface but also the extended types that are used in the infimaximal framework. The |Standard|-prefixed types from within |T| become the interface types of |Nef_polyhedron_2|. The non-prefixed\footnote{T is used as a traits class in our generic geometry based modules like |PM_overlayer|. Therefore, the extended types conform to a simpler naming scheme within |T|.} types within |T| become the extended types within |Nef_polyhedron_2|. <>= /*{\Mtypes 7}*/ typedef Nef_polyhedron_2 Self; typedef Handle_for< Nef_polyhedron_2_rep > Base; typedef typename T::Point_2 Extended_point; typedef typename T::Segment_2 Extended_segment; typedef typename T::Standard_line_2 Line; /*{\Mtypemember the oriented lines modeling half-planes}*/ typedef typename T::Standard_point_2 Point; /*{\Mtypemember the affine points of the plane.}*/ typedef typename T::Standard_direction_2 Direction; /*{\Mtypemember directions in our plane.}*/ typedef typename T::Standard_aff_transformation_2 Aff_transformation; /*{\Mtypemember affine transformations of the plane.}*/ @ \begin{ignoreindiss} <>= typedef bool Mark; /*{\Xtypemember marking set membership or exclusion.}*/ enum Boundary { EXCLUDED=0, INCLUDED=1 }; /*{\Menum construction selection.}*/ enum Content { EMPTY=0, COMPLETE=1 }; /*{\Menum construction selection}*/ @ \end{ignoreindiss} We import the type |Plane_map| and all decorator types like |Decorator|, |Overlayer|, |Locator|, |Slocator| from the representation type |Nef_polyhedron_2_rep|. Additionally, we import handles and iterators from |Decorator| \repdiss{}{(we do not list those typedef statements)}. \begin{ignoreindiss} <>= <> typedef Nef_polyhedron_2_rep Nef_rep; typedef typename Nef_rep::Plane_map Plane_map; typedef typename Nef_rep::Decorator Decorator; typedef typename Nef_rep::Const_decorator Const_decorator; typedef typename Nef_rep::Overlayer Overlayer; //typedef typename Nef_rep::T Transformer; typedef typename Nef_rep::Slocator Slocator; typedef typename Nef_rep::Locator Locator; Plane_map& pm() { return ptr()->pm_; } const Plane_map& pm() const { return ptr()->pm_; } friend std::ostream& operator<< <> (std::ostream& os, const Nef_polyhedron_2& NP); friend std::istream& operator>> <> (std::istream& is, Nef_polyhedron_2& NP); typedef typename Decorator::Vertex_handle Vertex_handle; typedef typename Decorator::Halfedge_handle Halfedge_handle; typedef typename Decorator::Face_handle Face_handle; typedef typename Decorator::Vertex_const_handle Vertex_const_handle; typedef typename Decorator::Halfedge_const_handle Halfedge_const_handle; typedef typename Decorator::Face_const_handle Face_const_handle; typedef typename Decorator::Vertex_iterator Vertex_iterator; typedef typename Decorator::Halfedge_iterator Halfedge_iterator; typedef typename Decorator::Face_iterator Face_iterator; typedef typename Const_decorator::Vertex_const_iterator Vertex_const_iterator; typedef typename Const_decorator::Halfedge_const_iterator Halfedge_const_iterator; typedef typename Const_decorator::Face_const_iterator Face_const_iterator; struct Except_frame_box_edges { Decorator D_; Face_handle f_; Except_frame_box_edges(Plane_map& P) : D_(P), f_(D_.faces_begin()) {} bool operator()(Halfedge_handle e) const { return D_.face(e)==f_ || D_.face(D_.twin(e))==f_; } }; @ \end{ignoreindiss} @ \subsection{Creating Polyhedra} We provide the construction methods for basic polyhedra. These are the empty set, the whole plane, open and closed half-planes, and construction of simple polygonal chains (Jordan Curves) where the modeled point set can be the bounded or unbounded part of the plane and the set can be open or closed. \displayeps{creation}{Elementary Nef polyhedra: (A) the empty set, (B) the whole plane, (C/D) a closed/open half-plane, (E/F) a closed/open bounded polygon, (G/H) the plane with a closed/open polygonal hole.}{10cm} The construction of simple Nef polyhedra is reduced to the overlay of a list of extended segments, the creation of the 2-faces, followed by setting the attribute marks that encode set inclusion. The first task is implemented in our overlayer module |PM_overlayer<>::create(s,e,DA)| where |S = tuple[s,e)| is the set of segments and |DA| is a data accessor that allows us to link plane map edges to the segments in |S|. Finally, we only have to take care of the correct marks of the plane map objects with respect to the construction information from our constructor interface. Note that by using the overlayer module we obtain all output properties of the plane map created from that module. All Nef polyhedra obtain an infimaximal frame embedded by four extended segments. We encapsulate this into the following operation. See the extended geometry module for the definition of this frame. <>= typedef std::list ES_list; typedef typename ES_list::const_iterator ES_iterator; void fill_with_frame_segs(ES_list& L) const /*{\Xop fills the list with the four segments which span our frame, the convex hull of SW,SE,NW,NE.}*/ { L.push_back(Extended_segment(EK.SW(),EK.NW())); L.push_back(Extended_segment(EK.SW(),EK.SE())); L.push_back(Extended_segment(EK.NW(),EK.NE())); L.push_back(Extended_segment(EK.SE(),EK.NE())); } @ We want to establish a link between a particular extended segment and its corresponding edge in the plane map. Our overlay module allows us to get a grip on this relation by means of a data accessor that is passed to the overlay algorithm. The class |Link_to_iterator| is a model for that data accessor concept. An object |D| of this type can store an iterator referencing a segment. Passed to the |PM_overlayer<>::create(...)| method it stores the corresponding edge after the executed overlay as its member |D._e|. |Link_to_iterator| also initializes all marks of the newly created skeleton objects. \begin{ignoreindiss} We show more details. We allow also degenerate segments. We associate a halfedge or vertex to an input segment |s = *it| referenced by an iterator |it|. If the segment |s| is trivial it will be associated to a vertex, else it is associated to a halfedge. The data accessor initializes the marks of the newly created skeleton objects to |m| (defined in the construction). For the concept of the data accessor see the manual page of |PM_overlayer<>::create(...)|. <>= struct Link_to_iterator { const Decorator& D; Halfedge_handle _e; Vertex_handle _v; ES_iterator _it; Mark _m; Link_to_iterator(const Decorator& d, ES_iterator it, Mark m) : D(d), _e(), _v(), _it(it), _m(m) {} void supporting_segment(Halfedge_handle e, ES_iterator it) { if ( it == _it ) _e = e; D.mark(e) = _m; } void trivial_segment(Vertex_handle v, ES_iterator it) { if ( it == _it ) _v = v; D.mark(v) = _m; } void starting_segment(Vertex_handle v, ES_iterator) { D.mark(v) = _m; } void passing_segment(Vertex_handle v, ES_iterator) { D.mark(v) = _m; } void ending_segment(Vertex_handle v, ES_iterator) { D.mark(v) = _m; } }; @ \end{ignoreindiss} \repdiss{}{We do not show the creation of the empty set or the full plane. This is just a trivial case of the following half-plane construction.} \begin{ignoreindiss} We add the box edges to |L|, overlay them and set the second face (inside the frame box) to the |plane| mark. We know from the output properties of |Overlayer| that the first face object is always the one surrounding the frame. <>= /*{\Mcreation 3}*/ Nef_polyhedron_2(Content plane = EMPTY) : Base(Nef_rep()) /*{\Mcreate creates an instance |\Mvar| of type |\Mname| and initializes it to the empty set if |plane == EMPTY| and to the whole plane if |plane == COMPLETE|.}*/ { ES_list L; fill_with_frame_segs(L); Overlayer D(pm()); Link_to_iterator I(D, --L.end(), false); D.create(L.begin(),L.end(),I); D.mark(++D.faces_begin()) = bool(plane); } @ \end{ignoreindiss} We come to the construction of a half-plane. A user describes an open or closed half-plane by an oriented line |l|. To create a plane map representing it we overlay a frame box plus an extended segment splitting the box into two faces along |l|. The overlayer module |PM_overlayer<>::create(...)| creates the plane map (including faces) out of the list of extended segments passed to it where no object is selected (concerning membership). The data accessor |Link_to_iterator I| obtains the edge |I._e| of |pm()| corresponding to |--L.end()| (the iterator pointing to the extended segment, which is the line) during the |create| phase. We can use that edge to mark its adjacent face and the edge itself according to the |line| flag. <>= Nef_polyhedron_2(const Line& l, Boundary line = INCLUDED) : Base(Nef_rep()) /*{\Mcreate creates a Nef polyhedron |\Mvar| containing the half-plane left of |l| including |l| if |line==INCLUDED|, excluding |l| if |line==EXCLUDED|.}*/ { TRACEN("Nconstruction from line "<>= template Nef_polyhedron_2(Forward_iterator it, Forward_iterator end, Boundary b = INCLUDED) : Base(Nef_rep()) /*{\Mcreate creates a Nef polyhedron |\Mvar| from the simple polygon |P| spanned by the list of points in the iterator range |[it,end)| and including its boundary if |b = INCLUDED| and excluding the boundary otherwise. |Forward_iterator| has to be an iterator with value type |Point|. This construction expects that |P| is simple. The degenerate cases where |P| contains no point, one point or spans just one segment (two points) are correctly handled. In all degenerate cases there's only one unbounded face adjacent to the degenerate polygon. If |b == INCLUDED| then |\Mvar| is just the boundary. If |b == EXCLUDED| then |\Mvar| is the whole plane without the boundary.}*/ { ES_list L; fill_with_frame_segs(L); bool empty = false; if (it != end) <> else empty = true; Overlayer D(pm()); Link_to_iterator I(D, --L.end(), true); D.create(L.begin(),L.end(),I); <> } @ We fill |L| with the segments cyclically spanned by the points in the input iterator range. <>= { Extended_point ef, ep = ef = EK.construct_point(*it); Forward_iterator itl=it; ++itl; if (itl == end) // case only one point L.push_back(EK.construct_segment(ep,ep)); else { // at least one segment while( itl != end ) { Extended_point en = EK.construct_point(*itl); L.push_back(EK.construct_segment(ep,en)); ep = en; ++itl; } L.push_back(EK.construct_segment(ep,ef)); } } @ We create the marks via the object stored in the |Link_to_iterator| object |I|. The object is determined by the last segment |s| in |L|. If that segment is trivial then |I. _v| stores the corresponding vertex. If |s| is non-trivial then |I. _e| contains the edge supported by the segment. We only have to extract the correct halfedge of the edge twins. Then, we can mark the face and the boundary accordingly. <>= if ( empty ) { D.mark(++D.faces_begin()) = !bool(b); return; } CGAL_assertion( I._e != Halfedge_handle() || I._v != Vertex_handle() ); if ( EK.is_degenerate(L.back()) ) { CGAL_assertion(I._v != Vertex_handle()); D.mark(D.face(I._v)) = !bool(b); D.mark(I._v) = b; } else { Halfedge_handle el = I._e; if ( D.point(D.target(el)) != EK.target(L.back()) ) el = D.twin(el); D.set_marks_in_face_cycle(el,bool(b)); if ( D.number_of_faces() > 2 ) D.mark(D.face(el)) = true; else D.mark(D.face(el)) = !bool(b); } clear_outer_face_cycle_marks(); @ \repdiss{}{We leave out the simple implementation of copy construction, cloning and basic operations like |clear()|, |is_empty()|, and |is_plane()|.} \begin{ignoreindiss} Now the standard copy construction, assignment and destruction. The first two are delegated to the |Handle| type. <>= Nef_polyhedron_2(const Nef_polyhedron_2& N1) : Base(N1) {} Nef_polyhedron_2& operator=(const Nef_polyhedron_2& N1) { Base::operator=(N1); return (*this); } ~Nef_polyhedron_2() {} #ifndef _MSC_VER template Nef_polyhedron_2(Forward_iterator first, Forward_iterator beyond, double p) : Base(Nef_rep()) /*{\Xcreate creates a random Nef polyhedron from the arrangement of the set of lines |S = set[first,beyond)|. The cells of the arrangement are selected uniformly at random with probability $p$. \precond $0 < p < 1$.}*/ { CGAL_assertion(0

>= protected: Nef_polyhedron_2(const Plane_map& H, bool clone=true) : Base(Nef_rep()) /*{\Xcreate makes |\Mvar| a new object. If |clone==true| then the underlying structure of |H| is copied into |\Mvar|.}*/ { if (clone) { Decorator D(pm()); // a decorator working on the rep plane map D.clone(H); // cloning H into pm() } } void clone_rep() { *this = Nef_polyhedron_2(pm()); } @ And now for the standard operations. We expect that the underlying plane map structure is simplified as described in the |PM_overlayer<>| module. Then for the simple configurations \emph{empty} and \emph{plane} the plane map has to be just the frame of four vertices, four uedges and two faces. <>= /*{\Moperations 4 3 }*/ public: void clear(Content plane = EMPTY) { *this = Nef_polyhedron_2(plane); } /*{\Mop makes |\Mvar| the empty set if |plane == EMPTY| and the full plane if |plane == COMPLETE|.}*/ bool is_empty() const /*{\Mop returns true if |\Mvar| is empty, false otherwise.}*/ { Const_decorator D(pm()); Face_const_iterator f = D.faces_begin(); return (D.number_of_vertices()==4 && D.number_of_edges()==4 && D.number_of_faces()==2 && D.mark(++f) == false); } bool is_plane() const /*{\Mop returns true if |\Mvar| is the whole plane, false otherwise.}*/ { Const_decorator D(pm()); Face_const_iterator f = D.faces_begin(); return (D.number_of_vertices()==4 && D.number_of_edges()==4 && D.number_of_faces()==2 && D.mark(++f) == true); } @ \end{ignoreindiss} \subsection{Unary Operations}\label{unary operations} For the unary operations on Nef polyhedra we implement several class-modifying methods. They can be chained together into larger units. We thereby save cloning operations if the representation object is only referenced by one handle. Note that the first line of any modifying operation has to check if the representation object is shared by several handles. If the representation object is shared, the plane map has to be cloned before modification. We first implement the three operations $\cpl$, $\int$, and $\bd$. As our planar Nef polyhedra completely partition the plane, the complement operation is easy to implement by an inversion (Boolean flip) of the selection markers. Note that this conforms to the result \cite[theorem 6;14]{nef:polybook} in which Nef showed that the low-dimensional faces of $P$ and $\cpl P$ (in their common boundary) are the same and the full-dimensional faces that are part of $P$ or $\cpl P$ obviously exchange their role (if non-empty). The local pyramid in any point $x$ of the plane is inverted by the Boolean flips according to \cite[theorem 3;15]{nef:polybook} $(\cpl P)^x = \cpl P^x$. Due to the special role\footnote{No affine object can be placed on or outside the infimaximal frame.} of the outer face and the edges and vertices that are part of the frame box we keep all objects of the frame unmarked (operation |clear_outer_face_cycle_marks|). Note that just by flipping we do not spoil the local views properties as specified in Lemma \ref{local view properties}. Thus, we do not have to simplify here. <>= void extract_complement() { TRACEN("extract complement"); if ( is_shared() ) clone_rep(); Overlayer D(pm()); Vertex_iterator v, vend = D.vertices_end(); for(v = D.vertices_begin(); v != vend; ++v) D.mark(v) = !D.mark(v); Halfedge_iterator e, eend = D.halfedges_end(); for(e = D.halfedges_begin(); e != eend; ++(++e)) D.mark(e) = !D.mark(e); Face_iterator f, fend = D.faces_end(); for(f = D.faces_begin(); f != fend; ++f) D.mark(f) = !D.mark(f); clear_outer_face_cycle_marks(); } @ The interior $\int P$ of a point set $P$ is the set of all points where an open ball of infinitesimal radius (a neighborhood) is contained in the point set. This is not the case for all low-dimensional faces of the plane map. Accordingly (see also \cite[theorem 3;19]{nef:polybook}), we have to keep all selected full-dimensional faces of $P$ and all objects of the $1$-skeleton have to be deselected. Afterwards the simplification operation minimizes the structure and makes it again consistent with Definition \ref{data type definition}. For example, marked isolated vertices within non-marked faces and marked edges within such faces are first unmarked and then deleted in the |simplify()| operation. The simplification operation has to exempt edges of the infimaximal frame. An object |Except_frame_box_edges(P)| has a function operator method |bool operator()(Halfedge_handle e)|that returns true iff the edge |e| of the plane map |P| is part of the frame box. Thereby within |simplify| a removal of edges is only executed on edges that partition the interior of the frame box. <>= void extract_interior() { TRACEN("extract interior"); if ( is_shared() ) clone_rep(); Overlayer D(pm()); Vertex_iterator v, vend = D.vertices_end(); for(v = D.vertices_begin(); v != vend; ++v) D.mark(v) = false; Halfedge_iterator e, eend = D.halfedges_end(); for(e = D.halfedges_begin(); e != eend; ++(++e)) D.mark(e) = false; D.simplify(Except_frame_box_edges(pm())); } @ The boundary of a point set $P$ is defined to be the intersection $\clos P \cap \clos\cpl P$. This is the set of all points that have a nonempty neighborhood with $\int P$ or $\ext P$. Any point $x$ on the $1$-skeleton of the plane map has this property due to the properties of its local pyramid $P^x$. The boundary $\bd P$ is thus obtained by selecting all low-dimensional objects and then deselecting all 2-faces. Finally we cope with the frame and simplify the structure. <>= void extract_boundary() { TRACEN("extract boundary"); if ( is_shared() ) clone_rep(); Overlayer D(pm()); Vertex_iterator v, vend = D.vertices_end(); for(v = D.vertices_begin(); v != vend; ++v) D.mark(v) = true; Halfedge_iterator e, eend = D.halfedges_end(); for(e = D.halfedges_begin(); e != eend; ++(++e)) D.mark(e) = true; Face_iterator f, fend = D.faces_end(); for(f = D.faces_begin(); f != fend; ++f) D.mark(f) = false; clear_outer_face_cycle_marks(); D.simplify(Except_frame_box_edges(pm())); } @ Now we use the above operations for \emph{closure} and \emph{regularization}. The closure of $P$ can be reduced to the operations \emph{interior} and \emph{complement} as $\clos P = \cpl\int\cpl P$. The regularization of $P$ is defined as $\clos\int P$. <>= void extract_closure() /*{\Xop converts |\Mvar| to its closure. }*/ { TRACEN("extract closure"); extract_complement(); extract_interior(); extract_complement(); } void extract_regularization() /*{\Xop converts |\Mvar| to its regularization. }*/ { TRACEN("extract regularization"); extract_interior(); extract_closure(); } @ The constructive interface methods are just mapped to the corresponding extract methods. <>= /*{\Mtext \headerline{Constructive Operations}}*/ Nef_polyhedron_2 complement() const /*{\Mop returns the complement of |\Mvar| in the plane.}*/ { Nef_polyhedron_2 res = *this; res.extract_complement(); return res; } @ All other operations like |interior()|, |closure()|, |boundary()|, and |regularization()| are implemented similarly. \begin{ignoreindiss} <>= Nef_polyhedron_2 interior() const /*{\Mop returns the interior of |\Mvar|.}*/ { Nef_polyhedron_2 res = *this; res.extract_interior(); return res; } Nef_polyhedron_2 closure() const /*{\Mop returns the closure of |\Mvar|.}*/ { Nef_polyhedron_2 res = *this; res.extract_closure(); return res; } Nef_polyhedron_2 boundary() const /*{\Mop returns the boundary of |\Mvar|.}*/ { Nef_polyhedron_2 res = *this; res.extract_boundary(); return res; } Nef_polyhedron_2 regularization() const /*{\Mop returns the regularized polyhedron (closure of interior).}*/ { Nef_polyhedron_2 res = *this; res.extract_regularization(); return res; } @ \end{ignoreindiss} \subsection{Binary Set Operations}\label{binary set operations} We follow Rossignac and O'Connor \cite{ro:SGC} and split the binary operations into three phases: subdivision, selection, and simplification. \repdiss{}{For the implementation see the module |PM_overlayer<>| in Section \ref{overlay module}.} The subdivision phase creates the overlay of the two input structures. This overlay has the property that each object (vertex, edge, face) has exactly one object from each input structure that supports it. \repdiss{}{This proposition is proven in Lemma \ref{supporting lemma}.} After the subdivison each object of the resulting plane map knows its mark in each of the two input structures and can thereby be qualified with respect to each input structure. The binary set operation is then reduced to a Boolean predicate on these marks. The resulting structure can be a planar partition that is not a legal Nef polyhedron due to the fact that plane map boundary objects can have local views that contradict Lemma \ref{local view properties}. Violations are fixed in the simplification phase without changing the represented point set. This simplification makes the plane map representation minimal with respect to the number of its objects and again consistent with Definition \ref{data type definition}. The above scheme refers to the theory as presented by Nef who showed the following general lemma. \begin{lemma}\label{intersection lemma} Let $P_0$, $P_1$ be polyhedra. Then every face of $P = P_0 \cap P_1$ is the union of intersections of faces of $P_0$ and $P_1$. \end{lemma} The proof follows \cite[Satz 6;16]{nef:polybook}. As a consequence the simplification just unions objects within $P$ to form the connected components of Nef faces. To implement the binary set operations we use functors\footnote{a short form for function objects.} that carry the underlying Boolean logic. <>= struct AND { bool operator()(bool b1, bool b2) const { return b1&&b2; } }; struct OR { bool operator()(bool b1, bool b2) const { return b1||b2; } }; struct DIFF { bool operator()(bool b1, bool b2) const { return b1&&!b2; } }; struct XOR { bool operator()(bool b1, bool b2) const { return (b1&&!b2)||(!b1&&b2); } }; <>= Nef_polyhedron_2 intersection(const Nef_polyhedron_2& N1) const /*{\Mop returns |\Mvar| $\cap$ |N1|. }*/ { Nef_polyhedron_2 res(pm(),false); // empty, no frame Overlayer D(res.pm()); D.subdivide(pm(),N1.pm()); AND _and; D.select(_and); res.clear_outer_face_cycle_marks(); D.simplify(Except_frame_box_edges(res.pm())); return res; } @ Join, difference, and symmetric difference follow similar schemes based on |OR|, |DIFF|, and |XOR|. \begin{ignoreindiss} <>= Nef_polyhedron_2 join(const Nef_polyhedron_2& N1) const /*{\Mop returns |\Mvar| $\cup$ |N1|. }*/ { Nef_polyhedron_2 res(pm(),false); // empty, no frame Overlayer D(res.pm()); D.subdivide(pm(),N1.pm()); OR _or; D.select(_or); res.clear_outer_face_cycle_marks(); D.simplify(Except_frame_box_edges(res.pm())); return res; } Nef_polyhedron_2 difference(const Nef_polyhedron_2& N1) const /*{\Mop returns |\Mvar| $-$ |N1|. }*/ { Nef_polyhedron_2 res(pm(),false); // empty, no frame Overlayer D(res.pm()); D.subdivide(pm(),N1.pm()); DIFF _diff; D.select(_diff); res.clear_outer_face_cycle_marks(); D.simplify(Except_frame_box_edges(res.pm())); return res; } Nef_polyhedron_2 symmetric_difference( const Nef_polyhedron_2& N1) const /*{\Mop returns the symmectric difference |\Mvar - T| $\cup$ |T - \Mvar|. }*/ { Nef_polyhedron_2 res(pm(),false); // empty, no frame Overlayer D(res.pm()); D.subdivide(pm(),N1.pm()); XOR _xor; D.select(_xor); res.clear_outer_face_cycle_marks(); D.simplify(Except_frame_box_edges(res.pm())); return res; } #if 0 Nef_polyhedron_2 transform(const Aff_transformation& t) const /*{\Mop returns $t(|\Mvar|)$.}*/ { Nef_polyhedron_2 res(pm()); // cloned Transformer PMT(res.pm()); PMT.transform(t); return res; } #endif @ The first face object is the one outside the bounding frame. Thus it's only hole face cycle consists of the edges of the frame. <>= void clear_outer_face_cycle_marks() { // unset all frame marks Decorator D(pm()); Face_iterator f = D.faces_begin(); D.mark(f) = false; Halfedge_handle e = D.holes_begin(f); D.set_marks_in_face_cycle(e, false); } @ \end{ignoreindiss} \repdiss{}{We do not show the implementation of the operators |*,+,-,^,!| which map to the above operations.} @ \begin{ignoreindiss} <>= /*{\Mtext Additionally there are operators |*,+,-,^,!| which implement the binary operations \emph{intersection}, \emph{union}, \emph{difference}, \emph{symmetric difference}, and the unary operation \emph{complement} respectively. There are also the corresponding modification operations |*=,+=,-=,^=|.}*/ Nef_polyhedron_2 operator*(const Nef_polyhedron_2& N1) const { return intersection(N1); } Nef_polyhedron_2 operator+(const Nef_polyhedron_2& N1) const { return join(N1); } Nef_polyhedron_2 operator-(const Nef_polyhedron_2& N1) const { return difference(N1); } Nef_polyhedron_2 operator^(const Nef_polyhedron_2& N1) const { return symmetric_difference(N1); } Nef_polyhedron_2 operator!() const { return complement(); } Nef_polyhedron_2& operator*=(const Nef_polyhedron_2& N1) { this = intersection(N1); return *this; } Nef_polyhedron_2& operator+=(const Nef_polyhedron_2& N1) { this = join(N1); return *this; } Nef_polyhedron_2& operator-=(const Nef_polyhedron_2& N1) { this = difference(N1); return *this; } Nef_polyhedron_2& operator^=(const Nef_polyhedron_2& N1) { this = symmetric_difference(N1); return *this; } @ \end{ignoreindiss} \subsection{Binary Comparison Operations} All set comparison operations are reduced to binary operations followed by an empty-set test. For two Nef polyhedra $P_1$, $P_2$ it holds \begin{eqnarray*} P_1 = P_2 & \Leftrightarrow & \mathit{symmetric\_difference}(P_1,P_2) = \emptyset\\ P_1 \subseteq P_2 & \Leftrightarrow & \mathit{difference}(P_1,P_2) = \emptyset\\ P_1 \varsubsetneq P_2 & \Leftrightarrow & \mathit{difference}(P_1,P_2) = \emptyset \ \ \AND\ \ \mathit{difference}(P_2,P_1) \neq \emptyset \end{eqnarray*} In our specification $\subseteq$ is |operator<=|, and $\varsubsetneq$ is |operator<|. The other operations are symmetric. <>= /*{\Mtext There are also comparison operations like |<,<=,>,>=,==,!=| which implement the relations subset, subset or equal, superset, superset or equal, equality, inequality, respectively.}*/ bool operator==(const Nef_polyhedron_2& N1) const { return symmetric_difference(N1).is_empty(); } bool operator!=(const Nef_polyhedron_2& N1) const { return !operator==(N1); } bool operator<=(const Nef_polyhedron_2& N1) const { return difference(N1).is_empty(); } bool operator<(const Nef_polyhedron_2& N1) const { return difference(N1).is_empty() && !N1.difference(*this).is_empty(); } @ \begin{ignoreindiss} <>= bool operator>=(const Nef_polyhedron_2& N1) const { return N1.difference(*this).is_empty(); } bool operator>(const Nef_polyhedron_2& N1) const { return N1.difference(*this).is_empty() && !difference(N1).is_empty(); } @ \end{ignoreindiss} \subsection{Point location and Ray shooting} Let |P| be the plane map underlying our Nef polyhedron stored in the |*this| object. The result of a point location query with an affine point |p| is the object of |P| whose embedding contains |p|. Ray shooting queries come in two flavors. One variant starts the ray shot in a point |p| and determines the closest object of |P| in direction |d| that is in the set (determined by the selection mark). The other variant determines the closest $1$-skeleton object in direction |d|. The point location and ray shooting functionality is taken from the two point location classes |PM_point_locator<>| and |PM_naive_point_locator<>|. All operations can choose between the two approaches by a mode flag |m|. The default is location as implemented by |PM_point_locator<>|. That class uses a further subdivision of |P| by a locally minimized weight constrained triangulations (LMWT) to allow so-called segment walks. The LMWT is calculated on demand, when the first point location or ray shooting operation is called. The naive point location method is based on a global examination of all objects of |P| to find the one that contains |p|. \begin{ignoreindiss} <>= /*{\Mtext \headerline{Exploration - Point location - Ray shooting} As Nef polyhedra are the result of forming complements and intersections starting from a set |H| of half-spaces that are defined by oriented lines in the plane, they can be represented by an attributed plane map $M = (V,E,F)$. For topological queries within |M| the following types and operations allow exploration access to this structure.}*/ /*{\Mtypes 3}*/ typedef Const_decorator Topological_explorer; typedef CGAL::PM_explorer Explorer; /*{\Mtypemember a decorator to examine the underlying plane map. See the manual page of |Explorer|.}*/ typedef typename Locator::Object_handle Object_handle; /*{\Mtypemember a generic handle to an object of the underlying plane map. The kind of object |(vertex, halfedge, face)| can be determined and the object can be assigned to a corresponding handle by the three functions:\\ |bool assign(Vertex_const_handle& h, Object_handle)|\\ |bool assign(Halfedge_const_handle& h, Object_handle)|\\ |bool assign(Face_const_handle& h, Object_handle)|\\ where each function returns |true| iff the assignment to |h| was done.}*/ enum Location_mode { DEFAULT, NAIVE, LMWT }; /*{\Menum selection flag for the point location mode.}*/ /*{\Moperations 3 1 }*/ void init_locator() const { const_cast(this)->ptr()->init_locator(); } const Locator& locator() const { assert(ptr()->pl_); return *(ptr()->pl_); } @ \end{ignoreindiss} The type |Object_handle| is a polymorphic handle that can reference vertices, edges, and faces. Conversion is done by an |assign| operation similarly to the polymorphic CGAL type |Object|. <>= bool contains(Object_handle h) const /*{\Mop returns true iff the object |h| is contained in the set represented by |\Mvar|.}*/ { Slocator PL(pm()); return PL.mark(h); } bool contained_in_boundary(Object_handle h) const /*{\Mop returns true iff the object |h| is contained in the $1$-skeleton of |\Mvar|.}*/ { Vertex_const_handle v; Halfedge_const_handle e; return ( CGAL::assign(v,h) || CGAL::assign(e,h) ); } @ The chosen point location method depends on |m|. In the non-naive case the locator object is initialized by a call to |init_locator()|. The corresponding locator object is stored in the representation object for further usage in iterated queries and can be accessed by the |locator()| method. The naive locate operation requires a segment as input that intersects the $1$-skeleton of the plane map. <>= Object_handle locate(const Point& p, Location_mode m = DEFAULT) const /*{\Mop returns a generic handle |h| to an object (face, halfedge, vertex) of the underlying plane map that contains the point |p| in its relative interior. The point |p| is contained in the set represented by |\Mvar| if |\Mvar.contains(h)| is true. The location mode flag |m| allows one to choose between different point location strategies.}*/ { if (m == DEFAULT || m == LMWT) { init_locator(); Extended_point ep = EK.construct_point(p); return locator().locate(ep); } else if (m == NAIVE) { Slocator PL(pm(),EK); Extended_segment s(EK.construct_point(p), PL.point(PL.vertices_begin())); return PL.locate(s); } CGAL_assertion_msg(0,"location mode not implemented."); return Object_handle(); } @ The ray shooting operation determines the closest object of |P| that is marked and hit by a ray shot from the point |p| in direction |d|. The search is delegated to the corresponding member of the locator object. The class |INSET| is the predicate class that stops the ray shot when a marked object is hit. See the manual page of the locator classes for its concept. <>= struct INSET { const Const_decorator& D; INSET(const Const_decorator& Di) : D(Di) {} bool operator()(Vertex_const_handle v) const { return D.mark(v); } bool operator()(Halfedge_const_handle e) const { return D.mark(e); } bool operator()(Face_const_handle f) const { return D.mark(f); } }; Object_handle ray_shoot(const Point& p, const Direction& d, Location_mode m = DEFAULT) const /*{\Mop returns a handle |h| with |\Mvar.contains(h)| that can be converted to a |Vertex_/Halfedge_/Face_const_handle| as described above. The object returned is intersected by the ray starting in |p| with direction |d| and has minimal distance to |p|. The operation returns the null handle |NULL| if the ray shoot along |d| does not hit any object |h| of |\Mvar| with |\Mvar.contains(h)|. The location mode flag |m| allows one to choose between different point location strategies.}*/ { if (m == DEFAULT || m == LMWT) { init_locator(); Extended_point ep = EK.construct_point(p), eq = EK.construct_point(p,d); return locator().ray_shoot(EK.construct_segment(ep,eq), INSET(locator())); } else if (m == NAIVE) { Slocator PL(pm(),EK); Extended_point ep = EK.construct_point(p), eq = EK.construct_point(p,d); return PL.ray_shoot(EK.construct_segment(ep,eq),INSET(PL)); } CGAL_assertion_msg(0,"location mode not implemented."); return Object_handle(); } @ A similar implementation is used for |ray_shoot_to_boundary|. Note that we only use a different predicate |INSKEL|. <>= struct INSKEL { bool operator()(Vertex_const_handle) const { return true; } bool operator()(Halfedge_const_handle) const { return true; } bool operator()(Face_const_handle) const { return false; } }; Object_handle ray_shoot_to_boundary(const Point& p, const Direction& d, Location_mode m = DEFAULT) const /*{\Mop returns a handle |h| that can be converted to a |Vertex_/Halfedge_const_handle| as described above. The object returned is part of the $1$-skeleton of |\Mvar|, intersected by the ray starting in |p| with direction |d| and has minimal distance to |p|. The operation returns the null handle |NULL| if the ray shoot along |d| does not hit any $1$-skeleton object |h| of |\Mvar|. The location mode flag |m| allows one to choose between different point location strategies.}*/ { if (m == DEFAULT || m == LMWT) { init_locator(); Extended_point ep = EK.construct_point(p), eq = EK.construct_point(p,d); return locator().ray_shoot(EK.construct_segment(ep,eq),INSKEL()); } else if (m == NAIVE) { Slocator PL(pm(),EK); Extended_point ep = EK.construct_point(p), eq = EK.construct_point(p,d); return PL.ray_shoot(EK.construct_segment(ep,eq),INSKEL()); } CGAL_assertion_msg(0,"location mode not implemented."); return Object_handle(); } @ To examine the plane map underlying the Nef polyhedron the user can obtain a decorator object that has read-only access to |pm()|. Thus, modifications can only take place via the interface operations of |Nef_polydron_2|. <>= Explorer explorer() const { return Explorer(pm(),EK); } /*{\Mop returns a decorator object which allows read-only access of the underlying plane map. See the manual page |Explorer| for its usage.}*/ /*{\Mtext\headerline{Input and Output} A Nef polyhedron |\Mvar| can be visualized in a |Window_stream W|. The output operator is defined in the file |CGAL/IO/Nef_\-poly\-hedron_2_\-Win\-dow_\-stream.h|. }*/ /*{\Mimplementation Nef polyhedra are implemented on top of a halfedge data structure and use linear space in the number of vertices, edges and facets. Operations like |empty| take constant time. The operations |clear|, |complement|, |interior|, |closure|, |boundary|, |regularization|, input and output take linear time. All binary set operations and comparison operations take time $O(n \log n)$ where $n$ is the size of the output plus the size of the input. The point location and ray shooting operations are implemented in two flavors. The |NAIVE| operations run in linear query time without any preprocessing, the |DEFAULT| operations (equals |LMWT|) run in sub-linear query time, but preprocessing is triggered with the first operation. Preprocessing takes time $O(N^2)$, the sub-linear point location time is either logarithmic when LEDA's persistent dictionaries are present or if not then the point location time is worst-case linear, but experiments show often sublinear runtimes. Ray shooting equals point location plus a walk in the constrained triangulation overlayed on the plane map representation. The cost of the walk is proportional to the number of triangles passed in direction |d| until an obstacle is met. In a minimum weight triangulation of the obstacles (the plane map representing the polyhedron) the theory provides a $O(\sqrt{n})$ bound for the number of steps. Our locally minimum weight triangulation approximates the minimum weight triangulation only heuristically (the calculation of the minimum weight triangulation is conjectured to be NP hard). Thus we have no runtime guarantee but a strong experimental motivation for its approximation.}*/ /*{\Mexample Nef polyhedra are parameterized by a so-called extended geometric kernel. There are three kernels, one based on a homogeneous representation of extended points called |Extended_homogeneous| where |RT| is a ring type providing additionally a |gcd| operation and one based on a cartesian representation of extended points called |Extended_cartesian| where |NT| is a field type, and finally |Filtered_extended_homogeneous| (an optimized version of the first). The member types of |Nef_polyhedron_2< Extended_homogeneous >| map to corresponding types of the CGAL geometry kernel (e.g. |Nef_polyhedron::Line| equals |CGAL::Homogeneous::Line_2| in the example below). \begin{Mverb} #include #include #include #include using namespace CGAL; typedef Extended_homogeneous Extended_kernel; typedef Nef_polyhedron_2 Nef_polyhedron; typedef Nef_polyhedron::Line Line; int main() { Nef_polyhedron N1(Line(1,0,0)); Nef_polyhedron N2(Line(0,1,0), Nef_polyhedron::EXCLUDED); Nef_polyhedron N3 = N1 * N2; // line (*) return 0; } \end{Mverb} After line (*) |N3| is the intersection of |N1| and |N2|.}*/ @ \begin{ignoreindiss} \subsection{The file wrapper} <>= <> // file : include/CGAL/Nef_polyhedron_2.h <> #ifndef CGAL_NEF_POLYHEDRON_2_H #define CGAL_NEF_POLYHEDRON_2_H #if defined(_MSC_VER) || defined(__BORLANDC__) #define CGAL_SIMPLE_HDS #endif #include #include #include #ifndef CGAL_SIMPLE_HDS #include #include #else #include #endif #include #include #include #include //#include #include #include #include #undef _DEBUG #define _DEBUG 11 #include CGAL_BEGIN_NAMESPACE <> <> CGAL_END_NAMESPACE #undef CGAL_SIMPLE_HDS #endif //CGAL_NEF_POLYHEDRON_2_H @ \end{ignoreindiss} \subsection{Visualization} In this section we provide a drawing routine for Nef polyhedra in a LEDA window. We want to draw faces, edges, vertices in this order, where faces are maximally connected point sets bounded by one outer face cycle and maybe by several inner hole cycles. We draw objects that are in our point set in black and objects that are not in our point set with a light color. Note that we face the following problem. Our window represents a rectangular view to our square frame, which is large enough to make the topology on this boundary constant. Imagine making our frame big enough and then shrinking it slowly down to zero. For each ray with slope not equal to one and not containing the origin there is a value $R$ when the ray tip on the frame leaves its correct frame segment. If we want to prevent topological difficulties when drawing the polyhedron we have to keep our frame radius above the minimum $R_m$. Thus visualization determines this $R_m$ and sets the internal evaluation parameter to this value. Then all points on the frame and segments containing such points have fixed coordinates and can be drawn. For the concrete technical details of drawing the objects see the class |PM_visualizor<>|. <>= static long frame_default = 100; static bool show_triangulation = false; template CGAL::Window_stream& operator<<(CGAL::Window_stream& ws, const Nef_polyhedron_2& P) { typedef Nef_polyhedron_2 Polyhedron; typedef typename T::RT RT; typedef typename T::Standard_RT Standard_RT; typedef typename Polyhedron::Topological_explorer TExplorer; typedef typename Polyhedron::Point Point; typedef typename Polyhedron::Line Line; typedef CGAL::PM_BooleColor BooleColor; typedef CGAL::PM_visualizor Visualizor; TExplorer D = P.explorer(); const T& E = Nef_polyhedron_2::EK; Standard_RT frame_radius = frame_default; E.determine_frame_radius(D.points_begin(),D.points_end(),frame_radius); RT::set_R(frame_radius); Visualizor PMV(ws,D); PMV.draw_map(); <> return ws; } @ Drawing of the constrained triangulation is done depending on the static variable |show_triangulation|. Of course such animation requires the necessary preprocessing with the locator object. <>= if (show_triangulation) { P.init_locator(); Visualizor V(ws,P.locator().triangulation()); V.draw_skeleton(CGAL::BLUE); } @ \begin{ignoreindiss} The header just wraps the above operations. <>= <> // file : include/CGAL/IO/Nef_polyhedron_2_Window_stream.h <> #ifndef NEF_POLYHEDRON_2_WINDOW_STREAM_H #define NEF_POLYHEDRON_2_WINDOW_STREAM_H #include #include CGAL_BEGIN_NAMESPACE <> CGAL_END_NAMESPACE #endif // NEF_POLYHEDRON_2_WINDOW_STREAM_H @ \end{ignoreindiss}% \subsection{Input and Output} Standard input and output is done by the plane map I/O class |PM_io_parser|. \repdiss{}{See Section \ref{plane map implementation} for more information.} \begin{ignoreindiss} We tag the output with an idendifier to be able to check correct coordinate representation when we read it as input. <>= template std::ostream& operator<< (std::ostream& os, const Nef_polyhedron_2& NP) { os << "Nef_polyhedron_2<" << NP.EK.output_identifier() << ">\n"; typedef typename Nef_polyhedron_2::Decorator Decorator; CGAL::PM_io_parser O(os, NP.pm()); O.print(); return os; } template std::istream& operator>> (std::istream& is, Nef_polyhedron_2& NP) { typedef typename Nef_polyhedron_2::Decorator Decorator; CGAL::PM_io_parser I(is, NP.pm()); if (I.check_sep("Nef_polyhedron_2<") && I.check_sep(NP.EK.output_identifier()) && I.check_sep(">")) I.read(); else { std::cerr << "Nef_polyhedron_2 input corrupted." << std::endl; NP = Nef_polyhedron_2(); } typename Nef_polyhedron_2::Topological_explorer D(NP.explorer()); D.check_integrity_and_topological_planarity(); return is; } @ \end{ignoreindiss} \subsection{Hiding extended geometry} The plane map explorer provides an interface that masks the properties of our extended kernel by reintroducing purely affine objects. We want to provide a simple interface for users who are not interested in the detailed features of extended objects. The methods of the class |PM_explorer<>| allow queries to the category of vertices and edges such as ``Is a vertex embedded in the affine space or on the frame box?'' or ``Is an edge part of the affine structure or part of the frame box''. \repdiss{}{Its implementation trivially maps to the operations of the extended kernel. See its interface in Section \ref{Explorer} on page \pageref{Explorer}.} \begin{ignoreindiss} <>= <> // file : include/CGAL/Nef_2/PM_explorer.h <> #ifndef CGAL_PM_EXPLORER_H #define CGAL_PM_EXPLORER_H #include #include CGAL_BEGIN_NAMESPACE <> CGAL_END_NAMESPACE #endif // CGAL_PM_EXPLORER_H <>= /*{\Moptions print_title=yes }*/ /*{\Moptions outfile=Explorer.man }*/ /*{\Msubst PM_explorer#Explorer }*/ /*{\Manpage {PM_explorer}{}{Plane map exploration}{E}}*/ /*{\Mdefinition An instance |\Mvar| of the data type |\Mname| is a decorator to explore the structure of the plane map underlying the Nef polyhedron. It inherits all topological adjacency exploration operations from |PMConstDecorator|. |\Mname| additionally allows one to explore the geometric embedding. The position of each vertex is given by a so-called extended point, which is either a standard affine point or the tip of a ray touching an infinimaximal square frame centered at the origin. A vertex |v| is called a \emph{standard} vertex if its embedding is a \emph{standard} point and \emph{non-standard} if its embedding is a \emph{non-standard} point. By the straightline embedding of their source and target vertices, edges correspond to either affine segments, rays or lines or are part of the bounding frame. \displayeps{extsegs}{Extended geometry: standard vertices are marked by S, non-standard vertices are marked by N. \textbf{A}: The possible embeddings of edges: an affine segment s1, an affine ray s2, an affine line s3. \textbf{B}: A plane map embedded by extended geometry: note that the frame is arbitrarily large, the 6 vertices on the frame are at infinity, the two faces represent a geometrically unbounded area, however they are topologically closed by the frame edges. No standard point can be placed outside the frame.}{10cm} }*/ /*{\Mgeneralization Topological_explorer}*/ template class PM_explorer : public PMCDEC { typedef PMCDEC Base; typedef PM_explorer Self; const GEOM* pK; public: /*{\Mtypes 4}*/ typedef PMCDEC Topological_explorer; /*{\Mtypemember The base class.}*/ typedef typename PMCDEC::Plane_map Plane_map; /*{\Xtypemember equals |PMCDEC::Plane_map|, the underlying plane map type.}*/ typedef GEOM Geometry; /*{\Xtypemember equals |GEOM|. Add link to GEOM model.\\ \precond |Geometry::Point_2| equals |Plane_map::Point|. }*/ typedef typename GEOM::Standard_point_2 Point; /*{\Mtypemember the point type of finite vertices.}*/ typedef typename GEOM::Standard_ray_2 Ray; /*{\Mtypemember the ray type of vertices on the frame.}*/ <> /*{\Mtext Iterators, handles, and circulators are inherited from |Topological_explorer|.}*/ /*{\Mcreation 3}*/ /*{\Mtext |\Mname| is copy constructable and assignable. An object can be obtained via the |Nef_polyhedron_2::explorer()| method of |Nef_polyhedron_2|.}*/ PM_explorer(const Self& E) : Base(E), pK(E.pK) {} Self& operator=(const Self& E) { Base::operator=(E); pK=E.pK; return *this; } PM_explorer(const Plane_map& P, const Geometry& k = Geometry()) : Base(P), pK(&k) {} /*{\Xcreate constructs a plane map explorer working on |P| with geometric predicates used from |k|.}*/ /*{\Moperations 2 }*/ bool is_standard(Vertex_const_handle v) const /*{\Mop returns true iff |v|'s position is a standard point.}*/ { return pK->is_standard(Base::point(v)); } Point point(Vertex_const_handle v) const /*{\Mop returns the standard point that is the embedding of |v|. \precond |\Mvar.is_standard(v)|.}*/ { return pK->standard_point(Base::point(v)); } Ray ray(Vertex_const_handle v) const /*{\Mop returns the ray defining the non-standard point on the frame. \precond |!\Mvar.is_standard(v)|.}*/ { return pK->standard_ray(Base::point(v)); } bool is_frame_edge(Halfedge_const_handle e) const /*{\Mop returns true iff |e| is part of the infinimaximal frame.}*/ { return ( face(e) == faces_begin() || face(twin(e)) == faces_begin() ); } }; // PM_explorer @ <>= typedef typename Base::Vertex_const_handle Vertex_const_handle; typedef typename Base::Halfedge_const_handle Halfedge_const_handle; typedef typename Base::Face_const_handle Face_const_handle; typedef typename Base::Vertex_const_iterator Vertex_const_iterator; typedef typename Base::Halfedge_const_iterator Halfedge_const_iterator; typedef typename Base::Face_const_iterator Face_const_iterator; typedef typename Base::Halfedge_around_face_const_circulator Halfedge_around_face_const_circulator; typedef typename Base::Halfedge_around_vertex_const_circulator Halfedge_around_vertex_const_circulator; typedef typename Base::Isolated_vertex_const_iterator Isolated_vertex_const_iterator; typedef typename Base::Hole_const_iterator Hole_const_iterator; @ \section{A Demo Program} The basic idea of our demo is simple. Store some basic polyhedra in a history list and let the user interactively extend this list by the binary and unary operations. The point location and ray shooting capabilities of the structure can be triggered by mouse clicks into the drawing window. <>= #include #ifdef CGAL_USE_LEDA #include "xpms/nef.xpm" #include #include #include #include #include #include #include #include template <> struct ring_or_field { typedef ring_with_gcd kind; typedef leda_integer RT; static RT gcd(const RT& r1, const RT& r2) { return ::gcd(r1,r2); } }; #define FILTERED_KERNEL #ifndef FILTERED_KERNEL typedef CGAL::Extended_homogeneous EKernel; #else typedef CGAL::Filtered_extended_homogeneous EKernel; #endif #if defined(_MSC_VER) || defined(__BORLANDC__) #define WIN32CONFIG #endif <> <> int main(int argc, char* argv[]) { /* Enable debugging by introducing prime into the product: RP 3 EP 5/59 PM 7 NefTOP 11 Overlayer 13 PointLoc 17 ConstrTriang 19 SegSweep 23 */ SETDTHREAD(113); CGAL::set_pretty_mode ( std::cerr ); std::cerr << "using " << CGAL::pointlocationversion << std::endl; std::cerr << "using " << CGAL::sweepversion << std::endl; <> <> <> <> leda_drawing_mode dm; Point p_down(0,0); main_panel.display(); int x0,y0,x1,y1; W.frame_box(x0,y0,x1,y1); W.display_help_text("help/nef-demo"); Object_handle h; for(;;) { double x, y; int val; switch( W.read_event(val, x, y) ) { case button_press_event: p_down = Point(x,y); if (val == MOUSE_BUTTON(1)) { std::cerr << "locating " << p_down << std::endl; dm = W.set_mode(leda_xor_mode); W<>= typedef CGAL::Nef_polyhedron_2 Nef_polyhedron; typedef Nef_polyhedron::Point Point; typedef Nef_polyhedron::Line Line; typedef Nef_polyhedron::Direction Direction; typedef Nef_polyhedron::Object_handle Object_handle; typedef Nef_polyhedron::Explorer Explorer; typedef Nef_polyhedron::Topological_explorer TExplorer; typedef Explorer::Vertex_const_handle Vertex_const_handle; typedef Explorer::Halfedge_const_handle Halfedge_const_handle; typedef Explorer::Face_const_handle Face_const_handle; @ We create one drawing window |W|, and two panels controlling the operations |main_panel|, |op_panel|. We store a history of polyhedra: |ML| stores the names displayed, |MH| maps the names to the existing objects. |N_display| stores the current object drawn in |W|. <>= #include #include #include #include #include #include static leda_panel main_panel; static leda_panel op_panel; static panel_item nef_menu_item; static panel_item op_item; static leda_string nef1; static leda_string nef2; static menu create_menu; static int num; #ifndef FILTERED_KERNEL static leda_string dname = "./homogeneous_data"; #else static leda_string dname = "./filtered_homogeneous_data"; #endif static leda_string fname = "none"; static leda_string filter = "*.nef"; CGAL::Window_stream* pW; Nef_polyhedron* pN; leda_list* pML; leda_d_array* pMH; static leda_string stripped(leda_string s) { int i = s.pos(" = "); return s.head(i); } <>= CGAL::Window_stream W(600,600); pW = &W; Nef_polyhedron N_display; pN = &N_display; leda_list ML; pML = &ML; leda_d_array MH; pMH = &MH; @ Redrawing the window is done via |win_redraw_handler|. Our exit handler is |win_del_handler|. Opening a panel is done by |open_panel|. <>= void win_redraw_handler(leda_window*) { pW->clear(); (*pW) << (*pN); } void win_del_handler(leda_window*) { leda_panel P("acknowledge"); P.text_item(""); P.text_item("\\bf\\blue Do you really want to quit~?"); P.fbutton("no",0); P.button("yes",1); if (P.open(main_panel) == 1) exit(0); } static int open_panel(leda_panel& p) { p.display(main_panel,0,0); int res = p.read_mouse(); p.close(); return res; } @ |store_new| stores a new polyhedron |N| with name |t| at the beginning of our history. |update_history| triggers the visual history update. <>= static void store_new(const Nef_polyhedron& N, leda_string t) { leda_string k = leda_string("N%i",++num) ; (*pMH)[k] = (*pN) = N; pML->push_front(k+" = "+t); win_redraw_handler(pW); } static void update_history() { nef1=pML->head(); nef2=pML->head(); main_panel.add_menu(nef_menu_item,(*pML)); op_panel.add_menu(op_item,(*pML)); } @ |create| is linked to the creation menu (a bitmap button). The enums control the different creation actions which are mapped to some input operation on |W| followed by a call to some constructor of |Nef_polyhedron_2|. <>= enum { EMPTY=31, FULL, HOPEN, HCLOSED, POPEN, PCLOSED }; enum { FILE_LOAD=111, FILE_SAVE }; void create(int i) { if (pML->back()=="none") pML->pop_back(); Line l; Point p; std::list Lp; leda_point pd; leda_list Lpd; string_ostream sos; CGAL::set_pretty_mode(sos); pW->clear(); switch (i) { case HOPEN: pW->message("Insert Half-Space by Line"); (*pW) >> l; sos << '(' << l << ')' << '\0'; if ( l.is_degenerate() ) { (*pW).acknowledge("Please enter non-degenerate line."); win_redraw_handler(pW); } else store_new(Nef_polyhedron(l,Nef_polyhedron::EXCLUDED),sos.str()); break; case HCLOSED: pW->message("Insert Half-Space by Line"); (*pW) >> l; sos << '[' << l << ']' << '\0'; if ( l.is_degenerate() ) { (*pW).acknowledge("Please enter non-degenerate line."); win_redraw_handler(pW); } else store_new(Nef_polyhedron(l,Nef_polyhedron::INCLUDED),sos.str()); break; case POPEN: pW->message("Insert Polygon by Point Sequence"); Lpd = pW->read_polygon(); forall(pd,Lpd) Lp.push_back(Point(pd.xcoord(),pd.ycoord())); sos << '[' << Lp.size() << "-gon"<< ']' << '\0'; store_new(Nef_polyhedron(Lp.begin(),Lp.end(), Nef_polyhedron::EXCLUDED),sos.str()); break; case PCLOSED: pW->message("Insert Polygon by Point Sequence"); Lpd = pW->read_polygon(); forall(pd,Lpd) Lp.push_back(Point(pd.xcoord(),pd.ycoord())); sos << '[' << Lp.size() << "-gon"<< ']' << '\0'; store_new(Nef_polyhedron(Lp.begin(),Lp.end(), Nef_polyhedron::INCLUDED),sos.str()); break; default: std::cout << "created nothing\n"; } sos.freeze(0); update_history(); main_panel.redraw_panel(); } @ File input and output is done via the operation |file_handler|. <>= static void read_file(leda_string fn) { std::ifstream in(fn); Nef_polyhedron N; in >> N; fn.replace_all(".nef",""); store_new(N,fn);update_history(); } static bool confirm_overwrite(const char* fname) { #if defined(_MSC_VER) || defined(__BORLANDC__) return true; #else leda_panel P; P.buttons_per_line(2); P.text_item(""); P.text_item(leda_string("\\bf\\blue File\\black %s\\blue exists.",fname)); P.button("overwrite",0); P.button("cancel",1); return (P.open() == 0); #endif } static void write_file(leda_string fname) { if (is_file(fname) && !confirm_overwrite(fname)) return; // win_ptr->set_status_string(" Writing " + fname); leda_string nef = stripped(nef1); if (pMH->defined(nef)) { std::ofstream out(fname); out << (*pMH)[nef]; } else error_handler(1,"Nef polyhedron "+nef+" not defined."); } static void file_handler(int what) { file_panel FP(fname,dname); switch (what) { case FILE_LOAD: FP.set_load_handler(read_file); break; case FILE_SAVE: FP.set_save_handler(write_file); break; } if (filter != "") FP.set_pattern(filter,filter); FP.open(); } @ We have three more operations. |view| just draws the currently chosen polyhedron of our history. |binop| allows a user to combine the currently chosen polyhedron with one which is chosen in the second panel |op_panel|. The binary operation used is defined by the button which is pressed in |main_panel|. |unop| just triggers the unary operation chosen by the button selection on the currently selected polyhedron of the history. <>= void view(int i) { leda_string nef = stripped(nef1); if (pMH->defined(nef)) { (*pN) = (*pMH)[nef]; win_redraw_handler(pW); } else error_handler(1,"Nef polyhedron "+nef+" not defined."); } void binop(int i) { leda_string op1; switch (i) { case 30: op1="intersection"; break; case 31: op1="union"; break; case 32: op1="difference"; break; case 33: op1="symmdiff"; break; default: break; } op_panel.set_button_label(111,op1); open_panel(op_panel); op_panel.flush(); leda_string arg1 = stripped(nef1), arg2 = stripped(nef2); Nef_polyhedron N1 = (*pMH)[arg1]; Nef_polyhedron N2 = (*pMH)[arg2]; std::ofstream log("nef-demo.log"); if ( !log ) CGAL_assertion_msg(0,"no output log nef-demo.log"); log << 2 << std::endl << N1 << N2 << std::endl; log.close(); switch (i) { case 30: *pN = N1*N2; break; case 31: *pN = N1+N2; break; case 32: *pN = N1-N2; break; case 33: *pN = N1^N2; break; default: return; } leda_string descr = op1+"("+arg1+","+arg2+")"; store_new(*pN,descr);update_history(); win_redraw_handler(pW); } void unop(int i) { leda_string op, arg = stripped(nef1); Nef_polyhedron N = (*pMH)[arg]; std::ofstream log("nef-demo.log"); if ( !log ) CGAL_assertion_msg(0,"no output log nef-demo.log"); log << 1 << std::endl << N << std::endl; TRACEN("writing nef to log"); log.close(); switch (i) { case 40: op="interior"; *pN = N.interior(); break; case 41: op="complement"; *pN = N.complement(); break; case 42: op="closure"; *pN = N.closure(); break; case 43: op="boundary"; *pN = N.boundary(); break; default: return; } leda_string descr = op+"("+arg+")"; store_new(*pN,descr);update_history(); win_redraw_handler(pW); } @ The |draw| operation just marks visually the object of the plane map referenced by |h|. <>= void draw(Object_handle h) { CGAL::PM_visualizor PMV(*pW,pN->explorer(),pN->EK, CGAL::PM_DefColor(CGAL::RED,CGAL::RED,6,6) ); leda_drawing_mode prev = pW->set_mode(leda_xor_mode); Vertex_const_handle vh; Halfedge_const_handle eh; Face_const_handle fh; if ( CGAL::assign(vh,h) ) PMV.draw(vh); if ( CGAL::assign(eh,h) ) PMV.draw(eh); if ( CGAL::assign(fh,h) ) PMV.draw(fh); pW->set_mode(prev); } @ We initialize the window to the default of the size of our frame and center it at the origin. <>= W.init(-CGAL::frame_default,CGAL::frame_default,-CGAL::frame_default); W.set_show_coordinates(true); W.set_grid_mode(5); W.set_node_width(3); W.set_redraw(&win_redraw_handler); W.display(0,0); @ We initialize the history with several simple nef polyhedra like an empty one, one representing the plane, one left of the $y$-axis, one above a diagonal and one defined by a simple quadratic polygon. <>= std::list Lp; const int r=70; Lp.push_back(Point(-r,-r)); Lp.push_back(Point(r,-r)); Lp.push_back(Point(r,r)); Lp.push_back(Point(-r,r)); store_new(Nef_polyhedron(),"empty"); store_new(Nef_polyhedron(Nef_polyhedron::COMPLETE),"plane"); store_new(Nef_polyhedron(Line(Point(0,0),Point(0,1)), Nef_polyhedron::INCLUDED),"neg y-plane (closed)"); store_new(Nef_polyhedron(Line(Point(-2,-1),Point(2,1)), Nef_polyhedron::EXCLUDED),"above diagonal (open)"); store_new(Nef_polyhedron(Lp.begin(),Lp.end(), Nef_polyhedron::INCLUDED),"square (closed)"); if ( argc == 2 ) { std::ifstream log( argv[1] ); if ( !log ) CGAL_assertion_msg(0,leda_string("no input log ")+argv[1]); int n; log >> n; for (int i=0; i> Ni; store_new(Ni,leda_string(argv[1])+leda_string("%d",i+1)); } } @ The head of our history list is displayed in the panels. <>= nef1=ML.head();nef2=ML.head(); nef_menu_item = main_panel.string_item("Polyhedra",nef1,ML); main_panel.set_window_delete_handler(win_del_handler); main_panel.buttons_per_line(10); main_panel.set_item_width(300); main_panel.set_frame_label("Operations on Nef Polyhedra"); main_panel.set_icon_label("Nef Polyhedra"); op_item = op_panel.string_item("Polyhedra",nef2,ML); op_panel.fbutton(" ",111); op_panel.set_frame_label("Choose second argument"); op_panel.set_item_width(300); <>= char* p_inter = main_panel.create_pixrect(intersection_xpm); char* p_union = main_panel.create_pixrect(union_xpm); char* p_diff = main_panel.create_pixrect(difference_xpm); char* p_exor = main_panel.create_pixrect(exor_xpm); char* p_int = main_panel.create_pixrect(interior_xpm); char* p_compl = main_panel.create_pixrect(complement_xpm); char* p_clos = main_panel.create_pixrect(closure_xpm); char* p_bound = main_panel.create_pixrect(boundary_xpm); char* p_eye = main_panel.create_pixrect(eye_xpm); char* p_draw = main_panel.create_pixrect(draw_xpm); main_panel.button(p_eye,p_eye,"show", 10, view); main_panel.button(p_draw,p_draw,"new polyhedron", 11, create_menu); main_panel.button(p_inter,p_inter,"intersection", 30, binop); main_panel.button(p_union,p_union,"union", 31, binop); main_panel.button(p_diff,p_diff,"difference", 32, binop); main_panel.button(p_exor,p_exor,"symmetric difference", 33, binop); main_panel.button(p_int,p_int,"interior", 40,unop); main_panel.button(p_compl,p_compl,"complement", 41,unop); main_panel.button(p_clos,p_clos,"closure", 42,unop); main_panel.button(p_bound,p_bound,"boundary", 43,unop); create_menu.button("half-plane (open)", HOPEN, create); create_menu.button("half-plane (closed)", HCLOSED, create); create_menu.button("polygon (open)", POPEN, create); create_menu.button("polygon (closed)", PCLOSED, create); create_menu.button("load from disk",FILE_LOAD, file_handler); create_menu.button("save to disk",FILE_SAVE, file_handler); @ \section{A Test program} <>= #include #include #include #include #include #include #ifdef CGAL_USE_LEDA #include typedef leda_integer Integer; template <> struct ring_or_field { typedef ring_with_gcd kind; typedef leda_integer RT; static RT gcd(const RT& r1, const RT& r2) { return ::gcd(r1,r2); } }; #else #ifdef CGAL_USE_GMP #include typedef CGAL::Gmpz Integer; template <> struct ring_or_field { typedef ring_with_gcd kind; typedef CGAL::Gmpz RT; static RT gcd(const RT& r1, const RT& r2) { return CGAL::gcd(r1,r2); } }; #else typedef long Integer; #endif #endif int main() { SETDTHREAD(911); // 911 CGAL::set_pretty_mode ( std::cerr ); std::cerr << "using " << CGAL::pointlocationversion << std::endl; std::cerr << "using " << CGAL::sweepversion << std::endl; CGAL_TEST_START; { <> <> } { <> <> Nef_polyhedron::EK.print_statistics(); } CGAL_TEST_END; } <>= typedef CGAL::Extended_homogeneous EKernel; typedef CGAL::Nef_polyhedron_2 Nef_polyhedron; typedef Nef_polyhedron::Point Point; typedef Nef_polyhedron::Direction Direction; typedef Nef_polyhedron::Line Line; <>= typedef CGAL::Filtered_extended_homogeneous EKernel; typedef CGAL::Nef_polyhedron_2 Nef_polyhedron; typedef Nef_polyhedron::Point Point; typedef Nef_polyhedron::Direction Direction; typedef Nef_polyhedron::Line Line; <>= typedef Nef_polyhedron::Object_handle Object_handle; typedef Nef_polyhedron::Explorer Explorer; typedef Explorer::Vertex_const_handle Vertex_const_handle; typedef Explorer::Halfedge_const_handle Halfedge_const_handle; typedef Explorer::Face_const_handle Face_const_handle; typedef Explorer::Vertex_const_iterator Vertex_const_iterator; typedef Explorer::Halfedge_const_iterator Halfedge_const_iterator; typedef Explorer::Face_const_iterator Face_const_iterator; typedef Explorer::Ray Ray; Point p1(0,0), p2(0,1), p3(1,0), p4(-1,-1), p5(0,-1), p6(-1,0), p7(1,1); Line l1(p2,p1); // neg y-axis Line l2(p1,p3); // pos x-axis Nef_polyhedron N1(l1), N2(l2, Nef_polyhedron::EXCLUDED), EMPTY(Nef_polyhedron::EMPTY),PLANE(Nef_polyhedron::COMPLETE); CGAL_TEST((N1*N1) == N1); CGAL_TEST((N1*!N1) == EMPTY); CGAL_TEST((N1+!N1) == PLANE); CGAL_TEST((N1^N2) == ((N1-N2)+(N2-N1))); CGAL_TEST((!(N1*N2)) == (!N1+!N2)); Nef_polyhedron N3 = N1.intersection(N2); /* N3 is the first quadrant including the positive y-axis but excluding the origin and the positive x-axis */ CGAL_TEST(N3 < N1 && N3 < N2); CGAL_TEST(N3 <= N1 && N3 <= N2); CGAL_TEST(N1 > N3 && N2 > N3); CGAL_TEST(N1 >= N3 && N2 >= N3); Explorer E = N3.explorer(); Vertex_const_iterator v = E.vertices_begin(); CGAL_TEST( !E.is_standard(v) && E.ray(v) == Ray(p1,p4) ); Halfedge_const_handle e = E.first_out_edge(v); CGAL_TEST( E.is_frame_edge(e) ); ++(++v); // third vertex CGAL_TEST( E.is_standard(v) && E.point(v) == p1 ); Vertex_const_handle v1,v2; Halfedge_const_handle e1,e2; Face_const_handle f1,f2; Object_handle h1,h2,h3; h1 = N3.locate(p1); h2 = N3.locate(p1,Nef_polyhedron::NAIVE); CGAL_TEST( CGAL::assign(v1,h1) && CGAL::assign(v2,h2) && v1 == v2 ); CGAL_TEST( E.is_standard(v1) && E.point(v1) == p1 ); h1 = N3.locate(p2); h2 = N3.locate(p2,Nef_polyhedron::NAIVE); CGAL_TEST( CGAL::assign(e1,h1) && CGAL::assign(e2,h2) ); CGAL_TEST( (e1==e2 || e1==E.twin(e2)) && E.mark(e1) ); h1 = N3.locate(p4); h2 = N3.locate(p4,Nef_polyhedron::NAIVE); CGAL_TEST( CGAL::assign(f1,h1) && CGAL::assign(f2,h2) && f1 == f2 && !E.mark(f1) ); // shooting along angular bisector: h1 = N3.ray_shoot(p4,Direction(1,1)); h2 = N3.ray_shoot(p4,Direction(1,1),Nef_polyhedron::NAIVE); CGAL_TEST( CGAL::assign(f1,h1) && CGAL::assign(f2,h2) && f1 == f2 && E.mark(f1) ); // shooting along x-axis: h1 = N3.ray_shoot(p6,Direction(1,0)); h2 = N3.ray_shoot(p6,Direction(1,0),Nef_polyhedron::NAIVE); CGAL_TEST( h1 == NULL && h2 == NULL ); // shooting along y-axis: h1 = N3.ray_shoot(p5,Direction(0,1)); h2 = N3.ray_shoot(p5,Direction(0,1),Nef_polyhedron::NAIVE); e = e1; CGAL_TEST( CGAL::assign(e1,h1) && CGAL::assign(e2,h2) && (e1==e2||e1==E.twin(e2)) && E.mark(e1) ); h1 = N3.ray_shoot_to_boundary(p5,Direction(0,1)); h2 = N3.ray_shoot_to_boundary(p5,Direction(0,1),Nef_polyhedron::NAIVE); CGAL_TEST( N3.contained_in_boundary(h1) && N3.contained_in_boundary(h2) ); CGAL_TEST( CGAL::assign(v1,h1) && CGAL::assign(v2,h2) && v1 == v2 ); h1 = N3.ray_shoot_to_boundary(p7,Direction(0,-1)); h2 = N3.ray_shoot_to_boundary(p7,Direction(0,-1),Nef_polyhedron::NAIVE); CGAL_TEST( N3.contained_in_boundary(h1) && N3.contained_in_boundary(h2) ); CGAL_TEST( CGAL::assign(e1,h1) && CGAL::assign(e2,h2) && (e1==e2 || e1==E.twin(e2)) ); std::list L; L.push_back(p1); N3 = Nef_polyhedron(L.begin(), L.end(), Nef_polyhedron::INCLUDED); E = N3.explorer(); h1 = N3.locate(p1); h2 = N3.locate(p2); CGAL_TEST( CGAL::assign(v1,h1) && E.point(v1)==p1 && E.mark(v1) ); CGAL_TEST( CGAL::assign(f1,h2) && !E.mark(f1) ); L.push_back(p2); N3 = Nef_polyhedron(L.begin(), L.end(), Nef_polyhedron::INCLUDED); E = N3.explorer(); h1 = N3.locate(p1); h2 = N3.locate(CGAL::midpoint(p1,p2)); h3 = N3.locate(p6); CGAL_TEST( CGAL::assign(v1,h1) && E.point(v1)==p1 && E.mark(v1) ); CGAL_TEST( CGAL::assign(e1,h2) && E.mark(e1) ); CGAL_TEST( CGAL::assign(f1,h3) && !E.mark(f1) ); L.push_back(p3); N3 = Nef_polyhedron(L.begin(), L.end(), Nef_polyhedron::INCLUDED); E = N3.explorer(); h1 = N3.locate(p1); h2 = N3.locate(CGAL::midpoint(p1,p2)); h3 = N3.locate(p6); CGAL_TEST( CGAL::assign(v1,h1) && E.point(v1)==p1 && E.mark(v1) ); CGAL_TEST( CGAL::assign(e1,h2) && E.mark(e1) ); CGAL_TEST( CGAL::assign(f1,h3) && E.mark(f1) ); h3 = N3.locate(Point(1,1,3)); CGAL_TEST( CGAL::assign(f1,h3) && !E.mark(f1) ); CGAL_IO_TEST(N1,N2); @ \newsavebox{\EXORB} \newsavebox{\INTERB} \newsavebox{\UNIONB} \sbox{\EXORB}{\mbox{$\wedge$}} \sbox{\INTERB}{\mbox{$\cap$}} \sbox{\UNIONB}{\mbox{$\cup$}} \newcommand{\EXOR}{\usebox{\EXORB}} \newcommand{\INTER}{\usebox{\INTERB}} \newcommand{\UNION}{\usebox{\UNIONB}} \newpage \section{A Runtime Test} For an evaluation of Nef polyhedra we do the following. We recursively create a random structure of desired complexity. We start from $n$ half-spaces in an array. We interpret the entries as the leaves of a balanced binary tree. Starting from the leaf level we combine two neighbored nodes in each level into a new polyhedron by a symmetric difference operation. The root of the tree thereby contains a structure of complexity $O(n^2)$ as the symmetric difference does not take away any structure. The result is an arrangement of the lines in the boundary of the half-spaces, where each vertex, edge, and face carries a random bit. We then evaluate the two binary operations |intersection| and |union| on the two such structures. To get an impression of how good the extended approach is we compare four different geometric treatments of the problem. We measure the time for the naive implementation with an explicit usage of a polynomial ring number type based on LEDA's multi-precision integer arithmetic (marked as \emph{naive}). We do the same test on a extended kernel that uses dynamic double filter techniques (based on the CGAL double interval arithmetic |Interval_nt_advanced|). As our predicate expressions are of bounded arithmetic depth, we can program these expressions explicitly in unrolled code blocks. The corresponding expressions are instantiated for the interval data type and for the multi-precision integer number type. The filter stage determines the result of our predicates in many cases by pure double arithmetic as long as the controled error bound guarantees the correctness of the result. We call this the \emph{filtered} approach. Finally we compare the two instantiations of our Nef polyhedron data type with the simpler scenarios of generic polygons (LEDA |rat_gen_polygon|s). The type has a simpler geometric domain as it only considers regularized polygons. This takes away some complications in the topology of the result of binary operations. generic polygons are programmed around the concept of simple polygonal chains which store the boundary cycles of the faces of the planar subdivision. They also do not consider boundary issues (the boundary is part of the face incident to it). The simpler topology should allow faster binary operations. Thus we cannot expect to beat the data structure with respect to algorithmic processing. Of course Nef polyhedra cover additionally the unbounded nature of half-spaces. To allow any testing we fix a static boundary large enough to make the topology on the boundary constant (the knowledge about the size is taken from the previously calculated nef polyhedron). Then we convert the geometry to the affine bounded scenario and use generic polygons. This gives us some kind of competitor that competes in a simpler domain and that profits from our knowledge. If we just use the binary operations of generic polygons recursively they loose the competition due to the accumulated mantissae in the multi-precision representation of the polygon vertex embedding. If we normalize the embedding to its minimal representation then the expected runtime hierarchy pops up again. In the following \emph{rgp} refers to |rat_gen_polygon| and |rgpn| refers to |rat_gen_polygon| including normalization after each binary operations. The lines contain times concerning the binary overlay of structures of a certain complexity. The first column determines the size $n$ of a set of half-spaces. Such a set implies a planar arrangement of $n(n-1)/2 + 2n + 4$ vertices, $n(n+1) + 2n + 4$ edges and $n(n+1)/2 + 2$ faces (in the non-degenerate case including the objects created by the framing box). The line marked \EXOR{} presents the complexity and times for the tree-recursive synthesis of the structure. Thus we measure $O(n)$ binary symmetric difference operations on operands of increasing complexity. The three column entries \#V, \#E, and \#F present the actual number of nodes, uedges and faces of the resulting arrangement (deviation from the formulas above due to degeneries are possible). The lines marked with \INTER{} and \UNION{} present the results of one corresponding binary operation where the result has the complexity shown in the columns \#V, \#E, \#F. Note that the generic polygons are actually slower in the synthesis phase as the normalization takes place for the whole structure and is therefore more expensive than our approach. The $-1$ entries tell you that we didn't measure those times anymore as the non-normalized approach took just too long. The times are measured in seconds on a SUN Ultra-Enterprise-10000 with an 333 MHz UltraSPARC processors. \smallskip \begin{tabular}{|c|c||c|c|c||c|c|c|c|}\hline \#lines & op & \#V & \#E & \#F & naive & filtered & rgpn & rgp \\ \hline\hline \input inputs/nef-rt.table \end{tabular} <>= #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include template <> struct ring_or_field { typedef ring_with_gcd kind; static leda_integer gcd(const leda_integer& i1, const leda_integer& i2) { return ::gcd(i1,i2); } }; typedef CGAL::Extended_homogeneous EKernel; typedef CGAL::Nef_polyhedron_2 Nef_polyhedron; typedef Nef_polyhedron::Point Point; typedef Nef_polyhedron::Direction Direction; typedef Nef_polyhedron::Line Line; typedef Nef_polyhedron::Explorer PMExplorer; typedef CGAL::Filtered_extended_homogeneous FEKernel; typedef CGAL::Nef_polyhedron_2 Nef_polyhedronF; typedef CGAL::Creator_uniform_2 Creator; enum { NAIVE=0, FILTERED, GENPOLY_NORMALIZED, GENPOLY }; enum { EXOR1=0, EXOR2, INTER, UNION }; static int n, Vn[4],En[4],Fn[4]; static float tt_start, tt_exor, tt_inter, tt_union; static float t_exor[4], t_inter[4], t_union[4]; static leda_integer R; static leda_string input_file; static bool verbose; <> <> <> <> <> int main(int argc, char* argv[]) { <> <> Nef_polyhedron N1,N2,N3,N4; Nef_polyhedronF NF1,NF2,NF3,NF4; std::vector lines1,lines2; <> <> combine_and_time(lines1,lines2,N1,N2,N3,N4,"NAIVE:"); save_times(NAIVE); combine_and_time(lines1,lines2,NF1,NF2,NF3,NF4,"FILTERED:"); save_times(FILTERED); save_structure(NF1,EXOR1); save_structure(NF2,EXOR2); save_structure(NF3,INTER); save_structure(NF4,UNION); std::ofstream poly1("nef1.log"), poly2("nef2.log"); CGAL_assertion(poly1&&poly2); poly1 << NF1; poly2 << NF2; poly1.close(); poly2.close(); std::ofstream poly3("nef3.log"), poly4("nef4.log"); poly3 << NF3; poly4 << NF4; poly3.close(); poly4.close(); R = min_frame_size(NF1); R = std::max(min_frame_size(NF2),R); R = std::max(min_frame_size(NF3),R); R = std::max(min_frame_size(NF4),R); leda_rat_gen_polygon G1,G2,G3,G4; combine_and_time_leda(lines1,lines2,G1,G2,G3,G4,true,"RGP normalized:"); save_times(GENPOLY_NORMALIZED); if ( n <= 20 ) { combine_and_time_leda(lines1,lines2,G1,G2,G3,G4,false,"RGP:"); save_times(GENPOLY); } else { tt_exor = -2.0; tt_inter = tt_union = -1.0; save_times(GENPOLY); } <> if (verbose) { <> <> } return 0; } <>= if ( input_file == "" ) { lines1 = std::vector(n); lines2 = std::vector(n); create_random_lines(n,lines1); create_random_lines(n,lines2); } else { std::ifstream input(input_file); CGAL_assertion_msg(input,"no input log."); input >> n; lines1 = std::vector(n); lines2 = std::vector(n); for (int j=0; j> lines1[j]; for (int j=0; j> lines2[j]; } @ We determine a random line by a random point $p$ and a random direction $d$. We have to avoid that the direction is trivial. <>= void create_random_lines(int r, std::vector& lines) { // n random lines that intersect a square of radius r CGAL::Random_points_in_square_2 points( r ); for (unsigned int i=0; i>= template void combine(const std::vector& lines, CGAL::Nef_polyhedron_2& N) { std::vector< CGAL::Nef_polyhedron_2 > V(lines.size()); int s = lines.size(),n(s); for (int i=0; i(lines[i]); } tt_start = used_time(); while (s > 1) { for (int i = 0; i>= template void combine_and_time(const std::vector& lines1, const std::vector& lines2, Nef& N1, Nef& N2, Nef& N3, Nef& N4, const char* title) { std::cerr << "\n" << title << "\n"; int n = lines1.size(); float t; combine(lines1,N1); t = tt_exor; combine(lines2,N2); tt_exor += t; tt_start = used_time(); N3 = N1 * N2; tt_inter = used_time(tt_start); tt_start = used_time(); N4 = N1 + N2; tt_union = used_time(tt_start); std::cerr << n << " line arrangement inter " << tt_inter << std::endl; std::cerr << n << " line arrangement union " << tt_union << std::endl; } @ Concerning general polygons we use the construction of nef polyhedra to extract quadrangles realizing half-spaces. We then use the bottom up tree combination as before. <>= void combine_leda(const std::vector& lines, leda_rat_gen_polygon& G, bool normalize=true) { int s = lines.size(); std::vector< Nef_polyhedronF > V(s); std::vector< leda_rat_gen_polygon > P(s); for (int j=0; j L; for (Face_const_iterator f = E.faces_begin(); f != E.faces_end(); ++f) { if ( !E.mark(f) ) continue; Halfedge_around_face_const_circulator e(E.halfedge(f)), ee(e); CGAL_For_all(e,ee) { L.append(cgal_to_leda(e->vertex()->point())); } } P[i] = leda_rat_gen_polygon(L); if (normalize) P[i].normalize(); } tt_start = used_time(); while (s > 1) { for (int i = 0; i>= void combine_and_time_leda( const std::vector& lines1, const std::vector& lines2, leda_rat_gen_polygon& G1, leda_rat_gen_polygon& G2, leda_rat_gen_polygon& G3, leda_rat_gen_polygon& G4, bool normalize, const char* title) { std::cerr << "\n" << title << "\n"; int n = lines1.size(); float t; combine_leda(lines1,G1,normalize); t = tt_exor; combine_leda(lines2,G2,normalize); tt_exor += t; tt_start = used_time(); G3 = G1.intersection(G2); tt_inter = used_time(tt_start); tt_start = used_time(); G4 = G1.unite(G2); tt_union = used_time(tt_start); std::cerr << n << " gen_polygon inter " << tt_inter << std::endl; std::cerr << n << " gen_polygon union " << tt_union << std::endl; } @ To determine the frame size is easy when we have a topologically correct nef polyhedron. For standard points the coordinates are a lower bound for the frame radius. For non-standard points the intersection of the underlying lines with the angular bisectors define a lower bound for the fram radius. <>= leda_integer min_frame_size(const Nef_polyhedronF& N) { typedef Nef_polyhedronF::Extended_kernel EKernel; typedef EKernel::Standard_RT Standard_RT; typedef Nef_polyhedronF::Explorer Explorer; typedef Explorer::Topological_explorer TExplorer; typedef Explorer::Vertex_const_iterator Vertex_const_iterator; TExplorer D = N.explorer(); EKernel& E = Nef_polyhedronF::EK; Vertex_const_iterator vit = D.vertices_begin(), vend = D.vertices_end(); leda_integer frame_radius(0); for (; vit != vend; ++vit) { if ( E.is_standard(D.point(vit)) ) { Point p = E.standard_point(D.point(vit)); Standard_RT m = std::max(p.hx()/p.hw(), p.hy()/p.hw()); frame_radius = std::max(frame_radius,m+1); } else { // non-standard Line l = E.standard_line(D.point(vit)); if ( abs(l.a()) != abs(l.b()) ) { Standard_RT m = std::max( abs(l.c()/(l.a()+l.b())), abs(l.c()/(l.a()-l.b())) ); frame_radius = std::max(frame_radius,m+1); } else { frame_radius = std::max(frame_radius,abs(l.c()/l.a())); } } } return frame_radius; } <>= SETDTHREAD(41); int nv = n*(n-1)/2 + 2*n + 4; std::cerr << CGAL::pointlocationversion << std::endl; std::cerr << CGAL::sweepversion << std::endl; std::cerr << "creating arrangement of " << nv << " vertices\n"; <>= leda_param_handler H(argc,argv,".rt",false); H.add_parameter("number_of_lines:-n:int:10"); H.add_parameter("file_of_lines:-i:string:"); H.add_parameter("verbose:-v:bool:false"); leda_param_handler::init_all(); H.get_parameter("-n",n); H.get_parameter("-i",input_file); H.get_parameter("-v",verbose); <>= std::cerr << std::endl; std::cout << setprecision(3); std::cout << n << " & \\EXOR & " << (Vn[EXOR1]+Vn[EXOR2])/2 << " & " << (En[EXOR1]+En[EXOR2])/2 << " & " << (Fn[EXOR1]+Fn[EXOR2])/2 << " & " << (t_exor[NAIVE]/2) << " & " << (t_exor[FILTERED]/2) << " & " << (t_exor[GENPOLY_NORMALIZED]/2) << " & " << (t_exor[GENPOLY]/2) << "\\\\\n"; std::cout << n << " & \\INTER & " << Vn[INTER] << " & " << En[INTER] << " & " << Fn[INTER] << " & " << t_inter[NAIVE] << " & " << t_inter[FILTERED] << " & " << t_inter[GENPOLY_NORMALIZED] << " & " << t_inter[GENPOLY] << "\\\\\n"; std::cout << n << " & \\UNION & " << Vn[UNION] << " & " << En[UNION] << " & " << Fn[UNION] << " & " << t_union[NAIVE] << " & " << t_union[FILTERED] << " & " << t_union[GENPOLY_NORMALIZED] << " & " << t_union[GENPOLY] << "\\\\ \\hline\n"; <>= std::cerr << std::endl << "frame size = " << R << std::endl; N1.explorer().print_statistics(); N2.explorer().print_statistics(); N3.explorer().print_statistics(); N4.explorer().print_statistics(); Nef_polyhedronF::EK.print_statistics(); ISOTEST(N1,NF1) ISOTEST(N2,NF2) ISOTEST(N3,NF3) ISOTEST(N4,NF4) <>= std::ofstream log("nef-rt.log"); CGAL_assertion_msg(log,"no output log nef-rt.log"); log << n << std::endl; for (int i=0; i>= CGAL::Window_stream W(600,600); W.init(-CGAL::frame_default,CGAL::frame_default,-CGAL::frame_default); W.set_show_coordinates(true); W.set_grid_mode(5); W.set_node_width(3); W.display(0,0); W << N3; W.read_mouse(); W << N4; W.read_mouse(); W.clear(); { leda_rat_polygon p; forall_polygons(p,G3) W.draw_filled_polygon(p.to_polygon(),leda_black); } W.read_mouse(); <>= template leda_rat_point cgal_to_leda(const P& p) { return leda_rat_point(p.hx().eval_at(R),p.hy().eval_at(R), p.hw()); } void save_times(int i) { t_exor[i]=tt_exor; t_inter[i]=tt_inter; t_union[i]=tt_union; } void save_structure(const Nef_polyhedronF& N, int i) { Vn[i] = N.explorer().number_of_vertices(); En[i] = N.explorer().number_of_edges(); Fn[i] = N.explorer().number_of_faces(); } #define ISOTEST(n1,n2)\ assert(n1.explorer().number_of_vertices()==\ n2.explorer().number_of_vertices());\ assert(n1.explorer().number_of_edges()==n2.explorer().number_of_edges());\ assert(n1.explorer().number_of_faces()==n2.explorer().number_of_faces()); @ \section{Printing Nef polyhedra} <>= #include #include #include #include #include #include #include #include template <> struct ring_or_field { typedef ring_with_gcd kind; }; //typedef CGAL::Extended_homogeneous EKernel; typedef CGAL::Filtered_extended_homogeneous EKernel; typedef CGAL::Nef_polyhedron_2 Nef_polyhedron; using namespace CGAL; int main(int argc, char* argv[]) { // Create test point set. Prepare a vector for 1000 points. if ( argc == 1 ) { std::cout << argv[0] << " input_file\n"; return 1; } file_istream f(argv[1]); if (f) { Nef_polyhedron N; f >> N; ps_file PS(10,10,"nef.ps"); PS << N; } return 0; } @ \begin{ignore} <>= // ============================================================================ // // Copyright (c) 1997-2000 The CGAL Consortium // // This software and related documentation is part of an INTERNAL release // of the Computational Geometry Algorithms Library (CGAL). It is not // intended for general use. // // ---------------------------------------------------------------------------- // // release : $CGAL_Revision$ // release_date : $CGAL_Date$ // <>= // package : Nef_2 // chapter : Nef Polyhedra // // source : nef_2d/Nef_polyhedron_2.lw // revision : $Revision$ // revision_date : $Date$ // // author(s) : Michael Seel // maintainer : Michael Seel // coordinator : Michael Seel // // implementation: Nef polyhedra in the plane // ============================================================================ @ \end{ignore} \end{ignoreindiss} %KILLSTART DISS REP \newpage \bibliographystyle{alpha} \bibliography{geo_mod,comp_geo,diss} \newpage \section{Appendix} \input manpages/ExtendedKernelTraits_2.man \end{document} %KILLEND DISS REP