mirror of
https://github.com/reanimate/reanimate.git
synced 2026-09-09 07:03:11 +00:00
124 lines
3.9 KiB
Haskell
Executable file
124 lines
3.9 KiB
Haskell
Executable file
#!/usr/bin/env stack
|
|
-- stack runghc --package reanimate
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
module Main (main) where
|
|
|
|
import Codec.Picture
|
|
import Control.Lens
|
|
import Data.Complex
|
|
import Graphics.SvgTree
|
|
import Linear.V2
|
|
import Reanimate
|
|
|
|
-- layer 3
|
|
main :: IO ()
|
|
main = reanimate $ setDuration 30 $ scene $ do
|
|
_ <- newSpriteSVG $ mkBackgroundPixel (PixelRGBA8 252 252 252 0xFF)
|
|
play $ fourierA (fromToS 0 5) -- Rotate 15 times
|
|
& setDuration 50
|
|
& signalA (reverseS . powerS 2 . reverseS) -- Start fast, end slow
|
|
& pauseAtEnd 2
|
|
play $ fourierA (constantS 0) -- Don't rotate at all
|
|
& setDuration 10
|
|
& reverseA
|
|
& signalA (powerS 2) -- Start slow, end fast
|
|
& pauseAtEnd 2
|
|
|
|
-- layer 2
|
|
fourierA :: (Double -> Double) -> Animation
|
|
fourierA genPhi = animate $ \t ->
|
|
let circles = setFourierLength (t*piFourierLen) piFourier
|
|
coeffs = fourierCoefficients $ rotateFourier (genPhi t) circles
|
|
in mkGroup
|
|
[ drawCircles coeffs
|
|
, withStrokeColor "green" $
|
|
withStrokeLineJoin JoinRound $
|
|
withFillOpacity 0 $
|
|
withStrokeWidth (defaultStrokeWidth*2) $
|
|
mkLinePath $ mkFourierOutline circles
|
|
, let x :+ y = sum coeffs in
|
|
translate x y $ withFillColor "red" $ mkCircle (defaultStrokeWidth*3)
|
|
]
|
|
|
|
drawCircles :: [Complex Double] -> SVG
|
|
drawCircles [] = mkGroup []
|
|
drawCircles ( x :+ y : xs) =
|
|
translate x y $ drawCircles' xs
|
|
|
|
drawCircles' :: [Complex Double] -> SVG
|
|
drawCircles' circles = mkGroup
|
|
[ worker circles
|
|
, withStrokeColor "black" $
|
|
withStrokeLineJoin JoinRound $
|
|
withFillOpacity 0 $
|
|
mkLinePath [ (x, y) | x :+ y <- scanl (+) 0 circles ]]
|
|
where
|
|
worker [] = None
|
|
worker (x :+ y : rest) =
|
|
let radius = sqrt(x*x+y*y) in
|
|
mkGroup
|
|
[ withStrokeColor "dimgrey" $
|
|
withFillOpacity 0 $
|
|
mkCircle radius
|
|
, translate x y $ worker rest ]
|
|
|
|
-- layer 1
|
|
newtype Fourier = Fourier {fourierCoefficients :: [Complex Double]}
|
|
|
|
piFourier :: Fourier
|
|
piFourier = mkFourier $ lineToPoints 500 $
|
|
toLineCommands $ extractPath $ scale 15 $
|
|
center $ latexAlign "\\pi"
|
|
|
|
piFourierLen :: Double
|
|
piFourierLen = sum $ map magnitude $ drop 1 $ take 500 $ fourierCoefficients piFourier
|
|
|
|
pointAtFourier :: Fourier -> Complex Double
|
|
pointAtFourier = sum . fourierCoefficients
|
|
|
|
mkFourier :: [RPoint] -> Fourier
|
|
mkFourier points = Fourier $ findCoefficient 0 :
|
|
concat [ [findCoefficient n, findCoefficient (-n)] | n <- [1..] ]
|
|
where
|
|
findCoefficient :: Int -> Complex Double
|
|
findCoefficient n =
|
|
sum [ toComplex point * exp (negate (fromIntegral n) * 2 *pi * i*t) * deltaT
|
|
| (idx, point) <- zip [0::Int ..] points, let t = fromIntegral idx/nPoints ]
|
|
i = 0 :+ 1
|
|
toComplex (V2 x y) = x :+ y
|
|
deltaT = recip nPoints
|
|
nPoints = fromIntegral (length points)
|
|
|
|
setFourierLength :: Double -> Fourier -> Fourier
|
|
setFourierLength _ (Fourier []) = Fourier []
|
|
setFourierLength len0 (Fourier (first:lst)) = Fourier $ first : worker len0 lst
|
|
where
|
|
worker _len [] = []
|
|
worker len (c:cs) =
|
|
if magnitude c < len
|
|
then c : worker (len - magnitude c) cs
|
|
else [c * realToFrac (len / magnitude c)]
|
|
|
|
rotateFourier :: Double -> Fourier -> Fourier
|
|
rotateFourier phi (Fourier coeffs) =
|
|
Fourier $ worker coeffs (0::Integer)
|
|
where
|
|
worker [] _ = []
|
|
worker (x:rest) 0 = x : worker rest 1
|
|
worker [left] n = worker [left,0] n
|
|
worker (left:right:rest) n =
|
|
let n' = fromIntegral n in
|
|
left * exp (negate n' * 2 * pi * i * phi') :
|
|
right * exp (n' * 2 * pi * i * phi') :
|
|
worker rest (n+1)
|
|
i = 0 :+ 1
|
|
phi' = realToFrac phi
|
|
|
|
mkFourierOutline :: Fourier -> [(Double, Double)]
|
|
mkFourierOutline fourier =
|
|
[ (x, y)
|
|
| idx <- [0 .. granularity]
|
|
, let x :+ y = pointAtFourier $ rotateFourier (idx/granularity) fourier
|
|
]
|
|
where
|
|
granularity = 500
|