
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2016 Matthias Koefferlein

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/



#include "dbEdgeProcessor.h"
#include "dbLayout.h"
#include "tlTimer.h"
#include "tlProgress.h"
#include "gsi.h"

#include <vector>
#include <deque>
#include <memory>

#if 0
#define DEBUG_POLYGON_GENERATOR
#define DEBUG_MERGEOP
#define DEBUG_BOOLEAN
#define DEBUG_EDGE_PROCESSOR
#endif

// #define DEBUG_SIZE_INTERMEDIATE

namespace db
{

// -------------------------------------------------------------------------------
//  Some utilities ..

struct NonZeroInsideFunc 
{
  inline bool operator() (int wc) const
  {
    return wc != 0;
  }
};

struct ProjectionCompare
{
  ProjectionCompare (const db::Edge &e)
    : m_e (e) { }

  bool operator () (const db::Point &a, const db::Point &b) const
  {
    db::coord_traits<db::Coord>::area_type sp1 = db::sprod (m_e, db::Edge (m_e.p1 (), a));
    db::coord_traits<db::Coord>::area_type sp2 = db::sprod (m_e, db::Edge (m_e.p1 (), b));
    if (sp1 != sp2) {
      return sp1 < sp2;
    } else {
      return a < b;
    }
  }

private:
  db::Edge m_e;
};

struct PolyMapCompare
{
  PolyMapCompare (db::Coord y)
    : m_y (y) { }

  bool operator() (const std::pair<db::Edge, size_t> &a, const std::pair<db::Edge, size_t> &b) const
  {
    //  simple cases ..
    if (a.first.dx () == 0 && b.first.dx () == 0) {
      return a.first.p1 ().x () < b.first.p1 ().x ();
    } else if (edge_xmax (a.first) < edge_xmin (b.first)) {
      return true;
    } else if (edge_xmin (a.first) > edge_xmax (b.first)) {
      return false;
    } else {

      //  complex case:
      double xa = edge_xaty (a.first, m_y);
      double xb = edge_xaty (b.first, m_y);

      if (xa != xb) {
        return xa < xb;
      } else {

        //  compare by angle of normalized edges

        db::Edge ea (a.first);
        db::Edge eb (b.first);

        if (ea.dy () < 0) {
          ea.swap_points ();
        }
        if (eb.dy () < 0) {
          eb.swap_points ();
        }

        return db::vprod_sign (ea, eb) < 0;

      }
    }
  }

private:
  db::Coord m_y;
};

inline bool
is_point_on_fuzzy (const db::Edge &e, const db::Point &pt)
{
  //  exclude the start and end point
  if (pt == e.p1 () || pt == e.p2 ()) {

    return false;

  } else if (pt.x () < db::edge_xmin (e) || pt.x () > db::edge_xmax (e) ||
             pt.y () < db::edge_ymin (e) || pt.y () > db::edge_ymax (e)) {

    return false;

  } else if (e.dy () == 0 || e.dx () == 0) {

    //  shortcut for orthogonal edges
    return true;

  } else {

    db::Vector offset;
    if ((e.dx () < 0 && e.dy () > 0) || (e.dx () > 0 && e.dy () < 0)) {
      offset = db::Vector (1, 1);
    } else {
      offset = db::Vector (-1, 1);
    }

    db::Vector pp1 = pt - e.p1 ();

    typedef db::coord_traits<db::Point::coord_type>::area_type area_type;
    area_type a1 = 2 * db::vprod (pp1, e.d ());
    if (a1 < 0) { a1 = -a1; }
    area_type a2 = db::vprod (offset, e.d ());
    if (a2 < 0) { a2 = -a2; }
    return a1 <= a2;

  }
}

inline bool
is_point_on_exact (const db::Edge &e, const db::Point &pt)
{
  if (pt.x () < db::edge_xmin (e) || pt.x () > db::edge_xmax (e) ||
      pt.y () < db::edge_ymin (e) || pt.y () > db::edge_ymax (e)) {

    return false;

  } else if (e.dy () == 0 || e.dx () == 0) {

    //  shortcut for orthogonal edges
    return true;

  } else {

    return db::vprod_sign (pt, e.p2 (), e.p1 ()) == 0;

  }
}

//  A intersection test that is more robust numerically.
//  In some cases (i.e. (3,-3;-8,-1) and (-4,-2;13,-4)), the intersection test gives different results
//  for the intersection point if the edges are swapped. This test is robust since it operates on ordered edges.
inline std::pair<bool, db::Point> safe_intersect_point (const db::Edge &e1, const db::Edge &e2)
{
  if (e1 < e2) {
    return e1.intersect_point (e2);
  } else {
    return e2.intersect_point (e1);
  }
}

// -------------------------------------------------------------------------------
//  PolygonGenerator implementation

struct PGPoint
{
  PGPoint () : point (), contour (0), first (false) { }
  PGPoint (const db::Point &p, size_t i, bool f) : point (p), contour (i), first (f) { }

  db::Point point;
  size_t contour;
  bool first;
};

class PGPolyContour
{
public:
  typedef std::deque <db::Point> contour_type;
  typedef contour_type::const_iterator const_iterator;
  typedef contour_type::iterator iterator;

  PGPolyContour ()
    : m_is_hole (false), m_next (-1), m_last (-1)
  {
    // ...
  }

  PGPolyContour (const PGPolyContour &d)
    : m_contour (d.m_contour), m_is_hole (d.m_is_hole), m_next (d.m_next), m_last (d.m_last)
  {
    // ...
  }

  PGPolyContour &operator= (const PGPolyContour &d)
  {
    if (this != &d) {
      m_contour = d.m_contour;
      m_is_hole = d.m_is_hole;
      m_next = d.m_next;
      m_last = d.m_last;
    }
    return *this;
  }

  const_iterator begin () const { return m_contour.begin (); }
  const_iterator end () const { return m_contour.end (); }
  iterator begin () { return m_contour.begin (); }
  iterator end () { return m_contour.end (); }
  const db::Point &front () const { return m_contour.front (); }
  db::Point &front () { return m_contour.front (); }
  const db::Point &back () const { return m_contour.back (); }
  db::Point &back () { return m_contour.back (); }
  void push_back (const db::Point &p) { m_contour.push_back (p); }
  void push_front (const db::Point &p) { m_contour.push_front (p); }
  bool empty () const { return m_contour.empty (); }
  size_t size () const { return m_contour.size (); }

  void last (long n) 
  {
    m_last = n;
  }

  long last () const
  {
    return m_last;
  }

  void next (long n) 
  {
    m_next = n;
  }

  long next () const
  {
    return m_next;
  }

  bool is_hole () const
  {
    return m_is_hole;
  }

  void is_hole (bool hole)
  {
    m_is_hole = hole;
  }

  void clear () 
  { 
    m_next = -1;
    m_last = -1;
    m_contour.clear (); 
  }

  void erase (iterator from, iterator to) 
  {
    m_contour.erase (from, to);
  }

  template <class I>
  void insert (iterator at, I from, I to) 
  {
    m_contour.insert (at, from, to);
  }

private:
  contour_type m_contour;
  bool m_is_hole;
  long m_next;
  long m_last;
};


class PGContourList 
{
public:
  PGContourList () 
    : m_free_contours (-1)
  { }

  PGPolyContour &operator[] (size_t n) 
  {
    return m_contours [n];
  }
  
  const PGPolyContour &operator[] (size_t n) const
  {
    return m_contours [n];
  }
  
  size_t size () const
  {
    return m_contours.size ();
  }

  size_t allocate ()
  {
    size_t index;

    if (m_free_contours >= 0) {
      index = m_free_contours;
      m_free_contours = m_contours [m_free_contours].next ();
      m_contours [index].next (-1);
    } else {
      index = m_contours.size ();
      m_contours.push_back (PGPolyContour ());
    }

    return index;
  }

  void join (size_t n1, size_t n2)
  {
    PGPolyContour &c1 = m_contours [n1];
    PGPolyContour &c2 = m_contours [n2];

    if (c1.next () < 0) {
      c1.next (c2.next ());
      c1.last (c2.last ());
    } else if (c2.next () >= 0) {
      m_contours [c1.last ()].next (c2.next ());
      c1.last (c2.last ());
    }

    c2.clear ();
    c2.next (m_free_contours);
    m_free_contours = long (n2);
  }

  void free (size_t n)
  {
    PGPolyContour &c = m_contours [n];
    c.clear ();
    c.next (m_free_contours);
    m_free_contours = long (n);
  }

  void free_all (size_t n)
  {
    long i = long (n);
    do {
      long ii = m_contours [i].next ();
      free (i);
      i = ii;
    } while (i >= 0);
  }

  void clear ()
  {
    m_free_contours = -1;
    m_contours.clear ();
  }

