@! ============================================================================ @! The CGAL Library @! Implementation: 2D Conic @! ---------------------------------------------------------------------------- @! file : web/Optimisation/Conic_2.aw @! author: Bernd Gärtner, Sven Schönherr @! ---------------------------------------------------------------------------- @! $CGAL_Chapter: Geometric Optimisation $ @! $CGAL_Package: Min_ellipse_2 WIP $ @! $Revision$ @! $Date$ @! ============================================================================ @documentclass[twoside]{article} @usepackage[latin1]{inputenc} @usepackage{a4wide2} @usepackage{epsf} @usepackage{cc_manual} @article \setlength{\parskip}{1ex} \setcounter{secnumdepth}{4} \setcounter{tocdepth}{4} @! ============================================================================ @! Title @! ============================================================================ \RCSdef{\rcsRevision}{$Revision$} \RCSdefDate{\rcsDate}{$Date$} \newcommand{\cgalWIP}{{\footnotesize{} (\rcsRevision{} , \rcsDate) }} @t vskip 5 mm @t title titlefont centre "2D Conic*" @t vskip 1 mm @t title smalltitlefont centre "Bernd Gärtner and Sven Schönherr" \smallskip \begin{center} \begin{tabular}{l} \verb+$CGAL_Chapter: Geometric Optimisation $+ \\ \verb+$CGAL_Package: Min_ellipse_2 WIP+\cgalWIP\verb+$+ \\ \end{tabular} \end{center} @t vskip 1 mm \renewcommand{\thefootnote}{\fnsymbol{footnote}} \footnotetext[1]{This work was supported by the ESPRIT IV LTR Project No.~21957 (CGAL).} @t table_of_contents \renewcommand{\R}{I\!\!R} \newcommand{\C}{{\cal C}} \renewcommand{\E}{{\cal E}} \newcommand{\Vol}{\mathop{\rm Vol}} \renewcommand{\r}{{\cal R}} \renewcommand{\d}{\partial} \newcommand{\sgn}{\mathop{\rm sgn}} \newcommand{\I}{{\bf i}} \newcommand{\acos}{\mathop{\rm acos}} \newcommand{\asin}{\mathop{\rm asin}} \newtheorem{Definition}{Definition}[section] \newtheorem{Lemma}[Definition]{Lemma} @! ============================================================================ @section{Introduction} @! ============================================================================ We define a class template @prg{Conic_2} to store, access and manipulate @em{conics} in the plane, a.k.a. @em{second order curves}. @! --------------------------------------------------------------------------- @subsection{Conics} @! --------------------------------------------------------------------------- For a given real vector $\r=(r,s,t,u,v,w)$, a conic $\C=\C(\r)$ is the set of @em{homogeneous} points $p = (x,y,h), h\neq 0$ satisfying \begin{equation} \label{conic_def_hom} \r(p) := rx^2 + sy^2 + txy + uxh + vyh + wh^2 = 0, \end{equation} equivalently \begin{equation} \label{conic_hom} (x, y, h) \left( \begin{array}{ccc} 2r & t & u \\ t & 2s & v \\ u & v & 2w \end{array} \right) \left( \begin{array}{c} x \\ y \\ h \end{array} \right) = 0. \end{equation} $\r$ is called a @em{representation} of $\C$. Note that the homogeneous point $(x,y,h)$ corresponds to the Cartesian point $(x/h, y/h)$ in the plane. Also, any Cartesian point $p=(x,y)$ can be identified with the homogeneous point $(x,y,1)$, in which case (\ref{conic_def_hom}) assumes the form \begin{equation} \label{conic_def_cart} \r(p) = rx^2 + sy^2 + txy + ux + vy + w = 0, \end{equation} equivalently \begin{equation} \label{conic_cart} (x,y) \left( \begin{array}{ccc} 2r & t \\ t & 2s \end{array} \right) \left( \begin{array}{c} x \\ y \end{array} \right) + (2u, 2v) \left( \begin{array}{c} x \\ y \end{array} \right) + 2w = 0. \end{equation} Thus, under the condition $h\neq 0$, homogeneous and Cartesian representation are equivalent, and we frequently switch between them. \footnote{The reason for scaling equations (\ref{conic_hom}) and (\ref{conic_cart}) by a factor of 2 is purely technical -- we want to argue with division-free terms.} $\C$ is called @em{trivial} if $\C=\R^2$ which is equivalent to $\r=0$. $\C$ is @em{empty} if $\C=\emptyset$, i.e. if (\ref{conic_def_hom}) has no real solutions $x,y,h$ with $h\neq 0$ (which happens e.g. in case of $\r = (1, 1, 0, 0, 0, 1)$, or $\r=(0,0,0,0,0,1)$). $\C$ is invariant under scaling its representation $\r$ by any nonzero factor. This means, a conic has five degrees of freedom, and in fact it holds that any five points uniquely determine a nontrivial conic passing through the points. Some care is in place: this does @em{not} mean that five points uniquely determine the conic's representation, up to scaling. For example, the $x$-axis is a conic uniquely determined by any five points on it, but as a representation we may choose $\{y=0\}$ or $\{y^2=0\}$. Recall that we have already seen two representations of the empty conic which are not multiples of each other. @! --------------------------------------------------------------------------- @subsection{Conic types} @! --------------------------------------------------------------------------- \label{types_sec} The number \begin{equation} \label{det} \det(\r) := \det \left( \begin{array}{cc} 2r & t \\ t & 2s \end{array}\right) \end{equation} determines the type of $\C(\r)$. If $\det(\r)>0$, $\C$ is an @em{ellipse}, if $\det(\r)<0$, we get a @em{hyperbola}, and for $\det(\r)=0$, a @em{parabola} is obtained. While the trivial conic is a degenerate parabola equal to the whole plane, any nontrivial one consists of at most two simple curves. As a special case, there is a conic $\C=\{p\}$ for any point $p=(x_0,y_0)$. It can be specified as \[ \C = \{(x,y)\mid (x-x_0)^2 + (y-y_0)^2 = 0\}, \] and a possible representation is $\r=(1, 1, 0, -2x_0, -2y_0, x_0^2+y_0^2)$. This implies $\det(\r)=4$, so $\C$ is a degenerate ellipse, see subsection \ref{orientation_sec}. Note that $\det(\r)> 0$ implies $r,s > 0$ or $r,s<0$ which is equivalent to \begin{equation} M := \left( \begin{array}{cc} 2r & t \\ t & 2s \end{array}\right) \end{equation} being positive definite ($x^TMx>0$ for $x\neq 0$) or negative definite ($x^TMx<0$ for $x\neq 0$). In case of $\det(\r)<0$, $M$ is indefinite, meaning that $x^TMx$ assumes positive and negative values. @! --------------------------------------------------------------------------- @subsection{Symmetry properties} @! --------------------------------------------------------------------------- \label{symmetry_sec} If the conic $\C(\r)$ is not a parabola, the matrix \[ M = \left(\begin{array}{cc} 2r & t \\ t & 2s \end{array}\right) \] is regular, and $\C$ has a unique center of symmetry $c$, given as \[ c = -M^{-1}\left(\begin{array}{c} u \\ v \end{array}\right). \] With this definition, (\ref{conic_cart}) can alternatively been written as \begin{equation} \label{center_form} (p-c)^T M (p-c) + 2w-c^TMc = 0, \end{equation} $p=(x,y)^T$, from which the symmetry is obvious. In case of a parabola, we get an axis of symmetry. @! --------------------------------------------------------------------------- @subsection{Orientation and degeneracy} @! --------------------------------------------------------------------------- \label{orientation_sec} An @em{oriented conic} is a pair $\C_{\r} = (\C(\r),\r)$, i.e. a conic with a particular representation. $\C_{\r}$ subdivides $\R^2\setminus \C(\r)$ into a @em{positive} side, formed by the set of points such that $\r(p)>0$, and a @em{negative} side ($\r(p)<0$). Replacing $\r$ with $-\r$ leads to an oriented conic with positive and negative sides interchanged. This concept of assigning positive and negative sides is purely algebraic. In addition, there is a geometric way of assigning sides to an oriented conic which does not depend on $\r$ (like an oriented circle has a bounded side, independent from its orientation). To this end, we define the @em{convex side} of a conic $\C$ as the the union of the convex connected components of $\R^2\setminus \C$. The @em{non-convex side} is then just the union of the non-convex components. Figure \ref{orientations} depicts the convex sides of an ellipse, hyperbola and parabola, labeled with the letter `$c$'. \begin{figure} \begin{center} \leavevmode \epsfxsize=12cm %\epsfbox{orientations.eps} \end{center} \caption{Ellipse, hyperbola and parabola with convex sides} \label{orientations} \end{figure} A conic is defined to be @em{degenerate} if either its convex side or its non-convex side is empty. Let us discuss the possibilities for this. First, the trivial conic is degenerate, with both sides being empty. The empty conic is degenerate, with the non-convex side being empty. An ellipse is degenerate if and only if it consists of just one point (and so the convex side is empty). A hyperbola is degenerate if and only if it contains its center of symmetry $c$. In this case, the hyperbola is a pair of lines crossing at $c$, and so the non-convex side is empty. A degenerate parabola is either a pair of parallel lines or just one line. In both cases, the non-convex side is empty. In the non-degenerate case, the classification of points by positive and negative side coincides with the one by convex and non-convex side. In the degenerate case, this exactly holds if positive or negative side disappear, like for a degenerate ellipse (but not for a degenerate hyperbola). An oriented conic $\C_{\r}$ is said to have positive (negative) orientation, if and only if the convex side coincides with the positive (negative) side. If neither is the case, the orientation is zero. Thus, a degenerate ellipse has nonzero orientation, but a degenerate hyperbola has not. While it is clear that positive and negative side of a conic are only defined with respect to some representation, it is interesting to note that even the partition of $\R^2\setminus\C_{\r}$ into positive and negative side does in general depend on $\r$. Coming back to the conic $\C = \{y=0\} = \{y^2=0\}$, the first representation of it leads to nonempty positive and negative side, while in the second one, the negative side is empty. @! --------------------------------------------------------------------------- @subsection{Ellipses and the volume formula} @! --------------------------------------------------------------------------- The volume of an ellipse $\E$, $\Vol(\E)$, is defined as the area of its convex side. If $\E$ is non-degenerate and presented in center form (\ref{center_form}), consider the matrix $$A := M / (2w - c^TMc).$$ It is easy to see that $A$ is invariant under scaling $\r$ by any nonzero factor. The following holds. \begin{Lemma} \label{ellipse_volume} $$\Vol(\E) = \frac{\pi}{\sqrt{\det(A)}}.$$ \end{Lemma} For this note that $\det(M)>0$ implies $\det(A)>0$. @! --------------------------------------------------------------------------- @section{Class Template {\tt CGAL\_Conic\_2}} @! --------------------------------------------------------------------------- An object of the class @prg{Conic_2} stores an oriented conic $\C_{\r} = (\C(\r),\r)$ by its representation $\r$. The template parameter @prg{R} is the representation type (homogeneous or Cartesian). In the sequel, a conic always means an oriented conic. @macro = @begin template < class R> class Conic_2; @end The class @prg{Conic_2} has several member functions, some of which can be considered general purpose (and are declared public), others are more specialized and needed only by the class template @prg{Optimisation_ellipse_2} -- they appear as private methods. @macro = @begin template < class _R > class Optimisation_ellipse_2; @end @macro = @begin template < class _R > CGAL::Window_stream& operator << ( CGAL::Window_stream&, const CGAL::Optimisation_ellipse_2<_R>&); @end As a general rule, a method only qualifies for the public domain if it can be specified without referring to particular values of $\r$, in other words, if its behavior only depends on the equivalence class of all positive multiples of $\r$. For example, this holds for the sign of $\r(p)$, $p$ some point, but not for the value of $\r(p)$. As usual, there is an exception to this rule: we have public methods to retrieve the components of the representation $\r$. All calls to member functions are directly mapped to calls of corresponding methods declared by the conic class @prg{R::Conic_2} of the representation type @prg{R}. @macro = @begin template < class _R> class Conic_2 : public _R::Conic_2 { friend class Optimisation_ellipse_2<_R>; public: // types typedef _R R; typedef typename _R::RT RT; typedef typename _R::FT FT; typedef typename _R::Conic_2 _Conic_2; // construction @ // general access @ // type related access @ // orientation related access @ // comparisons @ // set methods @ private: @ }; @end @! --------------------------------------------------------------------------- @subsection{Construction} @! --------------------------------------------------------------------------- We have a default constructor and a constructor to initialize a conic at coordinate level, providing the components of a representation $\r$. The orientation of the conic is determined by $\r$, as described in subsection \ref{orientation_sec}. @macro = @begin Conic_2 () {} Conic_2 (RT r, RT s, RT t, RT u, RT v, RT w) : R::Conic_2 (r, s, t, u, v, w) {} @end @! --------------------------------------------------------------------------- @subsection{General access} @! --------------------------------------------------------------------------- We provide access to the coordinates of the representation $\r$. Even if $\C_{\r}$ was constructed from some specific characteristic vector, the return coordinates do not necessarily belong to this vector (but at least to a nonnegative multiple of it). For example, if the coordinates are from some integer domain, the implementation might decide to divide all of them by their gcd to get smaller numbers. (In this implementation, this does not happen.) @macro += @begin RT r () const { return _Conic_2::r(); } RT s () const { return _Conic_2::s(); } RT t () const { return _Conic_2::t(); } RT u () const { return _Conic_2::u(); } RT v () const { return _Conic_2::v(); } RT w () const { return _Conic_2::w(); } @end We can obtain the center of symmetry of $\C_{\r}$. Precondition is that $\C$ is not a parabola. @macro += @begin CGAL::Point_2 center () const { return _Conic_2::center(); } @end The symmetry axis of a parabola and the two axes of an ellipse resp. a hyperbola require irrational coordinates in general, therefore they cannot be added here without further assumptions about the representation type @prg{R}. @! --------------------------------------------------------------------------- @subsection{Type related access} @! --------------------------------------------------------------------------- Here we have access methods and predicates related to the type of $\C_{\r}$. We can either directly retrieve the type or ask whether $\C_{\r}$ is of some specific type. To realize the former, we provide a suitable enumeration type. @macro = @begin enum Conic_type { HYPERBOLA = -1, PARABOLA, ELLIPSE }; @end @macro += @begin Conic_type conic_type () const { return _Conic_2::conic_type(); } bool is_hyperbola () const { return _Conic_2::is_hyperbola(); } bool is_parabola () const { return _Conic_2::is_parabola(); } bool is_ellipse () const { return _Conic_2::is_ellipse(); } @end We have three more predicates that -- in combination with the above -- yield a finer access to the type, namely a test for emptiness, triviality and degeneracy, where both the empty and the trivial conic are also degenerate. @macro += @begin bool is_empty () const { return _Conic_2::is_empty(); } bool is_trivial () const { return _Conic_2::is_trivial(); } bool is_degenerate () const { return _Conic_2::is_degenerate(); } @end @! --------------------------------------------------------------------------- @subsection{Orientation related access} @! --------------------------------------------------------------------------- We can retrieve the orientation of a conic as defined in subsection \ref{orientation_sec}. A notable difference to the class @prg{Circle_2} is that the orientation can be zero. @macro += @begin CGAL::Orientation orientation () const { return _Conic_2::orientation (); } @end The following methods classify points according to the positive and negative side of $\C_{\r}$. We can either retrieve the oriented side directly or ask whether the point lies on some specific side. @macro += @begin CGAL::Oriented_side oriented_side (const CGAL::Point_2& p) const { return _Conic_2::oriented_side (p); } bool has_on_positive_side (const CGAL::Point_2& p) const { return _Conic_2::has_on_positive_side (p); } bool has_on_negative_side (const CGAL::Point_2& p) const { return _Conic_2::has_on_negative_side (p); } bool has_on_boundary (const CGAL::Point_2& p) const { return _Conic_2::has_on_boundary (p); } bool has_on (const CGAL::Point_2& p) const { return _Conic_2::has_on (p); } @end Then we have the classification according to convex and non-convex side. Because this is a natural generalization of the classification according to bounded and unbounded side, we `extend' the type @prg{Bounded_side}. @macro = @begin typedef CGAL::Bounded_side Convex_side; const Convex_side ON_CONVEX_SIDE = CGAL::ON_BOUNDED_SIDE; const Convex_side ON_NONCONVEX_SIDE = CGAL::ON_UNBOUNDED_SIDE; @end @macro += @begin Convex_side convex_side (const CGAL::Point_2& p) const { return _Conic_2::convex_side (p); } bool has_on_convex_side (const CGAL::Point_2& p) const { return _Conic_2::has_on_convex_side (p); } bool has_on_nonconvex_side (const CGAL::Point_2& p) const { return _Conic_2::has_on_nonconvex_side (p); } @end @! --------------------------------------------------------------------------- @subsection{Comparison methods} @! --------------------------------------------------------------------------- We provide tests for equality and inequality of two conics. @macro = @begin bool operator == ( const Conic_2<_R>& c) const { return _Conic_2::operator == ( (Conic_2)c); } bool operator != ( const Conic_2<_R>& c) const { return( ! operator == ( c)); } @end @! --------------------------------------------------------------------------- @subsection{Public set methods} @! --------------------------------------------------------------------------- Apart from the most basic way of constructing an oriented conic from the coordinates of a representation, there are methods at a higher level, building a conic from points or other conics. Such methods appear here; they are not given as constructors, but as `set' functions that modify an already existing conic. But of course, there is also a set function that works on the coordinate level. @macro += @begin void set (RT r, RT s, RT t, RT u, RT v, RT w) { _Conic_2::set (r, s, t, u, v, w); } @end @! --------------------------------------------------------------------------- @subsubsection{Opposite conic} @! --------------------------------------------------------------------------- We can obtain the conic $\C_{-\r}$ of opposite orientation, having positive and negative sides interchanged (convex and non-convex side stay the same, of course). Note that if $\C_{\r}$ has zero orientation, so has $\C_{-\r}$. @macro += @begin void set_opposite () { _Conic_2::set_opposite(); } @end @! --------------------------------------------------------------------------- @subsubsection{Pair of lines through four points} @! --------------------------------------------------------------------------- The method @prg{set_linepair} builds the conic equal to union of the lines $\overline{p_1p_2}$ and $\overline{p_3p_4}$. This is either a degenerate hyperbola in case the two lines are not parallel, or a degenerate parabola. The precondition is that $p_1\neq p_2$ and $p_3\neq p_4$. The positive side is the region to the left resp. to the right of both oriented lines $\overline{p_1p_2}, \overline{p_3p_4}$. @macro += @begin void set_linepair (const CGAL::Point_2& p1, const CGAL::Point_2& p2, const CGAL::Point_2& p3, const CGAL::Point_2& p4) { _Conic_2::set_linepair (p1, p2, p3, p4); } @end @! --------------------------------------------------------------------------- @subsubsection{Smallest ellipse through three points} @! --------------------------------------------------------------------------- The following method @prg{set_ellipse} generates the ellipse of smallest volume through $p_1,p_2$ and $p_3$. This is at the same time the unique ellipse through these points whose center is equal to the center of gravity of the points. Precondition is that $p_1,p_2,p_3$ are not collinear. The orientation of the ellipse is the orientation of the point triple. @macro += @begin void set_ellipse (const CGAL::Point_2& p1, const CGAL::Point_2& p2, const CGAL::Point_2& p3) { _Conic_2::set_ellipse (p1, p2, p3); } @end @! --------------------------------------------------------------------------- @subsubsection{Some ellipse through four points in convex position} @! --------------------------------------------------------------------------- We have another @prg{set_ellipse} method, constructing an ellipse from four given points, assumed to be in convex position (if not, the result will still be some conic through the points, but not an ellipse). The result will be just some ellipse through the points; in particular, it will usually @em{not} be the smallest one passing through the points. The orientation of the ellipse can be specified and defaults to positive. @macro += @begin void set_ellipse (const CGAL::Point_2& p1, const CGAL::Point_2& p2, const CGAL::Point_2& p3, const CGAL::Point_2& p4, CGAL::Orientation o = POSITIVE) { _Conic_2::set_ellipse (p1, p2, p3, p4, o); } @end @! --------------------------------------------------------------------------- @subsubsection{Unique conic through five points} @! --------------------------------------------------------------------------- The method @prg{set} generates the unique nontrivial conic containing the points $p_1,p_2,p_3,p_4,p_5$. Precondition is that all points are distinct. The orientation can be specified but is automatically set to zero if the resulting conic has zero orientation. @macro += @begin void set (const CGAL::Point_2& p1, const CGAL::Point_2& p2, const CGAL::Point_2& p3, const CGAL::Point_2& p4, const CGAL::Point_2& p5, CGAL::Orientation o = POSITIVE) { _Conic_2::set (p1, p2, p3, p4, p5, o); } @end @! --------------------------------------------------------------------------- @subsection{Private methods} @! --------------------------------------------------------------------------- @! --------------------------------------------------------------------------- @subsubsection{Linear combination of conics} @! --------------------------------------------------------------------------- The linear combination of two conics $\C_{\r_1}$ and $\C_{\r_2}$ with coefficients $a_1,a_2$ is the conic with representation $\r := a_1\r_1+a_2\r_2$. This is not a geometric operation, because the result depends on $\r_1,\r_2$ and not only on $\C_{\r_1}$ and $\C_{\r_2}$. @macro += @begin void set_linear_combination ( const RT& a1, const Conic_2& c1, const RT& a2, const Conic_2& c2) { _Conic_2::set_linear_combination (a1, c1, a2, c2); } @end @! --------------------------------------------------------------------------- @subsubsection{Two pairs of lines through four points} @! --------------------------------------------------------------------------- The following method constructs two line-pairs through four given points $p_1,p_2,p_3,p_4$, assumed to be in convex position. If the points are enumerated in clockwise or counterclockwise order, these will be the line-pairs $\overline{p_1p_2}\cup\overline{p_3p_4}$ and $\overline{p_2p_3}\cup\overline{p_4p_1}$. Otherwise, points are renamed first to achieve (counter)clockwise orientation. The two resulting conics are passed to the method by reference, and their orientations depend on the points as described in the method @prg{set_linepair}. If $p_1,p_2,p_3,p_4$ are not in convex position, the method still computes two line-pairs through the points, but it is not specified which ones. This is a static method. @macro += @begin static void set_two_linepairs (const CGAL::Point_2& p1, const CGAL::Point_2& p2, const CGAL::Point_2& p3, const CGAL::Point_2& p4, Conic_2& pair1, Conic_2& pair2) { _Conic_2::set_two_linepairs (p1, p2, p3, p4, pair1, pair2); } @end @! --------------------------------------------------------------------------- @subsubsection{Some ellipse from two pairs of lines} @! --------------------------------------------------------------------------- We have a method to construct an ellipse through $p_1,p_2,p_3,p_4$ in convex position, using two line-pairs through the points as obtained from a call to the method @prg{set_two_linepairs} with parameters $p_1,p_2,p_3,p_4$. The orientation of the ellipse is not specified. In case the argument conics have not been constructed by the method @prg{set_two_linepairs} in the described way, the resulting conic is unspecified. @macro += @begin void set_ellipse (const Conic_2& pair1, const Conic_2& pair2) { _Conic_2::set_ellipse (pair1, pair2); } @end @! --------------------------------------------------------------------------- @subsubsection{Conic from two conics and a point} @!---------------------------------------------------------------------------- Assuming that we already have two conics intersecting in four points, the following alternative @prg{set} method is more efficient in constructing the unique nontrivial conic through them and another point $p$. It accepts two conics $\C_{\r_1}$ and $\C_{\r_2}$ and a point $p$, and computes some conic $\C_{\r}$ that goes through $\C_{\r_1}\cap \C_{\r_2} \cup\{p\}$. The method may construct the trivial conic. This happens if one of $\C_{\r_1}$ and $\C_{\r_2}$ is already trivial, $\C(\r_1)=\C(\r_2)$ holds, or if $p\in \C_{\r_1}\cap \C_{\r_2}$. In case of nonzero orientation, the actual orientation of $\C_{\r}$ is unspecified. @macro += @begin void set (const Conic_2& c1, const Conic_2& c2, const CGAL::Point_2& p) { _Conic_2::set( c1, c2, p); analyse(); } @end @! --------------------------------------------------------------------------- @subsubsection{Volume derivative of an ellipse} @! --------------------------------------------------------------------------- Given an ellipse $\E$ with representation $\r$ and some vector $\d \r := (\d r, \d s, \d t, \d u, \d v, \d w)$, define \[ \E(\tau) = \C_{\r + \tau \d \r}. \] For small values of $\tau$, $\E(\tau)$ is still an ellipse. The method @prg{vol_derivative} computes the sign of \[ \frac{\partial}{\partial \tau} \Vol(\E(\tau))\left|_{\tau=0}\right., \] i.e. decides how the volume develops when going from $\E(0)$ `in direction' $\d\r$. If $\E$ is not an ellipse, the result is meaningless. @macro += @begin CGAL::Sign vol_derivative (RT dr, RT ds, RT dt, RT du, RT dv, RT dw) const { return _Conic_2::vol_derivative (dr, ds, dt, du, dv, dw); } @end A related method computes the value $\tau^*$ such that $\Vol(\E(\tau))$ assumes its minimum over the set $T=\{\tau \mid \E(\tau) \mbox{~is an ellipse}\}$. Precondition is that this minimum exists. If so, $\tau^*$ is a local extremum of the volume function. $\tau^*$ might be irrational, but because the value is only used for drawing the ellipse $\E(\tau^*)$, a @prg{double}-approximation suffices. As before, if $\E$ is not an ellipse, the result is meaningless. @macro += @begin double vol_minimum (RT dr, RT ds, RT dt, RT du, RT dv, RT dw) const { return _Conic_2::vol_minimum (dr, ds, dt, du, dv, dw); } @end @! --------------------------------------------------------------------------- @subsection{IO routines} @! --------------------------------------------------------------------------- @macro = @begin template< class _R> std::ostream& operator << ( std::ostream& os, const Conic_2<_R>& c) { return( os << c.r() << ' ' << c.s() << ' ' << c.t() << ' ' << c.u() << ' ' << c.v() << ' ' << c.w()); } @end @! --------------------------------------------------------------------------- @subsubsection{Output to {\tt CGAL\_Window\_stream}} @! --------------------------------------------------------------------------- We provide an operator to write a conic to a @prg{Window_stream}. The function is not extraordinarily efficient but simple (and works without `understanding' the conic; other methods -- like the one by Maxwell \& Baker -- need to determine the conic type first, compute start values etc.). The method works in image space, proceeding in two phases. Phase 1 draws the conic in $x$-direction. This means that the width of the output window is scanned pixel-wise, for any $x$-value computing the at most two corresponding values $y_1,y_2$ such that $(x,y_1),(x,y_2)\in \C$. (This is done by solving a quadratic equation). The resulting pixels are stored for output, which is triggered after all $x$-values have been processed in this way. Phase 2 draws the conic in $y$-direction, proceeding similarly. Phases 1 and 2 together ensure that there are no gaps in the drawn curve(s). @macro = @begin #ifdef CGAL_CONIC_2_H #ifndef CGAL_IO_WINDOW_STREAM_CONIC_2 #define CGAL_IO_WINDOW_STREAM_CONIC_2 template< class R > CGAL::Window_stream& operator << ( CGAL::Window_stream& ws, const CGAL::Conic_2& c) { // length of a pixel in window-coordinates double pixel = 1/ws.scale(); // pixel dimensions of window int width = (int)((ws.xmax() - ws.xmin()) * ws.scale()) + 1, height = (int)((ws.ymax() - ws.ymin()) * ws.scale()) + 1, dim = std::max( width, height); // pixel coordinates, stored for faster output double *X = new double [2*dim]; double *Y = new double [2*dim]; // actual number of pixels to be drawn int pixels; // conic coordinates double r = CGAL::to_double (c.r()), s = CGAL::to_double (c.s()), t = CGAL::to_double (c.t()), u = CGAL::to_double (c.u()), v = CGAL::to_double (c.v()), w = CGAL::to_double (c.w()); // Phase I (drawing in x-direction) pixels = 0; // solve conic equation for y if (s != 0.0) for (double x = ws.xmin(); x <= ws.xmax(); x+=pixel) { double discr = (t*t-4.0*r*s)*(x*x) + (2.0*t*v-4.0*s*u)*x + v*v - 4.0*s*w; if (discr >= 0.0) { double y1 = (-t*x - v - CGAL::sqrt(discr))/(2.0*s); double y2 = (-t*x - v + CGAL::sqrt(discr))/(2.0*s); X[pixels] = x; Y[pixels++] = y1; X[pixels] = x; Y[pixels++] = y2; } } else for (double x = ws.xmin(); x <= ws.xmax(); x+=pixel) { double denom = t*x + v; if (denom != 0.0) { double y = -(r*x*x + u*x + w)/denom; X[pixels] = x; Y[pixels++] = y; } } ws.draw_pixels (pixels, X, Y); // Phase II (drawing in y-direction) pixels = 0; // solve conic equation for x if (r != 0.0) for (double y = ws.ymin(); y <= ws.ymax(); y+=pixel) { double discr = (t*t-4.0*r*s)*(y*y) + (2.0*t*u-4.0*r*v)*y + u*u - 4.0*r*w; if (discr >= 0.0) { double x1 = (-t*y - u - CGAL::sqrt(discr))/(2.0*r); double x2 = (-t*y - u + CGAL::sqrt(discr))/(2.0*r); X[pixels] = x1; Y[pixels++] = y; X[pixels] = x2; Y[pixels++] = y; } } else for (double y = ws.ymin(); y <= ws.ymax(); y+=pixel) { double denom = t*y + u; if (denom != 0.0) { double x = -(s*y*y + v*y + w)/denom; X[pixels] = x; Y[pixels++] = y; } } ws.draw_pixels (pixels, X, Y); // free memory delete[] Y; delete[] X; return( ws); } #endif // CGAL_IO_WINDOW_STREAM_CONIC_2 #endif // CGAL_CONIC_2_H @end @! --------------------------------------------------------------------------- @section{Class Templates {\tt CGAL\_ConicHPA2} and {\tt CGAL\_ConicCPA2}} @! --------------------------------------------------------------------------- The two classes described in this section realize the functionality of the general class @prg{Conic_2}, distinguished between Cartesian and homogeneous representation. Unlike it is practice in the kernel, the template parameters are not field and/or ring type but point and data accessor type. The reason is that the classes described here are also used to build traits class adapters for the class @prg{Min_ellipse_2}, where the adapters themselves are templatized with a point and data accessor type. To realize this scheme, the representation type @prg{R} of the class @prg{Conic_2} is enhanced with a data accessor class -- it will be the canonical one, realizing access to the coordinates of a @prg{Point_2} (see files @prg{homogeneous_rep.h} and @prg{cartesian_rep.h}). @macro = @begin template < class PT, class DA> class ConicHPA2; template < class PT, class DA> class _Min_ellipse_2_adapterH2__Ellipse; @end @macro = @begin template < class PT, class DA> class ConicCPA2; template < class PT, class DA> class _Min_ellipse_2_adapterC2__Ellipse; @end @! --------------------------------------------------------------------------- @subsection{Requirements for template parameters {\tt PT} and {\tt DA}} @! --------------------------------------------------------------------------- For @prg{ConicHPA2}, the data accessor type @prg{DA} must define a ring type @prg{RT} and a function @prg{get} to access $x$-, $y$- and $h$-coordinate of a point $p=(x,y,h)$ of type @prg{PT}. The type @prg{RT} is the coordinate type of @prg{PT}, and we expect @prg{PT} to have a constructor with three arguments $x,y,h$ of type @prg{RT}. In addition, we need an assignment constructor (for the @prg{center} method). A minimal interface of a type @prg{DA} for the homogeneous representation would look as follows. @macro zero = @begin class DA { public: // ring type typedef /* some CGAL-conform number type */ RT; // coordinate access void get (const PT& p, RT& x, RT& y, RT& h); }; @end In the Cartesian case, @prg{DA} must declare a field type @prg{FT} and access via @prg{get} to the $x$- and $y$-coordinate of a point $p=(x,y)$ of type @prg{FT}. @prg{PT} is expected to declare a constructor with two arguments of type @prg{FT}. In addition, we need an assignment constructor. A minimal interface of a type @prg{DA} in the Cartesian case would look as follows. @macro zero = @begin class DA { public: // field type typedef /* some CGAL-conform number type */ FT; // coordinate access void get (const PT& p, FT& x, FT& y); }; @end In both cases, class @prg{DA} needs to provide a default constructor. @! --------------------------------------------------------------------------- @subsection{Interfaces} @! --------------------------------------------------------------------------- The interfaces look similar to the interface of the class @prg{Conic_2} in the sense that for any public (private) member function of @prg{Conic_2}, we have a corresponding public (private) member function here. These are the @em{high-level} member functions. In addition, there are private data members to store the conic representation and a couple of additional @em{low-level} protected member functions. The reason for making them protected is that we do not want them to show up explicitly in the interface of the class @prg{Conic_2}, but on the other hand, friends of @prg{Conic_2} should be able to use them. @! --------------------------------------------------------------------------- @subsubsection{{\tt CGAL\_ConicHPA2}} @! --------------------------------------------------------------------------- @macro = @begin template < class _PT, class _DA> class ConicHPA2 { public: // types typedef _PT PT; typedef _DA DA; typedef typename _DA::RT RT; private: friend class Conic_2< CGAL::Homogeneous >; friend class _Min_ellipse_2_adapterH2__Ellipse; @ @ protected: @ public: @ }; @end @! --------------------------------------------------------------------------- @subsubsection{{\tt CGAL\_ConicCPA2}} @! --------------------------------------------------------------------------- @macro = @begin template < class _PT, class _DA> class ConicCPA2 { public: // types typedef _PT PT; typedef _DA DA; typedef typename _DA::FT FT; private: friend class Conic_2< CGAL::Cartesian >; friend class _Min_ellipse_2_adapterC2__Ellipse; @ @ protected: @ public: @ }; @end @! --------------------------------------------------------------------------- @section{Implementation of Class templates {\tt CGAL\_ConicCPA2} and {\tt CGAL\_ConicCPA2}} @! --------------------------------------------------------------------------- We implement the classes in their interface to cope with insufficiencies of the GNU compiler concerning scope operators in typenames. Because the implementations of most member functions are very similar for both representations, we always write them down in parallel. @! --------------------------------------------------------------------------- @subsection{Private data members} @! --------------------------------------------------------------------------- An oriented conic $\C_{\r}$ is stored by its representation $\r$ and certain @em{derived data}. These data are \begin{itemize} \item the type of $\C_{\r}$, \item the orientation of $\C_{\r}$, \item degeneracy information, consisting of three flags indicating whether $\C_{\r}$ is empty, trivial or degenerate. \end{itemize} Although type, orientation and degeneracy information can be retrieved from $\r$, it is more efficient to store them, because for example, repeated convex side tests on the same conic but with different points access these data over and over again. @macro = @begin DA dao; RT _r, _s, _t, _u, _v, _w; Conic_type type; CGAL::Orientation o; bool empty, trivial, degenerate; @end @macro = @begin DA dao; FT _r, _s, _t, _u, _v, _w; Conic_type type; CGAL::Orientation o; bool empty, trivial, degenerate; @end @! --------------------------------------------------------------------------- @subsection{Low-level private member functions} @! --------------------------------------------------------------------------- \label{private_methods} Let's start with the low-level members that do not have a counterpart in the interface of the class @prg{Conic_2}. @! --------------------------------------------------------------------------- @subsubsection{Determinant} @! --------------------------------------------------------------------------- The function @prg{det} just computes $\det(\r)=4rs-t^2$, and as mentioned in subsection \ref{types_sec}, this value determines the type of the conic. @macro += @begin RT det () const { return RT(4)*s()*r() - t()*t(); } @end @macro += @begin FT det () const { return FT(4)*s()*r() - t()*t(); } @end @! --------------------------------------------------------------------------- @subsubsection{Conic analysis} @! --------------------------------------------------------------------------- This method is the most important low-level method. It initializes the derived data from the representation $\r$, by first determining the conic's type and then handling the three possible types in a @prg{case} statement. @macro += @begin void analyse( ) { RT d = det(); type = (Conic_type)(CGAL_NTS sign(d)); switch (type) { case HYPERBOLA: { @ } break; case PARABOLA: { @ } break; case ELLIPSE: { @ } break; } } @end @macro += @begin void analyse( ) { FT d = det(); type = (Conic_type)(CGAL_NTS sign(d)); switch (type) { case HYPERBOLA: { @ } break; case PARABOLA: { @ } break; case ELLIPSE: { @ } break; } } @end Let us first deal with the case where $\C_{\r}$ is a hyperbola or ellipse. Then we have seen in subsection \ref{symmetry_sec} that $\C(\r)$ has a center of symmetry $c$ and can be written in the form \[ \{p\mid (p-c)^T M (p-c) + 2w-c^TMc = 0\}, \] Moreover, $\C(\r)$ is degenerate if the center lies on the conic. This is the case if and only if $z := 2w-c^TMc=0$. To compute this value $z$, we go back to the formulas of subsection \ref{symmetry_sec}, where we have seen that $$c = -M^{-1} \left(\begin{array}{c} u \\ v \end{array} \right),$$ therefore $$c^TMc = (u,v)M^{-1}\left(\begin{array}{c} u \\ v \end{array} \right) = \frac{1}{\det(M)} (u,v) \left( \begin{array}{cc} 2s & -t \\ -t & 2r \end{array}\right) \left(\begin{array}{c} u \\ v \end{array} \right) = \frac{1}{\det(M)}(2u^2 s + 2v^2 r - 2uvt).$$ This means, \begin{equation} \label{z} z = 2 \left(w - \frac{1}{\det(M)}(u^2 s + v^2 r - uvt)\right). \end{equation} To avoid divisions, we consider the value $z'=\det(M)z/2$ which satisfies $$ z' = \det(M) w - u^2 s - v^2 r + uvt. $$ $z'$ has the same sign as $z$ in case of an ellipse, and opposite sign in case of a hyperbola. To proceed further, we note that only a parabola can be trivial. Moreover, a hyperbola cannot be empty (the matrix $M$ is indefinite, therefore $x^TMx$ assumes arbitrary real values). In case of an ellipse, $M$ is either positive or negative definite, meaning $r>0$ or $r<0$. In the former case, the ellipse is empty if and only if $z>0$, in the latter case, $z<0$ leads to an empty ellipse. Summarizing, the ellipse is empty iff $rz>0$, equivalently $rz'>0$ Now consider orientation. A hyperbola is in positive orientation if and only if its center is on the negative side, equivalently $z<0$, or $z'>0$. Similarly, the orientation is negative in case of $z'<0$. For $z=z'=0$, the hyperbola is degenerate, and so its orientation is zero. A non-degenerate ellipse, on the other hand, has positive orientation if and only if its center is in the positive side, equivalently, if $z > 0$, or $z' > 0$. This is equivalent to $M$ being negative definite, or $r<0$. In case of the degenerate ellipse $\E=\{c\}$, we have $z=z'=0$, and the orientation is positive if and only if the negative side is nonempty (the positive side must agree with the empty convex side). As before, this is equivalent to $M$ being negative definite, or $r<0$. In contrast to this, the empty ellipse has positive orientation if and only if the negative side is empty (the positive side must agree with the convex side, which is the whole plane in this case). For this, we get the equivalent condition $r>0$. @macro = @begin trivial = empty = false; RT z_prime = d*w() - u()*u()*s() - v()*v()*r() + u()*v()*t(); o = (CGAL::Orientation)(CGAL_NTS sign (z_prime)); degenerate = (o == CGAL::ZERO); @end @macro = @begin trivial = empty = false; FT z_prime = d*w() - u()*u()*s() - v()*v()*r() + u()*v()*t(); o = (CGAL::Orientation)(CGAL_NTS sign (z_prime)); degenerate = (o == CGAL::ZERO); @end @macro = @begin trivial = false; RT z_prime = d*w() - u()*u()*s() - v()*v()*r() + u()*v()*t(); if (CGAL_NTS is_positive (r())) { empty = CGAL_NTS is_positive(CGAL_NTS sign (z_prime)); empty ? o = CGAL::POSITIVE : o = CGAL::NEGATIVE; } else { empty = CGAL_NTS is_negative(CGAL_NTS sign (z_prime)); empty ? o = CGAL::NEGATIVE : o = CGAL::POSITIVE; } degenerate = empty || CGAL_NTS is_zero (z_prime); @end @macro = @begin trivial = false; FT z_prime = d*w() - u()*u()*s() - v()*v()*r() + u()*v()*t(); if (CGAL_NTS is_positive (r())) { empty = CGAL_NTS is_positive(CGAL_NTS sign (z_prime)); empty ? o = CGAL::POSITIVE : o = CGAL::NEGATIVE; } else { empty = CGAL_NTS is_negative(CGAL_NTS sign (z_prime)); empty ? o = CGAL::NEGATIVE : o = CGAL::POSITIVE; } degenerate = empty || CGAL_NTS is_zero (z_prime); @end In the parabola case, we proceed as follows, first observing that $\det(M)=0$ implies $r,s\geq 0$ or $r,s\leq 0$. Assume that $r\neq 0$. Then the conic equation (\ref{conic_def_hom}) can be solved for $x$, obtaining $$x = \frac{-ty - u \pm \sqrt{2(tu - 2rv)y +u^2 - 4rw}}{2r}.$$ The parabola is non-degenerate exactly if the factor $(tu - 2rv)$ is nonzero, where we obtain a curved object. For $tu=2rv$, the parabola is either empty (this happens for $u^2<4rw$ when the radicant becomes negative), a single line (if $u^2=4rw$), or a pair of lines (for $u^2>4rw$). Let us treat the degenerate case first. In the empty case, the parabola has only one nonempty side, and the orientation is determined by $w$. $w>0$ means positive orientation (because then the point $(0,0)$ is on the positive side, which therefore equals the convex side in this case), $w<0$ means negative orientation. Because of $u^2<4rw$, the case $w=0$ cannot occur. In case of $u^2 = 4rw$, the conic is given by $$\r(p) = r(x + \frac{ty + u}{2r})^2,$$ and the orientation is positive if and only if $r>0$ (in which case every point $p=(x,y)$ is on the positive side, equivalently on the convex side). For $u^2 > 4rw$, we get a pair of parallel lines, of zero orientation (because both positive and negative sides are nonempty, while the non-convex side is empty). We can argue completely similar in the case $s\neq 0$. Here we get a degeneracy exactly if $tv = 2su$, and the discriminant $v^2-4sw$ determines whether the parabola is empty, equal to one line or to a pair of lines. If $r=s=0$ (implying $t=0$), we have the trivial conic if all other parameters are also zero. The trivial conic has always positive orientation (because both positive and convex side are empty). If $u=v=0$ but $w\neq 0$, we get the empty conic, where the orientation is given by $w$. In any other case, we obtain a single line, this time with zero orientation, because it has nonempty positive and negative side but empty non-convex side. Now consider the non-degenerate case. We claim that the orientation is positive if and only if $r,s\leq 0$. To see this, note that in this case, the parabola can be written in the form $$\r(p) = -(\sqrt{-r} x - \sqrt{-s} y)^2 + ux + vy + w.$$ $\r(p)$ is a concave function which implies that if $\r(p_1),\r(p_2)>0$, then also $\r(p)>0$, $p$ a convex combination of $p_1,p_2$. This means that the positive side is a convex set, thus equal to the convex side. @macro = @begin if (!CGAL_NTS is_zero (r())) { trivial = false; degenerate = (t()*u() == RT(2)*r()*v()); if (degenerate) { CGAL::Sign discr = (CGAL::Sign) CGAL_NTS sign(u()*u()-RT(4)*r()*w()); switch (discr) { case CGAL::NEGATIVE: empty = true; o = (CGAL::Orientation)(CGAL_NTS sign (w())); break; case CGAL::ZERO: empty = false; o = (CGAL::Orientation)(CGAL_NTS sign (r())); break; case CGAL::POSITIVE: empty = false; o = CGAL::ZERO; break; } } else { empty = false; o = (CGAL::Orientation)(-CGAL_NTS sign (r())); } } else if (!CGAL_NTS is_zero (s())) { trivial = false; degenerate = (t()*v() == RT(2)*s()*u()); if (degenerate) { CGAL::Sign discr = (CGAL::Sign) CGAL_NTS sign(v()*v()-RT(4)*s()*w()); switch (discr) { case CGAL::NEGATIVE: empty = true; o = (CGAL::Orientation)(CGAL_NTS sign (w())); break; case CGAL::ZERO: empty = false; o = (CGAL::Orientation)(CGAL_NTS sign (s())); break; case CGAL::POSITIVE: empty = false; o = CGAL::ZERO; break; } } else { empty = false; o = (CGAL::Orientation)(-CGAL_NTS sign (s())); } } else { // r=0, s=0 degenerate = true; bool uv_zero = CGAL_NTS is_zero (u()) && CGAL_NTS is_zero (v()); trivial = uv_zero && CGAL_NTS is_zero (w()); empty = uv_zero && !trivial; if (empty) o = (CGAL::Orientation)(CGAL_NTS sign (w())); else if (trivial) o = CGAL::POSITIVE; else o = CGAL::ZERO; } @end @macro = @begin if (!CGAL_NTS is_zero (r())) { trivial = false; degenerate = (t()*u() == FT(2)*r()*v()); if (degenerate) { CGAL::Sign discr = (CGAL::Sign) CGAL_NTS sign(u()*u()-FT(4)*r()*w()); switch (discr) { case CGAL::NEGATIVE: empty = true; o = (CGAL::Orientation)(CGAL_NTS sign (w())); break; case CGAL::ZERO: empty = false; o = (CGAL::Orientation)(CGAL_NTS sign (r())); break; case CGAL::POSITIVE: empty = false; o = CGAL::ZERO; break; } } else { empty = false; o = (CGAL::Orientation)(-CGAL_NTS sign (r())); } } else if (!CGAL_NTS is_zero (s())) { trivial = false; degenerate = (t()*v() == FT(2)*s()*u()); if (degenerate) { CGAL::Sign discr = (CGAL::Sign) CGAL_NTS sign(v()*v()-FT(4)*s()*w()); switch (discr) { case CGAL::NEGATIVE: empty = true; o = (CGAL::Orientation)(CGAL_NTS sign (w())); break; case CGAL::ZERO: empty = false; o = (CGAL::Orientation)(CGAL_NTS sign (s())); break; case CGAL::POSITIVE: empty = false; o = CGAL::ZERO; break; } } else { empty = false; o = (CGAL::Orientation)(-CGAL_NTS sign (s())); } } else { // r=0, s=0 degenerate = true; bool uv_zero = CGAL_NTS is_zero (u()) && CGAL_NTS is_zero (v()); trivial = uv_zero && CGAL_NTS is_zero (w()); empty = uv_zero && !trivial; if (empty) o = (CGAL::Orientation)(CGAL_NTS sign (w())); else if (trivial) o = CGAL::POSITIVE; else o = CGAL::ZERO; } @end @! --------------------------------------------------------------------------- @subsubsection{Conic evaluation} @! --------------------------------------------------------------------------- For $\C_{\r}$ given by $R=(r,s,t,u,v,w)$ and a point $p=(x,y,h)$, the homogeneous evaluation returns the value $\r(p) = rx^2+sy^2+txy+uxh+vyh+wh^2$. @macro += @begin RT evaluate (const PT& p) const { RT x, y, h; dao.get (p, x, y, h); return r()*x*x + s()*y*y + t()*x*y + u()*x*h + v()*y*h + w()*h*h; } @end The Cartesian version is obtained for $h=1$, i.e. it computes the value $\r(p) = rx^2+sy^2+txy+ux+vy+w$. @macro += @begin FT evaluate (const PT& p) const { FT x, y; dao.get (p, x, y); return r()*x*x + s()*y*y + t()*x*y + u()*x + v()*y + w(); } @end @! --------------------------------------------------------------------------- @subsection{High-level member functions} @! --------------------------------------------------------------------------- @! --------------------------------------------------------------------------- @subsubsection{Construction} @! --------------------------------------------------------------------------- The construction from the representation proceeds by first setting the vector components and then analysing the conic to obtain the derived data. @macro += @begin ConicHPA2 ( const DA& da = DA()) : dao( da) { } ConicHPA2 (RT r, RT s, RT t, RT u, RT v, RT w, const DA& da = DA()) : dao( da), _r(r), _s(s), _t(t), _u(u), _v(v), _w(w) { analyse(); } @end @macro += @begin ConicCPA2 ( const DA& da = DA()) : dao( da) { } ConicCPA2 (FT r, FT s, FT t, FT u, FT v, FT w, const DA& da = DA()) : dao( da), _r(r), _s(s), _t(t), _u(u), _v(v), _w(w) { analyse(); } @end @! --------------------------------------------------------------------------- @subsubsection{Data Accessor} @! --------------------------------------------------------------------------- @macro += @begin const DA& da() const { return dao; } @end @macro += @begin const DA& da() const { return dao; } @end @! --------------------------------------------------------------------------- @subsubsection{General access} @! --------------------------------------------------------------------------- The coordinate access is straightforward. @macro += @begin RT r() const { return _r;} RT s() const { return _s;} RT t() const { return _t;} RT u() const { return _u;} RT v() const { return _v;} RT w() const { return _w;} @end @macro += @begin FT r() const { return _r;} FT s() const { return _s;} FT t() const { return _t;} FT u() const { return _u;} FT v() const { return _v;} FT w() const { return _w;} @end To obtain the center, recall from subsection \ref{symmetry_sec} that it is given by the point \[ c = -M^{-1} \left(\begin{array}{c} u \\ v \end{array}\right) = -\frac{1}{\det(M)}\left(\begin{array}{cc} 2s & -t \\ -t & 2r \end{array}\right) \left(\begin{array}{c} u \\ v \end{array}\right) = -\frac{1}{\det(M)}\left(\begin{array}{cc} 2s\cdot u -t\cdot v \\ 2r\cdot v - t\cdot u\end{array}\right). \] In the homogeneous representation, the value $-\det(M)$ serves as the $h$-component of the center, in the Cartesian representation, we divide by it. @macro += @begin PT center () const { CGAL_optimisation_precondition (type != PARABOLA); PT p; RT two = RT(2); dao.set( p, two*s()*u() - t()*v(), two*r()*v() - t()*u(), -det()); return p; } @end @macro += @begin PT center () const { CGAL_optimisation_precondition (type != PARABOLA); PT p; FT two = FT(2); FT div = -det(); dao.set( p, (two*s()*u() - t()*v()) / div, (two*r()*v() - t()*u()) / div); return p; } @end @! --------------------------------------------------------------------------- @subsubsection{Type Related Access} @! --------------------------------------------------------------------------- Because the conic stores its type and degeneracy information, this is straightforward. @macro += @begin Conic_type conic_type () const { return type; } bool is_hyperbola () const { return (type == HYPERBOLA); } bool is_parabola () const { return (type == PARABOLA); } bool is_ellipse () const { return (type == ELLIPSE); } bool is_empty () const { return empty; } bool is_trivial () const { return trivial; } bool is_degenerate () const { return degenerate; } @end @macro += @begin Conic_type conic_type () const { return type; } bool is_hyperbola () const { return (type == HYPERBOLA); } bool is_parabola () const { return (type == PARABOLA); } bool is_ellipse () const { return (type == ELLIPSE); } bool is_empty () const { return empty; } bool is_trivial () const { return trivial; } bool is_degenerate () const { return degenerate; } @end @! --------------------------------------------------------------------------- @subsubsection{Orientation Related Access} @! --------------------------------------------------------------------------- @macro += @begin CGAL::Orientation orientation () const { return o; } @end @macro += @begin CGAL::Orientation orientation () const { return o; } @end The orientation queries just evaluate the conic at the given point, using the private method @prg{evaluate}. Recall that $p$ is in the positive resp. negative side iff $\r(p)>0$ resp. $\r(p)<0$. @macro += @begin CGAL::Oriented_side oriented_side (const PT& p) const { return (CGAL::Oriented_side)(CGAL_NTS sign (evaluate (p))); } bool has_on_positive_side (const PT& p) const { return (CGAL_NTS is_positive (evaluate(p))); } bool has_on_negative_side (const PT& p) const { return (CGAL_NTS is_negative (evaluate(p))); } bool has_on_boundary (const PT& p) const { return (CGAL_NTS is_zero (evaluate(p))); } bool has_on (const PT& p) const { return (CGAL_NTS is_zero (evaluate(p))); } @end @macro += @begin CGAL::Oriented_side oriented_side (const PT& p) const { return (CGAL::Oriented_side)(CGAL_NTS sign (evaluate (p))); } bool has_on_positive_side (const PT& p) const { return (CGAL_NTS is_positive (evaluate(p))); } bool has_on_negative_side (const PT& p) const { return (CGAL_NTS is_negative (evaluate(p))); } bool has_on_boundary (const PT& p) const { return (CGAL_NTS is_zero (evaluate(p))); } bool has_on (const PT& p) const { return (CGAL_NTS is_zero (evaluate(p))); } @end Then we have the convex side queries. Under nonzero orientation, the side is determined by a conic evaluation. If the orientation is zero, we know that the non-convex side is empty, see subsection \ref{orientation_sec}. @macro += @begin Convex_side convex_side (const PT& p) const { switch (o) { case CGAL::POSITIVE: return (Convex_side)( CGAL_NTS sign (evaluate (p))); case CGAL::NEGATIVE: return (Convex_side)(-CGAL_NTS sign (evaluate (p))); case CGAL::ZERO: return (Convex_side)( CGAL_NTS sign (CGAL_NTS abs (evaluate(p)))); } // keeps g++ happy return( Convex_side( 0)); } bool has_on_convex_side (const PT& p) const { return (convex_side (p) == ON_CONVEX_SIDE); } bool has_on_nonconvex_side (const PT& p) const { return (convex_side (p) == ON_NONCONVEX_SIDE); } @end @macro += @begin Convex_side convex_side (const PT& p) const { switch (o) { case CGAL::POSITIVE: return (Convex_side)( CGAL_NTS sign (evaluate (p))); case CGAL::NEGATIVE: return (Convex_side)(-CGAL_NTS sign (evaluate (p))); case CGAL::ZERO: return (Convex_side)( CGAL_NTS sign (CGAL_NTS abs (evaluate(p)))); } // keeps g++ happy return( Convex_side( 0)); } bool has_on_convex_side (const PT& p) const { return (convex_side (p) == ON_CONVEX_SIDE); } bool has_on_nonconvex_side (const PT& p) const { return (convex_side (p) == ON_NONCONVEX_SIDE); } @end @! --------------------------------------------------------------------------- @subsection{Comparison methods} @! --------------------------------------------------------------------------- We provide tests for equality and inequality of two conics. @macro += @begin bool operator == ( const ConicHPA2<_PT,_DA>& c) const { // find coefficient != 0 RT factor1; if ( ! CGAL_NTS is_zero( r())) factor1 = r(); else if ( ! CGAL_NTS is_zero( s())) factor1 = s(); else if ( ! CGAL_NTS is_zero( t())) factor1 = t(); else if ( ! CGAL_NTS is_zero( u())) factor1 = u(); else if ( ! CGAL_NTS is_zero( v())) factor1 = v(); else if ( ! CGAL_NTS is_zero( w())) factor1 = w(); else CGAL_optimisation_assertion_msg( false, "all coefficients zero"); // find coefficient != 0 RT factor2; if ( ! CGAL_NTS is_zero( c.r())) factor2 = c.r(); else if ( ! CGAL_NTS is_zero( c.s())) factor2 = c.s(); else if ( ! CGAL_NTS is_zero( c.t())) factor2 = c.t(); else if ( ! CGAL_NTS is_zero( c.u())) factor2 = c.u(); else if ( ! CGAL_NTS is_zero( c.v())) factor2 = c.v(); else if ( ! CGAL_NTS is_zero( c.w())) factor2 = c.w(); else CGAL_optimisation_assertion_msg( false, "all coefficients zero"); return( ( r()*factor2 == c.r()*factor1) && ( s()*factor2 == c.s()*factor1) && ( t()*factor2 == c.t()*factor1) && ( u()*factor2 == c.u()*factor1) && ( v()*factor2 == c.v()*factor1) && ( w()*factor2 == c.w()*factor1)); } @end @macro += @begin bool operator == ( const ConicCPA2<_PT,_DA>& c) const { // find coefficient != 0 FT factor1; if ( ! CGAL_NTS is_zero( r())) factor1 = r(); else if ( ! CGAL_NTS is_zero( s())) factor1 = s(); else if ( ! CGAL_NTS is_zero( t())) factor1 = t(); else if ( ! CGAL_NTS is_zero( u())) factor1 = u(); else if ( ! CGAL_NTS is_zero( v())) factor1 = v(); else if ( ! CGAL_NTS is_zero( w())) factor1 = w(); else CGAL_optimisation_assertion_msg( false, "all coefficients zero"); // find coefficient != 0 FT factor2; if ( ! CGAL_NTS is_zero( c.r())) factor2 = c.r(); else if ( ! CGAL_NTS is_zero( c.s())) factor2 = c.s(); else if ( ! CGAL_NTS is_zero( c.t())) factor2 = c.t(); else if ( ! CGAL_NTS is_zero( c.u())) factor2 = c.u(); else if ( ! CGAL_NTS is_zero( c.v())) factor2 = c.v(); else if ( ! CGAL_NTS is_zero( c.w())) factor2 = c.w(); else CGAL_optimisation_assertion_msg( false, "all coefficients zero"); return( ( r()*factor2 == c.r()*factor1) && ( s()*factor2 == c.s()*factor1) && ( t()*factor2 == c.t()*factor1) && ( u()*factor2 == c.u()*factor1) && ( v()*factor2 == c.v()*factor1) && ( w()*factor2 == c.w()*factor1)); } @end @! --------------------------------------------------------------------------- @subsubsection{Private Methods} @! --------------------------------------------------------------------------- A main difference between the public and the private set methods is that the private ones do not analyse the conic. If a conic is constructed in a sequence of calls to set functions, it is more efficient to do an analysis only once for the final result, rather than analysing any intermediate result. Therefore, the private set functions also do not allow to specify an orientation, because in order to orient the conic, an analysis would be necessary. Under this scheme, caution is in place, of course: a conic constructed from a private set method is not fully initialized, and calls to type and orientation related access functions return undefined results. @prg{friend} classes need to care for this, whenever they call a private set method. @! --------------------------------------------------------------------------- \paragraph{Linear combination of conics} ~ @! --------------------------------------------------------------------------- @macro += @begin void set_linear_combination (const RT& a1, const ConicHPA2& c1, const RT& a2, const ConicHPA2& c2) { _r = a1 * c1.r() + a2 * c2.r(); _s = a1 * c1.s() + a2 * c2.s(); _t = a1 * c1.t() + a2 * c2.t(); _u = a1 * c1.u() + a2 * c2.u(); _v = a1 * c1.v() + a2 * c2.v(); _w = a1 * c1.w() + a2 * c2.w(); } @end @macro += @begin void set_linear_combination (const FT& a1, const ConicCPA2& c1, const FT& a2, const ConicCPA2& c2) { _r = a1 * c1.r() + a2 * c2.r(); _s = a1 * c1.s() + a2 * c2.s(); _t = a1 * c1.t() + a2 * c2.t(); _u = a1 * c1.u() + a2 * c2.u(); _v = a1 * c1.v() + a2 * c2.v(); _w = a1 * c1.w() + a2 * c2.w(); } @end @! --------------------------------------------------------------------------- \paragraph{Two pairs of lines through four points} ~ @!---------------------------------------------------------------------------- Here is the method to get two line-pairs from four given points. It just brings the points into (counter)clockwise order and then calls the @prg{set_linepair} method for their two conic arguments, supplying the points in the right order. The reordering needs access to the orientations of certain point triples. For points $p_i=(x_i,y_i,h_i)$, $i=1\ldots3$, this orientation is given by the sign of the determinant $$\det \left( \begin{array}{ccc} x_1 & x_2 & x_3 \\ y_1 & y_2 & y_3 \\ h_1 & h_2 & h_3 \end{array}\right),$$ see Lemma \ref{det_lemma} below. The following macros define this determinant (the Cartesian version is obtained by setting $h_i=1,i=1\ldots 3$). @macro (3) many = @begin (CGAL::Orientation)(CGAL_NTS sign (-h@1*x@3*y@2+h@3*x@1*y@2 +h@1*x@2*y@3-h@2*x@1*y@3 +h@2*x@3*y@1-h@3*x@2*y@1)) @end @macro (3) many = @begin (CGAL::Orientation)(CGAL_NTS sign (-x@3*y@2+x@1*y@2 +x@2*y@3-x@1*y@3 +x@3*y@1-x@2*y@1)) @end @macro += @begin static void set_two_linepairs (const PT& p1, const PT& p2, const PT& p3, const PT& p4, ConicHPA2& pair1, ConicHPA2& pair2) { RT x1, y1, h1, x2, y2, h2, x3, y3, h3, x4, y4, h4; const DA& da = pair1.da(); da.get (p1, x1, y1, h1); da.get (p2, x2, y2, h2); da.get (p3, x3, y3, h3); da.get (p4, x4, y4, h4); CGAL::Orientation side1_24 = @("2", "4", "1"), side3_24 = @("2", "4", "3"); if (side1_24 != side3_24) { // (counter)clockwise order pair1.set_linepair (p1, p2, p3, p4); pair2.set_linepair (p2, p3, p4, p1); } else { CGAL::Orientation side1_32 = @("3", "2", "1"); if (side1_32 != side3_24) { // p1, p2 need to be swapped pair1.set_linepair (p2, p1, p3, p4); pair2.set_linepair (p1, p3, p4, p2); } else { // p2, p3 need to be swapped pair1.set_linepair (p1, p3, p2, p4); pair2.set_linepair (p3, p2, p4, p1); } } } @end @macro += @begin static void set_two_linepairs (const PT& p1, const PT& p2, const PT& p3, const PT& p4, ConicCPA2& pair1, ConicCPA2& pair2) { FT x1, y1, x2, y2, x3, y3, x4, y4; const DA& da = pair1.da(); da.get (p1, x1, y1); da.get (p2, x2, y2); da.get (p3, x3, y3); da.get (p4, x4, y4); CGAL::Orientation side1_24 = @("2", "4", "1"), side3_24 = @("2", "4", "3"); if (side1_24 != side3_24) { // (counter)clockwise order pair1.set_linepair (p1, p2, p3, p4); pair2.set_linepair (p2, p3, p4, p1); } else { CGAL::Orientation side1_32 = @("3", "2", "1"); if (side1_32 != side3_24) { // p1, p2 need to be swapped pair1.set_linepair (p2, p1, p3, p4); pair2.set_linepair (p1, p3, p4, p2); } else { // p2, p3 need to be swapped pair1.set_linepair (p1, p3, p2, p4); pair2.set_linepair (p3, p2, p4, p1); } } } @end @! --------------------------------------------------------------------------- \paragraph{Some ellipse from two pairs of lines} ~ @!---------------------------------------------------------------------------- Assuming that we have constructed two line-pairs $\C_{\r_1}, \C_{\r_2}$ using the method @prg{set_two_linepairs} with points $p_1,p_2,p_3,p_4$ in convex position, an ellipse through the points can be obtained as a linear combination $\C_{\r}$, $\r = \lambda \r_1 + \mu \r_2$, where \begin{eqnarray*} \lambda &=& \det({\r_2}) - 2(r_1s_2+r_2s_1) + t_1t_2, \\ \mu &=& \det({\r_1}) - 2(r_1s_2+r_2s_1) + t_1t_2, \end{eqnarray*} $r_i,\ldots,w_i$ the components of $\r_i,i=1\ldots 2$. @macro += @begin void set_ellipse (const ConicHPA2& pair1, const ConicHPA2& pair2) { RT b = RT(2) * (pair1.r() * pair2.s() + pair1.s() * pair2.r()) - pair1.t() * pair2.t(); set_linear_combination (pair2.det()-b, pair1, pair1.det()-b, pair2); } @end @macro += @begin void set_ellipse (const ConicCPA2& pair1, const ConicCPA2& pair2) { FT b = FT(2) * (pair1.r() * pair2.s() + pair1.s() * pair2.r()) - pair1.t() * pair2.t(); set_linear_combination (pair2.det()-b, pair1, pair1.det()-b, pair2); } @end @! --------------------------------------------------------------------------- \paragraph{Conic from two conics and a point} ~ @!---------------------------------------------------------------------------- If $\C_{\r_1}, \C_{\r_2}$ are two conics, $p$ some point, it is easy to see that the conic $\C_{\r}$ with $$\r = \r_2(p) \r_1 - \r_1(p) \r_2$$ is a conic containing the set $(\C_{\r_1} \cap \C_{\r_2}) \cup \{p\}.$ Exactly this conic is constructed here. @macro += @begin void set (const ConicHPA2& c1, const ConicHPA2& c2, const PT& p) { set_linear_combination (c2.evaluate(p), c1, -c1.evaluate(p), c2); } @end @macro += @begin void set (const ConicCPA2& c1, const ConicCPA2& c2, const PT& p) { set_linear_combination (c2.evaluate(p), c1, -c1.evaluate(p), c2); } @end @!---------------------------------------------------------------------------- \paragraph{Volume derivative of an ellipse} ~ @!---------------------------------------------------------------------------- Let $r(\tau),s(\tau), t(\tau), u(\tau), v(\tau), w(\tau)$ denote the parameters of $\E(\tau)$. Omitting the parameter $\tau$ for the sake of readability, we have \begin{eqnarray*} r &=& r_0 + \tau \d r, \\ s &=& s_0 + \tau \d s, \\ t &=& t_0 + \tau \d t, \\ u &=& u_0 + \tau \d u, \\ v &=& v_0 + \tau \d v, \\ w &=& w_0 + \tau \d w, \end{eqnarray*} $r_0,\ldots,w_0$ the representation of $\E$, $\d r,\ldots, \d w$ the given values $\d\r$. Recall that $$\Vol(\E(\tau)) = \pi / \sqrt{\det(M / (2w - c^TMc))}.$$ This implies \[ \sgn\left( \frac{\d}{\d \tau} \left. \Vol(\E(\tau))\right|_{\tau=0}\right) = -\sgn \left(\frac{\d}{\d \tau} \left. \det(M / (2w - c^TMc)) \right|_{\tau=0}\right). \] We have $\det(M / (2w - c^TMc)) = d / z^2$, where \begin{eqnarray*} d &=& \det(M) = 4rs -t^2, \\ z &=& 2w - c^TMc = 2 \left(w - \frac{1}{d} (u^2 s - uvt + v^2r)\right), \end{eqnarray*} see also (\ref{z}). Hence $$\frac{d}{z^2} = \frac{d^3}{4q^2}, \quad q = dw - u^2 s + uvt - v^2r.$$ It follows that \[ \sgn\left(\frac{\d}{\d \tau} \Vol(\E(\tau))\right) = -\sgn\left(\frac{d^2(3d'q-2dq')}{4q^3}\right) = -\sgn \left( 3d'q-2dq'\right) \sgn (q). \] Consider the term $3d'q-2dq'=0$. We have \begin{eqnarray*} d &=& a_2\tau^2 + a_1\tau + a_0, \\ q &=& b_3\tau^3 + b_2\tau^2 + b_1\tau + b, \end{eqnarray*} where \begin{eqnarray*} a_2 &=& 4\d r\d s - \d t^2, \\ a_1 &=& 4r_0\d s + 4s_0\d r - 2t_0\d t, \\ a_0 &=& 4r_0s_0-t_0^2, \\ b_3 &=& (4\d r\d s - \d t^2)\d w - \d u^2 \d s - \d v^2 \d r+\d t \d u \d v, \\ b_2 &=& (4r_0\d s+4\d rs_0-2t_0\d t)\d w+(4\d r\d s-\d t^2)w_0-2u_0\d u\d s \\ & & -\d u^2s_0-2 v_0\d v\d r-\d v^2r_0+(u_0\d v+\d uv_0)\d t+\d u\d vt_0, \\ b_1 &=& (4r_0s_0-t_0^2)\d w+(4r_0\d s+4\d rs_0-2t_0\d t)w_0-u_0^2\d s \\ & & -2u_0\d u s_0-v_0^2\d r-2v_0\d vr_0+u_0v_0\d t+(u_0\d v+\d uv_0)t_0, \\ b_0 &=& (4r_0s_0-t_0^2)w_0-u_0^2s_0-v_0^2r_0+u_0v_0t_0 \end {eqnarray*} Furthermore, $$3d'q-2dq' = c_3\tau^3 + c_2\tau^2 + c_1\tau + c_0,$$ with \begin{eqnarray*} c_3 &=& -3a_1b_3 + 2a_2b_2, \\ c_2 &=& -6a_0b_3 - a_1b_2 + 4a_2b_1, \\ c_1 &=& -4a_0b_2 + a_1b_1 + 6a_2b_0, \\ c_0 &=& -2a_0b_1 + 3a_1b_0. \end{eqnarray*} The desired sign of the volume derivative is obtained by evaluating the expression $$-\sgn(3d'q-2dq')\sgn(q)$$ at $\tau=0$, where we get $3d'q-2dq' = c_0.$ Moreover, since $q = \det(M)(2w-c^TMc)/2$ with $\det(M)>0$, the sign of $q$ is positive if and only if $2w-c^TMc$ is positive, equivalently if the ellipse $\E(0)$ is in positive orientation (see also the description of the method @prg{analyse}). This means, in case of positive orientation, the sign of $-\sgn(3d'q-2dq')\sgn(q)$ equals the sign of $-c_0$, otherwise, we return the sign of $c_0$. To compute $c_0$, we only need the values $a_1,a_0,b_1,b_0$ from above. @macro += @begin CGAL::Sign vol_derivative (RT dr, RT ds, RT dt, RT du, RT dv, RT dw) const { RT a1 = RT(4)*r()*ds+RT(4)*dr*s()-RT(2)*t()*dt, a0 = RT(4)*r()*s()-t()*t(), b1 = (RT(4)*r()*s()-t()*t())*dw+(RT(4)*r()*ds+RT(4)*dr*s()- RT(2)*t()*dt)*w()-u()*u()*ds - RT(2)*u()*du*s()-v()*v()*dr-RT(2)*v()*dv*r()+u()*v()*dt+ (u()*dv+du*v())*t(), b0 = (RT(4)*r()*s()-t()*t())*w() -u()*u()*s()-v()*v()*r()+u()*v()*t(), c0 = -RT(2)*a0*b1 + RT(3)*a1*b0; return CGAL::Sign (-CGAL_NTS sign (c0)*o); } @end @macro += @begin CGAL::Sign vol_derivative (FT dr, FT ds, FT dt, FT du, FT dv, FT dw) const { FT a1 = FT(4)*r()*ds+FT(4)*dr*s()-FT(2)*t()*dt, a0 = FT(4)*r()*s()-t()*t(), b1 = (FT(4)*r()*s()-t()*t())*dw+(FT(4)*r()*ds+FT(4)*dr*s()- FT(2)*t()*dt)*w()-u()*u()*ds - FT(2)*u()*du*s()-v()*v()*dr-FT(2)*v()*dv*r()+u()*v()*dt+ (u()*dv+du*v())*t(), b0 = (FT(4)*r()*s()-t()*t())*w() -u()*u()*s()-v()*v()*r()+u()*v()*t(), c0 = -FT(2)*a0*b1 + FT(3)*a1*b0; return CGAL::Sign (-CGAL_NTS sign (c0)*o); } @end To find the value $\tau^*$ such that $E(\tau^*)$ is the ellipse of smallest volume, we apply the Cardano formula to find the roots of the polynomial $p(\tau)=c_3\tau^3 + c_2\tau^2 + c_1\tau + c_0 = 0$. This is done by the function @prg{solve_cubic} which returns all (at most three) real roots. We then select the one which leads to largest (positive) volume. Here is an outline of the Cardano formula, for the polynomial $p(\tau)$, assuming that $c_3\neq 0$. \begin{enumerate} \item Divide by $c_3$ to normalize the equation, leading to $$p(\tau) = \tau^3 + \gamma_2 \tau^2 + \gamma_1 \tau + \gamma_0,\quad \gamma_i := c_i/c_3, ~i=0,\ldots,2.$$ \item Eliminate the quadratic term by substituting $\tau := x - \gamma_2/3$. This leads to $$p(x) = x^3 + a x + b, \quad a = \gamma_1 - \frac{\gamma_2^2}{3},~ b = \frac{2}{27}\gamma_2^3 - \frac{1}{3}\gamma_1\gamma_2 + \gamma_0.$$ If $a=0$, $p$ has only one real root, namely $x_1 = \sqrt[3]{-b}$, so let's assume $a\neq 0$. \item Define $$D := (a/3)^3 + (b/2)^2$$ and let $u$ be any number such that $$u^3 = -b/2 + \sqrt{D}.$$ Note that $u^3$ is a solution of the quadratic equation $$x^2 + bx - \left(\frac{a}{3}\right)^3 = 0.$$ This means, if $a\neq 0$, then also $u\neq 0$. If the discriminant $D$ is negative, $u$ is a complex number, otherwise it must be chosen as the real number $$u := \sqrt[3]{-b/2 + \sqrt{D}}.$$ \item Let $u$ be of the form $u = u_R - u_I \I$ (we possibly have $u_I=0$). $p$ has always a real root, given by $$x_1 = u_R \left(1 - \frac{a}{3\|u\|^2}\right).$$ \item If $D > 0$, the two other roots are complex. If $D \leq 0$, $p$ has two more real roots, given by \begin{eqnarray*} x_2 & = & -\frac{1}{2}\left(1 - \frac{a}{3\|u\|^2}\right)(u_R-u_I\sqrt{3}),\\ x_3 & = & -\frac{1}{2}\left(1 - \frac{a}{3\|u\|^2}\right)(u_R+u_I\sqrt{3}). \end{eqnarray*} If $D=0$, we have $u_I=0$ and these two roots coincide. \end{enumerate} It follows that if $D\geq 0$, the roots of $p$ can be found by using only $\sqrt{~}$ and $\sqrt[3]{~}$ operations over the real numbers. If $D<0$, it can be shown that this is not possible, and we need to approximate the values $u_R,u_I$ in some other way. For this, we need to solve the equation $$u^3 = C := -b/2 + i\sqrt{-D}$$ for $u$. Expressing $C$ in polar coordinates $(r,\phi)$ we get \begin{eqnarray*} r &=& \|C\| = \sqrt{b^2/4 -D} = \sqrt{-(a/3)^3}, \\ \cos \phi &=& -\frac{b/2}{\|C\|}, \end{eqnarray*} where $0\leq \phi < \pi$ because of $\sqrt{-D}>0$. Therefore, a possible choice for $u$ is $u=(r', \phi')$ with \begin{eqnarray*} r' &=& \sqrt{-a/3}, \\ \phi' &=& \acos \left(-\frac{b/2}{\|C\|}\right)/3. \end{eqnarray*} This gives \begin{eqnarray*} u_R &=& r' \cos \phi', \\ u_I &=& r' \sin \phi', \end{eqnarray*} Note that this implies $\|u\|^2 = -a/3$, showing that the roots of $p(x)$ assume the form \begin{eqnarray*} x_1 &=& 2u_R, \\ x_2 &=& -(u_R -u_I\sqrt{3}),\\ x_3 &=& -(u_R +u_I\sqrt{3}). \end{eqnarray*} If $u^3$ is real, however, this is not true, and we really need to evaluate the factor $$\alpha := \left(1 - \frac{a}{3\|u\|^2}\right) = \left(1 - \frac{a}{3u^2}\right)$$ in this case to obtain the roots of $p(x)$. In any case, we originally wanted to have the roots of $p(\tau)$. Using the substitution formula $\tau=x-\gamma_2/3$, these roots are given by $\tau_i = x_i -\gamma_2/3, i=1,\ldots,3$. The function @prg{solve_cubic} returns the number of distinct real roots of $p(\tau) = c_3\tau^3+c_2\tau^2+c_1\tau+c_0=0$ and stores them consecutively in @prg{r1}, @prg{r2} and @prg{r3}. Precondition is that $p(\tau)$ is not a constant function. @macro zero = @begin template < class NT > int solve_cubic (NT c3, NT c2, NT c1, NT c0, NT& r1, NT& r2, NT& r3) { if (c3 == 0.0) { // quadratic equation if (c2 == 0) { // linear equation CGAL_optimisation_precondition (c1 != 0); r1 = -c0/c1; return 1; } NT D = c1*c1-4*c2*c0; if (D < 0.0) // only complex roots return 0; if (D == 0.0) { // one real root r1 = -c1/(2.0*c2); return 1; } // two real roots r1 = (-c1 + CGAL::sqrt(D))/(2.0*c2); r2 = (-c1 - CGAL::sqrt(D))/(2.0*c2); return 2; } // cubic equation // define the gamma_i NT g2 = c2/c3, g1 = c1/c3, g0 = c0/c3; // define a, b NT a = g1 - g2*g2/3.0, b = 2.0*g2*g2*g2/27.0 - g1*g2/3.0 + g0; if (a == 0) { // one real root /***** r1 = cbrt(-b) - g2/3.0; *****/ r1 = exp(log(-b)/3.0) - g2/3.0; return 1; } // define D NT D = a*a*a/27.0 + b*b/4.0; if (D >= 0.0) { // real case /***** NT u = cbrt(-b/2.0 + CGAL::sqrt(D)), *****/ NT u = exp(log(-b/2.0 + CGAL::sqrt(D))), alpha = 1.0 - a/(3.0*u*u); if (D == 0) { // two distinct real roots r1 = u*alpha - g2/3.0; r2 = -0.5*alpha*u - g2/3.0; return 2; } // one real root r1 = u*alpha - g2/3.0; return 1; } // complex case NT r_prime = CGAL::sqrt(-a/3), phi_prime = acos (-b/(2.0*r_prime*r_prime*r_prime))/3.0, u_R = r_prime * cos (phi_prime), u_I = r_prime * sin (phi_prime); // three distinct real roots r1 = 2.0*u_R - g2/3.0; r2 = -u_R + u_I*CGAL::sqrt(3.0) - g2/3.0; r3 = -u_R - u_I*CGAL::sqrt(3.0) - g2/3.0; return 3; } @end Here comes the actual computation of the volume minimum. To this end, we compute the coefficients $c_3,c_2,c_1,c_0$ and find the roots of $p(\tau)$. Among the roots we then select the one which leads to the smallest volume, equivalently to the largest value of $\det(M/(2w-c^TMc))=d^3/4q^2$. The coefficients of $d$ and $q$ have been computed before in order to find the roots, so we can directly evaluate the determinant, using @prg{double} approximations of the coefficients. @macro += @begin double vol_minimum (RT dr, RT ds, RT dt, RT du, RT dv, RT dw) const { RT a2 = RT(4)*dr*ds-dt*dt, a1 = RT(4)*r()*ds+RT(4)*dr*s()-RT(2)*t()*dt, a0 = RT(4)*r()*s()-t()*t(), b3 = (RT(4)*dr*ds-dt*dt)*dw-du*du*ds-dv*dv*dr+du*dv*dt, b2 = (RT(4)*r()*ds+RT(4)*dr*s()-RT(2)*t()*dt)*dw+ (RT(4)*dr*ds-dt*dt)*w()-RT(2)*u()*du*ds-du*du*s()- RT(2)*v()*dv*dr-dv*dv*r()+(u()*dv+du*v())*dt+du*dv*t(), b1 = (RT(4)*r()*s()-t()*t())*dw+(RT(4)*r()*ds+RT(4)*dr*s()- RT(2)*t()*dt)*w()-u()*u()*ds - RT(2)*u()*du*s()-v()*v()*dr-RT(2)*v()*dv*r()+u()*v()*dt+ (u()*dv+du*v())*t(), b0 = (RT(4)*r()*s()-t()*t())*w() -u()*u()*s()-v()*v()*r()+u()*v()*t(), c3 = -RT(3)*a1*b3 + RT(2)*a2*b2, c2 = -RT(6)*a0*b3 - a1*b2 + RT(4)*a2*b1, c1 = -RT(4)*a0*b2 + a1*b1 + RT(6)*a2*b0, c0 = -RT(2)*a0*b1 + RT(3)*a1*b0; if ( CGAL_NTS is_zero( c0)) return 0;// E(0) is the smallest ellipse double roots[3]; int nr_roots = solve_cubic (CGAL::to_double(c3), CGAL::to_double(c2), CGAL::to_double(c1), CGAL::to_double(c0), roots[0], roots[1], roots[2]); CGAL_optimisation_precondition (nr_roots > 0); // minimum exists return best_value (roots, nr_roots, CGAL::to_double(a2), CGAL::to_double(a1), CGAL::to_double(a0), CGAL::to_double(b3), CGAL::to_double(b2), CGAL::to_double(b1), CGAL::to_double(b0)); } @end @macro += @begin double vol_minimum (FT dr, FT ds, FT dt, FT du, FT dv, FT dw) const { FT a2 = FT(4)*dr*ds-dt*dt, a1 = FT(4)*r()*ds+FT(4)*dr*s()-FT(2)*t()*dt, a0 = FT(4)*r()*s()-t()*t(), b3 = (FT(4)*dr*ds-dt*dt)*dw-du*du*ds-dv*dv*dr+du*dv*dt, b2 = (FT(4)*r()*ds+FT(4)*dr*s()-FT(2)*t()*dt)*dw+ (FT(4)*dr*ds-dt*dt)*w()-FT(2)*u()*du*ds-du*du*s()- FT(2)*v()*dv*dr-dv*dv*r()+(u()*dv+du*v())*dt+du*dv*t(), b1 = (FT(4)*r()*s()-t()*t())*dw+(FT(4)*r()*ds+FT(4)*dr*s()- FT(2)*t()*dt)*w()-u()*u()*ds - FT(2)*u()*du*s()-v()*v()*dr-FT(2)*v()*dv*r()+u()*v()*dt+ (u()*dv+du*v())*t(), b0 = (FT(4)*r()*s()-t()*t())*w() -u()*u()*s()-v()*v()*r()+u()*v()*t(), c3 = -FT(3)*a1*b3 + FT(2)*a2*b2, c2 = -FT(6)*a0*b3 - a1*b2 + FT(4)*a2*b1, c1 = -FT(4)*a0*b2 + a1*b1 + FT(6)*a2*b0, c0 = -FT(2)*a0*b1 + FT(3)*a1*b0; if ( CGAL_NTS is_zero( c0)) return 0;// E(0) is the smallest ellipse double roots[3]; int nr_roots = solve_cubic (CGAL::to_double(c3), CGAL::to_double(c2), CGAL::to_double(c1), CGAL::to_double(c0), roots[0], roots[1], roots[2]); CGAL_optimisation_precondition (nr_roots > 0); // minimum exists return best_value (roots, nr_roots, CGAL::to_double(a2), CGAL::to_double(a1), CGAL::to_double(a0), CGAL::to_double(b3), CGAL::to_double(b2), CGAL::to_double(b1), CGAL::to_double(b0)); } @end The function @prg{best_root} returns the value in its argument array which leads to the largest determinant $d^3/q^2$. A precondition was that an ellipse of smallest volume exists, so the largest determinant must be positive. @macro = @begin template < class NT > NT best_value (NT *values, int nr_values, NT a2, NT a1, NT a0, NT b3, NT b2, NT b1, NT b0) { bool det_positive = false; NT d, q, max_det = 0, det, best = -1; for (int i=0; i 0.0) if (!det_positive || (det > max_det)) { max_det = det; best = x; det_positive = true; } } CGAL_optimisation_precondition (det_positive); return best; } @end @! --------------------------------------------------------------------------- @subsubsection{Public Set Methods} @! --------------------------------------------------------------------------- Here is the set method at coordinate level. @macro += @begin void set (RT r_, RT s_, RT t_, RT u_, RT v_, RT w_) { _r = r_; _s = s_; _t = t_; _u = u_; _v = v_; _w = w_; analyse(); } @end @macro += @begin void set (FT r_, FT s_, FT t_, FT u_, FT v_, FT w_) { _r = r_; _s = s_; _t = t_; _u = u_; _v = v_; _w = w_; analyse(); } @end @! --------------------------------------------------------------------------- \paragraph{Opposite Conic} ~ @! --------------------------------------------------------------------------- The method @prg{set_opposite} just flips the representation $\r$ and the orientation, all other derived data are taken over. @macro += @begin void set_opposite () { _r = -r(); _s = -s(); _t = -t(); _u = -u(); _v = -v(); _w = -w(); o = CGAL::opposite(orientation()); } @end @macro += @begin void set_opposite () { _r = -r(); _s = -s(); _t = -t(); _u = -u(); _v = -v(); _w = -w(); o = CGAL::opposite(orientation()); } @end @! --------------------------------------------------------------------------- \paragraph{Pair of lines through four points} ~ @! --------------------------------------------------------------------------- Given $p_1 = (x_1, y_1, h_1)$, $p_2 = (x_2, y_2, h_2)$, $p_3 = (x_3, y_3, h_3)$, $p_4 = (x_4, y_4, h_4)$, we develop a formula for representing the pair of lines $\overline{p_1p_2}$, $\overline{p_3p_4}$ (forming a degenerate hyperbola) in the form of (\ref{conic_def_hom}). To this end, let $p=(x,y,h)$ be any point and define \begin{equation} \label{orientation_det} [p_i, p_j, p] := \det \left( \begin{array}{ccc} x_i & x_j & x \\ y_1 & y_2 & y \\ h_1 & h_2 & h \end{array}\right). \end{equation} It is well known that $[p_i,p_j,p]$ records the orientation of the point triple: let $\ell$ be the oriented line through $p_i$ and $p_j$. Then the following holds. \begin{Lemma} \label{det_lemma} \[ p \mbox{~lies} \left\{ \begin{array}{c} \mbox{to the left of} \\ \mbox{on} \\ \mbox{to the right of} \end{array} \right\} \ell \Leftrightarrow [p_i,p_j,p] \left\{\begin{array}{c} >0 \\ = 0 \\ <0 \end{array}\right\}. \] \end{Lemma} In particular, $p$ lies on $\overline{p_1p_2} \cup \overline{p_3p_4}$ iff $[p_1,p_2,p][p_3,p_4,p]=0$, and this expression turns out to be of the form (\ref{conic_def_hom}), where @prg{Maple} gives us the concrete values of $r,s,t,u,v,w$. Note that we must have $p_1\neq p_2$ and $p_3\neq p_4$ to obtain reasonable results (and this was a precondition). \begin{eqnarray*} r &=& (y_1h_2-h_1y_2)(y_3h_4-h_3y_4), \\ s &=& (h_1x_2-x_1h_2)(h_3x_4-x_3h_4), \\ t &=& (h_1x_2-x_1h_2)(y_3h_4-h_3y_4)+(y_1h_2-h_1y_2)(h_3x_4-x_3h_4), \\ u &=& (-y_1x_2+x_1y_2)(y_3h_4-h_3y_4)+(y_1h_2-h_1y_2)(-y_3x_4+x_3y_4), \\ v &=& (-y_1x_2+x_1y_2)(h_3x_4-x_3h_4)+(h_1x_2-x_1h_2)(-y_3x_4+x_3y_4), \\ w &=& (-y_1x_2+x_1y_2)(-y_3x_4+x_3y_4). \end{eqnarray*} @macro += @begin void set_linepair (const PT& p1, const PT& p2, const PT& p3, const PT& p4, const DA& da = DA()) { RT x1, y1, h1, x2, y2, h2, x3, y3, h3, x4, y4, h4; da.get (p1, x1, y1, h1); da.get (p2, x2, y2, h2); da.get (p3, x3, y3, h3); da.get (p4, x4, y4, h4); // precondition: p1 != p2, p3 != p4 CGAL_optimisation_precondition ( ((x1*h2 != x2*h1) || (y1*h2 != y2*h1)) && ((x3*h4 != x4*h3) || (y3*h4 != y4*h3)) ); RT h1x2_x1h2 = h1*x2-x1*h2; RT h3x4_x3h4 = h3*x4-x3*h4; RT y1h2_h1y2 = y1*h2-h1*y2; RT y3h4_h3y4 = y3*h4-h3*y4; RT x1y2_y1x2 = x1*y2-y1*x2; RT x3y4_y3x4 = x3*y4-y3*x4; _r = y1h2_h1y2 * y3h4_h3y4; _s = h1x2_x1h2 * h3x4_x3h4; _t = h1x2_x1h2 * y3h4_h3y4 + y1h2_h1y2 * h3x4_x3h4; _u = x1y2_y1x2 * y3h4_h3y4 + y1h2_h1y2 * x3y4_y3x4; _v = x1y2_y1x2 * h3x4_x3h4 + h1x2_x1h2 * x3y4_y3x4; _w = x1y2_y1x2 * x3y4_y3x4; analyse(); } @end For the Cartesian representation we proceed completely similar, replacing values $h_1,\ldots,h_4$ by 1. @macro += @begin void set_linepair (const PT& p1, const PT& p2, const PT& p3, const PT& p4) { FT x1, y1, x2, y2, x3, y3, x4, y4; dao.get (p1, x1, y1); dao.get (p2, x2, y2); dao.get (p3, x3, y3); dao.get (p4, x4, y4); // precondition: p1 != p2, p3 != p4 CGAL_optimisation_precondition ( ((x1 != x2) || (y1 != y2)) && ((x3 != x4) || (y3 != y4)) ); FT x2_x1 = x2-x1; FT x4_x3 = x4-x3; FT y1_y2 = y1-y2; FT y3_y4 = y3-y4; FT x1y2_y1x2 = x1*y2-y1*x2; FT x3y4_y3x4 = x3*y4-y3*x4; _r = y1_y2 * y3_y4; _s = x2_x1 * x4_x3; _t = x2_x1 * y3_y4 + y1_y2 * x4_x3; _u = x1y2_y1x2 * y3_y4 + y1_y2 * x3y4_y3x4; _v = x1y2_y1x2 * x4_x3 + x2_x1 * x3y4_y3x4; _w = x1y2_y1x2 * x3y4_y3x4; analyse(); } @end @! --------------------------------------------------------------------------- \paragraph{Smallest ellipse through three points} ~ @! --------------------------------------------------------------------------- Given $p_1 = (x_1, y_1, h_1)$, $p_2 = (x_2, y_2, h_2)$, $p_3 = (x_3, y_3, h_3)$, we give a formula for representing the ellipse of smallest volume containing $p_1,p_2,p_3$ in the form of (\ref{conic_def_hom}). For this, we use the following well-known formula for this ellipse in case of Cartesian points. \begin{Lemma} Let $q_1,q_2,q_3$ be non-collinear Cartesian points. Then the smallest ellipse containing $q_1,q_2,q_3$ can be written as the set of points $q=(x,y)$ satisfying \begin{equation} \label{ellipse_center_form} (q-c)^T M (q-c) = 1, \end{equation} where $$c = \frac{1}{3}\sum_{i=1}^3q_i, \quad M^{-1} = \frac{2}{3} \sum_{i=1}^3 (q_i-c)(q_i-c)^T.$$ \end{Lemma} To apply this Lemma to points $(p_1,p_2,p_3)$, we define $q_i = (x_i/h_i, y_i/h_i)$, $i=1,\ldots,3$ and observe that (\ref{ellipse_center_form}) can be written as \[ q^T M q - 2q^TMc + c^TMc - 1 = 0, \] from which a representation in form of (\ref{conic_cart}) is obtained via \[ \left( \begin{array}{cc} 2r & t \\ t & 2s \end{array}\right) := M, \quad \left(\begin{array}{c} u \\ v \end{array}\right) := -Mc, \quad 2w := c^TMc - 1. \] Using @prg{Maple}, we get the following values for $\r=(r,s,t,u,v,w)$. \begin{eqnarray*} r &=& 3(y_1^2h_2^2h_3^2-y_1h_2h_3^2y_2h_1-y_1h_2^2h_3y_3h_1+y_2^2 h_1^2h_3^2-y_2h_1^2h_3y_3h_2+y_3^2h_1^2h_2^2) / d, \\ s &=& 3(x_1^2h_2^2h_3^2-x_1h_2h_3^2x_2h_1-x_1h_2^2h_3x_3h_1+x_2^2 h_1^2h_3^2-x_2h_1^2h_3x_3h_2+x_3^2h_1^2h_2^2) / d, \\ t &=& 3(-2x_1h_2^2h_3^2y_1+x_1h_2h_3^2y_2h_1+x_1h_2^2h_3y_3h_1+ x_2h_1h_3^2y_1h_2\\ &~&-2x_2h_1^2h_3^2y_2+x_2h_1^2h_3y_3h_2+x_3h_1h_2^2 y_1h_3+x_3h_1^2h_2y_2h_3-2x_3h_1^2h_2^2y_3) / d, \\ u &=& -3(y_2^2h_1^2h_3x_3-y_2h_1^2h_3y_3x_2-y_2h_1^2y_3h_2x_3+ y_3^2h_1h_2^2x_1+y_3^2h_1^2h_2x_2+y_1^2h_2h_3^2x_2\\ &~& +y_1^2h_2^2h_3x_3-y_1 h_2h_3^2y_2x_1-y_1h_3^2y_2h_1x_2-y_1h_2^2h_3y_3x_1-y_1h_2^2y_3 h_1x_3+y_2^2h_1h_3^2x_1) / d, \\ v &=& -3(-x_1h_2h_3^2y_1x_2-x_1h_2^2h_3y_1x_3+x_1^2h_2h_3^2y_2-x_1 h_3^2y_2h_1x_2+x_1^2h_2^2h_3y_3-x_1h_2^2y_3h_1x_3\\ &~&+x_2^2h_1 h_3^2y_1-x_2h_1^2h_3y_2x_3+x_2^2h_1^2h_3y_3-x_2h_1^2y_3h_2x_3+x_3^2h_1 h_2^2y_1+x_3^2h_1^2h_2y_2) / d, \\ w &=& 3(x_2h_3x_3h_2y_1^2-x_1h_2h_3x_3y_1y_2-x_2h_1h_3x_3y_1y_2+ x_3^2h_1h_2y_1y_2-x_1h_2h_3x_2y_1y_3+x_2^2h_1h_3y_1y_3\\ &~&-x_2h_1x_3 h_2y_1y_3+x_1h_3x_3h_1y_2^2+x_1^2h_2h_3y_2y_3-x_1h_3x_2h_1y_2 y_3-x_1h_2x_3h_1y_2y_3+x_1h_2x_2h_1y_3^2) / d, \end{eqnarray*} where $\delta = \left(\det \left( \begin{array}{ccc} x_1 & x_2 & x_3 \\ y_1 & y_2 & y_3 \\ h_1 & h_2 & h_3 \end{array}\right)\right)^2 = (-h_1x_3y_2+x_1y_2h_3+h_1x_2y_3-x_1y_3h_2+h_2x_3y_1-x_2y_1h_3)^2.$ After pre-computing the values \[ x_i^2, y_i^2, x_ih_i, y_ih_i, h_i^2, \] for $i=1,\ldots,3$, the components of the vector $\delta\r/3$ are easy to obtain. Note that this vector is a legal representation of the ellipse if $\delta\neq 0$. This is the case if and only if $p_1,p_2,p_3$ are non-collinear, see Lemma \ref{det_lemma}. Moreover, one can show that the formulas above determine an ellipse of negative orientation, regardless of the point triple orientation. This means, if the orientation was positive, we still need to flip the representation. @macro += @begin void set_ellipse (const PT& p1, const PT& p2, const PT& p3) { RT x1, y1, h1, x2, y2, h2, x3, y3, h3; dao.get (p1, x1, y1, h1); dao.get (p2, x2, y2, h2); dao.get (p3, x3, y3, h3); // precondition: p1, p2, p3 not collinear RT det = -h1*x3*y2+h3*x1*y2+h1*x2*y3-h2*x1*y3+h2*x3*y1-h3*x2*y1; CGAL_optimisation_precondition (!CGAL_NTS is_zero (det)); RT x1x1 = x1*x1, y1y1 = y1*y1, x2x2 = x2*x2, y2y2 = y2*y2, x3x3 = x3*x3, y3y3 = y3*y3, // x_i^2, y_i^2 x1h1 = x1*h1, y1h1 = y1*h1, x2h2 = x2*h2, y2h2 = y2*h2, x3h3 = x3*h3, y3h3 = y3*h3, // x_i h_i, y_i h_i h1h1 = h1*h1, h2h2 = h2*h2, h3h3 = h3*h3, // h_i^2 two = RT(2); // 2 _r = y1y1*h2h2*h3h3 - y1h1*y2h2*h3h3 - y1h1*h2h2*y3h3 + h1h1*y2y2*h3h3 - h1h1*y2h2*y3h3 + h1h1*h2h2*y3y3; _s = x1x1*h2h2*h3h3 - x1h1*x2h2*h3h3 - x1h1*h2h2*x3h3 + h1h1*x2x2*h3h3 - h1h1*x2h2*x3h3 + h1h1*h2h2*x3x3; _t = -two*x1*y1*h2h2*h3h3 + x1h1*y2h2*h3h3 + x1h1*h2h2*y3h3 + y1h1*x2h2*h3h3 -two*h1h1*x2*y2*h3h3 + h1h1*x2h2*y3h3 + y1h1*h2h2*x3h3 + h1h1*y2h2*x3h3 -two*h1h1*h2h2*x3*y3; _u = -(h1h1*y2y2*x3h3 - h1h1*x2*y2*y3h3 - h1h1*y2h2*x3*y3 + x1h1*h2h2*y3y3 + h1h1*x2h2*y3y3 +y1y1*x2h2*h3h3 + y1y1*h2h2*x3h3 - x1*y1*y2h2*h3h3 - y1h1*x2*y2*h3h3 - x1*y1*h2h2*y3h3 - y1h1*h2h2*x3*y3 + x1h1*y2y2*h3h3); _v = -(h1h1*x2x2*y3h3 - h1h1*x2*y2*x3h3 - h1h1*x2h2*x3*y3 + y1h1*h2h2*x3x3 + h1h1*y2h2*x3x3 +x1x1*y2h2*h3h3 + x1x1*h2h2*y3h3 - x1*y1*x2h2*h3h3 - x1h1*x2*y2*h3h3 - x1*y1*h2h2*x3h3 - x1h1*h2h2*x3*y3 + y1h1*x2x2*h3h3); _w = y1y1*x2h2*x3h3 - x1*y1*y2h2*x3h3 - y1h1*x2*y2*x3h3 + y1h1*y2h2*x3x3 - x1*y1*x2h2*y3h3 + y1h1*x2x2*y3h3 - y1h1*x2h2*x3*y3 + x1h1*y2y2*x3h3 + x1x1*y2h2*y3h3 - x1h1*x2*y2*y3h3 - x1h1*y2h2*x3*y3 + x1h1*x2h2*y3y3; type = ELLIPSE; degenerate = trivial = empty = false; o = CGAL::NEGATIVE; if (CGAL_NTS is_positive (det)) set_opposite (); } @end As before, the Cartesian version is obtained by setting $h_1,h_2,h_3$ to 1. @macro += @begin void set_ellipse (const PT& p1, const PT& p2, const PT& p3) { FT x1, y1, x2, y2, x3, y3; dao.get (p1, x1, y1); dao.get (p2, x2, y2); dao.get (p3, x3, y3); // precondition: p1, p2, p3 not collinear FT det = -x3*y2+x1*y2+x2*y3-x1*y3+x3*y1-x2*y1; CGAL_optimisation_precondition (!CGAL_NTS is_zero (det)); FT x1x1 = x1*x1, y1y1 = y1*y1, x2x2 = x2*x2, y2y2 = y2*y2, x3x3 = x3*x3, y3y3 = y3*y3, // x_i^2, y_i^2 two = FT(2); _r = y1y1 - y1*y2 - y1*y3 + y2y2 - y2*y3 + y3y3; _s = x1x1 - x1*x2 - x1*x3 + x2x2 - x2*x3 + x3x3; _t = -two*x1*y1 + x1*y2 + x1*y3 + y1*x2 -two*x2*y2 + x2*y3 + y1*x3 + y2*x3 -two*x3*y3; _u = -(y2y2*x3 - x2*y2*y3 - y2*x3*y3 + x1*y3y3 + x2*y3y3 + y1y1*x2 + y1y1*x3 - x1*y1*y2 - y1*x2*y2 - x1*y1*y3 - y1*x3*y3 + x1*y2y2); _v = -(x2x2*y3 - x2*y2*x3 - x2*x3*y3 + y1*x3x3 + y2*x3x3 + x1x1*y2 + x1x1*y3 - x1*y1*x2 - x1*x2*y2 - x1*y1*x3 - x1*x3*y3 + y1*x2x2); _w = y1y1*x2*x3 - x1*y1*y2*x3 - y1*x2*y2*x3 + y1*y2*x3x3 - x1*y1*x2*y3 + y1*x2x2*y3 - y1*x2*x3*y3 + x1*y2y2*x3 + x1x1*y2*y3 - x1*x2*y2*y3 - x1*y2*x3*y3 + x1*x2*y3y3; type = ELLIPSE; degenerate = trivial = empty = false; o = CGAL::NEGATIVE; if (CGAL_NTS is_positive (det)) set_opposite(); } @end @! --------------------------------------------------------------------------- \paragraph{Some ellipse through four points in convex position} ~ @! --------------------------------------------------------------------------- This method builds on the private method to obtain an ellipse from two pairs of lines through the four points. For constructing this pair, we also have a method available. @macro += @begin void set_ellipse (const PT& p1, const PT& p2, const PT& p3, const PT& p4, CGAL::Orientation _o = POSITIVE) { ConicHPA2 pair1, pair2; set_two_linepairs (p1, p2, p3, p4, pair1, pair2); set_ellipse (pair1, pair2); analyse(); if (o != _o) set_opposite(); } @end @macro += @begin void set_ellipse (const PT& p1, const PT& p2, const PT& p3, const PT& p4, CGAL::Orientation _o = POSITIVE) { ConicCPA2 pair1, pair2; set_two_linepairs (p1, p2, p3, p4, pair1, pair2); set_ellipse (pair1, pair2); analyse(); if (o != _o) set_opposite(); } @end @! --------------------------------------------------------------------------- \paragraph{Unique conic through five points} ~ @! --------------------------------------------------------------------------- Using the previously defined methods, we implement the method to compute the unique nontrivial conic through five given points $p_1,p_2,p_3,p_4,p_5$. For this, we first compute the two conics $\C_1 = \overline{p_1p_2} \cup \overline{p_3p_4}$ and $\C_2 = \overline{p_1p_4} \cup \overline{p_2p_3}$, using the @prg{set_linepair} method. This gives two conics having the points $p_1,p_2,p_3,p_4$ in common. It follows that any linear combination of them goes through $p_1,p_2,p_3,p_4$ as well. A particular linear combination is given by \begin{equation} \label{unique_conic} \C := \C_2(p_5) \C_1 - \C_1(p_5) \C_2, \end{equation} and it has the property that $\C(p_5)=0$, i.e. $\C$ goes through $p_1,\ldots,p_5$. In case all points are distinct, this is the unique nontrivial conic through the points. @macro += @begin void set (const PT& p1, const PT& p2, const PT& p3, const PT& p4, const PT& p5, CGAL::Orientation _o = POSITIVE) { ConicHPA2 c1; c1.set_linepair (p1, p2, p3, p4); ConicHPA2 c2; c2.set_linepair (p1, p4, p2, p3); set_linear_combination (c2.evaluate (p5), c1, -c1.evaluate (p5), c2); analyse(); // precondition: all points distinct <=> conic nontrivial CGAL_optimisation_precondition (!is_trivial()); if (o != _o) set_opposite(); } @end @macro += @begin void set (const PT& p1, const PT& p2, const PT& p3, const PT& p4, const PT& p5, CGAL::Orientation _o = POSITIVE) { ConicCPA2 c1; c1.set_linepair (p1, p2, p3, p4); ConicCPA2 c2; c2.set_linepair (p1, p4, p2, p3); set_linear_combination (c2.evaluate (p5), c1, -c1.evaluate (p5), c2); analyse(); // precondition: all points distinct <=> conic nontrivial CGAL_optimisation_precondition (!is_trivial()); if (o != _o) set_opposite(); } @end @! --------------------------------------------------------------------------- @subsection{IO routines} @! --------------------------------------------------------------------------- @macro = @begin template< class _PT, class _DA> std::ostream& operator << ( std::ostream& os, const ConicHPA2<_PT,_DA>& c) { return( os << c.r() << ' ' << c.s() << ' ' << c.t() << ' ' << c.u() << ' ' << c.v() << ' ' << c.w()); } template< class _PT, class _DA> std::istream& operator >> ( std::istream& is, ConicHPA2<_PT,_DA>& c) { typedef ConicHPA2<_PT,_DA> Conic; typedef typename _DA::RT RT; RT r, s, t, u, v, w; is >> r >> s >> t >> u >> v >> w; c.set( r, s, t, u, v, w); return( is); } @end @macro = @begin template< class _PT, class _DA> std::ostream& operator << ( std::ostream& os, const ConicCPA2<_PT,_DA>& c) { return( os << c.r() << ' ' << c.s() << ' ' << c.t() << ' ' << c.u() << ' ' << c.v() << ' ' << c.w()); } template< class _PT, class _DA> std::istream& operator >> ( std::istream& is, ConicCPA2<_PT,_DA>& c) { typedef ConicCPA2<_PT,_DA> Conic; typedef typename _DA::FT FT; FT r, s, t, u, v, w; is >> r >> s >> t >> u >> v >> w; c.set( r, s, t, u, v, w); return( is); } @end @!---------------------------------------------------------------------------- @section{Files} @!---------------------------------------------------------------------------- @i share/namespace.awi @!---------------------------------------------------------------------------- @subsection{{\tt Conic\_misc.h}} @!---------------------------------------------------------------------------- Here we collect all types and functions that are independent of the representation type @prg{R}. These are the declarations of the enumeration types @prg{Conic_type} and @prg{Convex_side}, the conic output function and the functions in connection with the solution of cubic equations. @file = @begin @("include/CGAL/Conic_misc.h","2D Conic") #ifndef CGAL_CONIC_MISC_H #define CGAL_CONIC_MISC_H #ifndef CGAL_OPTIMISATION_ASSERTIONS_H # include #endif @("CGAL") @ @ @ @! @ @ @("CGAL") #endif // CGAL_CONIC_MISC_H @ @end @!---------------------------------------------------------------------------- @subsection{{\tt Conic\_2.h}} @!---------------------------------------------------------------------------- This file contains the implementation of the class @prg{Conic_2}. Depending on the loaded representation classes, the representation specific classes @prg{ConicHPA2} and/or @prg{ConicCPA2} are included before that. @file = @begin @("include/CGAL/Conic_2.h","2D Conic") #ifndef CGAL_CONIC_2_H #define CGAL_CONIC_2_H #ifndef CGAL_REP_CLASS_DEFINED # error no representation class defined #endif // CGAL_REP_CLASS_DEFINED #ifdef CGAL_HOMOGENEOUS_H # include #endif #ifdef CGAL_CARTESIAN_H # include #endif #ifndef CGAL_POINT_2_H # include #endif #ifndef CGAL_IO_FORWARD_DECL_WINDOW_STREAM_H #include #endif @("CGAL") @ @("CGAL") @ @("CGAL") @ #ifndef CGAL_NO_OSTREAM_INSERT_CONIC_2 @ #endif // CGAL_NO_OSTREAM_INSERT_CONIC_2 @("CGAL") #endif // CGAL_CONIC_2_H @ @end @!---------------------------------------------------------------------------- @subsection{{\tt ConicHPA2.h}} @!---------------------------------------------------------------------------- Here is the class @prg{ConicHPA2}\ldots @file = @begin @("include/CGAL/ConicHPA2.h","2D Conic") #ifndef CGAL_CONICHPA2_H #define CGAL_CONICHPA2_H // includes #ifndef CGAL_CONIC_MISC_H # include #endif #ifndef CGAL_OPTIMISATION_ASSERTIONS_H # include #endif @("CGAL") @ @ #ifndef CGAL_NO_OSTREAM_INSERT_CONICHPA2 @ #endif // CGAL_NO_OSTREAM_INSERT_CONICHPA2 @("CGAL") #endif // CGAL_CONICHPA2_H @ @end @!---------------------------------------------------------------------------- @subsection{{\tt ConicCPA2.h}} @!---------------------------------------------------------------------------- \ldots and the class @prg{ConicCPA2}. @file = @begin @("include/CGAL/ConicCPA2.h","2D Conic") #ifndef CGAL_CONICCPA2_H #define CGAL_CONICCPA2_H // includes #ifndef CGAL_CONIC_MISC_H # include #endif #ifndef CGAL_OPTIMISATION_ASSERTIONS_H # include #endif @("CGAL") @ @ #ifndef CGAL_NO_OSTREAM_INSERT_CONICCPA2 @ #endif // CGAL_NO_OSTREAM_INSERT_CONICCPA2 @("CGAL") #endif // CGAL_CONICCPA2_H @ @end @! ---------------------------------------------------------------------------- @! Conic_2_Window_stream.h @! ---------------------------------------------------------------------------- \subsection{Conic\_2\_Window\_stream.h} @file = @begin @( "include/CGAL/IO/Conic_2_Window_stream.h", "graphical output to `leda_window' for Conic_2 algo.") // Each of the following operators is individually // protected against multiple inclusion. // Window_stream I/O operators // =========================== // Conic_2 // ------- @ @ @end @! ---------------------------------------------------------------------------- @! File Header @! ---------------------------------------------------------------------------- \subsection*{File Header} @i share/file_header.awi And here comes the specific file header for the product files of this web file. @macro (2) many = @begin @ @(@1) @( "Min_ellipse_2", "Geometric Optimisation", "Conic_2", "$Revision$","$Date$", "Bernd Gärtner, Sven Schönherr ", "ETH Zürich (Bernd Gärtner )", "@2") @end @! ===== EOF ==================================================================