packages feed

pure-cdt-0.2.0.0: src/Triangulation/Geometry/Exact.hs

-- | Exact geometric predicates on 'Double' coordinates.
--
-- Each predicate is the sign of a determinant. The determinant is first
-- evaluated in floating point together with a bound on its rounding error
-- (the static filters of Shewchuk's /Adaptive Precision Floating-Point
-- Arithmetic and Fast Robust Geometric Predicates/); when the magnitude of the
-- result exceeds the bound, its sign is certain. Otherwise the determinant is
-- recomputed in 'Rational' arithmetic, which is exact because every 'Double'
-- converts to a 'Rational' without loss. The exact branch is a lazy thunk and
-- is only evaluated in the rare near-degenerate cases.
--
-- The predicates take raw coordinates so that this module sits below the
-- geometric types.
module Triangulation.Geometry.Exact (
  Coordinates,
  orientationSign,
  inCircleSign,
) where

-- | A point as an @(x, y)@ pair.
type Coordinates = (Double, Double)

-- | Half the machine epsilon of 'Double': the relative rounding error of one operation.
epsilon :: Double
epsilon = encodeFloat 1 (-53)

-- | Sign of @(a - c) × (b - c)@: 'GT' when @a -> b -> c@ turns counter-clockwise
-- (y axis up), 'LT' when clockwise, 'EQ' when the points are collinear.
--
-- The exact fallback is a separate function rather than an argument, so that
-- the common case does not build a 'Rational' thunk it never looks at.
orientationSign :: Coordinates -> Coordinates -> Coordinates -> Ordering
orientationSign a@(ax, ay) b@(bx, by) c@(cx, cy)
  | approximate > errorBound = GT
  | approximate < negate errorBound = LT
  | otherwise = exactOrientation a b c
  where
    detLeft = (ax - cx) * (by - cy)
    detRight = (ay - cy) * (bx - cx)
    approximate = detLeft - detRight
    errorBound = (3 + 16 * epsilon) * epsilon * (abs detLeft + abs detRight)

-- | The orientation determinant in exact arithmetic. Every 'Double' converts
-- to a 'Rational' without loss, so the sign is the true one.
exactOrientation :: Coordinates -> Coordinates -> Coordinates -> Ordering
{-# NOINLINE exactOrientation #-}
exactOrientation (ax, ay) (bx, by) (cx, cy) =
  compare ((r ax - r cx) * (r by - r cy) - (r ay - r cy) * (r bx - r cx)) 0
  where
    r = toRational

-- | Sign of the in-circle determinant: for @a@, @b@, @c@ in counter-clockwise
-- order, 'GT' when @d@ lies strictly inside their circumcircle, 'LT' when
-- strictly outside, 'EQ' when on it. The sign flips for a clockwise triple.
inCircleSign :: Coordinates -> Coordinates -> Coordinates -> Coordinates -> Ordering
inCircleSign a@(ax, ay) b@(bx, by) c@(cx, cy) d@(dx, dy)
  | det > errorBound = GT
  | det < negate errorBound = LT
  | otherwise = exactInCircle a b c d
  where
    (adx, ady) = (ax - dx, ay - dy)
    (bdx, bdy) = (bx - dx, by - dy)
    (cdx, cdy) = (cx - dx, cy - dy)
    (bdxcdy, cdxbdy, alift) = (bdx * cdy, cdx * bdy, adx * adx + ady * ady)
    (cdxady, adxcdy, blift) = (cdx * ady, adx * cdy, bdx * bdx + bdy * bdy)
    (adxbdy, bdxady, clift) = (adx * bdy, bdx * ady, cdx * cdx + cdy * cdy)
    det = alift * (bdxcdy - cdxbdy) + blift * (cdxady - adxcdy) + clift * (adxbdy - bdxady)
    permanent =
      (abs bdxcdy + abs cdxbdy) * alift
        + (abs cdxady + abs adxcdy) * blift
        + (abs adxbdy + abs bdxady) * clift
    errorBound = (10 + 96 * epsilon) * epsilon * permanent

-- | The in-circle determinant in exact arithmetic; see 'exactOrientation'.
exactInCircle :: Coordinates -> Coordinates -> Coordinates -> Coordinates -> Ordering
{-# NOINLINE exactInCircle #-}
exactInCircle (ax, ay) (bx, by) (cx, cy) (dx, dy) = compare determinant 0
  where
    (ax', ay', bx', by', cx', cy', dx', dy') = (r ax, r ay, r bx, r by, r cx, r cy, r dx, r dy)
    (adx, ady) = (ax' - dx', ay' - dy')
    (bdx, bdy) = (bx' - dx', by' - dy')
    (cdx, cdy) = (cx' - dx', cy' - dy')
    determinant =
      (adx * adx + ady * ady) * (bdx * cdy - cdx * bdy)
        + (bdx * bdx + bdy * bdy) * (cdx * ady - adx * cdy)
        + (cdx * cdx + cdy * cdy) * (adx * bdy - bdx * ady)
    r = toRational