Improve README examples (#68)

- Move examples higher, above any technical details.
- Use a consistent off-white background color.
- Use more interesting and less trivial examples.
- Play at 50fps rather than 25fps.
- Host offsite (imgur) as to not take up repo space.
- Link to source file for each example.

Former-commit-id: 7e62a3a1ca99e9abc5781568ff920066c94783ee
This commit is contained in:
David Himmelstrup 2020-05-06 19:16:25 +08:00 committed by GitHub
commit fd2935c2ba
14 changed files with 159 additions and 201 deletions

View file

@ -16,15 +16,29 @@ scripting.
In more practical terms, reanimate is a library for turning code like this:
```haskell
main = reanimate $ docEnv $ playThenReverseA $ mkAnimation duration $ \t ->
partialSvg t $ pathify $ mkCircle radius
where duration = 2; radius = screenHeight/3
main = reanimate $ docEnv $ playThenReverseA drawCircle
```
... into animations like this:
![Draw Circle](docs/gifs/doc_playThenReverseA.gif)
[![Draw Circle](https://i.imgur.com/C02hPw8.gif)](examples/doc_playThenReverseA.hs)
# What is reanimate good at?
## Vector graphics and math
[![Tangent/Normal](https://i.imgur.com/w6gEkbl.gif)](examples/demo_tangent.hs)
[![Fourier](https://i.imgur.com/pX4YRa4.gif)](examples/tut_glue_fourier.hs)
## Mapping and tracing
[![Geo JSON](https://i.imgur.com/OrKiOqF.gif)](videos/map-projection/gif.hs)
[![Object tracing](https://i.imgur.com/Y6NsPWF.gif)](examples/tut_glue_potrace.hs)
## Mathematical typesetting
[![LaTeX](https://i.imgur.com/e6oO4wz.gif)](examples/tut_glue_latex.hs)
## 2D physics and 3D graphics
[![2D Physics](https://i.imgur.com/ZHUfWdp.gif)](examples/tut_glue_physics.hs)
[![3D graphics](https://i.imgur.com/4wdtuJw.gif)](examples/tut_glue_povray.hs)
# Prerequisites
@ -62,22 +76,6 @@ animation source code, the browser window will automatically reload and show the
* Design overview: https://reanimate.readthedocs.io/en/latest/glue_tut/
* Gallery with source code: https://reanimate.readthedocs.io/en/latest/gallery/
# Examples
The example gifs are displayed at 25 fps.
![Map projections](gifs/map.gif)
![LaTeX wheel](gifs/latex_wheel.gif)
![Sunflower](gifs/sunflower.gif)
![Tangent](gifs/tangent.gif)
![Goo](gifs/goo.gif)
![Drawing LaTeX equations](gifs/latex_draw.gif)
![Bounding boxes](gifs/bbox.gif)
![Colorful LaTeX](gifs/latex_color.gif)
![Bezier curves](gifs/bezier.gif)
![Valentine's Day](gifs/valentine.gif)
![Basic LaTeX](gifs/latex_basic.gif)
# Authors
* David Himmelstrup.
@ -104,4 +102,4 @@ means.
Completed animations are uploaded to the [Reanimated Science](https://www.youtube.com/channel/UCbZujyI7i6JbI-I0shPvDgg) channel.
Animation snippets are uploaded to the [Reanimated Science Playground](https://www.youtube.com/channel/UCL7MwXLtQbhJeb6Ts3_HooA) channel.
Animation snippets are uploaded to the [Reanimated Science Shorts](https://www.youtube.com/channel/UCL7MwXLtQbhJeb6Ts3_HooA) channel.

110
examples/demo_tangent.hs Executable file
View file

@ -0,0 +1,110 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings #-}
module Main(main) where
import Control.Lens ((^.))
import Control.Monad.State
import qualified Data.Vector.Unboxed as V
import Geom2D.CubicBezier (AnyBezier (..), Point (..),
evalBezierDeriv)
import Graphics.SvgTree (Coord, Tree (..), mapTree,
pathDefinition)
import Linear.Metric
import Linear.V2 (V2 (..))
import Linear.Vector
import Reanimate
import Reanimate.Builtin.Documentation
main :: IO ()
main = reanimate $ docEnv $ mkAnimation 30 $ \t ->
let piSvg = pathify $ lowerTransformations $ center $ scale 10 $ latex "s" in
mkGroup
[ mkBackgroundPixel rtfdBackgroundColor
, piSvg
, drawTangent t piSvg ]
drawTangent :: Double -> SVG -> SVG
drawTangent alpha | alpha >= 1 = id
drawTangent alpha = mapTree worker
where
worker (PathTree path) =
let (V2 posX posY, tangent) =
atPartial alpha $ toLineCommands $ path^.pathDefinition
normed@(V2 tangentX tangentY) = normalize tangent ^* 4
V2 midX midY = lerp 0.5 0 normed
V2 normVectX normVectY = normalize tangent ^* (svgWidth normalTxt*1.1)
tangentSvg =
translate (posX) (posY) $
rotate (unangle normed/pi*180 + 180) $
translate 0 (svgHeight tangentTxt/2) $
tangentTxt
normalSvg =
translate (posX) (posY) $
rotate (unangle normed/pi*180 + 90) $
translate (svgWidth normalTxt/2*1.1) (svgHeight normalTxt/2*1.3) $
normalTxt
in mkGroup
[ withStrokeWidth (defaultStrokeWidth) $
withStrokeColor "black" $
translate (posX-midX) (posY-midY) $
mkLine (0, 0) (tangentX, tangentY)
, withStrokeWidth (defaultStrokeWidth) $
withStrokeColor "black" $
translate (posX) (posY) $
mkLine (0, 0) (-normVectY, normVectX)
, withStrokeWidth (defaultStrokeWidth*2) $
withStrokeColor "white" $
tangentSvg
, withFillOpacity 1 $ withFillColor "black" $ withStrokeWidth 0 $
tangentSvg
, withStrokeWidth (defaultStrokeWidth*2) $
withStrokeColor "white" $
normalSvg
, withFillOpacity 1 $ withFillColor "black" $ withStrokeWidth 0 $
normalSvg
]
worker t = t
tangentTxt = scale 1.1 $ center $ latex "tangent"
normalTxt = scale 1.1 $ center $ latex "normal"
atPartial :: Double -> [LineCommand] -> (V2 Double, V2 Double)
atPartial alpha cmds = evalState (worker 0 cmds) zero
where
worker _d [] = pure (0, 0)
worker d (cmd:xs) = do
from <- get
len <- lineLength cmd
let frac = (targetLen-d) / len
if len == 0 || frac >= 1
then worker (d+len) xs
else do
let bezier = lineCommandToBezier from cmd
(pos, tangent) = evalBezierDeriv bezier frac
pure $ (fromPoint pos, fromPoint tangent)
totalLen = evalState (sum <$> mapM lineLength cmds) zero
targetLen = totalLen * alpha
lineCommandToBezier :: V2 Coord -> LineCommand -> AnyBezier Coord
lineCommandToBezier from line =
case line of
LineBezier [a] ->
AnyBezier $ V.fromList [toTuple from, toTuple a]
LineBezier [a,b] ->
AnyBezier $ V.fromList [toTuple from, toTuple a, toTuple b]
LineBezier [a,b,c] ->
AnyBezier $ V.fromList [toTuple from, toTuple a, toTuple b, toTuple c]
_ -> error (show line)
fromPoint :: Point a -> V2 a
fromPoint (Point x y) = V2 x y
toTuple :: V2 a -> (a,a)
toTuple (V2 x y) = (x, y)
unangle :: (Floating a, Ord a) => V2 a -> a
unangle a@(V2 ax ay) =
let alpha = asin $ ay / norm a
in if ax < 0
then pi - alpha
else alpha

View file

@ -12,8 +12,9 @@ import Codec.Picture
-- layer 3
main :: IO ()
main = reanimate $ parA bg $ sceneAnimation $ do
play $ fourierA (fromToS 0 15) -- Rotate 15 times
main = reanimate $ setDuration 30 $ sceneAnimation $ 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
@ -22,8 +23,6 @@ main = reanimate $ parA bg $ sceneAnimation $ do
# 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

View file

@ -25,7 +25,7 @@ latexExample = sceneAnimation $ do
-- Draw equation
play $ drawAnimation strokedSvg
sprites <- forM glyphs $ \(fn, _, elt) ->
newSpriteA $ animate $ const $ fn elt
newSpriteSVG $ fn elt
-- Yoink each glyph
forM_ (reverse sprites) $ \sprite -> do
spriteE sprite (overBeginning 1 $ aroundCenterE $ highlightE)

View file

@ -18,13 +18,10 @@ main = reanimate $ parA bg $ sceneAnimation $ do
xRot <- newVar (-45)
yRot <- newVar 220
wf <- newSprite $ wireframe <$> unVar xRot <*> unVar yRot
tweenVar yRot spinDur (\t v -> fromToS v (v+60*3) $ curveS 2 (t/spinDur))
fork $ tweenVar yRot spinDur $ \v -> fromToS v (v+60*3) . curveS 2
replicateM_ wobbles $ do
tweenVar xRot (wobbleDur/2) (\t v -> fromToS v (v+90) $ curveS 2 (t/(wobbleDur/2)))
fork $ do
wait (wobbleDur/2)
tweenVar xRot (wobbleDur/2) (\t v -> fromToS v (v-90) $ curveS 2 (t/(wobbleDur/2)))
wait wobbleDur
tweenVar xRot (wobbleDur/2) $ \v -> fromToS v (v+90) . curveS 2
tweenVar xRot (wobbleDur/2) $ \v -> fromToS v (v-90) . curveS 2
destroySprite wf
play $ mkAnimation drawDuration (\t -> partialSvg t (wireframe (-45) 220))
# reverseA

View file

@ -22,7 +22,8 @@ import System.Random.Shuffle
main :: IO ()
main = reanimate $ parA bg $ sceneAnimation $ do
main = reanimate $ sceneAnimation $ do
newSpriteSVG $ mkBackgroundPixel $ PixelRGBA8 252 252 252 0xFF
zPos <- newVar 0
xRot <- newVar 0
zRot <- newVar 0
@ -41,8 +42,6 @@ main = reanimate $ parA bg $ sceneAnimation $ do
fork $ tweenVar zRot 9 $ \v -> fromToS v 360 . curveS 2
wait 10
tweenVar zPos 2 $ \v -> fromToS v 0 . curveS 3
where
bg = animate $ const $ mkBackgroundPixel $ PixelRGBA8 252 252 252 0xFF
texture :: Double -> SVG
texture t = frameAt (t*duration latexExample) latexExample
@ -92,7 +91,7 @@ latexExample = sceneAnimation $ do
-- Draw equation
play $ drawAnimation strokedSvg
sprites <- forM glyphs $ \(fn, _, elt) ->
newSpriteA $ animate $ const $ fn elt
newSpriteSVG $ fn elt
-- Yoink each glyph
forM_ (reverse sprites) $ \sprite -> do
spriteE sprite (overBeginning 1 $ aroundCenterE $ highlightE)

View file

@ -1,53 +0,0 @@
# blender --background --python 01_cube.py -- </path/to/output/image> <resolution_percentage>
import bpy
import os
import sys
import math
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
import utils
def get_output_file_path() -> str:
return str(sys.argv[sys.argv.index('--') + 1])
def get_resolution_percentage() -> int:
return int(sys.argv[sys.argv.index('--') + 2])
if __name__ == "__main__":
# Args
output_file_path = get_output_file_path()
resolution_percentage = get_resolution_percentage()
# Setting
default_scene = bpy.context.scene
default_camera_object = bpy.data.objects["Camera"]
bpy.ops.object.empty_add(location=(0.0, 0, 0.0))
focus_target = bpy.context.object
bpy.ops.object.select_all(action='DESELECT')
default_camera_object.select_set(True)
focus_target.select_set(True)
bpy.ops.object.parent_set()
focus_target.rotation_mode = 'XYZ'
focus_target.rotation_euler = (0,0,0)
focus_target.keyframe_insert(data_path='rotation_euler', frame=1)
focus_target.rotation_euler = (0,0,math.pi*2)
focus_target.keyframe_insert(data_path='rotation_euler', frame=120)
for k in focus_target.animation_data.action.fcurves.find('rotation_euler', index=2).keyframe_points:
k.interpolation = 'LINEAR'
num_samples = 32
utils.set_animation(default_scene, fps=60, frame_start=1, frame_end=120)
utils.set_cycles_renderer(default_scene, resolution_percentage, output_file_path, default_camera_object,
num_samples, use_denoising=True)
# Rendering
bpy.ops.render.render(animation=True, write_still=True)

View file

@ -1,45 +0,0 @@
import os
import math
import bpy
cam = bpy.data.objects['Camera']
origin = bpy.data.objects['Cube']
bpy.ops.object.select_all(action='DESELECT')
origin.select_set(True)
bpy.ops.object.delete()
#bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4)
#bpy.ops.mesh.primitive_uv_sphere_add()
x = 0.9
bpy.ops.mesh.primitive_plane_add()
plane = bpy.context.object
plane.scale = (1,1.778,1)
plane.scale = (1,2,1)
bpy.ops.object.shade_smooth()
modifier = plane.modifiers.new(name='Subsurf', type='SUBSURF')
modifier.levels = 5
modifier.render_levels = 5
modifier.subdivision_type = 'SIMPLE'
bpy.ops.object.empty_add(type='ARROWS',rotation=(math.pi/2,0,0))
empty = bpy.context.object
bendUp = plane.modifiers.new(name='Bend up', type='SIMPLE_DEFORM')
bendUp.deform_method = 'BEND'
bendUp.origin = empty
bendUp.deform_axis = 'Z'
bendUp.factor = math.pi*x
bendAround = plane.modifiers.new(name='Bend up', type='SIMPLE_DEFORM')
bendAround.deform_method = 'BEND'
bendAround.origin = empty
bendAround.deform_axis = 'X'
bendAround.factor = math.pi*2*x
scn = bpy.context.scene
# scn.render.engine = 'CYCLES'
scn.render.film_transparent = True
bpy.ops.render.render( write_still=True )

View file

@ -1,43 +0,0 @@
import os
import bpy
cam = bpy.data.objects['Camera']
origin = bpy.data.objects['Cube']
bpy.ops.object.select_all(action='DESELECT')
origin.select_set(True)
bpy.ops.object.delete()
#bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4)
bpy.ops.mesh.primitive_uv_sphere_add()
bpy.ops.object.shade_smooth()
# bpy.data.materials.new('Prog mat')
bpy.context.object.active_material = bpy.data.materials['Material']
mat = bpy.context.object.active_material
image_node = mat.node_tree.nodes.new('ShaderNodeTexImage')
texture = mat.node_tree.nodes['Principled BSDF']
texture.inputs['Roughness'].default_value = 1
mat.node_tree.links.new(image_node.outputs['Color'], texture.inputs['Base Color'])
bpy.ops.image.open(filepath='/home/lemmih/Downloads/earth.jpg')
image_node.image = bpy.data.images['earth.jpg']
# image_node.image = bpy.data.images['earth.jpg']
# bpy.ops.image.open(filepath='/home/lemmih/Downloads/earth.jpg')
# image_node = mat.node_tree.nodes.new('ShaderNodeTexImage')
# image_node = mat.node_tree.nodes[1]
# texture = mat.node_tree.nodes[2]
# mat.node_tree.links.new(image_node.outputs[0], texture.inputs[0])
# bpy.data.materials.new('Prog mat')
# bpy.context.object.active_material = bpy.data.materials['Prog mat']
scn = bpy.context.scene
# scn.render.engine = 'CYCLES'
scn.render.film_transparent = True
bpy.data.scenes["Scene"].render.filepath = '/tmp/blender.png'
bpy.ops.render.render( write_still=True )

View file

@ -91,7 +91,7 @@ library
JuicyPixels, attoparsec, parallel,
cubicbezier, websockets,
hashable, fsnotify, open-browser, random-shuffle, base64-bytestring,
vector, colour, cassava, ansi-wl-pprint, here, temporary,
vector >= 0.12.0.0, colour, cassava, ansi-wl-pprint, here, temporary,
optparse-applicative, chiphunk >= 0.1.2.1,
geojson, aeson >= 1.3.0.0
ghc-options: -Wall

View file

@ -48,3 +48,6 @@ showColorMap f = center $ scaleToSize screenWidth screenHeight $ embedImage img
height = 1
img = generateImage pixelRenderer width height
pixelRenderer x _y = f (fromIntegral x / fromIntegral (width-1))
rtfdBackgroundColor :: PixelRGBA8
rtfdBackgroundColor = PixelRGBA8 252 252 252 0xFF

View file

@ -1,4 +1,4 @@
resolver: lts-13.14
resolver: lts-13.19
allow-newer: false

View file

@ -41,7 +41,7 @@ packages:
hackage: matrices-0.5.0@sha256:b2761813f6a61c84224559619cc60a16a858ac671c8436bbac8ec89e85473058
snapshots:
- completed:
size: 497078
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/13/14.yaml
sha256: 9e5ba48e5188aeb52bb0df9bc65dbd858a12c33f5b8d495a3eb588dc9ffcb15a
original: lts-13.14
size: 498155
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/13/19.yaml
sha256: b9367a80d4393d02e58a46b8a9fdfbd7bc19f59c0c2bbf90034ba15cf52cf213
original: lts-13.19

View file

@ -14,6 +14,7 @@ import Reanimate
import Reanimate.Animation
import Reanimate.Scene
import Reanimate.GeoProjection
import Reanimate.Builtin.Documentation
import System.IO.Unsafe
import Data.Geospatial hiding (LonLat)
import Data.LinearRing
@ -27,8 +28,8 @@ import Control.Lens ((^.))
main :: IO ()
main = seq equirectangular $ reanimate $ sceneAnimation $ do
newSpriteSVG $ mkBackground "white"
main = reanimate $ sceneAnimation $ do
newSpriteSVG $ mkBackgroundPixel rtfdBackgroundColor
prevProj <- newVar equirectangularP
let push label proj = do
prev <- readVar prevProj
@ -43,24 +44,24 @@ main = seq equirectangular $ reanimate $ sceneAnimation $ do
-- [ grid equirectangularP ]
-- push "Lambert" lambertP
push "Web Mercator" mercatorP
--push "Web Mercator" mercatorP
push "Mollweide" mollweideP
-- push "Bottomley 30\\degree" (bottomleyP (toRads 30))
push "Bottomley 30\\degree" (bottomleyP (toRads 30))
-- 4
-- pushInterp "Werner" wernerP
push "Werner" wernerP
-- 5
-- pushInterp "Bonne 45\\degree" (bonneP (toRads 45))
-- push "Bonne 45\\degree" (bonneP (toRads 45))
-- pushT
-- (\t -> "Bonne " <> T.pack (show $ round $ fromToS 45 0 t) <> "\\degree")
-- (bonneP . toRads . fromToS 45 0)
-- 6
-- pushInterp "Eckert I" eckert1P
-- pushInterp "Eckert III" eckert3P
-- pushInterp "Eckert IV" eckert5P
-- push "Eckert I" eckert1P
-- push "Eckert III" eckert3P
-- push "Eckert IV" eckert5P
-- 7
-- push "Fahey" faheyP
-- 8
push "August" augustP
-- push "August" augustP
-- 9
push "Foucaut" foucautP
-- 10
@ -70,17 +71,9 @@ main = seq equirectangular $ reanimate $ sceneAnimation $ do
play $ signalA (curveS 2) $
mkAnimation morphT $ grid . mergeP prev equirectangularP
where
src = equirectangular
waitT = 0
morphT = 1
equirectangular :: Image PixelRGB8
equirectangular = unsafePerformIO $ do
dat <- BS.readFile "earth.jpg"
case decodeJpeg dat of
Left err -> error err
Right img -> return $ convertRGB8 img
toRads :: Double -> Double
toRads dec = dec/180 * pi