From 4283793c5a4efb3c647ef289ddb1e94468203256 Mon Sep 17 00:00:00 2001 From: David Himmelstrup Date: Tue, 3 Dec 2019 16:01:18 +0800 Subject: [PATCH] Elaborate tutorial. Former-commit-id: 31267dbb1675723b3b9afde806d215b358a7abac --- docs/gallery.md | 30 +++++++++ docs/gen_gallery.sh | 2 +- docs/glue_tut.md | 52 ++++++++++++--- examples/tut_glue_animate.hs | 6 +- examples/tut_glue_fourier.hs | 122 ++++++++++++++++++++++++++++++++++ examples/tut_glue_keyframe.hs | 84 +++++++++++++++++++++++ 6 files changed, 283 insertions(+), 13 deletions(-) create mode 100755 examples/tut_glue_fourier.hs create mode 100644 examples/tut_glue_keyframe.hs diff --git a/docs/gallery.md b/docs/gallery.md index a192809..55a0478 100644 --- a/docs/gallery.md +++ b/docs/gallery.md @@ -181,3 +181,33 @@ This file is auto-generated by docs/render_all.sh. DO NOT EDIT.


+## tut_glue_keyframe + +
+ View tut_glue_keyframe.hs +

+  {!examples/tut_glue_keyframe.hs!}
+  
+
+
+ + +


+ +## tut_glue_fourier + +
+ View tut_glue_fourier.hs +

+  {!examples/tut_glue_fourier.hs!}
+  
+
+
+ + +


+ diff --git a/docs/gen_gallery.sh b/docs/gen_gallery.sh index e5c81d2..d7a369d 100755 --- a/docs/gen_gallery.sh +++ b/docs/gen_gallery.sh @@ -3,7 +3,7 @@ ROOT=`stack path --project-root` EXAMPLES='boundingbox colormaps goo latex_basic latex_color latex_draw latex_wheel raster sphere blender_default_cube - tut_glue_svg tut_glue_animate' + tut_glue_svg tut_glue_animate tut_glue_keyframe tut_glue_fourier' WIDTH=640 HEIGHT=$((WIDTH*9/16)) diff --git a/docs/glue_tut.md b/docs/glue_tut.md index 7c35965..ccedd14 100644 --- a/docs/glue_tut.md +++ b/docs/glue_tut.md @@ -25,11 +25,13 @@ SVG features, as demonstrated in the below animation:
-## Animation = Time 🡢 SVG +## Animation = Time ➞ SVG + +Animations can be defined as SVG images over time (plus a bit of bookkeeping such as their duration). With this approach, the time variable can determine SVG properties such as radius, path lengths, rotation, and color. Reanimate ships with a bunch of combinators for composing and arranging animations.
Toggle source code. @@ -39,27 +41,59 @@ SVG features, as demonstrated in the below animation:

+Reanimate is not an opinionated framework, though, and also offers a more traditional keyframing tools. The example below uses an imperative API to schedule the various sub animations, transitions, and effects. + +
+ Toggle source code. +

+  {!examples/tut_glue_keyframe.hs!}
+  
+
+
+ + + ## Pillar I: Haskell -TODO: Write about fourier drawings. About how advanced math is doable with Haskell. +A large part of Reanimate's expressive power comes from using Haskell as the scripting language. Haskell tends to favor expressiveness and correctness over raw performance and that is exactly what is needed from a glue language. All the heavy-lifting of rendering frames and encoding videos is handled by external tools and Reanimate merely needs to concern itself with finding intuitive ways of describing animations. + +The following examples shows how something as seemingly complicated as fourier series can be expressed and animated in Haskell. Most noteworthy is the layered structure of the code: + + 1. The first layer handles the mathematics of fourier series without worrying about graphics or how properties should be animated, + 2. the second layer describes how to draw a single frame given the length of the fourier series and the degree of rotation, + 3. the third layer deals with time: order of animations, durations, number of rotations, transition timing functions, pauses, etc. + +
+ Toggle source code. +