  void append (size_t what, size_t to)
  {
    m_contours [m_contours [to].next () < 0 ? long (to) : m_contours [to].last ()].next (long (what));

    long l = m_contours [what].last ();
    m_contours [to].last (l < 0 ? long (what) : l);
  }

private:
  long m_free_contours;
  std::vector <PGPolyContour> m_contours;
};


PolygonGenerator::PolygonGenerator (PolygonSink &psink, bool resolve_holes, bool min_coherence) 
  : EdgeSink (), 
    mp_contours (new PGContourList ()),
    m_y (std::numeric_limits<db::Coord>::min ()),
    m_open_pos (m_open.end ()), 
    mp_psink (&psink),
    mp_spsink (0),
    m_resolve_holes (resolve_holes),
    m_min_coherence (min_coherence),
    m_compress (true)
{ 
  //  .. nothing yet ..
}

PolygonGenerator::PolygonGenerator (SimplePolygonSink &spsink, bool min_coherence) 
  : EdgeSink (), 
    mp_contours (new PGContourList ()),
    m_y (std::numeric_limits<db::Coord>::min ()),
    m_open_pos (m_open.end ()), 
    mp_psink (0),
    mp_spsink (&spsink),
    m_resolve_holes (true),
    m_min_coherence (min_coherence),
    m_compress (true)
{ 
  //  .. nothing yet ..
}

PolygonGenerator::~PolygonGenerator ()
{
  delete mp_contours;
  mp_contours = 0;
}

void
PolygonGenerator::start ()
{
  if (mp_psink) {
    mp_psink->start ();
  }
  if (mp_spsink) {
    mp_spsink->start ();
  }
}

void 
PolygonGenerator::flush ()
{
#ifdef DEBUG_POLYGON_GENERATOR
  for (open_map_iterator_type i = m_open.begin (); i != m_open.end (); ++i) { 
    printf ("%d:%s%c%c ", i->contour, i->point.to_string().c_str(), i->first ? '!' : ' ', i == m_open_pos ? '*' : ' '); 
  } 
  printf ("\n");
#endif
  tl_assert (m_open.empty ());

  mp_contours->clear ();
  m_open.clear ();

  if (mp_psink) {
    mp_psink->flush ();
  }
  if (mp_spsink) {
    mp_spsink->flush ();
  }
}

void 
PolygonGenerator::begin_scanline (db::Coord y)
{
#ifdef DEBUG_POLYGON_GENERATOR
  printf ("begin y=%d\n", y);
#endif
  m_open_pos = m_open.begin ();
  m_y = y;

#ifdef DEBUG_POLYGON_GENERATOR
  printf ("m_open=");
  for (open_map_type::const_iterator o = m_open.begin (); o != m_open.end (); ++o) {
    printf ("%d:%s ", o->contour, o->point.to_string().c_str()); 
  } 
  printf ("\n");
  printf ("contours:\n"); 
  for (size_t j = 0; j < mp_contours->size (); ++j) { 
    printf ("c%d%s: ", j, (*mp_contours)[j].is_hole () ? "H" : ""); 
    for (size_t i = 0; i < (*mp_contours)[j].size (); ++i) { 
      printf ("%s ", ((*mp_contours)[j].begin () + i)->to_string().c_str ()); 
    } 
    printf ("\n"); 
  }
#endif
}

void 
PolygonGenerator::end_scanline (db::Coord y)
{
  join_contours (std::numeric_limits<db::Coord>::max ());
}

void 
PolygonGenerator::crossing_edge (const db::Edge &e)
{
#ifdef DEBUG_POLYGON_GENERATOR
  printf ("xing(%s)\n", e.to_string().c_str());
#endif
  join_contours (std::numeric_limits<db::Coord>::max ());
  ++m_open_pos;
}

void 
PolygonGenerator::skip_n (size_t n)
{
  join_contours (std::numeric_limits<db::Coord>::max ());
#ifdef DEBUG_POLYGON_GENERATOR
  printf ("skip(%d)\n", n);
#endif
  while (n-- > 0) {
    ++m_open_pos;
  }
}

void 
PolygonGenerator::put (const db::Edge &e)
{
#ifdef DEBUG_POLYGON_GENERATOR
  printf ("put(%s) y=%d m_open(%d)=", e.to_string().c_str(),m_y,std::distance (m_open.begin (), m_open_pos));
  for (open_map_iterator_type i = m_open.begin (); i != m_open.end (); ++i) { 
    printf ("%d:%s%c%c ", i->contour, i->point.to_string().c_str(), i->first ? '!' : ' ', i == m_open_pos ? '*' : ' '); 
  } 
  printf ("\n");
#endif

  if (m_open_pos != m_open.end ()) {
    db::Coord x;
    if (e.p1 ().y () == m_y && e.p2 ().y () == m_y) {
      x = std::min (e.p1 ().x (), e.p2 ().x ());
    } else if (e.p1 ().y () == m_y) {
      x = e.p1 ().x ();
    } else {
      x = e.p2 ().x ();
    }
    join_contours (x);
  }

  if (m_open_pos != m_open.end () && e.p1 ().y () == m_y && m_open_pos->point == e.p1 () && (! m_min_coherence || e.dy () == 0)) {

    PGPolyContour &c = (*mp_contours) [m_open_pos->contour];
    tl_assert (c.back () == e.p1 ());
    c.push_back (e.p2 ());
    m_open_pos->point = e.p2 ();

    if (e.p2 ().y () > m_y) {
      ++m_open_pos;
    }

  } else if (m_open_pos != m_open.end () && e.p2 ().y () == m_y && m_open_pos->point == e.p2 () && (m_min_coherence || e.dy () == 0)) {

    PGPolyContour &c = (*mp_contours) [m_open_pos->contour];
    tl_assert (c.front () == e.p2 ());
    c.push_front (e.p1 ());
    m_open_pos->point = e.p1 ();

    if (e.p1 ().y () > m_y) {
      ++m_open_pos;
    }

  } else {

    bool hole = (e.dy () < 0);

    size_t poly_index = mp_contours->allocate ();

    (*mp_contours) [poly_index].is_hole (hole);
    (*mp_contours) [poly_index].push_back (e.p1 ());
    (*mp_contours) [poly_index].push_back (e.p2 ());

#ifdef DEBUG_POLYGON_GENERATOR
    printf ("create %s %d\n", hole ? "hole" : "hull", poly_index);
#endif
    m_open.insert (m_open_pos, PGPoint (hole ? e.p1 () : e.p2 (), poly_index, true));
    m_open.insert (m_open_pos, PGPoint (hole ? e.p2 () : e.p1 (), poly_index, false));

    --m_open_pos;

  }
#ifdef DEBUG_POLYGON_GENERATOR
  for (open_map_iterator_type i = m_open.begin (); i != m_open.end (); ++i) { 
    printf ("%d:%s%c%c ", i->contour, i->point.to_string().c_str(), i->first ? '!' : ' ', i == m_open_pos ? '*' : ' '); 
  } 
  printf ("\n");
#endif

}

void 
PolygonGenerator::join_contours (db::Coord x) 
{
  while (m_open_pos != m_open.end ()) {

    open_map_iterator_type n = m_open_pos;
    ++n;
    if (n == m_open.end () || m_open_pos->point.y () != m_y || m_open_pos->point != n->point || m_open_pos->point.x () > x) {
      return;
    }
    
    open_map_iterator_type nn = n;
    open_map_iterator_type next = ++nn;

    //  don't join, except -/+ pair (for max. coherence) or +/- pair (for min coherence)
    bool minus0 = (m_open_pos->first == (*mp_contours) [m_open_pos->contour].is_hole ());
    bool minus1 = (n->first == (*mp_contours) [n->contour].is_hole ());
    if (! (minus0 == !m_min_coherence && minus1 == m_min_coherence)) {

      //  join a southward pair consisting of the next and second-next edge. 
      if (nn != m_open.end () && m_open_pos->point == nn->point) {
        next = m_open_pos;
        m_open_pos = n;
        n = nn;
        ++nn;
      } else if (m_open_pos->point.x () == x) {
        return;
      }

    }

    size_t i1 = m_open_pos->contour;
    size_t i2 = n->contour;
#ifdef DEBUG_POLYGON_GENERATOR
    printf ("join %d and %d\n", i1, i2);
    for (open_map_iterator_type i = m_open.begin (); i != m_open.end (); ++i) { 
      printf ("%d:%s%c%c%c%c ", i->contour, i->point.to_string().c_str(), i->first ? '!' : ' ', i == m_open_pos ? '*' : ' ', i == n ? '+' : ' ', i == nn ? '#' : ' '); 
    } 
    printf ("\n");
    printf ("--> input contours:\n"); 
    for (size_t j = 0; j < mp_contours->size (); ++j) { 
      printf ("--> c%d%s: ", j, (*mp_contours)[j].is_hole () ? "H" : ""); 
      for (size_t i = 0; i < (*mp_contours)[j].size (); ++i) { 
        printf ("%s ", ((*mp_contours)[j].begin () + i)->to_string().c_str ()); 
      } 
      printf ("\n"); 
    }
#endif

    tl_assert (i1 < mp_contours->size ());
    tl_assert (i2 < mp_contours->size ());

    PGPolyContour &c1 = (*mp_contours) [i1];
    PGPolyContour &c2 = (*mp_contours) [i2];

    if (i1 != i2) {

      tl_assert (! c2.empty ());
      tl_assert (! c1.empty ());

      if (! m_open_pos->first && ! n->first) {

        //  remove c1 from list of contours, join with c2
        if (c2.is_hole ()) {
          c2.insert (c2.end (), c1.begin () + 1, c1.end ());
        } else {
          c2.insert (c2.begin (), c1.begin (), c1.end () - 1);
        }

        mp_contours->join (i2, i1);

        open_map_iterator_type o = m_open_pos;
        do {
          --o;
        } while (o != m_open.begin () && o->contour != i1);
        tl_assert (o != m_open.begin ());
        o->contour = i2;
        o->first = false;

      } else {

        //  remove c1 from list of contours, join with c2
        if (c2.is_hole ()) {  // yes! c2 is correct!
          c1.insert (c1.end (), c2.begin () + 1, c2.end ());
        } else {
          c1.insert (c1.begin (), c2.begin (), c2.end () - 1);
        }

        mp_contours->join (i1, i2);

        open_map_iterator_type o = n;
        do {
          ++o;
        } while (o != m_open.end () && o->contour != i2);
        tl_assert (o != m_open.end ());
        o->contour = i1;

        if (m_open_pos->first && n->first) {
          o->first = true;
        }

      }

    } else {

      if (! c1.is_hole ()) {
#ifdef DEBUG_POLYGON_GENERATOR
        printf ("finish %d (hull)\n", i1);
#endif

        produce_poly (c1);

        //  remove this contour plus all associated holes from the contour list
        mp_contours->free_all (i1);

      } else if (m_resolve_holes) {

        //  join with next contour by creating a stitch line
        tl_assert (nn != m_open.end ());

        size_t inext = nn->contour;
        PGPolyContour &cnext = (*mp_contours) [inext];

        tl_assert (cnext.size () >= 2);

        //  compute intersection point with next edge
        db::Edge enext (cnext.front (), cnext.begin () [1]);
        db::Coord xnext = db::coord_traits<db::Coord>::rounded (edge_xaty (enext, m_y));
        db::Point pnext (xnext, m_y);

        tl_assert (c1.size () >= 2);
        cnext.front () = pnext;
        if ((c1.begin () + 1)->y () == m_y) {
          cnext.insert (cnext.begin (), c1.begin () + 1, c1.end ());
        } else {
          cnext.insert (cnext.begin (), c1.begin (), c1.end ());
        }
        cnext.push_front (pnext);
        if (enext.p1 () != pnext) {
          cnext.push_front (enext.p1 ());
        }

        mp_contours->free (i1);

      } else {

        //  join with next contour by inserting as a hole
        tl_assert (nn != m_open.end ());

        mp_contours->append (i1, nn->contour);

      }

    }

    m_open.erase (m_open_pos);
    m_open.erase (n);

    m_open_pos = next;
#ifdef DEBUG_POLYGON_GENERATOR
    printf ("--> output contours:\n"); 
    for (size_t j = 0; j < mp_contours->size (); ++j) { 
      printf ("--> c%d%s: ", j, (*mp_contours)[j].is_hole () ? "H" : ""); 
      for (size_t i = 0; i < (*mp_contours)[j].size (); ++i) { 
        printf ("%s ", ((*mp_contours)[j].begin () + i)->to_string().c_str ()); 
      } 
      printf ("\n"); 
    }
#endif

  }
}

bool
PolygonGenerator::ms_compress = true;

void 
PolygonGenerator::produce_poly (const PGPolyContour &c)
{
  size_t n = 0;
  for (long inext = c.next (); inext >= 0; inext = (*mp_contours) [inext].next ()) {
    ++n;
  }

  if (mp_psink) {

    PGPolyContour::const_iterator p0 = c.begin ();
    PGPolyContour::const_iterator p1 = c.end ();
    // remove duplicate point at end
    tl_assert (p0 != p1);
    --p1;
    tl_assert (*p1 == *p0);

    if (n == 0 && m_poly.holes () == 0) {
      //  fast mode ..
      m_poly.assign_hull (p0, p1, m_compress && ms_compress /*compress*/);
    } else {

      m_poly.clear ((unsigned int) n);
      m_poly.assign_hull (p0, p1, m_compress && ms_compress /*compress*/);

      for (long inext = c.next (); inext >= 0; inext = (*mp_contours) [inext].next ()) {

        tl_assert ((*mp_contours) [inext].is_hole ());

        PGPolyContour::const_iterator p0 = (*mp_contours) [inext].begin ();
        PGPolyContour::const_iterator p1 = (*mp_contours) [inext].end ();
        // remove duplicate point at end
        tl_assert (p0 != p1);
        --p1;
        tl_assert (*p1 == *p0);

        m_poly.insert_hole (p0, p1, m_compress && ms_compress /*compress*/);

      }

    }

    mp_psink->put (m_poly);

  } 

  if (mp_spsink) {

    tl_assert (n == 0);  //  there should not be holes since we forced resolve_holes to true ...
    m_spoly.assign_hull (c.begin (), c.end (), m_compress && ms_compress /*compress*/);

    mp_spsink->put (m_spoly);

  } 

}

// -------------------------------------------------------------------------------
//  SizingPolygonSink implementation

void 
SizingPolygonFilter::put (const db::Polygon &polygon)
{
  m_sizing_processor.clear ();
  m_sizing_processor.insert (polygon.sized (m_dx, m_dy, m_mode));

  //  merge the resulting polygons to get the true outer contour
  db::SimpleMerge op (1 /*wc>0*/);
  m_sizing_processor.process (*mp_output, op);
}

/**
 *  @brief A structure for storing data in the first phase of the scanline algorithm
 *
 *  This data are the cut points (intersection points with other edges). The has_cutpoints
 *  flag indicates that the edge has cutpoints which the edge must follow. If the edge has strong
 *  cutpoints (strong_cutpoints), they will change the edge, hence non-cutpoints (attractors) must be
 *  included. 
 */
struct CutPoints
{
  std::vector <db::Point> cut_points;
  std::vector <std::pair<db::Point, size_t> > attractors;
  bool has_cutpoints : 8;
  bool strong_cutpoints : 8;

  CutPoints ()
    : has_cutpoints (false), strong_cutpoints (false)
  { }

  void add_attractor (const db::Point &p, size_t next)
  {
    if (strong_cutpoints) {
      cut_points.push_back (p);
    } else {
      attractors.push_back (std::make_pair (p, next));
    }
  }

  void add (const db::Point &p, std::vector <CutPoints> *cpvector, bool strong = true)
  {
    has_cutpoints = true;
    if (strong && !strong_cutpoints) {

      strong_cutpoints = true;
      if (! attractors.empty ()) {

        std::vector <std::pair<db::Point, size_t> > attr;
        attractors.swap (attr);

        cut_points.reserve (cut_points.size () + attr.size ());
        for (std::vector <std::pair<db::Point, size_t> >::const_iterator a = attr.begin (); a != attr.end (); ++a) {
          (*cpvector) [a->second].add (a->first, cpvector, true);
        }

      }

    } 

    cut_points.push_back (p);

  }

};

/**
 *  @brief A data object for the scanline algorithm
 */
struct WorkEdge
  : public db::Edge
{
  WorkEdge () 
    : db::Edge (), data (0), prop (0)
  { }

  WorkEdge (const db::Edge &e, EdgeProcessor::property_type p = 0, size_t d = 0) 
    : db::Edge (e), data (d), prop (p)
  { }

  WorkEdge (const WorkEdge &d)
    : db::Edge (d), data (d.data), prop (d.prop)
  { }

  WorkEdge &operator= (const WorkEdge &d)
  { 
    if (this != &d) {
      db::Edge::operator= (d);
      data = d.data;
      prop = d.prop;
    }
    return *this;
  }

  WorkEdge &operator= (const db::Edge &d)
  { 
    db::Edge::operator= (d);
    return *this;
  }

  CutPoints *make_cutpoints (std::vector <CutPoints> &cutpoints)
  {
    if (! data) {
      cutpoints.push_back (CutPoints ());
      data = cutpoints.size ();
    }
    return &cutpoints [data - 1];
  }

  size_t data;
  db::EdgeProcessor::property_type prop;
};

/**
 *  @brief Compare edges by property ID
 */
struct EdgePropCompare
{
  bool operator() (const db::WorkEdge &a, const db::WorkEdge &b) const
  {
    return a.prop < b.prop;
  }
};

/**
 *  @brief Compare edges by property ID (reverse)
 */
struct EdgePropCompareReverse
{
  bool operator() (const db::WorkEdge &a, const db::WorkEdge &b) const
  {
    return b.prop < a.prop;
  }
};

/**
 *  @brief A compare operator for edged
 *  This operator will compare edges by their x position on the scanline
 */
struct EdgeXAtYCompare
{
  EdgeXAtYCompare (db::Coord y)
    : m_y (y) { }

