{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
module Reanimate.Math.Balloon
( balloon
, balloon'
) where
import Control.Lens
import qualified Data.Vector as V
import Graphics.SvgTree (drawAttributes)
import Linear.V2
import Linear.Vector
import Reanimate.Animation
import Reanimate.Math.Common
import Reanimate.Math.Polygon
import Reanimate.Morph.Common (toShapes)
import Reanimate.Svg.Constructors
balloon :: SVG -> (Double -> SVG)
balloon = balloon' 0.01
balloon' :: Double -> SVG -> (Double -> SVG)
balloon' tol svg = \t ->
mkGroup
[ polygonShape (gen t) & drawAttributes .~ attr
| (attr, gen) <- lst ]
where
polygonShape :: Polygon -> SVG
polygonShape p = mkLinePathClosed
[ (x,y) | V2 x y <- map (fmap realToFrac) $ V.toList (polygonPoints p) ]
lst =
[ (attr, balloonP $ shiftLongestDiameter poly)
| (attr, poly) <- toShapes tol svg
]
balloonP :: Polygon -> Double -> Polygon
balloonP p = \t ->
let targetLength = d * t
nodeVisible x = ds V.! x <= targetLength
moveCloser :: Int -> V2 Rational -> V2 Rational
moveCloser target a =
let targetDist = ds V.! target
aDist = distance' (pAccess p target) a
frac = min 1 $ realToFrac $ (targetLength - targetDist) / aDist
in lerp frac a (pAccess p target)
worker 0 = [pAccess p 0]
worker a =
let b = pNext p a in
if nodeVisible a && nodeVisible b
then [pAccess p a, pAccess p b]
else
chunkRight a b (pAccess p a) (pAccess p b) (fst $ getFunnel a b) ++
chunkCenter a b ++
chunkLeft a b (pAccess p a) (pAccess p b) (snd $ getFunnel a b)
chunkRight ai bi a b (x:y:xs) =
case rayIntersect (a,b) (pAccess p x,pAccess p y) of
Just u ->
if nodeVisible x
then
map (moveCloser x) (split a u) ++
chunkRight ai bi u b (y:xs)
else chunkRight ai bi u b (y:xs)
_ ->
if nodeVisible x
then map (moveCloser x) [a]
else []
chunkRight _ai _bi _a _b _ = []
chunkLeft ai bi a b (x:y:xs) =
case rayIntersect (a,b) (pAccess p x,pAccess p y) of
Just u ->
if nodeVisible x
then
chunkLeft ai bi a u (y:xs) ++
map (moveCloser x) (split u b)
else chunkLeft ai bi a u (y:xs)
_ ->
if nodeVisible x
then map (moveCloser x) [b]
else []
chunkLeft _ai _bi _a _b _ = []
chunkCenter a b =
let (aF, bF) = getFunnel a b
aP = pAccess p a
bP = pAccess p b in
case (reverse aF, reverse bF) of
([x], [_]) | nodeVisible x ->
map (moveCloser x) (split aP bP)
([x], _:left:_) | nodeVisible x ->
case rayIntersect (aP,bP) (pAccess p x,pAccess p left) of
Just v ->
map (moveCloser x) (split aP v)
Nothing -> map (moveCloser x) [aP,bP]
(x:right:_, [_]) | nodeVisible x ->
case rayIntersect (aP,bP) (pAccess p x,pAccess p right) of
Just u -> map (moveCloser x) (split u bP)
Nothing -> map (moveCloser x) [aP,bP]
(x:right:_, _:left:_) | nodeVisible x ->
case rayIntersect (aP,bP) (pAccess p x,pAccess p right) of
Just u ->
case rayIntersect (aP,bP) (pAccess p x,pAccess p left) of
Just v -> map (moveCloser x) (split u v)
Nothing -> map (moveCloser x) [aP,bP]
Nothing -> map (moveCloser x) [aP,bP]
_ -> []
in mkPolygon $ V.fromList $ clearDups $
concatMap worker [0..pSize p-1]
where
clearDups (x:y:xs)
| x == y = clearDups (x:xs)
clearDups (x:xs) = x : clearDups xs
clearDups [] = []
getParents 0 = []
getParents x =
let parent = pParent p 0 x
in parent : getParents parent
getFunnel a b =
let aP = getParents a
bP = getParents b in
(takeUntil (`elem` bP) aP
,takeUntil (`elem` aP) bP)
split aP bP =
let steps = 50 in
[ lerp (t/steps) bP aP
| t <- [0 .. steps]
]
d = V.maximum ds
ds = ssspDistances p
takeUntil :: (a -> Bool) -> [a] -> [a]
takeUntil _fn [] = []
takeUntil fn (x:xs)
| fn x = [x]
| otherwise = x : takeUntil fn xs
diameter :: Polygon -> Double
diameter p = V.maximum (ssspDistances p)
shiftLongestDiameter :: Polygon -> Polygon
shiftLongestDiameter p = findBest 0 p (pCycles p)
where
margin = 0.01
findBest _score elt [] = elt
findBest score elt (x:xs) =
let newScore = diameter x in
if
| newScore-score > score * margin -> findBest newScore x xs
| score-newScore > newScore * margin -> findBest score elt xs
| isTopLeft x elt -> findBest newScore x xs
| otherwise -> findBest score elt xs
isTopLeft a b =
case pAccess a 0-pAccess b 0 of
V2 x y -> y > x
ssspDistances :: Polygon -> V.Vector Double
ssspDistances p = arr
where
arr = V.generate (pSize p) $ \i ->
case i of
0 -> 0
_ ->
let parent = pParent p 0 i in
arr V.! parent + distance' (pAccess p i) (pAccess p parent)