+  {!examples/tut_glue_fourier.hs!}
+  
+
+
+ -## Pillar II: Libraries TODO: Write about how lots of libraries are available for Haskell. Use chiphunk as an example. Show SVG primitives with 2D physics. -## Pillar III: LaTeX +## Pillar II: LaTeX TODO: Show that LaTeX is a provider of SVG graphics. It has the type 'latex :: Text -> SVG'. Caching is automatic and it plays well with other SVG functions (partialSvg, center, etc). -## Pillar IV: potrace +## Pillar III: potrace -## Pillar V: Povray +## Pillar IV: Povray -## Pillar VI: Blender +## Pillar V: Blender diff --git a/examples/tut_glue_animate.hs b/examples/tut_glue_animate.hs index 0e4504b..0a0a4f1 100644 --- a/examples/tut_glue_animate.hs +++ b/examples/tut_glue_animate.hs @@ -10,8 +10,8 @@ import Reanimate.Animation import Reanimate.Effect import Reanimate.Scene -bgColor :: String -bgColor = "white" +bgColor :: PixelRGBA8 +bgColor = PixelRGBA8 252 252 252 0xFF segmentDuration :: Double segmentDuration = 3 @@ -25,7 +25,7 @@ main = reanimate $ bg `parA` [animateCircleR, animateCircleP, animateRectR, animateColor ,signalA (constantS 0) $ setDuration transitionTime animateCircleR] where - bg = animate $ const $ mkBackground bgColor + bg = animate $ const $ mkBackgroundPixel bgColor animateCircleR :: Animation animateCircleR = mkSegment "radius" $ \t -> mkCircle (t*2) diff --git a/examples/tut_glue_fourier.hs b/examples/tut_glue_fourier.hs new file mode 100755 index 0000000..b058e8d --- /dev/null +++ b/examples/tut_glue_fourier.hs @@ -0,0 +1,122 @@ +#!/usr/bin/env stack +-- stack runghc --package reanimate +{-# LANGUAGE OverloadedStrings #-} +module Main (main) where + +import Data.Complex +import Graphics.SvgTree +import Linear.V2 +import Reanimate +import Reanimate.Signal +import Codec.Picture + +-- layer 3 +main :: IO () +main = reanimate $ parA bg $ sceneAnimation $ do + play $ fourierA (fromToS 0 15) -- 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 + where + bg = animate $ const $ mkBackgroundPixel (PixelRGBA8 252 252 252 0xFF) + +-- layer 2 +fourierA :: (Double -> Double) -> Animation +fourierA genPhi = animate $ \t -> + let circles = setFourierLength (t*piFourierLen) piFourier + in mkGroup + [ drawCircles $ fourierCoefficients $ rotateFourier (genPhi t) circles + , withStrokeColor "green" $ + withStrokeLineJoin JoinRound $ + withFillOpacity 0 $ + withStrokeWidth (defaultStrokeWidth*2) $ + mkLinePath $ mkFourierOutline circles + ] + +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 +data 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 + diff --git a/examples/tut_glue_keyframe.hs b/examples/tut_glue_keyframe.hs new file mode 100644 index 0000000..dc4723a --- /dev/null +++ b/examples/tut_glue_keyframe.hs @@ -0,0 +1,84 @@ +#!/usr/bin/env stack +-- stack runghc --package reanimate +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecursiveDo #-} +module Main (main) where + +import Control.Monad (forM_) +import Graphics.SvgTree (Tree) +import Reanimate +import Reanimate.Driver (reanimate) +import Reanimate.Effect +import Codec.Picture + +main :: IO () +main = reanimate $ bg `parA` mainScene + where + bg = animate $ const $ mkBackgroundPixel (PixelRGBA8 252 252 252 0xFF) + +mainScene :: Animation +mainScene = sceneAnimation $ mdo + play $ drawCircle + # setDuration drawCircleT + # applyE (constE flipXAxis) + # signalA (curveS 2) + fork $ play $ drawCircle + # freezeAtPercentage 1 + # setDuration rotDur + rotDur <- withSceneDuration $ waitAll $ + forM_ svgs $ \svg -> do + fork $ play $ drawTick + # setDuration rotateT + # repeatA rotateN + # applyE (overBeginning 0.5 drawInE) + # applyE (overEnding 0.5 drawOutE) + fork $ play $ drawSVG svg + # setDuration rotateT + # repeatA rotateN + # applyE (overBeginning rotateT drawInE) + # applyE (delayE rotateT $ overBeginning 1 fillInE) + # applyE (overEnding 0.5 fadeOutE) + wait (rotateT / fromIntegral (1+length svgs)) + play $ drawCircle + # setDuration drawCircleT + # reverseA + # signalA (curveS 2) + return () + where + drawCircleT = 2.5 + rotateT = 5 + rotateN = 3 + + svgCAF = center $ latex "\\LaTeX" + getNth n = snd (splitGlyphs [n] svgCAF) + svgs = [ + withStrokeWidth 0.01 $ + scale 2 $ + translate 0 (tickLength*2) $ + withStrokeColor "black" $ + withFillColor "black" $ + center $ getNth n + | n <- [0..4]] + +radius, tickLength :: Double +radius = 1.25 +tickLength = 0.25 + +drawCircle :: Animation +drawCircle = animate $ \t -> + withFillOpacity 0 $ + withStrokeColor "black" $ + rotate (-90) $ + partialSvg t circPath + where + circPath = pathify $ mkCircle radius + +drawTick :: Animation +drawTick = drawSVG $ mkLine (0, 0) (0, tickLength) + +drawSVG :: Tree -> Animation +drawSVG svg = animate $ \t -> + withStrokeColor "black" $ + rotate (t*360) $ + translate 0 radius $ + svg