  bool operator() (const db::Edge &a, const db::Edge &b) const
  {
    //  simple cases ..
    if (a.dx () == 0 && b.dx () == 0) {
      return a.p1 ().x () < b.p1 ().x ();
    } else if (edge_xmax (a) < edge_xmin (b)) {
      return true;
    } else if (edge_xmin (a) > edge_xmax (b)) {
      return false;
    } else {

      //  complex case:
      //  HINT: "volatile" forces xa and xb into memory and disables FPU register optimisation.
      //  That way, we can exactly compare doubles afterwards.
      volatile double xa = edge_xaty (a, m_y);
      volatile double xb = edge_xaty (b, m_y);

      if (xa != xb) {
        return xa < xb;
      } else if (a.dy () == 0) {
        return false;
      } else if (b.dy () == 0) {
        return true;
      } else {

        //  compare by angle of normalized edges

        db::Edge ea (a);
        db::Edge eb (b);

        if (ea.dy () < 0) {
          ea.swap_points ();
        }
        if (eb.dy () < 0) {
          eb.swap_points ();
        }

        return db::vprod_sign (ea, eb) < 0;

      }
    }
  }

  bool equal (const db::Edge &a, const db::Edge &b) const
  {
    //  simple cases ..
    if (a.dx () == 0 && b.dx () == 0) {
      return a.p1 ().x () == b.p1 ().x ();
    } else if (edge_xmax (a) < edge_xmin (b)) {
      return false;
    } else if (edge_xmin (a) > edge_xmax (b)) {
      return false;
    } else {

      //  complex case:
      //  HINT: "volatile" forces xa and xb into memory and disables FPU register optimisation.
      //  That way, we can exactly compare doubles afterwards.
      volatile double xa = edge_xaty (a, m_y);
      volatile double xb = edge_xaty (b, m_y);

      if (xa != xb) {
        return false;
      } else if (a.dy () == 0 || b.dy () == 0) {
        return (a.dy () == 0) == (b.dy () == 0);
      } else {

        //  compare by angle of normalized edges

        db::Edge ea (a);
        db::Edge eb (b);

        if (ea.dy () < 0) {
          ea.swap_points ();
        }
        if (eb.dy () < 0) {
          eb.swap_points ();
        }

        return db::vprod_sign (ea, eb) == 0;

      }
    }
  }

private:
  db::Coord m_y;
};

/**
 *  @brief A compare operator for edges used within EdgeXAtYCompare2
 *  This operator is an extension of db::edge_xaty and will deliver
 *  the minimum x if the edge is horizontal.
 */
static inline double edge_xaty2 (db::Edge e, db::Coord y)
{
  if (e.p1 ().y () > e.p2 ().y ()) {
    e.swap_points ();
  }

  if (y <= e.p1 ().y ()) {
    if (y == e.p2 ().y ()) {
      return std::min (e.p1 ().x (), e.p2 ().x ());
    } else {
      return e.p1 ().x ();
    }
  } else if (y >= e.p2 ().y ()) {
    return e.p2 ().x ();
  } else {
    return double (e.p1 ().x ()) + double (e.dx ()) * double (y - e.p1 ().y ()) / double (e.dy ());
  }
}

/**
 *  @brief A compare operator for edged
 *  This operator will compare edges by their x position on the scanline
 *  In Addition to EdgeXAtYCompare, this operator will also compare the 
 *  direction of the edges. Edges are equal if the position at which they cross
 *  the scanline and their direction is identical.
 */
struct EdgeXAtYCompare2
{
  EdgeXAtYCompare2 (db::Coord y)
    : m_y (y) { }

  bool operator() (const db::Edge &a, const db::Edge &b) const
  {
    //  simple cases ..
    if (a.dx () == 0 && b.dx () == 0) {
      return a.p1 ().x () < b.p1 ().x ();
    } else if (edge_xmax (a) < edge_xmin (b)) {
      return true;
    } else if (edge_xmin (a) > edge_xmax (b)) {
      return false;
    } else {

      //  complex case:
      //  HINT: "volatile" forces xa and xb into memory and disables FPU register optimisation.
      //  That way, we can exactly compare doubles afterwards.
      volatile double xa = edge_xaty2 (a, m_y);
      volatile double xb = edge_xaty2 (b, m_y);

      if (xa != xb) {
        return xa < xb;
      } else if (a.dy () == 0) {
        return false;
      } else if (b.dy () == 0) {
        return true;
      } else {

        //  In that case the edges will not intersect but rather touch in one point. This defines
        //  a sorting which preserves the scanline order of the edges when y advances.

        db::Edge ea (a);
        db::Edge eb (b);

        if (ea.dy () < 0) {
          ea.swap_points ();
        }
        if (eb.dy () < 0) {
          eb.swap_points ();
        }

        bool fa = ea.p2 ().y () > m_y;
        bool fb = eb.p2 ().y () > m_y;

        if (fa && fb) {
          //  Both edges advance
          return db::vprod_sign (ea, eb) < 0;
        } else if (fa || fb) {
          //  Only one edge advances - equality
          return false;
        } else {
          return db::vprod_sign (ea, eb) > 0;
        }

      }
    }
  }

