]> git.donarmstrong.com Git - lilypond.git/blobdiff - lily/skyline.cc
Issue3383: old-straight-flag + smaller Stem.thickness gives no output and huge over
[lilypond.git] / lily / skyline.cc
index bf95fe35418153ce65237a415a29c4d4553ecde0..624334ab7964f695ee0233a3e3832f753589fb38 100644 (file)
    a DOWN skyline with an UP skyline, we need to flip the DOWN skyline first.
    This means that the merging routine doesn't need to be aware of direction,
    but the distance routine does.
-*/
 
-/* If we start including very thin buildings, numerical accuracy errors can
-   arise. Therefore, we ignore all buildings that are less than epsilon wide. */
-#define EPS 1e-5
+   From 2007 through 2012, buildings of width less than EPS were discarded,
+   citing numerical accuracy concerns.  We remember that floating point
+   comparisons of nearly-equal values can be affected by rounding error.
+   Also, some target machines use the x87 floating point unit, which provides
+   extended precision for intermediate results held in registers. On this type
+   of hardware comparisons such as
+     double c = 1.0/3.0; boolean compare = (c == 1.0/3.0)
+   could go either way because the 1.0/3.0 is allowed to be kept
+   higher precision than the variable 'c'.
+   Alert to these considerations, we now accept buildings of zero-width.
+*/
 
 static void
 print_buildings (list<Building> const &b)
@@ -114,30 +121,42 @@ Building::precompute (Real start, Real start_height, Real end_height, Real end)
   assert (!isinf (slope_) && !isnan (slope_));
 
   if (isinf (start))
-    {
-      assert (start_height == end_height);
-      y_intercept_ = start_height;
-    }
-  else
-    y_intercept_ = start_height - slope_ * start;
+    assert (start_height == end_height);
+
+  start_height_ = start_height;
 }
 
+// Because of the way slope is calculated as a ratio of usually small
+// differences, its precision is not sufficient for extrapolating
+// significantly from the original interval.  For that reason, we
+// don't store the y0 value that would allow more precalculation by using
+// y = x * slope + y0 
+// rather than
+// y = (x - xstart) * slope + ystart
+
 inline Real
 Building::height (Real x) const
 {
-  return isinf (x) ? y_intercept_ : slope_ * x + y_intercept_;
+  return (isinf (x) || isinf (start_)) ? start_height_
+    : slope_ * (x - start_) + start_height_;
 }
 
 void
 Building::print () const
 {
-  printf ("%f x + %f from %f to %f\n", slope_, y_intercept_, start_, end_);
+  printf ("%f (x - x0) + %f from %f to %f\n", slope_, start_height_, start_, end_);
 }
 
 inline Real
 Building::intersection_x (Building const &other) const
 {
-  Real ret = (y_intercept_ - other.y_intercept_) / (other.slope_ - slope_);
+  Real ret = start_
+    - (other.start_height_ - start_height_
+       - other.slope_ * (other.start_ - start_))
+    /(other.slope_ - slope_);
+  /* A numerically symmetric version would be
+     x = (x1+x0)/2 - (y1-y0 - (s1+s0)(x1-x0)/2)/(s1-s0)
+   */
   return isnan (ret) ? -infinity_f : ret;
 }
 
@@ -148,19 +167,6 @@ Building::leading_part (Real chop)
   end_ = chop;
 }
 
