packages feed

juicy-gcode-0.3.0.0: src/Approx/Linear.hs

module Approx.Linear (
    linearApprox
) where

import qualified Graphics.CubicBezier as B
import qualified Graphics.Line as L
import Graphics.Path
import Graphics.Point

import Linear.Metric

-- Approximate a bezier curve with a series of points (line segments)
-- Weiyin Ma, Renjiang Zhang, Efficient Piecewise Linear Approximation of Bézier Curves with Improved Sharp Error Bound (2006)
linearApprox :: B.CubicBezier
             -> Double
             -> [PathCommand]
linearApprox mbezier resolution
    -- Edge case: all points on the same line -> it is a line 
    | L.isOnLine (L.fromPoints (B._p2 mbezier) (B._p1 mbezier)) (B._c1 mbezier) &&
      L.isOnLine (L.fromPoints (B._p2 mbezier) (B._p1 mbezier)) (B._c2 mbezier)
        = [LineTo (toPoint (B._p2 mbezier))]
    -- Just a regular bezier
    | otherwise
        = approx mbezier
    where
        approx :: B.CubicBezier -> [PathCommand]
        approx bezier
            | maxError > resolution
                = splitAndRecur 0.5
            | otherwise
                = [LineTo (toPoint b1), LineTo (toPoint b2), LineTo (toPoint b3)]

            where
                b1 = ((9 * B._p1 bezier) + (15 * B._c1 bezier) + (7 * B._c2 bezier) + B._p2 bezier) / 32
                b2 = (B._p1 bezier + (7 * B._c1 bezier) + (15 * B._c2 bezier) + (9 * B._p2 bezier)) / 32
                b3 = B._p2 bezier

                maxError = max (norm (B._p1 bezier - 2 * B._c1 bezier + B._c2 bezier))
                               (norm (B._c1 bezier - 2 * B._c2 bezier + B._p2 bezier))

                splitAndRecur t = let (nb1, nb2) = B.splitAt bezier t
                                   in approx nb1 ++ approx nb2