  bool equal (const db::Edge &a, const db::Edge &b) const
  {
    //  simple cases ..
    if (a.dx () == 0 && b.dx () == 0) {
      return a.p1 ().x () == b.p1 ().x ();
    } else if (edge_xmax (a) < edge_xmin (b)) {
      return false;
    } else if (edge_xmin (a) > edge_xmax (b)) {
      return false;
    } else {

      //  complex case:
      //  HINT: "volatile" forces xa and xb into memory and disables FPU register optimisation.
      //  That way, we can exactly compare doubles afterwards.
      volatile double xa = edge_xaty2 (a, m_y);
      volatile double xb = edge_xaty2 (b, m_y);

      if (xa != xb) {
        return false;
      } else if (a.dy () == 0 || b.dy () == 0) {
        return (a.dy () == 0) == (b.dy () == 0);
      } else {

        //  compare by angle of normalized edges

        db::Edge ea (a);
        db::Edge eb (b);

        if (ea.dy () < 0) {
          ea.swap_points ();
        }
        if (eb.dy () < 0) {
          eb.swap_points ();
        }

        return db::vprod_sign (ea, eb) == 0;

      }
    }
  }

private:
  db::Coord m_y;
};

// -------------------------------------------------------------------------------
//  EdgePolygonOp implementation

EdgePolygonOp::EdgePolygonOp (bool outside, bool include_touching, int polygon_mode) 
  : m_outside (outside), m_include_touching (include_touching),
    m_function (polygon_mode),
    m_wcp_n (0), m_wcp_s (0)
{
}

void EdgePolygonOp::reset () 
{ 
  m_wcp_n = m_wcp_s = 0;
}

bool EdgePolygonOp::select_edge (bool horizontal, property_type p) 
{
  if (p == 0) {

    return false;

  } else if (horizontal) {

    bool res;
    if (m_include_touching) {
      res = (m_function (m_wcp_n) || m_function (m_wcp_s));
    } else {
      res = (m_function (m_wcp_n) && m_function (m_wcp_s));
    }

    return m_outside ? !res : res;

  } else {

    return m_outside ? !m_function (m_wcp_n) : m_function (m_wcp_n);

  }
}

int EdgePolygonOp::edge (bool north, bool enter, property_type p) 
{ 
  if (p == 0) {
    int *wc = north ? &m_wcp_n : &m_wcp_s;
    if (enter) {
      ++*wc;
    } else {
      --*wc;
    }
  }

  return 0; 
}

bool EdgePolygonOp::is_reset () const 
{ 
  return (m_wcp_n == 0 && m_wcp_s == 0);
}

bool EdgePolygonOp::prefer_touch () const 
{ 
  return m_include_touching; 
}

bool EdgePolygonOp::selects_edges () const 
{ 
  return true; 
}

// -------------------------------------------------------------------------------
//  InteractionDetector implementation

InteractionDetector::InteractionDetector (int mode, property_type container_id)
  : m_mode (mode), m_include_touching (true), m_container_id (container_id)
{
  // .. nothing yet ..
}

void 
InteractionDetector::reset ()
{
  m_wcv_n.clear ();
  m_wcv_s.clear ();
  m_inside.clear ();
}

void 
InteractionDetector::reserve (size_t n)
{
  m_wcv_n.clear ();
  m_wcv_s.clear ();
  m_wcv_n.resize (n, 0);
  m_wcv_s.resize (n, 0);
  m_inside.clear ();
}

int 
InteractionDetector::edge (bool north, bool enter, property_type p)
{
  tl_assert (p < m_wcv_n.size () && p < m_wcv_s.size ());

  int *wcv = north ? &m_wcv_n [p] : &m_wcv_s [p];

  bool inside_before = (*wcv != 0);
  *wcv += (enter ? 1 : -1);
  bool inside_after = (*wcv != 0);

  //  In "interacting" mode we need to handle both north and south events because
  //  we have to catch interactions between objects north and south to the scanline
  if (north || (m_mode == 0 && m_include_touching)) {

    if (inside_after < inside_before) {

      m_inside.erase (p);

      if (m_mode != 0) {

        //  the container objects are delivered last of all coincident edges
        //  (due to prefer_touch == true and the sorting of coincident edges by property id)
        //  hence every remaining parts count as non-interacting (outside)
        if (p == m_container_id) {
          for (std::set <property_type>::const_iterator i = m_inside.begin (); i != m_inside.end (); ++i) {
            if (*i != m_container_id) {
              m_non_interactions.insert (*i);
            }
          }
        }

      }

    } else if (inside_after > inside_before) {

      if (m_mode != 0) {

        //  in inside/outside mode we are only interested in interactions with the container_id property
        if (p != m_container_id) {
          //  note that the container parts will be delivered first of all coincident 
          //  edges hence we can check whether the container is present even for coincident
          //  edges
          if (m_inside.find (m_container_id) != m_inside.end ()) {
            m_interactions.insert (std::make_pair (m_container_id, p));
          } else {
            m_non_interactions.insert (p);
          }
        } else {
          for (std::set <property_type>::const_iterator i = m_inside.begin (); i != m_inside.end (); ++i) {
            if (*i != m_container_id) {
              m_interactions.insert (std::make_pair (m_container_id, *i));
            }
          }
        }

      } else {

        for (std::set <property_type>::const_iterator i = m_inside.begin (); i != m_inside.end (); ++i) {
          if (*i < p) {
            m_interactions.insert (std::make_pair (*i, p));
          } else if (*i > p) {
            m_interactions.insert (std::make_pair (p, *i));
          }
        }

      }

      m_inside.insert (p);

    }

  }

  return 0;
}

int 
InteractionDetector::compare_ns () const
{
  return 0;
}

void
InteractionDetector::finish ()
{
  if (m_mode < 0) {

    //  In inside mode remove those objects which have a non-interaction with the container_id property
    for (std::set<property_type>::const_iterator p = m_non_interactions.begin (); p != m_non_interactions.end (); ++p) {
      m_interactions.erase (std::make_pair (m_container_id, *p));
    }

    m_non_interactions.clear ();

  } else if (m_mode > 0) {

    //  In outside mode leave those objects which don't participate in an interaction
    for (iterator pp = begin (); pp != end (); ++pp) {
      m_non_interactions.erase (pp->second);
    }

    m_interactions.clear ();
    for (std::set<property_type>::const_iterator p = m_non_interactions.begin (); p != m_non_interactions.end (); ++p) {
      m_interactions.insert (m_interactions.end (), std::make_pair (m_container_id, *p));
    }

    m_non_interactions.clear ();

  }

}

// -------------------------------------------------------------------------------
//  MergeOp implementation

MergeOp::MergeOp (unsigned int min_wc)
  : m_wc_n (0), m_wc_s (0), m_min_wc (min_wc), m_zeroes (0)
{
  //  .. nothing yet ..
}

void  
MergeOp::reset ()
{
  m_wcv_n.clear ();
  m_wcv_s.clear ();
  m_wc_n = 0;
  m_wc_n = 0;
  m_zeroes = 0;
}

void 
MergeOp::reserve (size_t n)
{
  m_wcv_n.clear ();
  m_wcv_s.clear ();
  m_wcv_n.resize (n, 0);
  m_wcv_s.resize (n, 0);
  m_zeroes = 2 * n;
}

static inline 
bool result_by_mode (int wc, unsigned int min_wc)
{
  return wc > int (min_wc);
}

int 
MergeOp::edge (bool north, bool enter, property_type p)
{
  tl_assert (p < m_wcv_n.size () && p < m_wcv_s.size ());

  int *wcv = north ? &m_wcv_n [p] : &m_wcv_s [p];
  int *wc = north ? &m_wc_n : &m_wc_s;

  bool inside_before = (*wcv != 0);
  *wcv += (enter ? 1 : -1);
  bool inside_after = (*wcv != 0);
  m_zeroes += (!inside_after) - (!inside_before);
#ifdef DEBUG_MERGEOP
  printf ("north=%d, enter=%d, prop=%d -> %d\n", north, enter, p, m_zeroes);
#endif
  tl_assert (long (m_zeroes) >= 0);

  bool res_before = result_by_mode (*wc, m_min_wc);
  if (inside_before != inside_after) {
    *wc += (inside_after - inside_before);
  }
  bool res_after = result_by_mode (*wc, m_min_wc);

  return res_after - res_before;
}

int 
MergeOp::compare_ns () const
{
  return result_by_mode (m_wc_n, m_min_wc) - result_by_mode (m_wc_s, m_min_wc);
}

// -------------------------------------------------------------------------------
//  BooleanOp implementation

BooleanOp::BooleanOp (BoolOp mode)
  : m_wc_na (0), m_wc_nb (0), m_wc_sa (0), m_wc_sb (0), m_mode (mode), m_zeroes (0)
{
  //  .. nothing yet ..
}

void  
BooleanOp::reset ()
{
  m_wcv_n.clear ();
  m_wcv_s.clear ();
  m_wc_na = m_wc_sa = 0;
  m_wc_nb = m_wc_sb = 0;
  m_zeroes = 0;
}

void 
BooleanOp::reserve (size_t n)
{
  m_wcv_n.clear ();
  m_wcv_s.clear ();
  m_wcv_n.resize (n, 0);
  m_wcv_s.resize (n, 0);
  m_zeroes = 2 * n;
}

template <class InsideFunc>
inline bool 
BooleanOp::result (int wca, int wcb, const InsideFunc &inside_a, const InsideFunc &inside_b) const
{
  switch (m_mode) {
  case BooleanOp::And:
    return inside_a (wca) && inside_b (wcb);
  case BooleanOp::ANotB:
    return inside_a (wca) && ! inside_b (wcb);
  case BooleanOp::BNotA:
    return ! inside_a (wca) && inside_b (wcb);
  case BooleanOp::Xor:
    return (inside_a (wca) && ! inside_b (wcb)) || (! inside_a (wca) && inside_b (wcb));
  case BooleanOp::Or:
    return inside_a (wca) || inside_b (wcb);
  default:
    return false;
  }
}

template <class InsideFunc>
inline int 
BooleanOp::edge_impl (bool north, bool enter, property_type p, const InsideFunc &inside_a, const InsideFunc &inside_b) 
{
  tl_assert (p < m_wcv_n.size () && p < m_wcv_s.size ());

  int *wcv = north ? &m_wcv_n [p] : &m_wcv_s [p];
  int *wca = north ? &m_wc_na : &m_wc_sa;
  int *wcb = north ? &m_wc_nb : &m_wc_sb;

  bool inside_before = ((p % 2) == 0 ? inside_a (*wcv) : inside_b (*wcv));
  *wcv += (enter ? 1 : -1);
  bool inside_after = ((p % 2) == 0 ? inside_a (*wcv) : inside_b (*wcv));
  m_zeroes += (!inside_after) - (!inside_before);
#ifdef DEBUG_BOOLEAN
  printf ("north=%d, enter=%d, prop=%d -> %d\n", north, enter, p, m_zeroes);
#endif
  tl_assert (long (m_zeroes) >= 0);

  bool res_before = result (*wca, *wcb, inside_a, inside_b);
  if (inside_before != inside_after) {
    if ((p % 2) == 0) {
      *wca += (inside_after - inside_before);
    } else {
      *wcb += (inside_after - inside_before);
    }
  }
  bool res_after = result (*wca, *wcb, inside_a, inside_b);

  return res_after - res_before;
}

template <class InsideFunc> 
inline int 
BooleanOp::compare_ns_impl (const InsideFunc &inside_a, const InsideFunc &inside_b) const
{
  return result (m_wc_na, m_wc_nb, inside_a, inside_b) - result (m_wc_sa, m_wc_sb, inside_a, inside_b);
}

int 
BooleanOp::edge (bool north, bool enter, property_type p)
{
  NonZeroInsideFunc inside;
  return edge_impl (north, enter, p, inside, inside);
}

int 
BooleanOp::compare_ns () const
{
  NonZeroInsideFunc inside;
  return compare_ns_impl (inside, inside);
}

// -------------------------------------------------------------------------------
//  BooleanOp2 implementation

BooleanOp2::BooleanOp2 (BoolOp op, int wc_mode_a, int wc_mode_b)
  : BooleanOp (op), m_wc_mode_a (wc_mode_a), m_wc_mode_b (wc_mode_b)
{
  //  .. nothing yet ..
}

int 
BooleanOp2::edge (bool north, bool enter, property_type p)
{
  ParametrizedInsideFunc inside_a (m_wc_mode_a);
  ParametrizedInsideFunc inside_b (m_wc_mode_b);
  return edge_impl (north, enter, p, inside_a, inside_b);
}

int 
BooleanOp2::compare_ns () const
{
  ParametrizedInsideFunc inside_a (m_wc_mode_a);
  ParametrizedInsideFunc inside_b (m_wc_mode_b);
  return compare_ns_impl (inside_a, inside_b);
}

// -------------------------------------------------------------------------------
//  EdgeProcessor implementation

EdgeProcessor::EdgeProcessor (bool report_progress, const std::string &progress_desc)
  : m_report_progress (report_progress), m_progress_desc (progress_desc)
{
  mp_work_edges = new std::vector <WorkEdge> ();
  mp_cpvector = new std::vector <CutPoints> ();
}

EdgeProcessor::~EdgeProcessor ()
{
  if (mp_work_edges) {
    delete mp_work_edges;
    mp_work_edges = 0;
  }
  if (mp_cpvector) {
    delete mp_cpvector;
    mp_cpvector = 0;
  }
}

void 
EdgeProcessor::disable_progress ()
{
  m_report_progress = false;
}

void 
EdgeProcessor::enable_progress (const std::string &progress_desc)
{
  m_report_progress = true;
  m_progress_desc = progress_desc;
}

void 
EdgeProcessor::reserve (size_t n)
{
  mp_work_edges->reserve (n);
}

void 
EdgeProcessor::insert (const db::Edge &e, EdgeProcessor::property_type p)
{
  if (e.p1 () != e.p2 ()) {
    mp_work_edges->push_back (WorkEdge (e, p));
  }
}

void 
EdgeProcessor::insert (const db::Polygon &q, EdgeProcessor::property_type p)
{
  for (db::Polygon::polygon_edge_iterator e = q.begin_edge (); ! e.at_end (); ++e) {
    insert (*e, p);
  }
}

void 
EdgeProcessor::clear ()
{
  mp_work_edges->clear ();
  mp_cpvector->clear ();
}

static void 
get_intersections_per_band_90 (std::vector <CutPoints> &cutpoints, std::vector <WorkEdge>::iterator current, std::vector <WorkEdge>::iterator future, db::Coord y, db::Coord yy, bool with_h)
{
  std::sort (current, future, edge_xmin_compare<db::Coord> ());

#ifdef DEBUG_EDGE_PROCESSOR
  printf ("y=%d..%d (90 degree)\n", y, yy);
  printf ("edges:"); 
  for (std::vector <WorkEdge>::iterator c1 = current; c1 != future; ++c1) { 
    printf (" %s", c1->to_string().c_str ()); 
  } 
  printf ("\n");
#endif
  db::Coord x = edge_xmin (*current);

  std::vector <WorkEdge>::iterator f = current;
  for (std::vector <WorkEdge>::iterator c = current; c != future; ) {

    while (f != future && edge_xmin (*f) <= x) {
      ++f;
    }

    db::Coord xx = std::numeric_limits <db::Coord>::max ();
    if (f != future) {
      xx = edge_xmin (*f);
    }

#ifdef DEBUG_EDGE_PROCESSOR
    printf ("edges %d..%d:", x, xx); 
    for (std::vector <WorkEdge>::iterator c1 = c; c1 != f; ++c1) { 
      printf (" %s", c1->to_string().c_str ()); 
    } 
    printf ("\n");
#endif

    if (std::distance (c, f) > 1) {

      db::Box cell (x, y, xx, yy);

      for (std::vector <WorkEdge>::iterator c1 = c; c1 != f; ++c1) {

        bool c1p1_in_cell = cell.contains (c1->p1 ());
        bool c1p2_in_cell = cell.contains (c1->p2 ());

        for (std::vector <WorkEdge>::iterator c2 = c; c2 != f; ++c2) {

          if (c1 == c2) {
            continue;
          }

          if (c2->dy () == 0) {

            if ((with_h || c1->dy () != 0) && c1 < c2 && c1->p1 () != c2->p1 () && c1->p2 () != c2->p1 () &&
                                                         c1->p1 () != c2->p2 () && c1->p2 () != c2->p2 ()) {

              std::pair <bool, db::Point> cp = c1->intersect_point (*c2);
              if (cp.first) {
                
                //  add a cut point to c1 and c2 (c2 only if necessary)
                c1->make_cutpoints (cutpoints)->add (cp.second, &cutpoints, true);
                if (with_h) {
                  c2->make_cutpoints (cutpoints)->add (cp.second, &cutpoints, true);
                }

#ifdef DEBUG_EDGE_PROCESSOR
                printf ("intersection point %s between %s and %s (1).\n", cp.second.to_string ().c_str (), c1->to_string ().c_str (), c2->to_string ().c_str ()); 
#endif

              }

            } 
          
          } else if (c1->dy () == 0) {
            
            if (c1 < c2 && c1->p1 () != c2->p1 () && c1->p2 () != c2->p1 () &&
                           c1->p1 () != c2->p2 () && c1->p2 () != c2->p2 ()) {

              std::pair <bool, db::Point> cp = c1->intersect_point (*c2);
              if (cp.first) {
                
                //  add a cut point to c1 and c2
                c2->make_cutpoints (cutpoints)->add (cp.second, &cutpoints, true);
                if (with_h) {
                  c1->make_cutpoints (cutpoints)->add (cp.second, &cutpoints, true);
                }

#ifdef DEBUG_EDGE_PROCESSOR
                printf ("intersection point %s between %s and %s (2).\n", cp.second.to_string ().c_str (), c1->to_string ().c_str (), c2->to_string ().c_str ()); 
#endif

              }

            }

          } else if (c1->p1 ().x () == c2->p1 ().x ()) {

            //  both edges are coincident - produce the ends of the edges involved as cut points
            if (c1p1_in_cell && c1->p1 ().y () > db::edge_ymin (*c2) && c1->p1 ().y () < db::edge_ymax (*c2)) {
              c2->make_cutpoints (cutpoints)->add (c1->p1 (), &cutpoints, true);
            }
            if (c1p2_in_cell && c1->p2 ().y () > db::edge_ymin (*c2) && c1->p2 ().y () < db::edge_ymax (*c2)) {
              c2->make_cutpoints (cutpoints)->add (c1->p2 (), &cutpoints, true);
            }

          }

        }

      }

    }

    x = xx;
    for (std::vector <WorkEdge>::iterator cc = c; cc != f; ++cc) {
      if (edge_xmax (*cc) < x) {
        if (c != cc) {
          std::swap (*cc, *c);
        }
        ++c;
      }
    }

  }
}

/**
 *  @brief Computes the x value of an edge at the given y value
 *
 *  HINT: for application in the scanline algorithm 
 *  it is important that this method delivers exactly (!) the same x for the same edge 
 *  (after normalization to dy()>0) and same y!
 */
template <class C>
inline double edge_xaty_double (db::edge<C> e, double y)
{
  if (e.p1 ().y () > e.p2 ().y ()) {
    e.swap_points ();
  }

  if (y <= e.p1 ().y ()) {
    return e.p1 ().x ();
  } else if (y >= e.p2 ().y ()) {
    return e.p2 ().x ();
  } else {
    return double (e.p1 ().x ()) + double (e.dx ()) * (y - double (e.p1 ().y ())) / double (e.dy ());
  }
}

/**
 *  @brief Computes the left bound of the edge geometry for a given band [y1..y2].
 */
template <class C>
inline C edge_xmin_at_yinterval_double (const db::edge<C> &e, double y1, double y2) 
{
  if (e.dx () == 0) {
    return e.p1 ().x ();
  } else if (e.dy () == 0) {
    return std::min (e.p1 ().x (), e.p2 ().x ());
  } else {
    return C (floor (edge_xaty_double (e, ((e.dy () < 0) ^ (e.dx () < 0)) == 0 ? y1 : y2)));
  }
}

/**
 *  @brief Computes the right bound of the edge geometry for a given band [y1..y2].
 */
template <class C>
inline C edge_xmax_at_yinterval_double (const db::edge<C> &e, double y1, double y2) 
{
  if (e.dx () == 0) {
    return e.p1 ().x ();
  } else if (e.dy () == 0) {
    return std::max (e.p1 ().x (), e.p2 ().x ());
  } else {
    return C (ceil (edge_xaty_double (e, ((e.dy () < 0) ^ (e.dx () < 0)) != 0 ? y1 : y2)));
  }
}

/**
 *  @brief Functor that compares two edges by their left bound for a given interval [y1..y2].
 *
 *  This function is intended for use in scanline scenarious to determine what edges are 
 *  interacting in a certain y interval.
 */
template <class C>
struct edge_xmin_at_yinterval_double_compare
{
  edge_xmin_at_yinterval_double_compare (double y1, double y2)
    : m_y1 (y1), m_y2 (y2)
  {
    // .. nothing yet ..
  }