-// Returns a shift s such that (x + s, y) intersects the roof of
-// this building.  If no such shift exists, returns infinity_f.
-Real
-Building::shift_to_intersect (Real x, Real y) const
-{
-  // Solve for s: y = (x + s)*m + b
-  Real ret = (y - y_intercept_ - slope_ * x) / slope_;
-
-  if (ret >= start_ && ret <= end_ && !isinf (ret))
-    return ret;
-  return infinity_f;
-}
-
 static Real
 first_intersection (Building const &b, list<Building> *const s, Real start_x)
 {
@@ -170,7 +176,7 @@ first_intersection (Building const &b, list<Building> *const s, Real start_x)
 
       // conceals and intersection_x involve multiplication and
       // division. Avoid that, if we can.
-      if (c.y_intercept_ == -infinity_f)
+      if (c.start_height_ == -infinity_f)
         {
           if (c.end_ > b.end_)
             return b.end_;
@@ -193,16 +199,12 @@ first_intersection (Building const &b, list<Building> *const s, Real start_x)
   return b.end_;
 }
 
+// conceals returns true if *this is strictly above other at x
+
 bool
 Building::conceals (Building const &other, Real x) const
 {
-  if (slope_ == other.slope_)
-    return y_intercept_ > other.y_intercept_;
-
-  /* their slopes were not equal, so there is an intersection point */
-  Real i = intersection_x (other);
-  return (i <= x && slope_ > other.slope_)
-         || (i > x && slope_ < other.slope_);
+  return height (x) > other.height (x);
 }
 
 // Remove redundant empty buildings from the skyline.
@@ -216,7 +218,7 @@ Skyline::normalize ()
 
   for (i = buildings_.begin (); i != buildings_.end (); i++)
     {
-      if (last_empty && i->y_intercept_ == -infinity_f)
+      if (last_empty && i->start_height_ == -infinity_f)
         {
           list<Building>::iterator last = i;
           last--;
@@ -224,7 +226,7 @@ Skyline::normalize ()
           buildings_.erase (i);
           i = last;
         }
-      last_empty = (i->y_intercept_ == -infinity_f);
+      last_empty = (i->start_height_ == -infinity_f);
     }
 
   assert (buildings_.front ().start_ == -infinity_f);
@@ -243,10 +245,10 @@ Skyline::deholify ()
 
   for (right = buildings_.begin (); right != buildings_.end (); right++)
     {
-      if (center != buildings_.begin () && center->y_intercept_ == -infinity_f)
+      if (center != buildings_.begin () && center->start_height_ == -infinity_f)
         {
           Real p1 = left->height (left->end_);
-          Real p2 = right->height (right->start_);
+          Real p2 = right->start_height_;
           *center = Building (center->start_, p1, p2, center->end_);
 
           left = center;
@@ -278,7 +280,7 @@ Skyline::internal_merge_skyline (list<Building> *s1, list<Building> *s2,
       // Optimization: if the other skyline is empty at this point,
       // we can avoid testing some intersections. Just grab as many
       // buildings from s1 as we can, and shove them onto the output.
-      if (c.y_intercept_ == -infinity_f
+      if (c.start_height_ == -infinity_f
           && c.end_ >= b.end_)
         {
           list<Building>::iterator i = s1->begin ();
@@ -286,6 +288,7 @@ Skyline::internal_merge_skyline (list<Building> *s1, list<Building> *s2,
           while (i != s1->end () && i->end_ <= c.end_)
             i++;
 
+          s1->front ().start_height_ = s1->front ().height (x);
           s1->front ().start_ = x;
           result->splice (result->end (), *s1, s1->begin (), i);
           x = result->back ().end_;
@@ -296,15 +299,16 @@ Skyline::internal_merge_skyline (list<Building> *s1, list<Building> *s2,
       Real end = first_intersection (b, s2, x);
       if (s2->empty ())
         {
+          b.start_height_ = b.height (last_end);
           b.start_ = last_end;
           result->push_back (b);
           break;
         }
 
-      /* only include buildings wider than epsilon */
-      if (end > x + EPS)
+      if (end >= x)
         {
           b.leading_part (end);
+          b.start_height_ = b.height (last_end);
           b.start_ = last_end;
           last_end = b.end_;
           result->push_back (b);
@@ -329,20 +333,15 @@ empty_skyline (list<Building> *const ret)
 static void
 single_skyline (Building b, list<Building> *const ret)
 {
-  if (b.end_ > b.start_ + EPS)
-    {
-      if (b.start_ != -infinity_f)
-        ret->push_back (Building (-infinity_f, -infinity_f,
-                                  -infinity_f, b.start_));
-      ret->push_back (b);
-      if (b.end_ != infinity_f)
-        ret->push_back (Building (b.end_, -infinity_f,
-                                  -infinity_f, infinity_f));
-    }
-  else
-    {
-      empty_skyline (ret);
-    }
+  assert (b.end_ >= b.start_);
+
+  if (b.start_ != -infinity_f)
+    ret->push_back (Building (-infinity_f, -infinity_f,
+                              -infinity_f, b.start_));
+  ret->push_back (b);
+  if (b.end_ != infinity_f)
+    ret->push_back (Building (b.end_, -infinity_f,
+                              -infinity_f, infinity_f));
 }
 
 /* remove a non-overlapping set of boxes from BOXES and build a skyline
@@ -357,7 +356,7 @@ non_overlapping_skyline (list<Building> *const buildings)
   while (i != buildings->end ())
     {
       Real x1 = i->start_;
-      Real y1 = i->height (i->start_);
+      Real y1 = i->start_height_;
       Real x2 = i->end_;
       Real y2 = i->height (i->end_);
 
@@ -377,7 +376,8 @@ non_overlapping_skyline (list<Building> *const buildings)
           continue;
         }
 
-      if (x1 > last_end + EPS)
+      // Insert empty Buildings into any gaps. (TODO: is this needed? -KOH)
+      if (x1 > last_end)
         result.push_back (Building (last_end, -infinity_f, -infinity_f, x1));
 
       result.push_back (*i);
@@ -399,7 +399,7 @@ public:
   bool operator () (Building const &b1, Building const &b2)
   {
     return b1.start_ < b2.start_
-           || (b1.start_ == b2.start_ && b1.height (b1.start_) > b2.height (b1.start_));
+           || (b1.start_ == b2.start_ && b1.start_height_ > b2.start_height_);
   }
 };
 
@@ -476,20 +476,17 @@ Skyline::Skyline (Direction sky)
 /*
   Build skyline from a set of boxes.
 
-  Boxes should have fatness in the horizon_axis, otherwise they are ignored.
+  Boxes should be non-empty on both axes.  Otherwise, they will be ignored
  */
 Skyline::Skyline (vector<Box> const &boxes, Axis horizon_axis, Direction sky)
 {
   list<Building> buildings;
   sky_ = sky;
 
-  Axis vert_axis = other_axis (horizon_axis);
   for (vsize i = 0; i < boxes.size (); i++)
-    {
-      Interval iv = boxes[i][horizon_axis];
-      if (iv.length () > EPS && !boxes[i][vert_axis].is_empty ())
-        buildings.push_front (Building (boxes[i], horizon_axis, sky));
-    }
+    if (!boxes[i].is_empty (X_AXIS)
+        && !boxes[i].is_empty (Y_AXIS))
+      buildings.push_front (Building (boxes[i], horizon_axis, sky));
 
   buildings_ = internal_build_skyline (&buildings);
   normalize ();
@@ -498,7 +495,8 @@ Skyline::Skyline (vector<Box> const &boxes, Axis horizon_axis, Direction sky)
 /*
   build skyline from a set of line segments.
 
-  Buildings should have fatness in the horizon_axis, otherwise they are ignored.
+  Segments can be articulated from left to right or right to left.
+  In the case of the latter, they will be stored internally as left to right.
  */
 Skyline::Skyline (vector<Drul_array<Offset> > const &segments, Axis horizon_axis, Direction sky)
 {
@@ -518,7 +516,7 @@ Skyline::Skyline (vector<Drul_array<Offset> > const &segments, Axis horizon_axis
       Real y1 = left[other_axis (horizon_axis)] * sky;
       Real y2 = right[other_axis (horizon_axis)] * sky;
 
-      if (x1 + EPS < x2)
+      if (x1 <= x2)
         buildings.push_back (Building (x1, y1, y2, x2));
     }
 
@@ -554,9 +552,12 @@ Skyline::Skyline (vector<Skyline_pair> const &skypairs, Direction sky)
 Skyline::Skyline (Box const &b, Axis horizon_axis, Direction sky)
 {
   sky_ = sky;
-  Building front (b, horizon_axis, sky);
-  single_skyline (front, &buildings_);
-  normalize ();
+  if (!b.is_empty (X_AXIS) && !b.is_empty (Y_AXIS))
+    {
+      Building front (b, horizon_axis, sky);
+      single_skyline (front, &buildings_);
+      normalize ();
+    }
 }
 
 void
@@ -594,8 +595,7 @@ Skyline::insert (Box const &b, Axis a)
     }
 
   /* do the same filtering as in Skyline (vector<Box> const&, etc.) */
-  Interval iv = b[a];
-  if (iv.length () <= EPS || b[other_axis (a)].is_empty ())
+  if (b.is_empty (X_AXIS) || b.is_empty (Y_AXIS))
     return;
 
   my_bld.splice (my_bld.begin (), buildings_);
@@ -609,7 +609,7 @@ Skyline::raise (Real r)
 {
   list<Building>::iterator end = buildings_.end ();
   for (list<Building>::iterator i = buildings_.begin (); i != end; i++)
-    i->y_intercept_ += sky_ * r;
+    i->start_height_ += sky_ * r;
 }
 
 void
@@ -620,7 +620,6 @@ Skyline::shift (Real s)
     {
       i->start_ += s;
       i->end_ += s;
-      i->y_intercept_ -= s * i->slope_;
     }
 }
 
@@ -654,12 +653,18 @@ Skyline::internal_distance (Skyline const &other, Real horizon_padding, Real *to
 Skyline
 Skyline::padded (Real horizon_padding) const
 {
+  if (horizon_padding < 0.0)
+    warning ("Cannot have negative horizon padding.  Junking.");
+
+  if (horizon_padding <= 0.0)
+    return *this;
+
   list<Building> pad_buildings;
   for (list<Building>::const_iterator i = buildings_.begin (); i != buildings_.end (); ++i)
     {
       if (i->start_ > -infinity_f)
         {
-          Real height = i->height (i->start_);
+          Real height = i->start_height_;
           if (height > -infinity_f)
             {
               // Add the sloped building that pads the left side of the current building.
@@ -763,7 +768,7 @@ Skyline::max_height () const
   list<Building>::const_iterator i;
   for (i = buildings_.begin (); i != buildings_.end (); ++i)
     {
-      ret = max (ret, i->height (i->start_));
+      ret = max (ret, i->start_height_);
       ret = max (ret, i->height (i->end_));
     }
 
@@ -781,7 +786,7 @@ Skyline::left () const
 {
   for (list<Building>::const_iterator i (buildings_.begin ());
        i != buildings_.end (); i++)
-    if (i->y_intercept_ > -infinity_f)
+    if (i->start_height_ > -infinity_f)
       return i->start_;
 
   return infinity_f;
@@ -792,7 +797,7 @@ Skyline::right () const
 {
   for (list<Building>::const_reverse_iterator i (buildings_.rbegin ());
        i != buildings_.rend (); ++i)
-    if (i->y_intercept_ > -infinity_f)
+    if (i->start_height_ > -infinity_f)
       return i->end_;
 
   return -infinity_f;
@@ -810,7 +815,7 @@ void
 Skyline::set_minimum_height (Real h)
 {
   Skyline s (sky_);
-  s.buildings_.front ().y_intercept_ = h * sky_;
+  s.buildings_.front ().start_height_ = h * sky_;
   merge (s);
 }
 
@@ -841,7 +846,7 @@ Skyline::is_empty () const
   if (!buildings_.size ())
     return true;
   Building b = buildings_.front ();
-  return b.end_ == infinity_f && b.y_intercept_ == -infinity_f;
+  return b.end_ == infinity_f && b.start_height_ == -infinity_f;
 }
 
 void