cgal/Nef_2/noweb/Nef_polyhedron_2.lw

3539 lines
140 KiB
Plaintext

%------------------------------------------------------------------------------
%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<RT>|,
the plane map decorator |PM_decorator<HDS>|, the two algorithmic
modules |PM_overlayer<PMDEC,GEOM>| and
|PM_point_locator<PMDEC,GEOM>|.}{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}
<<nef polyhedron definition>>=
template <typename T> class Nef_polyhedron_2;
template <typename T> class Nef_polyhedron_2_rep;
template <typename T>
std::ostream& operator<<(std::ostream&, const Nef_polyhedron_2<T>&);
template <typename T>
std::istream& operator>>(std::istream&, Nef_polyhedron_2<T>&);
@ \end{ignoreindiss}
Our data type |Nef_polyhedron_2<T>| 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<T>|. 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<T>| 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}.}
<<nef rep types>>=
struct HDS_traits {
typedef typename T::Point_2 Point;
typedef bool Mark;
};
typedef CGAL_HALFEDGEDS_DEFAULT<HDS_traits,HDS_items> Plane_map;
typedef CGAL::PM_const_decorator<Plane_map> Const_decorator;
typedef CGAL::PM_decorator<Plane_map> Decorator;
typedef CGAL::PM_naive_point_locator<Decorator,T> Slocator;
typedef CGAL::PM_point_locator<Decorator,T> Locator;
typedef CGAL::PM_overlayer<Decorator,T> Overlayer;
@ \begin{ignoreindiss} For Microsoft we use a hardwired Halfedge data
structure specially adapted to its weaknesses.
<<nef rep types msc>>=
struct HDS_traits {
typedef typename T::Point_2 Point;
typedef bool Mark;
};
typedef CGAL::HalfedgeDS_default_MSC<HDS_traits> Plane_map;
typedef CGAL::PM_const_decorator<Plane_map> Const_decorator;
typedef CGAL::PM_decorator<Plane_map> Decorator;
typedef CGAL::PM_naive_point_locator<Decorator,T> Slocator;
typedef CGAL::PM_point_locator<Decorator,T> Locator;
typedef CGAL::PM_overlayer<Decorator,T> Overlayer;
<<nef polyhedron definition>>=
template <typename T>
class Nef_polyhedron_2_rep
{ typedef Nef_polyhedron_2_rep<T> Self;
friend class Nef_polyhedron_2<T>;
#ifndef CGAL_SIMPLE_HDS
<<nef rep types>>
#else
<<nef rep types msc>>
#endif
//typedef CGAL::PM_transformer<Decorator,T> 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<T>| has a static member object |EK| of
type |T| which allows us to interface a kernel object.
<<nef polyhedron definition>>=
/*{\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 <typename T>
class Nef_polyhedron_2 : public Handle_for< Nef_polyhedron_2_rep<T> >
{
public:
typedef T Extended_kernel;
static T EK; // static extended kernel
<<nef interface types>>
protected:
<<nef protected members>>
public:
<<nef interface operations>>
}; // end of Nef_polyhedron_2
template <typename T>
T Nef_polyhedron_2<T>::EK;
@ \end{ignoreindiss}
In the class |Nef_polyhedron_2<T>| 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<T>|. The
non-prefixed\footnote{T is used as a traits class in our generic
geometry based modules like |PM_overlayer<T>|. Therefore, the extended
types conform to a simpler naming scheme within |T|.} types within |T|
become the extended types within |Nef_polyhedron_2<T>|.
<<nef interface types>>=
/*{\Mtypes 7}*/
typedef Nef_polyhedron_2<T> Self;
typedef Handle_for< Nef_polyhedron_2_rep<T> > 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}
<<nef interface types>>=
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}
<<nef protected members>>=
<<boolean classes>>
typedef Nef_polyhedron_2_rep<T> 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<T>& NP);
friend std::istream& operator>> <>
(std::istream& is, Nef_polyhedron_2<T>& 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.
<<nef protected members>>=
typedef std::list<Extended_segment> 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(...)|.
<<nef protected members>>=
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.
<<nef interface operations>>=
/*{\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 interface operations>>=
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 "<<l);
ES_list L;
fill_with_frame_segs(L);
Extended_point ep1 = EK.construct_opposite_point(l);
Extended_point ep2 = EK.construct_point(l);
L.push_back(EK.construct_segment(ep1,ep2));
Overlayer D(pm());
Link_to_iterator I(D, --L.end(), false);
D.create(L.begin(),L.end(),I);
CGAL_assertion( I._e != Halfedge_handle() );
Halfedge_handle el = I._e;
if ( D.point(D.target(el)) != EK.target(L.back()) )
el = D.twin(el);
D.mark(D.face(el)) = true;
D.mark(el) = bool(line);
}
@ The construction of a simple polygon defined by an iterator range of
standard affine points (the value type of |Forward_iterator| is
|Point|) follows the same idea, however we also accept degenerate
polygons.
The iterator range of points can present us with the following cases:
(1) the list is empty, (2) the list has only one point, (3) the list
contains at least two points spanning line segments where the
following cases and problems can occur: (a) the segment(s) has (have)
affine dimension 1 (the hull is a segment). (b) the segments enclose
a simple polygon. (c) the segments enclose no simple polygon (touching
or intersecting inside). We cover one point, two points spanning a
segment and $n$ points spanning a simple polygon (which we do not
check). The construction of the plane map still succeeds when |P| is
not simple as the overlayer module just constructs the planar
subdivision implied by the segments in the iterator range. However,
the face marks will in general be incorrect.
<<nef interface operations>>=
template <class Forward_iterator>
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)
<<fill segment list L>>
else empty = true;
Overlayer D(pm());
Link_to_iterator I(D, --L.end(), true);
D.create(L.begin(),L.end(),I);
<<mark face and boundary>>
}
@ We fill |L| with the segments cyclically spanned by the points in
the input iterator range.
<<fill segment list L>>=
{
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.
<<mark face and boundary>>=
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 interface operations>>=
Nef_polyhedron_2(const Nef_polyhedron_2<T>& N1) : Base(N1) {}
Nef_polyhedron_2& operator=(const Nef_polyhedron_2<T>& N1)
{ Base::operator=(N1); return (*this); }
~Nef_polyhedron_2() {}
#ifndef _MSC_VER
template <class Forward_iterator>
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<p && p<1);
ES_list L; fill_with_frame_segs(L);
while ( first != beyond ) {
Extended_point ep1 = EK.construct_opposite_point(*first);
Extended_point ep2 = EK.construct_point(*first);
L.push_back(EK.construct_segment(ep1,ep2)); ++first;
}
Overlayer D(pm());
Link_to_iterator I(D, --L.end(), false);
D.create(L.begin(),L.end(),I);
Vertex_iterator v; Halfedge_iterator e; Face_iterator f;
for (v = D.vertices_begin(); v != D.vertices_end(); ++v)
D.mark(v) = ( default_random.get_double() < p ? true : false );
for (e = D.halfedges_begin(); e != D.halfedges_end(); ++(++e))
D.mark(e) = ( default_random.get_double() < p ? true : false );
for (f = D.faces_begin(); f != D.faces_end(); ++f)
D.mark(f) = ( default_random.get_double() < p ? true : false );
D.simplify(Except_frame_box_edges(pm()));
clear_outer_face_cycle_marks();
}
#endif
@ The construction from a plane map |H| is a clone action. We create a
representation object which has the same topology, geometry and
attributes as |H|.
<<nef interface operations>>=
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<T>(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.
<<nef interface operations>>=
/*{\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.
<<nef interface operations>>=
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.
<<nef interface operations>>=
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.
<<nef interface operations>>=
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$.
<<nef interface operations>>=
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.
<<nef interface operations>>=
/*{\Mtext \headerline{Constructive Operations}}*/
Nef_polyhedron_2<T> complement() const
/*{\Mop returns the complement of |\Mvar| in the plane.}*/
{ Nef_polyhedron_2<T> res = *this;
res.extract_complement();
return res;
}
@ All other operations like |interior()|, |closure()|, |boundary()|,
and |regularization()| are implemented similarly.
\begin{ignoreindiss}
<<nef interface operations>>=
Nef_polyhedron_2<T> interior() const
/*{\Mop returns the interior of |\Mvar|.}*/
{ Nef_polyhedron_2<T> res = *this;
res.extract_interior();
return res;
}
Nef_polyhedron_2<T> closure() const
/*{\Mop returns the closure of |\Mvar|.}*/
{ Nef_polyhedron_2<T> res = *this;
res.extract_closure();
return res;
}
Nef_polyhedron_2<T> boundary() const
/*{\Mop returns the boundary of |\Mvar|.}*/
{ Nef_polyhedron_2<T> res = *this;
res.extract_boundary();
return res;
}
Nef_polyhedron_2<T> regularization() const
/*{\Mop returns the regularized polyhedron (closure of interior).}*/
{ Nef_polyhedron_2<T> 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.
<<boolean classes>>=
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 interface operations>>=
Nef_polyhedron_2<T> intersection(const Nef_polyhedron_2<T>& N1) const
/*{\Mop returns |\Mvar| $\cap$ |N1|. }*/
{ Nef_polyhedron_2<T> 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 interface operations>>=
Nef_polyhedron_2<T> join(const Nef_polyhedron_2<T>& N1) const
/*{\Mop returns |\Mvar| $\cup$ |N1|. }*/
{ Nef_polyhedron_2<T> 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<T> difference(const Nef_polyhedron_2<T>& N1) const
/*{\Mop returns |\Mvar| $-$ |N1|. }*/
{ Nef_polyhedron_2<T> 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<T> symmetric_difference(
const Nef_polyhedron_2<T>& N1) const
/*{\Mop returns the symmectric difference |\Mvar - T| $\cup$
|T - \Mvar|. }*/
{ Nef_polyhedron_2<T> 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<T> transform(const Aff_transformation& t) const
/*{\Mop returns $t(|\Mvar|)$.}*/
{ Nef_polyhedron_2<T> 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.
<<nef protected members>>=
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}
<<nef interface operations>>=
/*{\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<T> operator*(const Nef_polyhedron_2<T>& N1) const
{ return intersection(N1); }
Nef_polyhedron_2<T> operator+(const Nef_polyhedron_2<T>& N1) const
{ return join(N1); }
Nef_polyhedron_2<T> operator-(const Nef_polyhedron_2<T>& N1) const
{ return difference(N1); }
Nef_polyhedron_2<T> operator^(const Nef_polyhedron_2<T>& N1) const
{ return symmetric_difference(N1); }
Nef_polyhedron_2<T> operator!() const
{ return complement(); }
Nef_polyhedron_2<T>& operator*=(const Nef_polyhedron_2<T>& N1)
{ this = intersection(N1); return *this; }
Nef_polyhedron_2<T>& operator+=(const Nef_polyhedron_2<T>& N1)
{ this = join(N1); return *this; }
Nef_polyhedron_2<T>& operator-=(const Nef_polyhedron_2<T>& N1)
{ this = difference(N1); return *this; }
Nef_polyhedron_2<T>& operator^=(const Nef_polyhedron_2<T>& 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.
<<nef interface operations>>=
/*{\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<T>& N1) const
{ return symmetric_difference(N1).is_empty(); }
bool operator!=(const Nef_polyhedron_2<T>& N1) const
{ return !operator==(N1); }
bool operator<=(const Nef_polyhedron_2<T>& N1) const
{ return difference(N1).is_empty(); }
bool operator<(const Nef_polyhedron_2<T>& N1) const
{ return difference(N1).is_empty() && !N1.difference(*this).is_empty(); }
@ \begin{ignoreindiss}
<<nef interface operations>>=
bool operator>=(const Nef_polyhedron_2<T>& N1) const
{ return N1.difference(*this).is_empty(); }
bool operator>(const Nef_polyhedron_2<T>& 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}
<<nef interface operations>>=
/*{\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<Const_decorator,T> 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<Self*>(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|.
<<nef interface operations>>=
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.
<<nef interface operations>>=
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.
<<nef interface operations>>=
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|.
<<nef interface operations>>=
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|.
<<nef interface operations>>=
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<RT>|
where |RT| is a ring type providing additionally a |gcd| operation and
one based on a cartesian representation of extended points called
|Extended_cartesian<NT>| where |NT| is a field type, and finally
|Filtered_extended_homogeneous<RT>| (an optimized version of the
first).
The member types of |Nef_polyhedron_2< Extended_homogeneous<NT> >|
map to corresponding types of the CGAL geometry kernel
(e.g. |Nef_polyhedron::Line| equals
|CGAL::Homogeneous<leda_integer>::Line_2| in the example below).
\begin{Mverb}
#include <CGAL/basic.h>
#include <CGAL/leda_integer.h>
#include <CGAL/Extended_homogeneous.h>
#include <CGAL/Nef_polyhedron_2.h>
using namespace CGAL;
typedef Extended_homogeneous<leda_integer> Extended_kernel;
typedef Nef_polyhedron_2<Extended_kernel> 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}
<<Nef_polyhedron_2.h>>=
<<CGAL Header1>>
// file : include/CGAL/Nef_polyhedron_2.h
<<CGAL Header2>>
#ifndef CGAL_NEF_POLYHEDRON_2_H
#define CGAL_NEF_POLYHEDRON_2_H
#if defined(_MSC_VER) || defined(__BORLANDC__)
#define CGAL_SIMPLE_HDS
#endif
#include <CGAL/basic.h>
#include <CGAL/Handle_for.h>
#include <CGAL/Random.h>
#ifndef CGAL_SIMPLE_HDS
#include <CGAL/Nef_2/HDS_items.h>
#include <CGAL/HalfedgeDS_default.h>
#else
#include <CGAL/Nef_2/HalfedgeDS_default_MSC.h>
#endif
#include <CGAL/Nef_2/PM_explorer.h>
#include <CGAL/Nef_2/PM_decorator.h>
#include <CGAL/Nef_2/PM_io_parser.h>
#include <CGAL/Nef_2/PM_overlayer.h>
//#include <CGAL/Nef_2/PM_transformer.h>
#include <CGAL/Nef_2/PM_point_locator.h>
#include <vector>
#include <list>
#undef _DEBUG
#define _DEBUG 11
#include <CGAL/Nef_2/debug.h>
CGAL_BEGIN_NAMESPACE
<<nef polyhedron definition>>
<<nef polyhedron input and output>>
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<>|.
<<window stream output>>=
static long frame_default = 100;
static bool show_triangulation = false;
template <typename T>
CGAL::Window_stream& operator<<(CGAL::Window_stream& ws,
const Nef_polyhedron_2<T>& P)
{
typedef Nef_polyhedron_2<T> 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<TExplorer> BooleColor;
typedef CGAL::PM_visualizor<TExplorer,T,BooleColor> Visualizor;
TExplorer D = P.explorer();
const T& E = Nef_polyhedron_2<T>::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();
<<draw the refining constrained triangulation>>
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.
<<draw the refining constrained triangulation>>=
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.
<<Nef_polyhedron_2_Window_stream.h>>=
<<CGAL Header1>>
// file : include/CGAL/IO/Nef_polyhedron_2_Window_stream.h
<<CGAL Header2>>
#ifndef NEF_POLYHEDRON_2_WINDOW_STREAM_H
#define NEF_POLYHEDRON_2_WINDOW_STREAM_H
#include <CGAL/Nef_polyhedron_2.h>
#include <CGAL/Nef_2/PM_visualizor.h>
CGAL_BEGIN_NAMESPACE
<<window stream output>>
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.
<<nef polyhedron input and output>>=
template <typename T>
std::ostream& operator<<
(std::ostream& os, const Nef_polyhedron_2<T>& NP)
{
os << "Nef_polyhedron_2<" << NP.EK.output_identifier() << ">\n";
typedef typename Nef_polyhedron_2<T>::Decorator Decorator;
CGAL::PM_io_parser<Decorator> O(os, NP.pm()); O.print();
return os;
}
template <typename T>
std::istream& operator>>
(std::istream& is, Nef_polyhedron_2<T>& NP)
{
typedef typename Nef_polyhedron_2<T>::Decorator Decorator;
CGAL::PM_io_parser<Decorator> 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<T>();
}
typename Nef_polyhedron_2<T>::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}
<<PM_explorer.h>>=
<<CGAL Header1>>
// file : include/CGAL/Nef_2/PM_explorer.h
<<CGAL Header2>>
#ifndef CGAL_PM_EXPLORER_H
#define CGAL_PM_EXPLORER_H
#include <CGAL/basic.h>
#include <CGAL/Nef_2/PM_const_decorator.h>
CGAL_BEGIN_NAMESPACE
<<PM explorer>>
CGAL_END_NAMESPACE
#endif // CGAL_PM_EXPLORER_H
<<PM explorer>>=
/*{\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 <typename PMCDEC, typename GEOM>
class PM_explorer : public PMCDEC
{ typedef PMCDEC Base;
typedef PM_explorer<PMCDEC,GEOM> 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.}*/
<<local typedefs of explorer handles>>
/*{\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<PMCDEC,GEOM>
@
<<local typedefs of explorer handles>>=
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.
<<Nef_polyhedron_2-demo.C>>=
#include <CGAL/basic.h>
#ifdef CGAL_USE_LEDA
#include "xpms/nef.xpm"
#if CGAL_LEDA_VERSION < 500
#include <LEDA/pixmaps/button32/eye.xpm>
#include <LEDA/pixmaps/button32/draw.xpm>
#else
#include <LEDA/graphics/pixmaps/button32/eye.xpm>
#include <LEDA/graphics/pixmaps/button32/draw.xpm>
#endif
#include <CGAL/leda_integer.h>
#include <CGAL/Extended_homogeneous.h>
#include <CGAL/Filtered_extended_homogeneous.h>
#include <CGAL/IO/Filtered_extended_homogeneous_Window_stream.h>
#include <CGAL/Nef_polyhedron_2.h>
#include <CGAL/IO/Nef_polyhedron_2_Window_stream.h>
template <>
struct ring_or_field<leda_integer> {
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<leda_integer> EKernel;
#else
typedef CGAL::Filtered_extended_homogeneous<leda_integer> EKernel;
#endif
#if defined(_MSC_VER) || defined(__BORLANDC__)
#define WIN32CONFIG
#endif
<<getting types in global scope>>
<<a small interactive polyhedron editor>>
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;
<<initializing the drawing window>>
<<initializing the history>>
<<initializing the two panels>>
<<load bitmaps and add buttons to panels>>
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<<CGAL::GREEN<<p_down; W.set_mode(dm);
h = N_display.locate(p_down);
draw(h);
}
if (val == MOUSE_BUTTON(2)) {
std::cerr << "shooting down from " << p_down << std::endl;
dm = W.set_mode(leda_xor_mode);
W<<CGAL::GREEN<<p_down; W.set_mode(dm);
//h = N_display.ray_shoot(p_down,Direction(0,-1),Nef_polyhedron::NAIVE);
h = N_display.ray_shoot(p_down,Direction(0,-1));
draw(h);
}
if (val == MOUSE_BUTTON(3)) {
CGAL::show_triangulation = !CGAL::show_triangulation;
win_redraw_handler(&W);
}
break;
case button_release_event:
if (val == MOUSE_BUTTON(1))
#ifndef WIN32CONFIG
{ dm = W.set_mode(leda_xor_mode);
W<<CGAL::GREEN<<p_down; W.set_mode(dm); draw(h); }
#else
{ win_redraw_handler(&W); }
#endif
if (val == MOUSE_BUTTON(2))
#ifndef WIN32CONFIG
{ dm = W.set_mode(leda_xor_mode);
W<<CGAL::GREEN<<p_down; W.set_mode(dm); draw(h); }
#else
{ win_redraw_handler(&W); }
#endif
break;
case key_press_event:
if (val == KEY_UP) { // ZOOM IN
CGAL::frame_default*=2;
Nef_polyhedron::Extended_kernel::RT::set_R(CGAL::frame_default);
int r = CGAL::frame_default+10;
W.init(-r,r,-r);
win_redraw_handler(&W);
}
if (val == KEY_DOWN) { // ZOOM OUT
CGAL::frame_default/=2;
Nef_polyhedron::Extended_kernel::RT::set_R(CGAL::frame_default);
int r = CGAL::frame_default+10;
W.init(-r,r,-r);
win_redraw_handler(&W);
}
default:
break;
}
}
#if !defined(__KCC) && !defined(__BORLANDC__)
return 0; // never reached
#endif
}
#else // CGAL_USE_LEDA
int main() { return 0; }
#endif // CGAL_USE_LEDA
<<getting types in global scope>>=
typedef CGAL::Nef_polyhedron_2<EKernel> 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|.
<<a small interactive polyhedron editor>>=
#if CGAL_LEDA_VERSION < 500
#include <LEDA/panel.h>
#include <LEDA/list.h>
#include <LEDA/d_array.h>
#include <LEDA/file_panel.h>
#include <LEDA/file.h>
#include <LEDA/stream.h>
#else
#include <LEDA/graphics/panel.h>
#include <LEDA/core/list.h>
#include <LEDA/core/d_array.h>
#include <LEDA/graphics/file_panel.h>
#include <LEDA/system/file.h>
#include <LEDA/system/stream.h>
#endif
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<leda_string>* pML;
leda_d_array<leda_string,Nef_polyhedron>* pMH;
static leda_string stripped(leda_string s)
{ int i = s.pos(" = "); return s.head(i); }
<<initializing the drawing window>>=
CGAL::Window_stream W(600,600); pW = &W;
Nef_polyhedron N_display; pN = &N_display;
leda_list<leda_string> ML; pML = &ML;
leda_d_array<leda_string,Nef_polyhedron> 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|.
<<a small interactive polyhedron editor>>=
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.
<<a small interactive polyhedron editor>>=
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|.
<<a small interactive polyhedron editor>>=
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<Point> Lp;
leda_point pd;
leda_list<leda_point> 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|.
<<a small interactive polyhedron editor>>=
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.
<<a small interactive polyhedron editor>>=
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|.
<<a small interactive polyhedron editor>>=
void draw(Object_handle h)
{ CGAL::PM_visualizor<TExplorer,EKernel>
PMV(*pW,pN->explorer(),pN->EK,
CGAL::PM_DefColor<TExplorer>(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.
<<initializing the drawing window>>=
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.
<<initializing the history>>=
std::list<Point> 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<n; ++i) {
Nef_polyhedron Ni;
log >> Ni;
store_new(Ni,leda_string(argv[1])+leda_string("%d",i+1));
}
}
@ The head of our history list is displayed in the panels.
<<initializing the two 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);
<<load bitmaps and add buttons to panels>>=
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}
<<Nef_polyhedron_2-test.C>>=
#include <CGAL/basic.h>
#include <CGAL/test_macros.h>
#include <CGAL/Nef_2/redefine_MSC.h>
#include <CGAL/Extended_homogeneous.h>
#include <CGAL/Filtered_extended_homogeneous.h>
#include <CGAL/Nef_polyhedron_2.h>
#ifdef CGAL_USE_LEDA
#include <CGAL/leda_integer.h>
typedef leda_integer Integer;
template <>
struct ring_or_field<leda_integer> {
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 <CGAL/Gmpz.h>
typedef CGAL::Gmpz Integer;
template <>
struct ring_or_field<CGAL::Gmpz> {
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;
{
<<simple extended>>
<<nef test suite>>
}
{
<<filtered extended>>
<<nef test suite>>
Nef_polyhedron::EK.print_statistics();
}
CGAL_TEST_END;
}
<<simple extended>>=
typedef CGAL::Extended_homogeneous<Integer> EKernel;
typedef CGAL::Nef_polyhedron_2<EKernel> Nef_polyhedron;
typedef Nef_polyhedron::Point Point;
typedef Nef_polyhedron::Direction Direction;
typedef Nef_polyhedron::Line Line;
<<filtered extended>>=
typedef CGAL::Filtered_extended_homogeneous<Integer> EKernel;
typedef CGAL::Nef_polyhedron_2<EKernel> Nef_polyhedron;
typedef Nef_polyhedron::Point Point;
typedef Nef_polyhedron::Direction Direction;
typedef Nef_polyhedron::Line Line;
<<nef test suite>>=
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<Point> 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}
<<Nef_polyhedron_2-rt.C>>=
#include <CGAL/basic.h>
#include <CGAL/Random.h>
#include <CGAL/Homogeneous.h>
#include <vector>
#include <algorithm>
#include <iomanip>
#include <CGAL/leda_integer.h>
#include <CGAL/Extended_homogeneous.h>
#include <CGAL/Filtered_extended_homogeneous.h>
#include <CGAL/Nef_polyhedron_2.h>
#include <CGAL/basic_constructions_2.h>
#include <CGAL/Point_2.h>
#include <CGAL/point_generators_2.h>
#include <CGAL/copy_n.h>
#include <CGAL/random_selection.h>
#include <CGAL/IO/Window_stream.h>
#include <CGAL/IO/Nef_polyhedron_2_Window_stream.h>
#if CGAL_LEDA_VERSION < 500
#include <LEDA/misc.h>
#include <LEDA/rat_gen_polygon.h>
#include <LEDA/rat_window.h>
#include <LEDA/param_handler.h>
#else
#include <LEDA/system/misc.h>
#include <LEDA/geo/rat_gen_polygon.h>
#include <LEDA/graphics/rat_window.h>
#include <LEDA/system/param_handler.h>
#endif
template <>
struct ring_or_field<leda_integer> {
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<leda_integer> EKernel;
typedef CGAL::Nef_polyhedron_2<EKernel> 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<leda_integer> FEKernel;
typedef CGAL::Nef_polyhedron_2<FEKernel> Nef_polyhedronF;
typedef CGAL::Creator_uniform_2<leda_integer,Point> 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;
<<tool operations>>
<<creating random lines>>
<<determining the frame size>>
<<combination of lines into nef polyhedron>>
<<combination of lines into gen polygon>>
int main(int argc, char* argv[]) {
<<extract command line parameters>>
<<verbose introduction>>
Nef_polyhedron N1,N2,N3,N4;
Nef_polyhedronF NF1,NF2,NF3,NF4;
std::vector<Line> lines1,lines2;
<<creating random lines or reading them from file>>
<<write lines to a log file>>
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);
}
<<print runtimes>>
if (verbose) {
<<report verbose results>>
<<visualize results>>
}
return 0;
}
<<creating random lines or reading them from file>>=
if ( input_file == "" ) {
lines1 = std::vector<Line>(n);
lines2 = std::vector<Line>(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<Line>(n);
lines2 = std::vector<Line>(n);
for (int j=0; j<n; ++j) input >> lines1[j];
for (int j=0; j<n; ++j) input >> 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.
<<creating random lines>>=
void create_random_lines(int r, std::vector<Line>& lines)
{ // n random lines that intersect a square of radius r
CGAL::Random_points_in_square_2<Point,Creator> points( r );
for (unsigned int i=0; i<lines.size(); ++i) {
int dx(0),dy(0);
while ( dx==0 && dy==0 ) {
dx = CGAL::default_random.get_int(-r,r);
dy = CGAL::default_random.get_int(-r,r);
}
Point p = *points++;
Direction d(dx,dy);
lines[i] = Line(p,d);
}
}
@ We create elemtary nef polyhedra in a vector and use tree
constuction bottom-up with the vector entries as the leafs of the
tree. We use the symmetric difference operation for the construction
of internal nodes of the tree. We measure the time to construct the
structure in the root of the tree, which is in general an arrangement
of lines where all faces are randomly marked (according to the random
selection of half-spaces). The size of the resulting polyhedron is
quadratic with respect to the number of lines.
<<combination of lines into nef polyhedron>>=
template <typename L, typename K>
void
combine(const std::vector<L>& lines, CGAL::Nef_polyhedron_2<K>& N)
{ std::vector< CGAL::Nef_polyhedron_2<K> > V(lines.size());
int s = lines.size(),n(s);
for (int i=0; i<s; ++i) {
V[i] = CGAL::Nef_polyhedron_2<K>(lines[i]);
}
tt_start = used_time();
while (s > 1) {
for (int i = 0; i<s; i+=2) {
if ( i+1 == s )
V[i/2] = V[i];
else {
V[i/2] = V[i] ^ V[i+1];
std::cerr << ".";
}
}
s = s/2 + s%2;
}
tt_exor = used_time(tt_start);
N = V[0];
std::cerr << " " << n << " lines exor combined in " << tt_exor
<< std::endl;
}
@ We use the \emph{combine} operation to create one complex nef
polyhedron from each set of lines. Then we measure the time used for
the two binary operations \emph{intersection} and \emph{union}.
<<combination of lines into nef polyhedron>>=
template <class Nef>
void combine_and_time(const std::vector<Line>& lines1,
const std::vector<Line>& 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.
<<combination of lines into gen polygon>>=
void combine_leda(const std::vector<Line>& 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<s; ++j ) {
V[j] = Nef_polyhedronF(lines[j]);
}
for (int i=0; i<s; ++i) {
typedef Nef_polyhedronF::Explorer Explorer;
typedef Explorer::Face_const_iterator Face_const_iterator;
typedef Explorer::Halfedge_around_face_const_circulator
Halfedge_around_face_const_circulator;
Explorer E = V[i].explorer();
leda_list<leda_rat_point> 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<s; i+=2) {
if ( i+1 == s )
P[i/2] = P[i];
else {
P[i/2] = P[i].sym_diff(P[i+1]);
if (normalize) P[i/2].normalize();
std::cerr << ".";
}
}
s = s/2 + s%2;
}
G = P[0];
tt_exor = used_time(tt_start);
std::cerr << " " << lines.size() << " gen_polygons exor combined in "
<< tt_exor << std::endl;
}
@ The combination of generic polygons representing half-spaces into
larger units follows the same scheme as above. First create the
symmetric difference structure of the input objects (elementary
polygons representing half-spaces). Then calculate the intersection
and union of the two objects.
<<combination of lines into gen polygon>>=
void combine_and_time_leda(
const std::vector<Line>& lines1, const std::vector<Line>& 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.
<<determining the frame size>>=
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;
}
<<verbose introduction>>=
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";
<<extract command line parameters>>=
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);
<<print runtimes>>=
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";
<<report verbose results>>=
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)
<<write lines to a log file>>=
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<n; ++i) log << lines1[i] << " ";
log << std::endl;
for (int i=0; i<n; ++i) log << lines2[i] << " ";
log << std::endl;
log.close();
<<visualize results>>=
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();
<<tool operations>>=
template <typename P>
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}
<<Nef_polyhedron_2-ps.C>>=
#include <CGAL/basic.h>
#include <CGAL/Homogeneous.h>
#include <CGAL/leda_integer.h>
#include <CGAL/Extended_homogeneous.h>
#include <CGAL/Filtered_extended_homogeneous.h>
#include <CGAL/Nef_polyhedron_2.h>
#include <CGAL/IO/Nef_polyhedron_2_PS_stream.h>
#if CGAL_LEDA_VERSION < 500
#include <LEDA/stream.h>
#else
#include <LEDA/system/stream.h>
#endif
template <>
struct ring_or_field<leda_integer> {
typedef ring_with_gcd kind;
};
//typedef CGAL::Extended_homogeneous<leda_integer> EKernel;
typedef CGAL::Filtered_extended_homogeneous<leda_integer> EKernel;
typedef CGAL::Nef_polyhedron_2<EKernel> 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}
<<CGAL Header1>>=
// ============================================================================
//
// 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$
//
<<CGAL Header2>>=
// package : Nef_2
// chapter : Nef Polyhedra
//
// source : nef_2d/Nef_polyhedron_2.lw
// revision : $Id$
// revision_date : $Date$
//
// author(s) : Michael Seel <seel@mpi-sb.mpg.de>
// maintainer : Michael Seel <seel@mpi-sb.mpg.de>
// coordinator : Michael Seel <seel@mpi-sb.mpg.de>
//
// 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