module Gelatin.Core.Rendering.Polylines where import Gelatin.Core.Rendering.Types import Gelatin.Core.Triangulation.Common (triangleArea) import Linear hiding (trace) import Debug.Trace polygonExpand :: Float -> [V2 Float] -> [V2 Float] polygonExpand t ps = trace (show (length ps, length vs, length poly)) poly where poly = zipWith f ps vs f p v = p + (v ^* t) bows = zip3 ps' (tail ps') (tail $ tail ps') vs = map (\(a,b,c) -> perp $ tangentOf a b c) bows ps' = start ++ ps ++ end start = case ps of x:_ -> [x] _ -> [] end = case reverse ps of x:_ -> [x] _ -> [] -- | The polyline outline of another polyline drawn at a given thickness. outlinePolyline :: EndCap -> LineJoin -> Float -> [V2 Float] -> [V2 Float] outlinePolyline c j t ps = scap ++ ptans ++ ecap ++ reverse ntans ++ h where js = joints c j t ps (ptans,ntans) = both concat $ unzip $ map tangentPoints js both f (a,b) = (f a, f b) scap = case js of (Cap _ xs:_) -> reverse xs _ -> [] ecap = case reverse js of (Cap _ xs:_) -> reverse xs _ -> [] h = case scap of h':_ -> [h'] _ -> [] polyline :: EndCap -> LineJoin -> Float -> [V2 Float] -> [Triangle (V2 Float)] polyline c j t ps = triangulate $ joints c j t ps triangulate :: [Joint] -> [Triangle (V2 Float)] -- start triangulate (j@Cap{}:j':js) = cap ++ arm ++ (triangulate $ j':js) where cap = triangulateCap j arm = triangulateArm j j' -- end triangulate [j, j'@Cap{}] = arm ++ bow ++ cap where arm = triangulateArm j j' bow = triangulateElbow j cap = triangulateCap j' triangulate (j:j':js) = arm ++ bow ++ (triangulate $ j':js) where arm = triangulateArm j j' bow = triangulateElbow j triangulate _ = [] -- | Returns the points in a joint separated by the line's winding -- direction. Points on the side of the line in the positive tangent direction -- are `fst` and points in the negative tangent direction are `snd`. tangentPoints :: Joint -> ([V2 Float], [V2 Float]) -- There isn't enough info in a cap to provide this. tangentPoints (Cap _ _) = ([], []) tangentPoints (Elbow _ (p,n) []) = ([p],[n]) tangentPoints (Elbow Clockwise (p,_) ps) = ([p],ps) tangentPoints (Elbow CounterCW (_,n) ps) = (ps,[n]) exitLine :: Joint -> (V2 Float, V2 Float) exitLine (Cap _ ps) = (head ps, head $ reverse ps) exitLine (Elbow _ l []) = l exitLine (Elbow Clockwise (p,_) ps) = (p, head $ reverse ps) exitLine (Elbow CounterCW (_,n) ps) = (head $ reverse ps, n) entryLine :: Joint -> (V2 Float, V2 Float) entryLine (Cap _ ps) = (head $ reverse ps, head ps) entryLine (Elbow _ l []) = l entryLine (Elbow Clockwise (p,_) ps) = (p, head ps) entryLine (Elbow CounterCW (_,n) ps) = (head ps, n) triangulateElbow :: Joint -> [Triangle (V2 Float)] triangulateElbow (Elbow Clockwise (p,_) ps) = map (uncurry $ Triangle p) $ zip ps $ tail ps triangulateElbow (Elbow CounterCW (_,n) ps) = map (uncurry $ Triangle n) $ zip ps $ tail ps triangulateElbow _ = [] triangulateArm :: Joint -> Joint -> [Triangle (V2 Float)] triangulateArm j j' = [Triangle a b c, Triangle b c d] where (a,b) = exitLine j (c,d) = entryLine j' triangulateCap :: Joint -> [Triangle (V2 Float)] -- This is a butt cap so do nothing. triangulateCap (Cap p ps) = map (uncurry $ Triangle p) $ zip ps $ tail ps triangulateCap _ = [] joints :: EndCap -> LineJoin -> Float -> [V2 Float] -> [Joint] joints _ _ _ [] = [] joints _ _ _ [_] = [] joints c j t ps@(a:b:_) = start : mid ++ [end] where start = capFunc c t a b end = capFunc c t z y mid = miters j t ps [z,y] = take 2 $ reverse ps capFunc :: EndCap -> Float -> V2 Float -> V2 Float -> Joint capFunc EndCapButt t a b = Cap a [lp,hp] where (lp,hp) = miterLine (capJoin t a b) a capFunc EndCapBevel t a b = Cap a [lp,p,hp] where (lp,hp) = miterLine (capJoin t a b) a p = a + (signorm $ a - b) ^* t capFunc EndCapSquare t a b = Cap a [lp,p'',p',hp] where (lp,hp) = miterLine (capJoin t a b) a p = a + (signorm $ a - b) ^* t p' = p + ((signorm $ hp - a) ^* t) p'' = p + ((signorm $ lp - a) ^* t) capFunc EndCapRound t a b = Cap a ps where ps = map f [(pi/2) + r + (d * pi/180) | d <- [0..180]] V2 x y = signorm $ b - a r = atan2 y x f th = a + (V2 (cos th) (sin th) ^* t) miters :: LineJoin -> Float -> [V2 Float] -> [Joint] miters j t (a:b:c:ps) = miterFunc j t a b c : (miters j t $ b:c:ps) miters _ _ _ = [] miterFunc :: LineJoin -> Float -> V2 Float -> V2 Float -> V2 Float -> Joint miterFunc LineJoinMiter = miterJoint miterFunc LineJoinBevel = bevelJoint --miterFunc LineJoinRound = roundJoint -- --roundJoint :: Float -> V2 Float -> V2 Float -> V2 Float -> Joint --roundJoint t a b c = -- if triangleArea a b c > 0 -- then Elbow Clockwise (p,n) ps -- else Elbow CounterCW (p,n) $ reverse ps -- where j = join t a b c -- (p,n) = miterLine j b -- v' = t *^ (perp ab) -- v'' = t *^ (perp bc) -- ps = map f [r + d | d <- [0, pi/2, pi]] -- V2 x y = signorm $ v'' - v' -- r = atan2 y x -- f th = b + (V2 (cos th) (sin th) ^* (0.5 * distance v'' v')) -- ab = signorm $ b - a -- bc = signorm $ c - b bevelJoint :: Float -> V2 Float -> V2 Float -> V2 Float -> Joint bevelJoint t a b c = if triangleArea a b c >= 0 then Elbow Clockwise (p,n) [b - v', b - v''] else Elbow CounterCW (p,n) [b + v', b + v''] where j = join t a b c (p,n) = miterLine j b v' = t *^ (perp ab) v'' = t *^ (perp bc) ab = signorm $ b - a bc = signorm $ c - b miterJoint :: Float -> V2 Float -> V2 Float -> V2 Float -> Joint miterJoint t a b c = if triangleArea a b c >= 0 then Elbow Clockwise (ptan,ntan) [] else Elbow CounterCW (ptan,ntan) [] where j = join t a b c (ptan,ntan) = miterLine j b -- | Finds the miter line through a midpoint for a given join. miterLine :: Join -> V2 Float -> (V2 Float, V2 Float) miterLine (Join v l) p = (ptan,ntan) -- ptan is the point on the miterline in the direction the -- perpendicular tangent is pointing. ntan is in the opposite -- direction. This means that for clockwise winding elbows ptan -- will lie within the bend, on the inside of the elbow, while -- ntan will lie outside. This is reversed for elbows winding -- counter-clockwise. where ptan = p + v' ntan = p - v' v' = (v ^* l) -- | Finds the joint of three points with a thickness. -- A join with a positive angle denotes an elbow that bends -- counter-clockwise. A join with a negative angle denotes an elbow that -- bends clockwise. -- The join with an angle == 0 is the join of two parallel lines. -- The join with an angle == pi is the join of two opposite but parallel -- lines, which is used to denote a line cap. join :: Float -> V2 Float -> V2 Float -> V2 Float -> Join join t a b c = Join v ln where tgnt = tangentOf a b c v = perp tgnt ln = min d $ t / (v `dot` n) n = signorm $ perp $ b - a d = min (distance (c - b) zero) (distance (b - a) zero) -- | Finds the join of a start or end line with a thickness. capJoin :: Float -> V2 Float -> V2 Float -> Join capJoin t a b = Join v t where v = signorm $ perp $ b - a -- | Finds the tangent of an elbow. tangentOf :: V2 Float -> V2 Float -> V2 Float -> V2 Float tangentOf a b c = signorm $ (signorm l2) + (signorm l1) where l1 = b - a l2 = c - b -- | Finds the angle between two vectors. angleBetween :: V2 Float -> V2 Float -> Float angleBetween v1 v2 = a - b where V2 x1 y1 = signorm v1 V2 x2 y2 = signorm v2 a = atan2 y1 x1 b = atan2 y2 x2 -- | A join is the 'miter line' that runs through the shared point of two lines -- perpendicular to their tangent. data Join = Join { joinVector :: V2 Float , joinLength :: Float } deriving (Show, Eq)