  bool operator() (const db::edge<C> &a, const db::edge<C> &b) const
  {
    if (edge_xmax (a) < edge_xmin (b)) {
      return true;
    } else if (edge_xmin (a) >= edge_xmax (b)) {
      return false;
    } else {
      C xa = edge_xmin_at_yinterval_double (a, m_y1, m_y2);
      C xb = edge_xmin_at_yinterval_double (b, m_y1, m_y2);
      if (xa != xb) {
        return xa < xb;
      } else {
        return a < b;
      }
    }
  }

public:
  double m_y1, m_y2;
};

static void 
get_intersections_per_band_any (std::vector <CutPoints> &cutpoints, std::vector <WorkEdge>::iterator current, std::vector <WorkEdge>::iterator future, db::Coord y, db::Coord yy, bool with_h)
{
  std::vector <WorkEdge *> p1_weak; // holds weak interactions of edge endpoints with other edges
  std::vector <WorkEdge *> ip_weak;
  double dy = y - 0.5;
  double dyy = yy + 0.5;

  std::sort (current, future, edge_xmin_at_yinterval_double_compare<db::Coord> (dy, dyy));

#ifdef DEBUG_EDGE_PROCESSOR
  printf ("y=%d..%d\n", y, yy);
  printf ("edges:"); 
  for (std::vector <WorkEdge>::iterator c1 = current; c1 != future; ++c1) { 
    printf (" %s", c1->to_string().c_str ()); 
  } 
  printf ("\n");
#endif
  db::Coord x = edge_xmin_at_yinterval_double (*current, dy, dyy);

  std::vector <WorkEdge>::iterator f = current;
  for (std::vector <WorkEdge>::iterator c = current; c != future; ) {

    while (f != future && edge_xmin_at_yinterval_double (*f, dy, dyy) <= x) {
      ++f;
    }

    db::Coord xx = std::numeric_limits <db::Coord>::max ();
    if (f != future) {
      xx = edge_xmin_at_yinterval_double (*f, dy, dyy);
    }

#ifdef DEBUG_EDGE_PROCESSOR
    printf ("edges %d..%d:", x, xx); 
    for (std::vector <WorkEdge>::iterator c1 = c; c1 != f; ++c1) { 
      printf (" %s", c1->to_string().c_str ()); 
    } 
    printf ("\n");
#endif

    if (std::distance (c, f) > 1) {

      db::Box cell (x, y, xx, yy);

      for (std::vector <WorkEdge>::iterator c1 = c; c1 != f; ++c1) {

        p1_weak.clear (); 

        bool c1p1_in_cell = cell.contains (c1->p1 ());
        bool c1p2_in_cell = cell.contains (c1->p2 ());

        for (std::vector <WorkEdge>::iterator c2 = c; c2 != f; ++c2) {

          if (c1 == c2) {
            continue;
          }

          if (c2->dy () == 0) {

            if ((with_h || c1->dy () != 0) && c1 < c2 && c1->p1 () != c2->p1 () && c1->p2 () != c2->p1 () &&
                                                         c1->p1 () != c2->p2 () && c1->p2 () != c2->p2 ()) {

              std::pair <bool, db::Point> cp = safe_intersect_point (*c1, *c2);
              if (cp.first) {

                bool on_edge1 = is_point_on_exact (*c1, cp.second);
                
                //  add a cut point to c1 and c2 (points not on the edge give strong attractors)
                c1->make_cutpoints (cutpoints)->add (cp.second, &cutpoints, !on_edge1);
                if (with_h) {
                  c2->make_cutpoints (cutpoints)->add (cp.second, &cutpoints, false);
                }

#ifdef DEBUG_EDGE_PROCESSOR
                if (on_edge1) {
                  printf ("weak intersection point %s between %s and %s.\n", cp.second.to_string ().c_str (), c1->to_string ().c_str (), c2->to_string ().c_str ()); 
                } else {
                  printf ("intersection point %s between %s and %s.\n", cp.second.to_string ().c_str (), c1->to_string ().c_str (), c2->to_string ().c_str ()); 
                }
#endif

                //  The new cutpoint must be inserted into other edges as well.
                //  If the cutpoint is exactly on the edge and there is just one other edge
                //  the cutpoint will be a weak attractor - that is an optional cutpoint.
                //  In that case we can skip the cutpoint because no related edge will move.
                ip_weak.clear ();
                for (std::vector <WorkEdge>::iterator cc = c; cc != f; ++cc) {
                  if ((with_h || cc->dy () != 0) && cc != c1 && cc != c2 && is_point_on_fuzzy (*cc, cp.second)) {
                    ip_weak.push_back (&*cc);
                  }
                }
                for (std::vector <WorkEdge *>::iterator icc = ip_weak.begin (); icc != ip_weak.end (); ++icc) {
                  if (ip_weak.size () > 1 || !on_edge1) {
                    (*icc)->make_cutpoints (cutpoints)->add (cp.second, &cutpoints, true);
#ifdef DEBUG_EDGE_PROCESSOR
                    printf ("intersection point %s gives cutpoint in %s.\n", cp.second.to_string ().c_str (), (*icc)->to_string ().c_str ()); 
#endif
                  } else {
                    CutPoints *cpp = (*icc)->make_cutpoints (cutpoints);
                    cpp->add_attractor (cp.second, (*icc)->data - 1);
#ifdef DEBUG_EDGE_PROCESSOR
                    printf ("intersection point %s gives weak attractor in %s.\n", cp.second.to_string ().c_str (), (*icc)->to_string ().c_str ()); 
#endif
                  }
                }

              }

            } 
          
          } else if (c1->parallel (*c2) && c1->side_of (c2->p1 ()) == 0) {

#ifdef DEBUG_EDGE_PROCESSOR
            printf ("%s and %s are parallel.\n", c1->to_string ().c_str (), c2->to_string ().c_str ()); 
#endif

            //  both edges are coincident - produce the ends of the edges involved as cut points
            if (c1p1_in_cell && c2->contains (c1->p1 ()) && c2->p1 () != c1->p1 () && c2->p2 () != c1->p1 ()) {
              c2->make_cutpoints (cutpoints)->add (c1->p1 (), &cutpoints, !is_point_on_exact(*c2, c1->p1 ()));
#ifdef DEBUG_EDGE_PROCESSOR
              if (! is_point_on_exact(*c2, c1->p1 ())) {
                printf ("intersection point %s between %s and %s.\n", c1->p1 ().to_string ().c_str (), c1->to_string ().c_str (), c2->to_string ().c_str ()); 
              } else {
                printf ("weak intersection point %s between %s and %s.\n", c1->p1 ().to_string ().c_str (), c1->to_string ().c_str (), c2->to_string ().c_str ()); 
              }
#endif
            }
            if (c1p2_in_cell && c2->contains (c1->p2 ()) && c2->p1 () != c1->p2 () && c2->p2 () != c1->p2 ()) {
              c2->make_cutpoints (cutpoints)->add (c1->p2 (), &cutpoints, !is_point_on_exact(*c2, c1->p2 ()));
#ifdef DEBUG_EDGE_PROCESSOR
              if (! is_point_on_exact(*c2, c1->p2 ())) {
                printf ("intersection point %s between %s and %s.\n", c1->p2 ().to_string ().c_str (), c1->to_string ().c_str (), c2->to_string ().c_str ()); 
              } else {
                printf ("weak intersection point %s between %s and %s.\n", c1->p2 ().to_string ().c_str (), c1->to_string ().c_str (), c2->to_string ().c_str ()); 
              }
#endif
            }

          } else {

            if (c1 < c2 && c1->p1 () != c2->p1 () && c1->p2 () != c2->p1 () &&
                           c1->p1 () != c2->p2 () && c1->p2 () != c2->p2 ()) {

              std::pair <bool, db::Point> cp = safe_intersect_point (*c1, *c2);
              if (cp.first) {
                
                bool on_edge1 = true;
                bool on_edge2 = is_point_on_exact (*c2, cp.second);

                //  add a cut point to c1 and c2
                if (with_h || c1->dy () != 0) {
                  on_edge1 = is_point_on_exact (*c1, cp.second);
                  c1->make_cutpoints (cutpoints)->add (cp.second, &cutpoints, !on_edge1);
                }

                c2->make_cutpoints (cutpoints)->add (cp.second, &cutpoints, !on_edge2);

#ifdef DEBUG_EDGE_PROCESSOR
                if (!on_edge1 || !on_edge2) {
                  printf ("intersection point %s between %s and %s.\n", cp.second.to_string ().c_str (), c1->to_string ().c_str (), c2->to_string ().c_str ()); 
                } else {
                  printf ("weak intersection point %s between %s and %s.\n", cp.second.to_string ().c_str (), c1->to_string ().c_str (), c2->to_string ().c_str ()); 
                }
#endif

                //  The new cutpoint must be inserted into other edges as well.
                //  If the cutpoint is exactly on the edge and there is just one other edge
                //  the cutpoint will be a weak attractor - that is an optional cutpoint.
                //  In that case we can skip the cutpoint because no related edge will move.
                ip_weak.clear ();
                for (std::vector <WorkEdge>::iterator cc = c; cc != f; ++cc) {
                  if ((with_h || cc->dy () != 0) && cc != c1 && cc != c2 && is_point_on_fuzzy (*cc, cp.second)) {
                    ip_weak.push_back (&*cc);
                  }
                }
                for (std::vector <WorkEdge *>::iterator icc = ip_weak.begin (); icc != ip_weak.end (); ++icc) {
                  if (ip_weak.size () > 1 || !on_edge1 || !on_edge2) {
                    (*icc)->make_cutpoints (cutpoints)->add (cp.second, &cutpoints, true);
#ifdef DEBUG_EDGE_PROCESSOR
                    printf ("intersection point %s gives cutpoint in %s.\n", cp.second.to_string ().c_str (), (*icc)->to_string ().c_str ()); 
#endif
                  } else {
                    CutPoints *cpp = (*icc)->make_cutpoints (cutpoints);
                    cpp->add_attractor (cp.second, (*icc)->data - 1);
#ifdef DEBUG_EDGE_PROCESSOR
                    printf ("intersection point %s gives weak attractor in %s.\n", cp.second.to_string ().c_str (), (*icc)->to_string ().c_str ()); 
#endif
                  }
                }

              }

            } 

            //  The endpoints of the other edge must be inserted into the edge 
            //  if they are within the modification range (but only then).
            //  We first collect these endpoints because we have to decide whether that can be 
            //  a weak attractor or, if it affects two or more edges in which case it will become a strong attractor. 
            //  It's sufficient to do this for p1 only because we made sure we caught all edges
            //  in the +-0.5DBU vicinity by choosing the cell large enough (.._double operators).
            //  For end points exactly on the line we insert a cutpoint to ensure we use the
            //  endpoints as cutpoints in any case.
            if (c1p1_in_cell && is_point_on_fuzzy (*c2, c1->p1 ())) {
              if (is_point_on_exact (*c2, c1->p1 ())) {
#ifdef DEBUG_EDGE_PROCESSOR
                printf ("end point %s gives intersection point between %s and %s.\n", c1->p1 ().to_string ().c_str (), c1->to_string ().c_str (), c2->to_string ().c_str ()); 
#endif
                c2->make_cutpoints (cutpoints)->add (c1->p1 (), &cutpoints, true);
              } else {
                p1_weak.push_back (&*c2);
              }
            }

          }

        }

        if (! p1_weak.empty ()) {

          bool strong = false;
          for (std::vector<WorkEdge *>::const_iterator cp = p1_weak.begin (); cp != p1_weak.end () && ! strong; ++cp) {
            if ((*cp)->data > 0 && cutpoints [(*cp)->data - 1].strong_cutpoints) {
              strong = true;
            }
          }

          p1_weak.back ()->make_cutpoints (cutpoints);
          size_t n = p1_weak.back ()->data - 1;
          for (std::vector<WorkEdge *>::const_iterator cp = p1_weak.begin (); cp != p1_weak.end (); ++cp) {

            (*cp)->make_cutpoints (cutpoints);
            size_t nn = (*cp)->data - 1;
            if (strong) {
              cutpoints [nn].add (c1->p1 (), &cutpoints);
#ifdef DEBUG_EDGE_PROCESSOR
              printf ("Insert strong attractor %s in %s.\n", c1->p1 ().to_string ().c_str (), (*cp)->to_string ().c_str ()); 
#endif
            } else {
              cutpoints [nn].add_attractor (c1->p1 (), n);
#ifdef DEBUG_EDGE_PROCESSOR
              printf ("Insert weak attractor %s in %s.\n", c1->p1 ().to_string ().c_str (), (*cp)->to_string ().c_str ()); 
#endif
            }

            n = nn;

          }

        }

      }

    }

    x = xx;
    for (std::vector <WorkEdge>::iterator cc = c; cc != f; ++cc) {
      if (edge_xmax (*cc) < x || edge_xmax_at_yinterval_double (*cc, dy, dyy) < x) {
        if (c != cc) {
          std::swap (*cc, *c);
        }
        ++c;
      }
    }

  }
}

void 
EdgeProcessor::process (db::EdgeSink &es, EdgeEvaluatorBase &op)
{
  tl::SelfTimer timer (tl::verbosity () >= 31, "EdgeProcessor: process");

  bool prefer_touch = op.prefer_touch (); 
  bool selects_edges = op.selects_edges (); 
  
  db::Coord y;
  std::vector <WorkEdge>::iterator future;

  //  step 1: preparation

  if (mp_work_edges->empty ()) {
    es.start ();
    es.flush ();
    return;
  }

  mp_cpvector->clear ();

  property_type n_props = 0;
  for (std::vector <WorkEdge>::iterator e = mp_work_edges->begin (); e != mp_work_edges->end (); ++e) {
    if (e->prop > n_props) {
      n_props = e->prop;
    }
  }
  ++n_props;

  size_t todo_max = 1000000;

  std::auto_ptr<tl::AbsoluteProgress> progress (0);
  if (m_report_progress) {
    if (m_progress_desc.empty ()) {
      progress.reset (new tl::AbsoluteProgress (tl::to_string (QObject::tr ("Processing")), 1000));
    } else {
      progress.reset (new tl::AbsoluteProgress (m_progress_desc, 1000));
    }
    progress->set_format (tl::to_string (QObject::tr ("%.0f%%")));
    progress->set_res (1.0);
    progress->set_unit (todo_max / 100);
  }

  size_t todo_next = 0;
  size_t todo = todo_next;
  todo_next += (todo_max - todo) / 5;


  //  step 2: find intersections
  std::sort (mp_work_edges->begin (), mp_work_edges->end (), edge_ymin_compare<db::Coord> ());

  y = edge_ymin ((*mp_work_edges) [0]);
  future = mp_work_edges->begin ();

  for (std::vector <WorkEdge>::iterator current = mp_work_edges->begin (); current != mp_work_edges->end (); ) {

    if (m_report_progress) {
      double p = double (std::distance (mp_work_edges->begin (), current)) / double (mp_work_edges->size ());
      progress->set (size_t (double (todo_next - todo) * p) + todo);
    }

    size_t n = std::distance (current, future);
    db::Coord yy = y;

    //  Use as many scanlines as to fetch approx. 50% new edges into the scanline (this
    //  is an empirically determined factor)
    do {

      while (future != mp_work_edges->end () && edge_ymin (*future) <= yy) {
        ++future;
      }

      if (future != mp_work_edges->end ()) {
        yy = edge_ymin (*future);
      } else {
        yy = std::numeric_limits <db::Coord>::max ();
      }

    } while (future != mp_work_edges->end () && std::distance (current, future) < long (n + n / 2));

    bool is90 = true;

    if (current != future) {

      for (std::vector <WorkEdge>::iterator c = current; c != future && is90; ++c) {
        if (c->dx () != 0 && c->dy () != 0) {
          is90 = false;
        }
      }

      if (is90) {
        get_intersections_per_band_90 (*mp_cpvector, current, future, y, yy, selects_edges);
      } else {
        get_intersections_per_band_any (*mp_cpvector, current, future, y, yy, selects_edges);
      }

    }

    y = yy;
    for (std::vector <WorkEdge>::iterator c = current; c != future; ++c) {
      //  Hint: we have to keep the edges ending a y (the new lower band limit) in the all angle case because these edges
      //  may receive cutpoints because the enter the -0.5DBU region below the band
      if ((!is90 && edge_ymax (*c) < y) || (is90 && edge_ymax (*c) <= y)) {
        if (current != c) {
          std::swap (*current, *c);
        }
        ++current;
      }
    }
    
  }

  //  step 3: create new edges from the ones with cutpoints
  //
  //  Hint: when we create the edges from the cutpoints we use the projection to sort the cutpoints along the
  //  edge. However, we have some freedom to connect the points which we use to avoid "z" configurations which could
  //  create new intersections in a 1x1 pixel box.
  
  todo = todo_next;
  todo_next += (todo_max - todo) / 5;

  size_t n_work = mp_work_edges->size ();
  size_t nw = 0;
  for (size_t n = 0; n < n_work; ++n) {

    if (m_report_progress) {
      double p = double (n) / double (n_work);
      progress->set (size_t (double (todo_next - todo) * p) + todo);
    }

    WorkEdge &ew = (*mp_work_edges) [n];

    CutPoints *cut_points = ew.data ? & ((*mp_cpvector) [ew.data - 1]) : 0;
    ew.data = 0;

    if (ew.dy () == 0 && ! selects_edges) {

      //  don't care about horizontal edges 

    } else if (cut_points) {

      if (cut_points->has_cutpoints && ! cut_points->cut_points.empty ()) {

        db::Edge e = ew;
        property_type p = ew.prop;
        std::sort (cut_points->cut_points.begin (), cut_points->cut_points.end (), ProjectionCompare (e));

        db::Point pll = e.p1 ();
        db::Point pl = e.p1 ();

        for (std::vector <db::Point>::iterator cp = cut_points->cut_points.begin (); cp != cut_points->cut_points.end (); ++cp) {
          if (*cp != pl) {
            WorkEdge ne = WorkEdge (db::Edge (pl, *cp), p);
            if (pl.y () == pll.y () && ne.p2 ().x () != pl.x () && ne.p2 ().x () == pll.x ()) {
              ne = db::Edge (pll, ne.p2 ());
            } else if (pl.x () == pll.x () && ne.p2 ().y () != pl.y () && ne.p2 ().y () == pll.y ()) {
              ne = db::Edge (ne.p1 (), pll);
            } else {
              pll = pl;
            }
            pl = *cp;
            if (selects_edges || ne.dy () != 0) {
              if (nw <= n) {
                (*mp_work_edges) [nw++] = ne;
              } else {
                mp_work_edges->push_back (ne);
              }
            }
          }
        }

        if (cut_points->cut_points.back () != e.p2 ()) {
          WorkEdge ne = WorkEdge (db::Edge (pl, e.p2 ()), p);
          if (pl.y () == pll.y () && ne.p2 ().x () != pl.x () && ne.p2 ().x () == pll.x ()) {
            ne = db::Edge (pll, ne.p2 ());
          } else if (pl.x () == pll.x () && ne.p2 ().y () != pl.y () && ne.p2 ().y () == pll.y ()) {
            ne = db::Edge (ne.p1 (), pll);
          }
          if (selects_edges || ne.dy () != 0) {
            if (nw <= n) {
              (*mp_work_edges) [nw++] = ne;
            } else {
              mp_work_edges->push_back (ne);
            }
          }
        }

      } else {

        if (nw < n) {
          (*mp_work_edges) [nw] = (*mp_work_edges) [n];
        }
        ++nw;

      }

    } else {

      if (nw < n) {
        (*mp_work_edges) [nw] = (*mp_work_edges) [n];
      }
      ++nw;

    }

  }

  if (nw != n_work) {
    mp_work_edges->erase (mp_work_edges->begin () + nw, mp_work_edges->begin () + n_work);
  }

#ifdef DEBUG_EDGE_PROCESSOR
  printf ("Output edges:\n");
  for (std::vector <WorkEdge>::iterator c1 = mp_work_edges->begin (); c1 != mp_work_edges->end (); ++c1) { 
    printf ("%s\n", c1->to_string().c_str ()); 
  } 
#endif


  tl::SelfTimer timer2 (tl::verbosity () >= 41, "EdgeProcessor: production");

  //  step 4: compute the result edges 
  
  es.start (); // call this as late as possible. This way, input containers can be identical with output containers ("clear" is done after the input is read)

  op.reset ();
  op.reserve (n_props);

  std::sort (mp_work_edges->begin (), mp_work_edges->end (), edge_ymin_compare<db::Coord> ());

  y = edge_ymin ((*mp_work_edges) [0]);
  size_t skip_unit = 1;

  future = mp_work_edges->begin ();
  for (std::vector <WorkEdge>::iterator current = mp_work_edges->begin (); current != mp_work_edges->end (); ) {

    if (m_report_progress) {
      double p = double (std::distance (mp_work_edges->begin (), current)) / double (mp_work_edges->size ());
      progress->set (size_t (double (todo_max - todo_next) * p) + todo_next);
    }

    std::vector <WorkEdge>::iterator f0 = future;
    while (future != mp_work_edges->end () && edge_ymin (*future) <= y) {
      tl_assert (future->data == 0); // HINT: for development
      ++future;
    }
    std::sort (f0, future, EdgeXAtYCompare2 (y));

    db::Coord yy = std::numeric_limits <db::Coord>::max ();
    if (future != mp_work_edges->end ()) {
      yy = edge_ymin (*future);
    }
    for (std::vector <WorkEdge>::const_iterator c = current; c != future; ++c) {
      if (edge_ymax (*c) > y) {
        yy = std::min (yy, edge_ymax (*c));
      }
    }

    db::Coord ysl = y;
    es.begin_scanline (y);

    tl_assert (op.is_reset ()); // HINT: for development

    if (current != future) {

      std::inplace_merge (current, f0, future, EdgeXAtYCompare2 (y));
#ifdef DEBUG_EDGE_PROCESSOR
      printf ("y=%d ", y);
      for (std::vector <WorkEdge>::iterator c = current; c != future; ++c) { 
        printf ("%ld-", long (c->data)); 
      } 
      printf ("\n");
#endif

      db::Coord hx = 0;
      int ho = 0;

      size_t new_skip_unit = std::distance (current, future);

      for (std::vector <WorkEdge>::iterator c = current; c != future; ) {

        size_t skip = c->data % skip_unit;
        size_t skip_res = c->data / skip_unit;
#ifdef DEBUG_EDGE_PROCESSOR
        printf ("X %ld->%d,%d\n", long(c->data), skip, skip_res);
#endif

        if (skip != 0 && (c + skip >= future || (c + skip)->data != 0)) {

          tl_assert (c + skip <= future);

          es.skip_n (skip_res);

          c->data = skip + new_skip_unit * skip_res;

          //  skip this interval - has not changed
          c += skip;

        } else {

          std::vector <WorkEdge>::iterator c0 = c;
          size_t n_res = 0;

          do {

            c->data = 0;
            std::vector <WorkEdge>::iterator f = c + 1;

            //  HINT: "volatile" forces x and xx into memory and disables FPU register optimisation.
            //  That way, we can exactly compare doubles afterwards.
            volatile double x = edge_xaty (*c, y);

            while (f != future) {
              volatile double xx = edge_xaty (*f, y);
              if (xx != x) {
                break;
              }
              f->data = 0;
              ++f;
            }

            //  compute edges that occure at this vertex
            
            bool vertex = false;
            
            //  treat all edges crossing the scanline in a certain point
            for (std::vector <WorkEdge>::iterator cc = c; cc != f; ) {

              std::vector <WorkEdge>::iterator e = mp_work_edges->end ();

              int pn = 0, ps = 0;

              std::vector <WorkEdge>::iterator cc0 = cc;

              std::vector <WorkEdge>::iterator fc = cc;
              do {
                ++fc;
              } while (fc != f && EdgeXAtYCompare2 (y).equal (*fc, *cc));

              //  sort the coincident edges by property ID - that will
              //  simplify algorithms like "inside" and "outside".
              if (fc - cc > 1) {
                //  for prefer_touch we first deliver the opening edges in ascending
                //  order, in the other case we the other way round so that the opening
                //  edges are always delivered with ascending property ID order.
                if (prefer_touch) {
                  std::sort (cc, fc, EdgePropCompare ());
                } else {
                  std::sort (cc, fc, EdgePropCompareReverse ());
                }
              }

              //  treat all coincident edges
              do {

                if (cc->dy () != 0) {

                  if (e == mp_work_edges->end () && edge_ymax (*cc) > y) {
                    e = cc;
                  }
                  
                  if ((cc->dy () > 0) == prefer_touch) {
                    if (edge_ymax (*cc) > y) {
                      pn += op.edge (true, prefer_touch, cc->prop);
                    }
                    if (edge_ymin (*cc) < y) {
                      ps += op.edge (false, prefer_touch, cc->prop);
                    }
                  }

                }

                ++cc;

              } while (cc != fc);

              //  Give the edge selection operator a chance to select edges now
              if (selects_edges) {

                for (std::vector <WorkEdge>::iterator sc = cc0; sc != fc; ++sc) {
                  if (edge_ymin (*sc) == y && op.select_edge (sc->dy () == 0, sc->prop)) {
                    es.put (*sc);
#ifdef DEBUG_EDGE_PROCESSOR
                    printf ("put(%s)\n", sc->to_string().c_str());
#endif
                  }
                }

              }

              //  report the closing or opening edges in the opposite order 
              //  than the other ones (see previous loop). Hence we have some
              //  symmetry of events which simplify implementatin of the 
              //  InteractionDetector for example.
              do {

                --fc;

                if (fc->dy () != 0 && (fc->dy () > 0) != prefer_touch) {
                  if (edge_ymax (*fc) > y) {
                    pn += op.edge (true, ! prefer_touch, fc->prop);
                  }
                  if (edge_ymin (*fc) < y) {
                    ps += op.edge (false, ! prefer_touch, fc->prop);
                  }
                }

              } while (fc != cc0);

              if (! vertex && (ps != 0 || pn != 0)) {

                if (ho != 0) {
                  db::Edge he (db::Point (hx, y), db::Point (db::coord_traits<db::Coord>::rounded (x), y));
                  if (ho > 0) {
                    he.swap_points ();
                  }
                  es.put (he);
#ifdef DEBUG_EDGE_PROCESSOR
                  printf ("put(%s)\n", he.to_string().c_str());
#endif
                } 

                vertex = true;

              }

              if (e != mp_work_edges->end ()) {

                db::Edge edge (*e);

                if ((pn > 0 && edge.dy () < 0) || (pn < 0 && edge.dy () > 0)) {
                  edge.swap_points ();
                }

                if (pn != 0) {
                  ++n_res;
                  if (edge_ymin (edge) == y) {
                    es.put (edge);
#ifdef DEBUG_EDGE_PROCESSOR
                    printf ("put(%s)\n", edge.to_string().c_str());
#endif
                  } else {
                    es.crossing_edge (edge);
#ifdef DEBUG_EDGE_PROCESSOR
                    printf ("xing(%s)\n", edge.to_string().c_str());
#endif
                  }
                }

              }

            }

            if (vertex) {
              hx = db::coord_traits<db::Coord>::rounded (x);
              ho = op.compare_ns ();
            }

            c = f;

          } while (c != future && ! op.is_reset ());

          //  TODO: assert that there is no overflow here:
          c0->data = size_t (std::distance (c0, c) + new_skip_unit * n_res);

        }

      }

      skip_unit = new_skip_unit;

      y = yy;

#ifdef DEBUG_EDGE_PROCESSOR
      for (std::vector <WorkEdge>::iterator c = current; c != future; ++c) { 
        printf ("%ld-", long (c->data)); 
      } 
      printf ("\n");
#endif
      std::vector <WorkEdge>::iterator c0 = current;
      std::vector <WorkEdge>::iterator last_interval = future;
      current = future;

      bool valid = true;

      for (std::vector <WorkEdge>::iterator c = future; c != c0; ) {

        --c;

        bool start_interval = (c->data != 0);
        size_t data = c->data;
        c->data = 0;

        db::Coord ymax = edge_ymax (*c);
        if (ymax >= y) {
          --current;
          if (current != c) {
            *current = *c;
          }
        }
        if (ymax <= y) {
          //  an edge ends now. The interval is not valid, i.e. cannot be skipped easily.
          valid = false;
        }

        if (start_interval && current != future) {
          current->data = valid ? data : 0;
          last_interval = current;
          valid = true;
        }

      }
#ifdef DEBUG_EDGE_PROCESSOR
      for (std::vector <WorkEdge>::iterator c = current; c != future; ++c) { 
        printf ("%ld-", long (c->data)); 
      } 
      printf ("\n");
#endif
    
    }

    tl_assert (op.is_reset ()); // HINT: for development (second)

    es.end_scanline (ysl);

  }

  es.flush ();

}

void
EdgeProcessor::simple_merge (const std::vector<db::Edge> &in, std::vector <db::Edge> &edges, int mode)
{
  clear ();
  reserve (in.size ());
  insert_sequence (in.begin (), in.end ());

  db::SimpleMerge op (mode);
  db::EdgeContainer out (edges);
  process (out, op);
}

void
EdgeProcessor::simple_merge (const std::vector<db::Edge> &in, std::vector <db::Polygon> &polygons, bool resolve_holes, bool min_coherence, int mode)
{
  clear ();
  reserve (in.size ());
  insert_sequence (in.begin (), in.end ());

  db::SimpleMerge op (mode);
  db::PolygonContainer pc (polygons);
  db::PolygonGenerator out (pc, resolve_holes, min_coherence);
  process (out, op);
}

void
EdgeProcessor::simple_merge (const std::vector<db::Polygon> &in, std::vector <db::Edge> &edges, int mode)
{
  clear ();
  reserve (count_edges (in));
  for (std::vector<db::Polygon>::const_iterator q = in.begin (); q != in.end (); ++q) {
    insert (*q);
  }

  db::SimpleMerge op (mode);
  db::EdgeContainer out (edges);
  process (out, op);
}

void
EdgeProcessor::simple_merge (const std::vector<db::Polygon> &in, std::vector <db::Polygon> &out, bool resolve_holes, bool min_coherence, int mode)
{
  clear ();
  reserve (count_edges (in));

  if (&in == &out) {
    while (! out.empty ()) {
      insert (out.back ());
      out.pop_back ();
    }
  } else {
    for (std::vector<db::Polygon>::const_iterator q = in.begin (); q != in.end (); ++q) {
      insert (*q);
    }
  }

  db::SimpleMerge op (mode);
  db::PolygonContainer pc (out);
  db::PolygonGenerator pg (pc, resolve_holes, min_coherence);
  process (pg, op);
}

void
EdgeProcessor::merge (const std::vector<db::Polygon> &in, std::vector <db::Edge> &edges, unsigned int min_wc)
{
  clear ();
  reserve (count_edges (in));

  size_t n = 0;
  for (std::vector<db::Polygon>::const_iterator q = in.begin (); q != in.end (); ++q, ++n) {
    insert (*q, n);
  }

  db::MergeOp op (min_wc);
  db::EdgeContainer out (edges);
  process (out, op);
}

void
EdgeProcessor::merge (const std::vector<db::Polygon> &in, std::vector <db::Polygon> &out, unsigned int min_wc, bool resolve_holes, bool min_coherence)
{
  clear ();
  reserve (count_edges (in));

  if (&in == &out) {
    size_t n = 0;
    while (! out.empty ()) {
      insert (out.back (), n);
      out.pop_back ();
      ++n;
    }
  } else {
    size_t n = 0;
    for (std::vector<db::Polygon>::const_iterator q = in.begin (); q != in.end (); ++q, ++n) {
      insert (*q, n);
    }
  }

  db::MergeOp op (min_wc);
  db::PolygonContainer pc (out);
  db::PolygonGenerator pg (pc, resolve_holes, min_coherence);
  process (pg, op);
}

void
EdgeProcessor::size (const std::vector<db::Polygon> &in, db::Coord dx, db::Coord dy, std::vector <db::Edge> &out, unsigned int mode)
{
  clear ();
  reserve (count_edges (in));

  size_t n = 0;
  for (std::vector<db::Polygon>::const_iterator q = in.begin (); q != in.end (); ++q, n += 2) {
    insert (*q, n);
  }

  //  Merge the polygons and feed them into the sizing filter
  db::EdgeContainer ec (out);
  db::SizingPolygonFilter siz (ec, dx, dy, mode);
  db::PolygonGenerator pg (siz, false /*don't resolve holes*/, false /*min. coherence*/);
  db::BooleanOp op (db::BooleanOp::Or);
  process (pg, op);
}

void
EdgeProcessor::size (const std::vector<db::Polygon> &in, db::Coord dx, db::Coord dy, std::vector <db::Polygon> &out, unsigned int mode, bool resolve_holes, bool min_coherence)
{
  clear ();
  reserve (count_edges (in));

  if (&in == &out) {
    size_t n = 0;
    while (! out.empty ()) {
      insert (out.back (), n);
      out.pop_back ();
      n += 2;
    }
  } else {
    size_t n = 0;
    for (std::vector<db::Polygon>::const_iterator q = in.begin (); q != in.end (); ++q, n += 2) {
      insert (*q, n);
    }
  }

  //  Merge the polygons and feed them into the sizing filter
#if ! defined(DEBUG_SIZE_INTERMEDIATE)
  db::PolygonContainer pc (out);
  db::PolygonGenerator pg2 (pc, resolve_holes, min_coherence);
  db::SizingPolygonFilter siz (pg2, dx, dy, mode);
  db::PolygonGenerator pg (siz, false /*don't resolve holes*/, false /*min. coherence*/);
  db::BooleanOp op (db::BooleanOp::Or);
  process (pg, op);
#else
  //  Intermediate output for debugging 
  db::PolygonContainer pc (out);
  db::PolygonGenerator pg2 (pc, false, false);
  db::BooleanOp op (db::BooleanOp::Or);
  process (pg2, op);
  for (std::vector <db::Polygon>::iterator p = out.begin (); p != out.end (); ++p) {
    *p = p->sized (dx, dy, mode);
  }
#endif
}

void 
EdgeProcessor::boolean (const std::vector<db::Polygon> &a, const std::vector<db::Polygon> &b, std::vector <db::Edge> &out, int mode)
{
  clear ();
  reserve (count_edges (a) + count_edges (b));

  size_t n;
  
  n = 0;
  for (std::vector<db::Polygon>::const_iterator q = a.begin (); q != a.end (); ++q, n += 2) {
    insert (*q, n);
  }

  n = 1;
  for (std::vector<db::Polygon>::const_iterator q = b.begin (); q != b.end (); ++q, n += 2) {
    insert (*q, n);
  }

  db::BooleanOp op ((db::BooleanOp::BoolOp) mode);
  db::EdgeContainer ec (out);
  process (ec, op);
}

void 
EdgeProcessor::boolean (const std::vector<db::Polygon> &a, const std::vector<db::Polygon> &b, std::vector <db::Polygon> &out, int mode, bool resolve_holes, bool min_coherence)
{
  clear ();
  reserve (count_edges (a) + count_edges (b));

  size_t n;
  
  n = 0;
  if (&a == &out && &b != &out) {
    while (! out.empty ()) {
      insert (out.back (), n);
      out.pop_back ();
      n += 2;
    }
  } else {
    for (std::vector<db::Polygon>::const_iterator q = a.begin (); q != a.end (); ++q, n += 2) {
      insert (*q, n);
    }
  }

  n = 1;
  if (&b == &out) {
    while (! out.empty ()) {
      insert (out.back (), n);
      out.pop_back ();
      n += 2;
    }
  } else {
    for (std::vector<db::Polygon>::const_iterator q = b.begin (); q != b.end (); ++q, n += 2) {
      insert (*q, n);
    }
  }

  db::BooleanOp op ((db::BooleanOp::BoolOp) mode);
  db::PolygonContainer pc (out);
  db::PolygonGenerator pg (pc, resolve_holes, min_coherence);
  process (pg, op);
}

void 
EdgeProcessor::boolean (const std::vector<db::Edge> &a, const std::vector<db::Edge> &b, std::vector <db::Edge> &out, int mode)
{
  clear ();
  reserve (a.size () + b.size ());

  insert_sequence (a.begin (), a.end (), 0);
  insert_sequence (b.begin (), b.end (), 1);

  db::BooleanOp op ((db::BooleanOp::BoolOp) mode);
  db::EdgeContainer ec (out);
  process (ec, op);
}

void 
EdgeProcessor::boolean (const std::vector<db::Edge> &a, const std::vector<db::Edge> &b, std::vector <db::Polygon> &out, int mode, bool resolve_holes, bool min_coherence)
{
  clear ();
  reserve (a.size () + b.size ());

  insert_sequence (a.begin (), a.end (), 0);
  insert_sequence (b.begin (), b.end (), 1);

  db::BooleanOp op ((db::BooleanOp::BoolOp) mode);
  db::PolygonContainer pc (out);
  db::PolygonGenerator pg (pc, resolve_holes, min_coherence);
  process (pg, op);
}

// -------------------------------------------------------------------------------
//  ShapeProcessor implementation

ShapeProcessor::ShapeProcessor (bool report_progress, const std::string &progress_desc)
  : m_processor (report_progress, progress_desc)
{
  // .. nothing yet ..
}

void 
ShapeProcessor::clear ()
{
  m_processor.clear ();
}

void 
ShapeProcessor::reserve (size_t n)
{
  m_processor.reserve (n);
}

void 
ShapeProcessor::process (db::EdgeSink &es, EdgeEvaluatorBase &op)
{
  m_processor.process (es, op);
}

size_t
ShapeProcessor::count_edges (const db::Shape &shape) const
{
  size_t n = 0;

  if (shape.is_polygon ()) {

    //  KLUDGE: Shape should have a point_count property (or similar)
    for (db::Shape::polygon_edge_iterator e = shape.begin_edge (); ! e.at_end (); ++e) {
      ++n;
    }

  } else if (shape.is_path ()) {

    //  KLUDGE: This is rather inefficient ..
    db::Polygon poly;
    shape.polygon (poly);
    for (db::Polygon::polygon_edge_iterator e = poly.begin_edge (); ! e.at_end (); ++e) {
      ++n;
    }

  } else if (shape.is_box ()) {
    n += 4;
  }

  return n;
}

void
ShapeProcessor::merge (const std::vector<db::Shape> &in, const std::vector<db::CplxTrans> &trans, std::vector <db::Edge> &out_edges, unsigned int min_wc)
{
  clear ();

  size_t e = 0;
  for (std::vector<db::Shape>::const_iterator i = in.begin (); i != in.end (); ++i) {
    e += count_edges (*i);
  }
  reserve (e + e / 4); // heuristic reserve for crossing points

  size_t n = 0;
  for (std::vector<db::Shape>::const_iterator i = in.begin (); i != in.end (); ++i, ++n) {
    if (n < trans.size ()) {
      insert (*i, db::ICplxTrans (trans [n]), n);
    } else {
      insert (*i, n);
    }
  }

  db::MergeOp op (min_wc);
  db::EdgeContainer out (out_edges);
  process (out, op);
}

void
ShapeProcessor::merge (const std::vector<db::Shape> &in, const std::vector<db::CplxTrans> &trans, std::vector <db::Polygon> &out_polygons, unsigned int min_wc, bool resolve_holes, bool min_coherence)
{
  clear ();

  size_t e = 0;
  for (std::vector<db::Shape>::const_iterator i = in.begin (); i != in.end (); ++i) {
    e += count_edges (*i);
  }
  reserve (e + e / 4); // heuristic reserve for crossing points

  size_t n = 0;
  for (std::vector<db::Shape>::const_iterator i = in.begin (); i != in.end (); ++i, ++n) {
    if (n < trans.size ()) {
      insert (*i, db::ICplxTrans (trans [n]), n);
    } else {
      insert (*i, n);
    }
  }

  db::MergeOp op (min_wc);
  db::PolygonContainer pc (out_polygons);
  db::PolygonGenerator out (pc, resolve_holes, min_coherence);
  process (out, op);
}

void
ShapeProcessor::boolean (const std::vector<db::Shape> &in_a, const std::vector<db::CplxTrans> &trans_a,
                         const std::vector<db::Shape> &in_b, const std::vector<db::CplxTrans> &trans_b,
                         int mode, std::vector <db::Edge> &out_edges)
{
  clear ();

  size_t e = 0;
  for (std::vector<db::Shape>::const_iterator i = in_a.begin (); i != in_a.end (); ++i) {
    e += count_edges (*i);
  }
  for (std::vector<db::Shape>::const_iterator i = in_b.begin (); i != in_b.end (); ++i) {
    e += count_edges (*i);
  }
  reserve (e + e / 4); // heuristic reserve for crossing points

  size_t n;
  n = 0;
  for (std::vector<db::Shape>::const_iterator i = in_a.begin (); i != in_a.end (); ++i, ++n) {
    if (n < trans_a.size ()) {
      insert (*i, db::ICplxTrans (trans_a [n]), n * 2);
    } else {
      insert (*i, n * 2);
    }
  }
  n = 0;
  for (std::vector<db::Shape>::const_iterator i = in_b.begin (); i != in_b.end (); ++i, ++n) {
    if (n < trans_b.size ()) {
      insert (*i, db::ICplxTrans (trans_b [n]), n * 2 + 1);
    } else {
      insert (*i, n * 2 + 1);
    }
  }

  db::BooleanOp op ((db::BooleanOp::BoolOp) mode);
  db::EdgeContainer out (out_edges);
  process (out, op);
}

void
ShapeProcessor::boolean (const std::vector<db::Shape> &in_a, const std::vector<db::CplxTrans> &trans_a,
                         const std::vector<db::Shape> &in_b, const std::vector<db::CplxTrans> &trans_b,
                         int mode, std::vector <db::Polygon> &out_polygons, bool resolve_holes, bool min_coherence)
{
  clear ();

  size_t e = 0;
  for (std::vector<db::Shape>::const_iterator i = in_a.begin (); i != in_a.end (); ++i) {
    e += count_edges (*i);
  }
  for (std::vector<db::Shape>::const_iterator i = in_b.begin (); i != in_b.end (); ++i) {
    e += count_edges (*i);
  }
  reserve (e + e / 4); // heuristic reserve for crossing points

  size_t n;
  n = 0;
  for (std::vector<db::Shape>::const_iterator i = in_a.begin (); i != in_a.end (); ++i, ++n) {
    if (n < trans_a.size ()) {
      insert (*i, db::ICplxTrans (trans_a [n]), n * 2);
    } else {
      insert (*i, n * 2);
    }
  }
  n = 0;
  for (std::vector<db::Shape>::const_iterator i = in_b.begin (); i != in_b.end (); ++i, ++n) {
    if (n < trans_b.size ()) {
      insert (*i, db::ICplxTrans (trans_b [n]), n * 2 + 1);
    } else {
      insert (*i, n * 2 + 1);
    }
  }

  db::BooleanOp op ((db::BooleanOp::BoolOp) mode);
  db::PolygonContainer pc (out_polygons);
  db::PolygonGenerator out (pc, resolve_holes, min_coherence);
  process (out, op);
}

void 
ShapeProcessor::size (const std::vector<db::Shape> &in, const std::vector<db::CplxTrans> &trans,
                      db::Coord dx, db::Coord dy, std::vector <db::Polygon> &out, unsigned int mode, bool resolve_holes, bool min_coherence)
{
  //  1st step: merge input
  clear ();

  size_t e = 0;
  for (std::vector<db::Shape>::const_iterator i = in.begin (); i != in.end (); ++i) {
    e += count_edges (*i);
  }
  reserve (e + e / 4); // heuristic reserve for crossing points

  size_t n;
  n = 0;
  for (std::vector<db::Shape>::const_iterator i = in.begin (); i != in.end (); ++i, ++n) {
    if (n < trans.size ()) {
      insert (*i, db::ICplxTrans (trans [n]), n * 2);
    } else {
      insert (*i, n * 2);
    }
  }

  //  Merge the polygons and feed them into the sizing filter
#if ! defined(DEBUG_SIZE_INTERMEDIATE)
  db::PolygonContainer pc (out);
  db::PolygonGenerator pg2 (pc, resolve_holes, min_coherence);
  db::SizingPolygonFilter siz (pg2, dx, dy, mode);
  db::PolygonGenerator pg (siz, false /*don't resolve holes*/, false /*min. coherence*/);
  db::BooleanOp op (db::BooleanOp::Or);
  process (pg, op);
#else
  //  Intermediate output for debugging 
  db::PolygonContainer pc (out);
  db::PolygonGenerator pg2 (pc, false, false);
  db::BooleanOp op (db::BooleanOp::Or);
  process (pg2, op);
  for (std::vector <db::Polygon>::iterator p = out.begin (); p != out.end (); ++p) {
    *p = p->sized (dx, dy, mode);
  }
#endif
}

void 
ShapeProcessor::size (const std::vector<db::Shape> &in, const std::vector<db::CplxTrans> &trans,
                      db::Coord dx, db::Coord dy, std::vector <db::Edge> &out, unsigned int mode)
{
  //  1st step: merge input
  clear ();

  size_t e = 0;
  for (std::vector<db::Shape>::const_iterator i = in.begin (); i != in.end (); ++i) {
    e += count_edges (*i);
  }
  reserve (e + e / 4); // heuristic reserve for crossing points

  size_t n;
  n = 0;
  for (std::vector<db::Shape>::const_iterator i = in.begin (); i != in.end (); ++i, ++n) {
    if (n < trans.size ()) {
      insert (*i, db::ICplxTrans (trans [n]), n * 2);
    } else {
      insert (*i, n * 2);
    }
  }

  //  Merge the polygons and feed them into the sizing filter
  db::EdgeContainer ec (out);
  db::SizingPolygonFilter siz (ec, dx, dy, mode);
  db::PolygonGenerator pg (siz, false /*don't resolve holes*/, false /*min. coherence*/);
  db::BooleanOp op (db::BooleanOp::Or);
  process (pg, op);
}

void 
ShapeProcessor::collect_shapes_hier (const db::CplxTrans &tr, const db::Layout &layout, const db::Cell &cell, unsigned int layer, int hier_levels, size_t &pn, size_t pdelta) 
{
  db::ICplxTrans tri (tr);
  for (db::Shapes::shape_iterator s = cell.shapes (layer).begin (db::ShapeIterator::All); ! s.at_end (); ++s) {
    insert (*s, tri, pn);
    pn += pdelta;
  }

  if (hier_levels != 0) {

    for (db::Cell::const_iterator i = cell.begin (); ! i.at_end (); ++i) {
      for (db::CellInstArray::iterator a = i->begin (); ! a.at_end (); ++a) {
        collect_shapes_hier (tr * i->complex_trans (*a), layout, layout.cell (i->cell_index ()), layer, hier_levels > 0 ? hier_levels - 1 : hier_levels, pn, pdelta);
      }
    }

  }
}

size_t
ShapeProcessor::count_edges_hier (const db::Layout &layout, const db::Cell &cell, unsigned int layer, std::map<std::pair<db::cell_index_type, int>, size_t> &cache, int hier_levels) const
{
  std::map<std::pair<db::cell_index_type, int>, size_t>::iterator c = cache.find (std::make_pair (cell.cell_index (), hier_levels));

  if (c != cache.end ()) {
    return c->second;
  } else {

    size_t n = 0;
    for (db::Shapes::shape_iterator s = cell.shapes (layer).begin (db::ShapeIterator::All); ! s.at_end (); ++s) {
      n += count_edges (*s);
    }

    if (hier_levels != 0) {
      for (db::Cell::const_iterator i = cell.begin (); ! i.at_end (); ++i) {
        n += count_edges_hier (layout, layout.cell (i->cell_index ()), layer, cache, hier_levels > 0 ? hier_levels - 1 : hier_levels) * i->size ();
      }
    }

    cache.insert (std::make_pair (std::make_pair (cell.cell_index (), hier_levels), n));
    return n;

  }
}

void 
ShapeProcessor::boolean (const db::Layout &layout_in_a, const db::Cell &cell_in_a, const std::vector<unsigned int> &layers_in_a, 
                         const db::Layout &layout_in_b, const db::Cell &cell_in_b, const std::vector<unsigned int> &layers_in_b, 
                         db::Shapes &shapes, int mode, bool with_sub_hierarchy, bool resolve_holes, bool min_coherence)
{
  double fa = 1.0, fb = 1.0;
  if (shapes.cell () && shapes.cell ()->layout ()) {
    double target_dbu = shapes.cell ()->layout ()->dbu ();
    fa = layout_in_a.dbu () / target_dbu;
    fb = layout_in_b.dbu () / target_dbu;
  }

  //  count the edges so we know how much memory to reserve
  size_t ne = 0;
  std::map<std::pair<db::cell_index_type, int>, size_t> cache;
  for (std::vector<unsigned int>::const_iterator l = layers_in_a.begin (); l != layers_in_a.end (); ++l) {
    ne += count_edges_hier (layout_in_a, cell_in_a, *l, cache, with_sub_hierarchy ? -1 : 0);
    cache.clear ();
  }
  for (std::vector<unsigned int>::const_iterator l = layers_in_b.begin (); l != layers_in_b.end (); ++l) {
    ne += count_edges_hier (layout_in_b, cell_in_b, *l, cache, with_sub_hierarchy ? -1 : 0);
    cache.clear ();
  }

  clear ();
  reserve (ne + ne / 4); // heuristic reserve for crossing points

  size_t pn;

  //  collect all shapes of layout a into property ID's 0, 2, 4, 6, ...
  pn = 0;
  for (std::vector<unsigned int>::const_iterator l = layers_in_a.begin (); l != layers_in_a.end (); ++l) {
    collect_shapes_hier (db::CplxTrans (fa), layout_in_a, cell_in_a, *l, with_sub_hierarchy ? -1 : 0, pn, 2);
  }

  //  collect all shapes of layout a into property ID's 1, 3, 5, 7, ...
  pn = 1;
  for (std::vector<unsigned int>::const_iterator l = layers_in_b.begin (); l != layers_in_b.end (); ++l) {
    collect_shapes_hier (db::CplxTrans (fb), layout_in_b, cell_in_b, *l, with_sub_hierarchy ? -1 : 0, pn, 2);
  }

  db::BooleanOp op ((db::BooleanOp::BoolOp) mode);
  db::ShapeGenerator sg (shapes, true /*clear shapes*/);
  db::PolygonGenerator out (sg, resolve_holes, min_coherence);
  process (out, op);
}

void 
ShapeProcessor::size (const db::Layout &layout_in, const db::Cell &cell_in, const std::vector<unsigned int> &layers_in, 
                      db::Shapes &out, db::Coord dx, db::Coord dy, unsigned int mode, bool with_sub_hierarchy, bool resolve_holes, bool min_coherence)
{
  double f = 1.0;
  if (out.cell () && out.cell ()->layout ()) {
    double target_dbu = out.cell ()->layout ()->dbu ();
    f = layout_in.dbu () / target_dbu;
  }

  //  count the edges so we know how much memory to reserve
  size_t ne = 0;
  std::map<std::pair<db::cell_index_type, int>, size_t> cache;
  for (std::vector<unsigned int>::const_iterator l = layers_in.begin (); l != layers_in.end (); ++l) {
    ne += count_edges_hier (layout_in, cell_in, *l, cache, with_sub_hierarchy ? -1 : 0);
    cache.clear ();
  }

  clear ();
  reserve (ne + ne / 4); // heuristic reserve for crossing points

  //  collect all shapes of layout a into property ID's 0, 2, 4, 6, ... so we can use boolean OR
  size_t pn = 0;
  for (std::vector<unsigned int>::const_iterator l = layers_in.begin (); l != layers_in.end (); ++l) {
    collect_shapes_hier (db::CplxTrans (f), layout_in, cell_in, *l, with_sub_hierarchy ? -1 : 0, pn, 2);
  }

  //  1st step: merge input
  out.clear ();

  //  Merge the polygons and feed them into the sizing filter
#if ! defined(DEBUG_SIZE_INTERMEDIATE)
  db::ShapeGenerator sg (out, true /*clear shapes*/);
  db::PolygonGenerator pg2 (sg, resolve_holes, min_coherence);
  db::SizingPolygonFilter siz (pg2, dx, dy, mode);
  db::PolygonGenerator pg (siz, false /*don't resolve holes*/, false /*min. coherence*/);
  db::BooleanOp op (db::BooleanOp::Or);
  process (pg, op);
#else
  //  Intermediate output for debugging 
  db::ShapeGenerator sg (out, true /*clear shapes*/);
  std::vector <db::Polygon> out2;
  db::PolygonContainer pc (out2);
  db::PolygonGenerator pg2 (pc, false, false);
  db::BooleanOp op (db::BooleanOp::Or);
  process (pg2, op);
  sg.start ();
  for (std::vector <db::Polygon>::iterator p = out2.begin (); p != out2.end (); ++p) {
    sg.put (p->sized (dx, dy, mode));
  }
#endif
}

void 
ShapeProcessor::merge (const db::Layout &layout_in, const db::Cell &cell_in, const std::vector<unsigned int> &layers_in, 
                       db::Shapes &shapes, bool with_sub_hierarchy, unsigned int min_wc, bool resolve_holes, bool min_coherence)
{
  double f = 1.0;
  if (shapes.cell () && shapes.cell ()->layout ()) {
    double target_dbu = shapes.cell ()->layout ()->dbu ();
    f = layout_in.dbu () / target_dbu;
  }

  //  count the edges so we know how much memory to reserve
  size_t ne = 0;
  std::map<std::pair<db::cell_index_type, int>, size_t> cache;
  for (std::vector<unsigned int>::const_iterator l = layers_in.begin (); l != layers_in.end (); ++l) {
    ne += count_edges_hier (layout_in, cell_in, *l, cache, with_sub_hierarchy ? -1 : 0);
    cache.clear ();
  }

  clear ();
  reserve (ne + ne / 4); // heuristic reserve for crossing points

  //  collect all shapes of layout a into property ID's 0, 1, 2, 3, ... 
  size_t pn = 0;
  for (std::vector<unsigned int>::const_iterator l = layers_in.begin (); l != layers_in.end (); ++l) {
    collect_shapes_hier (db::CplxTrans (f), layout_in, cell_in, *l, with_sub_hierarchy ? -1 : 0, pn, 1);
  }

  db::MergeOp op (min_wc);
  db::ShapeGenerator sg (shapes, true /*clear shapes*/);
  db::PolygonGenerator out (sg, resolve_holes, min_coherence);
  process (out, op);
}

} // namespace db

