packages feed

juicy-gcode-0.1.0.4: src/SvgArcSegment.hs

module SvgArcSegment ( 
                       convertSvgArc
                     ) where

import Types                     
                
radiansPerDegree :: Double     
radiansPerDegree = pi / 180.0

calculateVectorAngle :: Double -> Double -> Double -> Double -> Double
calculateVectorAngle ux uy vx vy
    | tb >= ta
        = tb - ta
    | otherwise
        = pi * 2 - (ta - tb)
    where
        ta = atan2 uy ux
        tb = atan2 vy vx
        
-- ported from: https://github.com/vvvv/SVG/blob/master/Source/Paths/SvgArcSegment.cs
convertSvgArc :: Point -> Double -> Double -> Double -> Bool -> Bool -> Point -> [DrawOp]
convertSvgArc (x0,y0) radiusX radiusY angle largeArcFlag sweepFlag (x,y)
    | x0 == x && y0 == y0
        = []
    | radiusX == 0.0 && radiusY == 0.0
        = [DLineTo (x,y)]
    | otherwise 
        = calcSegments x0 y0 theta1' segments'
    where
        sinPhi = sin (angle * radiansPerDegree)
        cosPhi = cos (angle * radiansPerDegree)

        x1dash = cosPhi * (x0 - x) / 2.0 + sinPhi * (y0 - y) / 2.0
        y1dash = -sinPhi * (x0 - x) / 2.0 + cosPhi * (y0 - y) / 2.0

        numerator = radiusX * radiusX * radiusY * radiusY - radiusX * radiusX * y1dash * y1dash - radiusY * radiusY * x1dash * x1dash

        s = sqrt(1.0 - numerator / (radiusX * radiusX * radiusY * radiusY))
        rx   = if' (numerator < 0.0) (radiusX * s) radiusX
        ry   = if' (numerator < 0.0) (radiusY * s) radiusY
        root = if' (numerator < 0.0) 
                   (0.0) 
                   ((if' ((largeArcFlag && sweepFlag) || (not largeArcFlag && not sweepFlag)) (-1.0) 1.0) * 
                        sqrt(numerator / (radiusX * radiusX * y1dash * y1dash + radiusY * radiusY * x1dash * x1dash)))
  
        cxdash = root * rx * y1dash / ry
        cydash = -root * ry * x1dash / rx

        cx = cosPhi * cxdash - sinPhi * cydash + (x0 + x) / 2.0
        cy = sinPhi * cxdash + cosPhi * cydash + (y0 + y) / 2.0
        
        theta1'  = calculateVectorAngle 1.0 0.0 ((x1dash - cxdash) / rx) ((y1dash - cydash) / ry)
        dtheta' = calculateVectorAngle ((x1dash - cxdash) / rx) ((y1dash - cydash) / ry) ((-x1dash - cxdash) / rx) ((-y1dash - cydash) / ry)
        dtheta  = if' (not sweepFlag && dtheta' > 0) 
                      (dtheta' - 2 * pi)
                      (if' (sweepFlag && dtheta' < 0) (dtheta' + 2 * pi) dtheta')
  
        segments' = ceiling (abs (dtheta / (pi / 2.0)))
        delta = dtheta / fromInteger segments'
        t = 8.0 / 3.0 * sin(delta / 4.0) * sin(delta / 4.0) / sin(delta / 2.0)
  
        calcSegments startX startY theta1 segments 
            | segments == 0
                = []
            | otherwise
                = (DBezierTo (startX + dx1, startY + dy1) (endpointX + dxe, endpointY + dye) (endpointX, endpointY) : calcSegments endpointX endpointY theta2 (segments - 1))
            where
                cosTheta1 = cos theta1
                sinTheta1 = sin theta1
                theta2 = theta1 + delta
                cosTheta2 = cos theta2
                sinTheta2 = sin theta2

                endpointX = cosPhi * rx * cosTheta2 - sinPhi * ry * sinTheta2 + cx
                endpointY = sinPhi * rx * cosTheta2 + cosPhi * ry * sinTheta2 + cy

                dx1 = t * (-cosPhi * rx * sinTheta1 - sinPhi * ry * cosTheta1)
                dy1 = t * (-sinPhi * rx * sinTheta1 + cosPhi * ry * cosTheta1)

                dxe = t * (cosPhi * rx * sinTheta2 + sinPhi * ry * cosTheta2)
                dye = t * (sinPhi * rx * sinTheta2 - cosPhi * ry * cosTheta2)

{-                
-- ported from: http://www.java2s.com/Code/Java/2D-Graphics-GUI/AgeometricpathconstructedfromstraightlinesquadraticandcubicBeziercurvesandellipticalarc.htm   
-- works without angle and with circle segments only             
convertArc :: Double -> Double -> Double -> Bool -> Bool -> Double -> Double -> Arc
convertArc x0 y0 radius largeArcFlag sweepFlag x y = Arc (x0,y0) (x,y) (cx,cy) dir
    where
        x1 = (x0 - x) / 2.0
        y1 = (y0 - y) / 2.0
                
        pr' = radius * radius
        px1 = x1 * x1
        py1 = y1 * y1

        radiiCheck = px1 / pr' + py1 / pr'
        
        r = if' (radiiCheck > 1) (sqrt radiiCheck * abs radius) (abs radius)
        pr = r * r
        
        sign = if' (largeArcFlag == sweepFlag) (-1) 1
        sq' = ((pr * pr) - (pr * py1) - (pr * px1)) / ((pr * py1) + (pr * px1))
        coef = sign * sqrt (max 0.0 sq')
        cx1 = coef * y1
        cy1 = coef * (-x1)
        
        sx2 = (x0 + x) / 2.0
        sy2 = (y0 + y) / 2.0            
        cx = sx2 + cx1
        cy = sy2 + cy1
        
        ux = (x1 - cx1) / r
        uy = (y1 - cy1) / r
        vx = (-x1 - cx1) / r
        vy = (-y1 - cy1) / r
        
        -- compute direction. True -> Clockwise
        dir' = ux * vy - uy * vx >= 0
        dir = if' (not sweepFlag && dir') 
                  False 
                  (if' (sweepFlag && not dir') True dir')
-}