More documentation, parameters, update blender example.

Former-commit-id: ce16e476bf8f417dafd091f12559f83da25ebbff
This commit is contained in:
David Himmelstrup 2019-12-09 17:22:14 +08:00
commit 073b28bbf8
12 changed files with 241 additions and 107 deletions

View file

@ -2,13 +2,13 @@
Reanimate is a library for programmatically generating animations with a twist Reanimate is a library for programmatically generating animations with a twist
towards mathematics / vector drawings. A lot of inspiration was drawn from towards mathematics / vector drawings. A lot of inspiration was drawn from
3b1b's manim library. [3b1b's](https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw) [manim](https://github.com/3b1b/manim) library.
Reanimate aims at being a batteries-included way of gluing together different technologies: SVG as a universal image format, LaTeX for typesetting, ffmpeg for video encoding, inkscape/imagemagick for rasterization, potrace for vectorization, blender/povray for 3D graphics, and Haskell for scripting. Reanimate aims at being a batteries-included way of gluing together different technologies: SVG as a universal image format, LaTeX for typesetting, ffmpeg for video encoding, inkscape/imagemagick for rasterization, potrace for vectorization, blender/povray for 3D graphics, and Haskell for scripting.
## Scalable Vector Graphics ## Scalable Vector Graphics
Movies consists of a sequence of frames and, in reanimate, these frames are SVG images. SVGs can easily reference raster images, includes a set of drawing primitives, and offers image advanced manipulation through filter effects. Movies consists of a sequence of frames and, in reanimate, these frames are [SVG](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Introduction) images. SVGs can easily reference raster images, includes a set of drawing primitives, and offers image advanced manipulation through filter effects.
Since SVGs are plain-text documents, tools can be written to analyse and modify images. For example, reanimate includes code for applying 2D physics to shapes in SVG images. Since SVGs are plain-text documents, tools can be written to analyse and modify images. For example, reanimate includes code for applying 2D physics to shapes in SVG images.
SVG features, as demonstrated in the below animation: SVG features, as demonstrated in the below animation:
@ -82,7 +82,7 @@ The following examples shows how something as seemingly complicated as fourier s
<source src="https://github.com/Lemmih/reanimate/raw/master/docs/rendered/tut_glue_fourier.mp4"> <source src="https://github.com/Lemmih/reanimate/raw/master/docs/rendered/tut_glue_fourier.mp4">
</video> </video>
Scripting in Haskell also gives access to the extensive body of code libraries. There are Haskell libraries for syntax highlighting, font manipulation, and much, much more. In the spirit of being a batteries-included framework, Reanimate ships with a built-in 2D physics library, called Chipmunk2D. The example below demonstrates how SVG shapes can be used nearly effortlessly in a physics simulation. Scripting in Haskell also gives access to the extensive body of code libraries. There are Haskell libraries for syntax highlighting, font manipulation, and much, much more. In the spirit of being a batteries-included framework, Reanimate ships with a built-in 2D physics library, called [Chipmunk2D](https://chipmunk-physics.net/). The example below demonstrates how SVG shapes can be used nearly effortlessly in a physics simulation.
<details> <details>
<summary>Toggle source code.</summary> <summary>Toggle source code.</summary>
@ -100,7 +100,7 @@ Scripting in Haskell also gives access to the extensive body of code libraries.
## Pillar II: LaTeX ## Pillar II: LaTeX
LaTeX is a widely used system for typesetting equations and documents. It is most commonly used by writing TeX documents which are then converted to pdfs. However, since the output of LaTeX is natively vector graphics, it is trivial to get SVG documents instead of pdfs. Armed with this knowledge, Reanimate offers a simple yet powerful function: `latex :: Text -> SVG` [LaTeX](https://www.latex-project.org/) is a widely used system for typesetting equations and documents. It is most commonly used by writing TeX documents which are then converted to pdfs. However, since the output of LaTeX is natively vector graphics, it is trivial to get SVG documents instead of pdfs. Armed with this knowledge, Reanimate offers a simple yet powerful function: `latex :: Text -> SVG`
The `latex` function takes a snippet of TeX code, passes it through the LaTeX system, and converts the result to an SVG image. Furthermore, since the result is entirely determined by the TeX code, caching is used to hide the overhead of invoking LaTeX. The `latex` function takes a snippet of TeX code, passes it through the LaTeX system, and converts the result to an SVG image. Furthermore, since the result is entirely determined by the TeX code, caching is used to hide the overhead of invoking LaTeX.
@ -120,7 +120,7 @@ The resulting SVGs can be manipulated just like any other. The below examples il
## Pillar III: povray ## Pillar III: povray
Although incredibly expressive, SVGs are strictly limited to 2D graphics. This limitation can be overcome with a 3D renderer such as povray: povray is a nearly 30 year-old raytracer with a relatively small but solid set of features. Reanimate offers convenient functions for importing povray scenes as well as exporting animations to be used as textures. In the video below, the LaTeX animation is projected upon a plane which is then rotated and translated in 3D space. A key thing to note is that both the 2D and 3D elements are managed entirely through code. Although incredibly expressive, SVGs are strictly limited to 2D graphics. This limitation can be overcome with a 3D renderer such as [povray](https://www.povray.org/): povray is a nearly 30 year-old raytracer with a relatively small but solid set of features. Reanimate offers convenient functions for importing povray scenes as well as exporting animations to be used as textures. In the video below, the LaTeX animation is projected upon a plane which is then rotated and translated in 3D space. A key thing to note is that both the 2D and 3D elements are managed entirely through code.
<details> <details>
<summary>Toggle source code.</summary> <summary>Toggle source code.</summary>
@ -135,7 +135,7 @@ Although incredibly expressive, SVGs are strictly limited to 2D graphics. This l
</video> </video>
The video above uses a perspective camera, ie. objects further away appears to be smaller. This gives the appearance of three dimensions but it also makes it difficult to interlace SVG objects and 3D objects with pixel-perfect precision. For example, aligning a cube and a square requires the exact position of the pinhole camera. All of this can be dramatically simplified with an orthographic projection where 'x' an 'y' coordinates in 3D space always map to the same 'x' and 'y' coordinates on the screen. Shapes lose their perspective but in many cases, especially when illustrating mathematical concepts, drawing "idealized" shapes is perfectly fine. The video below shows an orthographic projection of a sphere. The sphere (3D shape) could be completely eclipsed by a circle (2D shape) of the same radius with pixel-perfection. The video above uses a perspective camera, ie. objects further away appears to be smaller. This gives the appearance of three dimensions but it also makes it difficult to interlace SVG objects and 3D objects with pixel-perfect precision. For example, aligning a cube and a square requires the exact position of the pinhole camera. All of this can be dramatically simplified with an [orthographic projection](https://en.wikipedia.org/wiki/Orthographic_projection) where 'x' an 'y' coordinates in 3D space always map to the same 'x' and 'y' coordinates on the screen. Shapes lose their perspective but in many cases, especially when illustrating mathematical concepts, drawing "idealized" shapes is perfectly fine. The video below shows an orthographic projection of a sphere. The sphere (3D shape) could be completely eclipsed by a circle (2D shape) of the same radius with pixel-perfect accuracy.
<details> <details>
<summary>Toggle source code.</summary> <summary>Toggle source code.</summary>
@ -152,12 +152,26 @@ The video above uses a perspective camera, ie. objects further away appears to b
## Pillar IV: Blender ## Pillar IV: Blender
TODO: Crumble SVG animation. [Blender](https://www.blender.org/) is a vastly more modern and capable 3D modeller than povray but has a slightly steeper learning curve. Most people interact with Blender through a graphical user interface but all of blender's features can also be used directly from Python. The [Python API](https://docs.blender.org/api/current/index.html) is sizable and, at first glance, it might seem that there are almost no tutorials for blender scripting. However, Blender can tell you the Python command for every action in the GUI, making it easy to translate graphical tutorials to scritping tutorials.
TODO: Morph SVG animation into sphere. The example below uses built-in modifiers to bend a plane into a sphere and is rendered using the EEVEE engine.
<details>
<summary>Toggle source code.</summary>
<pre><code class="haskell">
{!examples/tut_glue_blender.hs!}
</code></pre>
</details>
<br/>
<video width="640" height="360" autoplay loop>
<source src="../rendered/tut_glue_blender.mp4">
<source src="https://github.com/Lemmih/reanimate/raw/master/docs/rendered/tut_glue_blender.mp4">
</video>
## Pillar V: potrace ## Pillar V: potrace
[Potrace](https://en.wikipedia.org/wiki/Potrace) takes pixel data (from an image file, or perhaps generated by povray or Blender) and automatically convert it to vector graphics. Once an image has been vectorized, it can be manipulated with the standard SVG tools. In the example below, a sphere is rendered with povray, vectorized, and then line-drawn.
<details> <details>
<summary>Toggle source code.</summary> <summary>Toggle source code.</summary>
<pre><code class="haskell"> <pre><code class="haskell">

11
examples/parameters.hs Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import qualified Data.Text as T
main :: IO ()
main = reanimate $ animate $ const $
mkText $ T.pack $ "Test parameters: " ++ show (pFPS, pWidth, pHeight)

View file

@ -5,16 +5,21 @@
{-# LANGUAGE BangPatterns #-} {-# LANGUAGE BangPatterns #-}
module Main (main) where module Main (main) where
import Codec.Picture
import Data.String.Here import Data.String.Here
import qualified Data.Text as T import qualified Data.Text as T
import Reanimate import Reanimate
import Reanimate.Blender import Reanimate.Blender
import Reanimate.Raster import Reanimate.Raster
import Reanimate.Scene import Reanimate.Scene
import Reanimate.Animation
import Reanimate.Effect
import Graphics.SvgTree import Graphics.SvgTree
import System.IO.Unsafe import System.Random
import qualified Data.Text.IO as T import System.Random.Shuffle
import Control.Lens ((^.))
import Control.Monad
import Data.Monoid
import Codec.Picture.Types
main :: IO () main :: IO ()
main = seq texture $ reanimate $ parA bg $ sceneAnimation $ do main = seq texture $ reanimate $ parA bg $ sceneAnimation $ do
@ -27,8 +32,8 @@ main = seq texture $ reanimate $ parA bg $ sceneAnimation $ do
getTrans <- freezeVar trans getTrans <- freezeVar trans
getRotX <- freezeVar rotX getRotX <- freezeVar rotX
getRotY <- freezeVar rotY getRotY <- freezeVar rotY
return $ \real_t dur t -> return $ \real_t dur t -> seq (texture (t/dur)) $
blender (script (getBend real_t) (getTrans real_t) (getRotX real_t) (getRotY real_t)) blender (script (texture (t/dur)) (getBend real_t) (getTrans real_t) (getRotX real_t) (getRotY real_t))
tweenVar trans 5 (\t v -> fromToS v (-2) $ curveS 2 (t/5)) tweenVar trans 5 (\t v -> fromToS v (-2) $ curveS 2 (t/5))
tweenVar bend 5 (\t v -> fromToS v 1 $ curveS 2 (t/5)) tweenVar bend 5 (\t v -> fromToS v 1 $ curveS 2 (t/5))
tweenVar rotY 15 (\t v -> fromToS v (pi*2*2) $ curveS 2 (t/15)) tweenVar rotY 15 (\t v -> fromToS v (pi*2*2) $ curveS 2 (t/15))
@ -37,7 +42,7 @@ main = seq texture $ reanimate $ parA bg $ sceneAnimation $ do
wait 5 wait 5
tweenVar rotX 5 (\t v -> fromToS v (pi/5) $ curveS 2 (t/5)) tweenVar rotX 5 (\t v -> fromToS v (pi/5) $ curveS 2 (t/5))
wait (15-5) wait (15-5)
tweenVar bend 5 (\t v -> fromToS 1 0 $ curveS 2 (t/5)) tweenVar bend 5 (\t v -> fromToS v 0 $ curveS 2 (t/5))
tweenVar rotX 5 (\t v -> fromToS v 0 $ curveS 2 (t/5)) tweenVar rotX 5 (\t v -> fromToS v 0 $ curveS 2 (t/5))
tweenVar trans 5 (\t v -> fromToS v 0 $ curveS 2 (t/5)) tweenVar trans 5 (\t v -> fromToS v 0 $ curveS 2 (t/5))
wait 4 wait 4
@ -46,15 +51,14 @@ main = seq texture $ reanimate $ parA bg $ sceneAnimation $ do
where where
bg = animate $ const $ mkBackground "grey" bg = animate $ const $ mkBackground "grey"
texture :: FilePath texture :: Double -> FilePath
texture = svgAsPngFile (mkGroup texture t = svgAsPngFile $ mkGroup
[ checker 10 10 [ checker 20 20
, withFillColor "red" $ , frameAt (t*duration latexExample) latexExample
scale 2 $ center $ ]
latexAlign "\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}"])
script :: Double -> Double -> Double -> Double -> T.Text script :: FilePath -> Double -> Double -> Double -> Double -> T.Text
script bend transZ rotX rotY = [iTrim| script img bend transZ rotX rotY = [iTrim|
import os import os
import math import math
@ -91,7 +95,7 @@ texture = mat.node_tree.nodes['Principled BSDF']
texture.inputs['Roughness'].default_value = 1 texture.inputs['Roughness'].default_value = 1
mat.node_tree.links.new(image_node.outputs['Color'], texture.inputs['Base Color']) mat.node_tree.links.new(image_node.outputs['Color'], texture.inputs['Base Color'])
image_node.image = bpy.data.images.load('${T.pack texture}') image_node.image = bpy.data.images.load('${T.pack img}')
modifier = plane.modifiers.new(name='Subsurf', type='SUBSURF') modifier = plane.modifiers.new(name='Subsurf', type='SUBSURF')
@ -115,9 +119,9 @@ bendAround.deform_axis = 'Z'
bendAround.factor = -math.pi*2*x bendAround.factor = -math.pi*2*x
bpy.context.view_layer.objects.active = plane bpy.context.view_layer.objects.active = plane
print(bpy.ops.object.modifier_apply(modifier='Subsurf')) #print(bpy.ops.object.modifier_apply(modifier='Subsurf'))
print(bpy.ops.object.modifier_apply(modifier='Bend up')) #print(bpy.ops.object.modifier_apply(modifier='Bend up'))
print(bpy.ops.object.modifier_apply(modifier='Bend around')) #print(bpy.ops.object.modifier_apply(modifier='Bend around'))
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
plane.select_set(True); plane.select_set(True);
@ -128,21 +132,22 @@ bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN')
plane.rotation_euler = (0, ${rotY}, 0) plane.rotation_euler = (0, ${rotY}, 0)
scn = bpy.context.scene scn = bpy.context.scene
# scn.render.engine = 'CYCLES'
scn.render.engine = 'CYCLES'
scn.render.resolution_percentage = 10
scn.render.film_transparent = True scn.render.film_transparent = True
bpy.ops.render.render( write_still=True ) bpy.ops.render.render( write_still=True )
|] |]
checker :: Int -> Int -> SVG checker :: Int -> Int -> SVG
checker w h = checker w h =
withFillColor "white" $ withStrokeColor "lightblue" $
withStrokeColor "white" $ withStrokeWidth (defaultStrokeWidth/2) $
withStrokeWidth 0.1 $
mkGroup mkGroup
[ withStrokeWidth 0 $ [ withStrokeWidth 0 $
withFillOpacity 0.8 $ mkBackground "blue" withFillOpacity 0.8 $ mkBackground "white"
, mkGroup , mkGroup
[ translate (stepX*x-offsetX + stepX/2) 0 $ [ translate (stepX*x-offsetX + stepX/2) 0 $
mkLine (0, -screenHeight/2*0.9) (0, screenHeight/2*0.9) mkLine (0, -screenHeight/2*0.9) (0, screenHeight/2*0.9)
@ -160,3 +165,83 @@ checker w h =
stepY = screenHeight/fromIntegral h stepY = screenHeight/fromIntegral h
offsetX = screenWidth/2 offsetX = screenWidth/2
offsetY = screenHeight/2 offsetY = screenHeight/2
-----------------------------------
-- COPIED FROM tut_glue_latex.hs --
latexExample :: Animation
latexExample = sceneAnimation $ do
-- Draw equation
play $ drawAnimation strokedSvg
sprites <- forM glyphs $ \(fn, _, elt) ->
newSpriteA $ animate $ const $ fn elt
-- Yoink each glyph
forM_ (reverse sprites) $ \sprite -> do
spriteE sprite (overBeginning 1 $ aroundCenterE $ highlightE)
wait 0.5
-- Flash glyphs randomly with color
forM_ (shuffleList (sprites++sprites)) $ \sprite -> do
spriteE sprite (overBeginning 0.5 $ aroundCenterE $ flashE)
wait 0.1
wait 0.5
mapM_ destroySprite sprites
-- Undraw equations
play $ drawAnimation' (Just 0xdeadbeef) 1 0.1 strokedSvg
# reverseA
where
glyphs = svgGlyphs svg
strokedSvg =
withStrokeWidth (defaultStrokeWidth*0.5) $
withStrokeColor "black" svg
svg = lowerTransformations $ simplify $ scale 2 $ center $
latexAlign "\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}"
shuffleList lst = shuffle' lst (length lst) (mkStdGen 0xdeadbeef)
highlightE :: Effect
highlightE d t =
scale (1 + bellS 2 (t/d)*0.5) . rotate (wiggleS (t/d) * 20)
flashE :: Effect
flashE d t =
withStrokeColor "black" .
withStrokeWidth (defaultStrokeWidth*0.5*bellS 2 (t/d)) .
withFillColorPixel (promotePixel $ turbo (t/d))
-- s-curve, sin, s-curve
wiggleS :: Signal
wiggleS t
| t < 0.25 = curveS 2 (t*4)
| t < 0.75 = sin ((t-0.25)*2*pi+pi/2)
| otherwise = curveS 2 ((t-0.75)*4)-1
--
drawAnimation :: SVG -> Animation
drawAnimation = drawAnimation' Nothing 0.5 0.3
drawAnimation' :: Maybe Int -> Double -> Double -> SVG -> Animation
drawAnimation' mbSeed fillDur step svg = sceneAnimation $ do
forM_ (zip [0..] $ shuf $ svgGlyphs svg) $ \(n, (fn, attr, tree)) -> do
let sWidth =
case toUserUnit defaultDPI <$> getLast (attr ^. strokeWidth) of
Just (Num d) -> d
_ -> defaultStrokeWidth
fork $ do
wait (n*step)
play $ mapA fn $ (animate (\t -> withFillOpacity 0 $ partialSvg t tree)
# applyE (overEnding fillDur $ fadeLineOutE sWidth))
fork $ do
wait (n*step+(1-fillDur))
newSprite $ do
return $ \_real_t _d t ->
withStrokeWidth 0 $ fn $ withFillOpacity (min 1 $ t/fillDur) tree
where
shuf lst =
case mbSeed of
Nothing -> lst
Just seed -> shuffle' lst (length lst) (mkStdGen seed)

View file

@ -67,6 +67,7 @@ library
Reanimate.Effect Reanimate.Effect
Reanimate.Builtin.TernaryPlot Reanimate.Builtin.TernaryPlot
Reanimate.Constants Reanimate.Constants
Reanimate.Parameters
Reanimate.Chiphunk Reanimate.Chiphunk
Reanimate.PolyShape Reanimate.PolyShape
Reanimate.Builtin.Documentation Reanimate.Builtin.Documentation

View file

@ -104,7 +104,11 @@ module Reanimate
screenWidth, screenWidth,
screenHeight, screenHeight,
defaultDPI, defaultDPI,
defaultStrokeWidth defaultStrokeWidth,
-- * Parameters
pFPS,
pHeight,
pWidth
) where ) where
import Reanimate.Animation import Reanimate.Animation
@ -120,3 +124,4 @@ import Reanimate.Svg.BoundingBox
import Reanimate.Svg.Constructors import Reanimate.Svg.Constructors
import Reanimate.Svg.LineCommand import Reanimate.Svg.LineCommand
import Reanimate.Scene import Reanimate.Scene
import Reanimate.Parameters

View file

@ -13,12 +13,14 @@ import Graphics.SvgTree (Tree (..))
import Reanimate.Cache import Reanimate.Cache
import Reanimate.Misc import Reanimate.Misc
import Reanimate.Raster import Reanimate.Raster
import Reanimate.Parameters
import Reanimate.Animation
import Reanimate.Svg.Constructors import Reanimate.Svg.Constructors
import System.FilePath (replaceExtension, (<.>)) import System.FilePath (replaceExtension, (<.>))
import System.IO.Unsafe (unsafePerformIO) import System.IO.Unsafe (unsafePerformIO)
import Data.Hashable import Data.Hashable
blender :: Text -> Tree blender :: Text -> SVG
blender script = blender script =
(unsafePerformIO $ mkBlenderImage script) (unsafePerformIO $ mkBlenderImage script)
@ -27,6 +29,7 @@ blender' script =
(unsafePerformIO $ mkBlenderImage' script) (unsafePerformIO $ mkBlenderImage' script)
mkBlenderImage :: Text -> IO Tree mkBlenderImage :: Text -> IO Tree
mkBlenderImage script | pNoExternals = pure $ mkText script
mkBlenderImage script = do mkBlenderImage script = do
png <- B.readFile =<< mkBlenderImage' script png <- B.readFile =<< mkBlenderImage' script
case decodePng png of case decodePng png of
@ -34,6 +37,7 @@ mkBlenderImage script = do
Right img -> return $ center $ scaleToSize 16 9 $ embedDynamicImage img Right img -> return $ center $ scaleToSize 16 9 $ embedDynamicImage img
mkBlenderImage' :: Text -> IO FilePath mkBlenderImage' :: Text -> IO FilePath
mkBlenderImage' _ | pNoExternals = pure "/blender/has/been/disabled"
mkBlenderImage' script = cacheFile template $ \target -> do mkBlenderImage' script = cacheFile template $ \target -> do
exec <- requireExecutable "blender" exec <- requireExecutable "blender"
let py_file = replaceExtension target "py" let py_file = replaceExtension target "py"

View file

@ -8,6 +8,7 @@ import Reanimate.Driver.Check
import Reanimate.Driver.CLI import Reanimate.Driver.CLI
import Reanimate.Driver.Compile import Reanimate.Driver.Compile
import Reanimate.Driver.Server import Reanimate.Driver.Server
import Reanimate.Parameters
import Reanimate.Render (FPS, Format (..), Height, Width, import Reanimate.Render (FPS, Format (..), Height, Width,
render, renderSnippets, renderSvgs) render, renderSnippets, renderSvgs)
import System.Directory import System.Directory
@ -94,6 +95,7 @@ reanimate animation = do
case optsCommand of case optsCommand of
Raw -> renderSvgs animation Raw -> renderSvgs animation
Test -> do Test -> do
setNoExternals True
-- hSetBinaryMode stdout True -- hSetBinaryMode stdout True
renderSnippets animation renderSnippets animation
Check -> checkEnvironment Check -> checkEnvironment
@ -137,6 +139,9 @@ reanimate animation = do
,"--target", target ,"--target", target
,"+RTS", "-N", "-RTS"] ,"+RTS", "-N", "-RTS"]
else do else do
setFPS fps
setWidth width
setHeight height
printf "Animation options:\n\ printf "Animation options:\n\
\ fps: %d\n\ \ fps: %d\n\
\ width: %d\n\ \ width: %d\n\

View file

@ -12,6 +12,7 @@ import Graphics.SvgTree (Tree (..), defaultSvg, parseSvgFile)
import Reanimate.Cache import Reanimate.Cache
import Reanimate.Misc import Reanimate.Misc
import Reanimate.Svg import Reanimate.Svg
import Reanimate.Parameters
import System.FilePath (replaceExtension, takeFileName, (</>)) import System.FilePath (replaceExtension, takeFileName, (</>))
import System.IO.Unsafe (unsafePerformIO) import System.IO.Unsafe (unsafePerformIO)
@ -24,6 +25,7 @@ import System.IO.Unsafe (unsafePerformIO)
-- --
-- <<docs/gifs/doc_latex.gif>> -- <<docs/gifs/doc_latex.gif>>
latex :: T.Text -> Tree latex :: T.Text -> Tree
latex tex | pNoExternals = mkText tex
latex tex = (unsafePerformIO . (cacheMem . cacheDiskSvg) (latexToSVG "dvi" exec args)) script latex tex = (unsafePerformIO . (cacheMem . cacheDiskSvg) (latexToSVG "dvi" exec args)) script
where where
exec = "latex" exec = "latex"
@ -39,6 +41,7 @@ latex tex = (unsafePerformIO . (cacheMem . cacheDiskSvg) (latexToSVG "dvi" exec
-- --
-- <<docs/gifs/doc_xelatex.gif>> -- <<docs/gifs/doc_xelatex.gif>>
xelatex :: Text -> Tree xelatex :: Text -> Tree
xelatex tex | pNoExternals = mkText tex
xelatex tex = (unsafePerformIO . (cacheMem . cacheDiskSvg) (latexToSVG "xdv" exec args)) script xelatex tex = (unsafePerformIO . (cacheMem . cacheDiskSvg) (latexToSVG "xdv" exec args)) script
where where
exec = "xelatex" exec = "xelatex"

View file

@ -0,0 +1,60 @@
module Reanimate.Parameters
( pFPS
, pWidth
, pHeight
, pNoExternals
, setFPS
, setWidth
, setHeight
, setNoExternals
) where
import System.IO.Unsafe
import Data.IORef
import Reanimate.Render
{-# NOINLINE pFPSRef #-}
pFPSRef :: IORef FPS
pFPSRef = unsafePerformIO (newIORef 0)
{-# NOINLINE pFPS #-}
pFPS :: FPS
pFPS = unsafePerformIO (readIORef pFPSRef)
setFPS :: FPS -> IO ()
setFPS = writeIORef pFPSRef
{-# NOINLINE pWidthRef #-}
pWidthRef :: IORef FPS
pWidthRef = unsafePerformIO (newIORef 0)
{-# NOINLINE pWidth #-}
pWidth :: Width
pWidth = unsafePerformIO (readIORef pWidthRef)
setWidth :: Width -> IO ()
setWidth = writeIORef pWidthRef
{-# NOINLINE pHeightRef #-}
pHeightRef :: IORef FPS
pHeightRef = unsafePerformIO (newIORef 0)
{-# NOINLINE pHeight #-}
pHeight :: Height
pHeight = unsafePerformIO (readIORef pHeightRef)
setHeight :: Height -> IO ()
setHeight = writeIORef pHeightRef
{-# NOINLINE pNoExternalsRef #-}
pNoExternalsRef :: IORef Bool
pNoExternalsRef = unsafePerformIO (newIORef False)
{-# NOINLINE pNoExternals #-}
pNoExternals :: Bool
pNoExternals = unsafePerformIO (readIORef pNoExternalsRef)
setNoExternals :: Bool -> IO ()
setNoExternals = writeIORef pNoExternalsRef

View file

@ -19,6 +19,7 @@ import Graphics.SvgTree (Tree (..))
import Reanimate.Cache import Reanimate.Cache
import Reanimate.Misc import Reanimate.Misc
import Reanimate.Raster import Reanimate.Raster
import Reanimate.Parameters
import Reanimate.Svg.Constructors import Reanimate.Svg.Constructors
import System.FilePath (replaceExtension, (<.>)) import System.FilePath (replaceExtension, (<.>))
import System.IO.Unsafe (unsafePerformIO) import System.IO.Unsafe (unsafePerformIO)
@ -57,6 +58,7 @@ povrayExtreme' :: [String] -> Text -> FilePath
povrayExtreme' args = povrayRaw' (["+H2160","+W3840", "+A"] ++ args) povrayExtreme' args = povrayRaw' (["+H2160","+W3840", "+A"] ++ args)
mkPovrayImage :: [String] -> Text -> IO Tree mkPovrayImage :: [String] -> Text -> IO Tree
mkPovrayImage _ script | pNoExternals = pure $ mkText script
mkPovrayImage args script = do mkPovrayImage args script = do
out <- mkPovrayImage' args script out <- mkPovrayImage' args script
-- return $ center $ scaleToSize 16 9 $ embedImageFile out -- return $ center $ scaleToSize 16 9 $ embedImageFile out
@ -66,6 +68,7 @@ mkPovrayImage args script = do
Right img -> return $ center $ scaleToSize 16 9 $ embedDynamicImage img Right img -> return $ center $ scaleToSize 16 9 $ embedDynamicImage img
mkPovrayImage' :: [String] -> Text -> IO FilePath mkPovrayImage' :: [String] -> Text -> IO FilePath
mkPovrayImage' _ _ | pNoExternals = pure "/povray/has/been/disabled"
mkPovrayImage' args script = cacheFile template $ \target -> do mkPovrayImage' args script = cacheFile template $ \target -> do
exec <- requireExecutable "povray" exec <- requireExecutable "povray"
let pov_file = replaceExtension target "pov" let pov_file = replaceExtension target "pov"
@ -74,3 +77,4 @@ mkPovrayImage' args script = cacheFile template $ \target -> do
where where
template = show (hash key) <.> "png" template = show (hash key) <.> "png"
key = T.concat (script:map T.pack args) key = T.concat (script:map T.pack args)

View file

@ -154,74 +154,16 @@ withObject obj@(Object ref) scene = do
liftST $ writeSTRef ref (Just tl) liftST $ writeSTRef ref (Just tl)
return a return a
data Param s a = Param (STRef s (Time, Time, Time -> a))
newParam :: a -> Scene s (Param s a)
newParam initVal = do
now <- queryNow
Param <$> liftST (newSTRef (now, -1, const initVal))
destroyParam :: Param s a -> Scene s ()
destroyParam (Param ref) = do
now <- queryNow
liftST $ do
(startT, _endT, fn) <- readSTRef ref
writeSTRef ref (startT, now, fn)
readParam :: Param s a -> Scene s a
readParam (Param ref) = do
now <- queryNow
(_, _, fn) <- liftST $ readSTRef ref
return $ fn now
paramAt :: Param s a -> Time -> ST s a
paramAt = undefined
paramFn :: Param s a -> ST s (Time -> a)
paramFn (Param ref) = do
(_, _, fn) <- readSTRef ref
return fn
withParamAt :: Param s a -> Time -> (a -> ST s SVG) -> ST s SVG
withParamAt = undefined
fromParams :: Gen s -> Scene s () fromParams :: Gen s -> Scene s ()
fromParams gen = M $ \_ -> return ((), 0, 0, emptyTimeline, [gen]) fromParams gen = M $ \_ -> return ((), 0, 0, emptyTimeline, [gen])
setParam :: Param s a -> a -> Scene s () simpleParam :: (a -> SVG) -> a -> Scene s (Var s a)
setParam = undefined
-- setParamZIndex :: Param s a -> ZIndex -> Scene s ()
-- setParamZIndex = undefined
--
-- adjustParamZIndex :: Param s a -> Duration -> (Time -> ZIndex -> ZIndex) -> Scene s ()
-- adjustParamZIndex = undefined
--
-- adjustParamZIndex_ :: Param s a -> (Time -> ZIndex -> ZIndex) -> Scene s ()
-- adjustParamZIndex_ = undefined
tweenParam :: Param s a -> Duration -> (Double -> a -> a) -> Scene s ()
tweenParam (Param ref) dur fn = do
now <- queryNow
liftST $ do
(startT,endT,prevFn) <- readSTRef ref
let worker t
| t > now = fn (min 1 ((t-now)/dur)) (prevFn t)
| otherwise = prevFn t
writeSTRef ref (startT, endT, worker)
wait dur
tweenParam_ :: Param s a -> (Time -> a -> a) -> Scene s ()
tweenParam_ = undefined
simpleParam :: (a -> SVG) -> a -> Scene s (Param s a)
simpleParam render def = do simpleParam render def = do
p <- newParam def v <- newVar def
fromParams $ do _ <- newSprite $ do
fn <- paramFn p getV <- freezeVar v
return $ \_d t -> (render $ fn t, 0) return $ \real_t _d _t -> render (getV real_t)
return p return v
data Var s a = Var (STRef s (Time -> a)) data Var s a = Var (STRef s (Time -> a))

View file

@ -267,7 +267,7 @@ simpleSort_ lst = do
worker dir rest worker dir rest
worker dir ((i, (nth,t)):rest) = do worker dir ((i, (nth,t)):rest) = do
z <- round <$> queryNow z <- round <$> queryNow
fork $ tweenParam (params!!i) 1 $ \t (x,y,elt) -> fork $ tweenVar (params!!i) 1 $ \t (x,y,elt) ->
let s = curveS 2 t in let s = curveS 2 t in
(fromToS x (fromIntegral nth) s, y+sin (pi*s )*dir, elt) (fromToS x (fromIntegral nth) s, y+sin (pi*s )*dir, elt)
wait 0.5 wait 0.5
@ -289,9 +289,9 @@ quicksort_ lst = do
let partition pivot lo hi = do let partition pivot lo hi = do
loValP <- liftST (V.read params lo) loValP <- liftST (V.read params lo)
loVal <- readParam loValP loVal <- readVar loValP
hiValP <- liftST (V.read params hi) hiValP <- liftST (V.read params hi)
hiVal <- readParam hiValP hiVal <- readVar hiValP
if getKey loVal < getKey pivot if getKey loVal < getKey pivot
then partition pivot (lo+1) hi then partition pivot (lo+1) hi
else if getKey hiVal > getKey pivot else if getKey hiVal > getKey pivot
@ -301,10 +301,10 @@ quicksort_ lst = do
else do else do
liftST $ V.write params lo hiValP liftST $ V.write params lo hiValP
liftST $ V.write params hi loValP liftST $ V.write params hi loValP
fork $ tweenParam hiValP 1 $ \t (x,y,elt) -> fork $ tweenVar hiValP 1 $ \t (x,y,elt) ->
let s = curveS 2 t in let s = curveS 2 t in
(fromToS x (getPos loVal) s, y+sin (pi*s), elt) (fromToS x (getPos loVal) s, y+sin (pi*s), elt)
fork $ tweenParam loValP 1 $ \t (x,y,elt) -> fork $ tweenVar loValP 1 $ \t (x,y,elt) ->
let s = curveS 2 t in let s = curveS 2 t in
(fromToS x (getPos hiVal) s, y-sin (pi*s), elt) (fromToS x (getPos hiVal) s, y-sin (pi*s), elt)
wait 1 wait 1
@ -312,12 +312,12 @@ quicksort_ lst = do
let worker lo hi | lo >= hi = return () let worker lo hi | lo >= hi = return ()
worker lo hi = do worker lo hi = do
pivotP <- getPivot params lo hi pivotP <- getPivot params lo hi
pivot <- readParam pivotP pivot <- readVar pivotP
tweenParam pivotP 1 $ \t (x,y,elt) -> tweenVar pivotP 1 $ \t (x,y,elt) ->
let s = curveS 2 t in let s = curveS 2 t in
(x, y-s/2, elt) (x, y-s/2, elt)
p <- partition pivot lo hi p <- partition pivot lo hi
tweenParam pivotP 1 $ \t (x,y,elt) -> tweenVar pivotP 1 $ \t (x,y,elt) ->
let s = curveS 2 t in let s = curveS 2 t in
(x, y+s/2, elt) (x, y+s/2, elt)
fork $ worker lo p fork $ worker lo p
@ -328,7 +328,7 @@ quicksort_ lst = do
let middle = lo + (hi-lo) `div` 2 let middle = lo + (hi-lo) `div` 2
indices = filter (< hi) $ filter (>= lo) [middle-1,middle,middle+1] indices = filter (< hi) $ filter (>= lo) [middle-1,middle,middle+1]
selected <- forM indices $ \idx -> liftST (V.read params idx) selected <- forM indices $ \idx -> liftST (V.read params idx)
keys <- mapM readParam selected keys <- mapM readVar selected
return $ head $ drop (length indices `div` 2) $ map snd $ sortBy (comparing fst) $ zip (map getKey keys) selected return $ head $ drop (length indices `div` 2) $ map snd $ sortBy (comparing fst) $ zip (map getKey keys) selected
getPos (x, _, _) = x getPos (x, _, _) = x
getKey (_x, _y, (i, _)) = i getKey (_x, _y, (i, _)) = i