mirror of
https://github.com/reanimate/reanimate.git
synced 2026-09-10 23:52:22 +00:00
Feature morph (#62)
* Disable windows CI (because of hmatrix) * Implement ear-clip triangulation. * Implement common framework for polygon morphing. * Draft implementations of compatible triangulation and mesh smoothing. * Implement least-work morphing. * Implement as-rigid-as-possible morphing. * Implement SSSP. * Draft article about polygon morphing. Former-commit-id: f6f19f58b9dd58c655bcca0ed2d65f72726f0a06
This commit is contained in:
parent
a256dd0a96
commit
4356efbc7e
70 changed files with 6504 additions and 195 deletions
|
|
@ -1,3 +1,10 @@
|
|||
|
||||
# Reanimate is:
|
||||
* **An animation library** - a system for turning code into movies, similar to [3b1b's manim](https://github.com/3b1b/manim) of [YouTube fame](https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw).
|
||||
* **User friendly** - extensively documented with example animations covering the [entire API](http://hackage.haskell.org/package/reanimate/docs/Reanimate.html).
|
||||
* **Cross-platform** - works on Linux, Mac and Windows.
|
||||
|
||||
|
||||
# Getting started
|
||||
|
||||
## Viewing a basic animation
|
||||
|
|
@ -6,19 +13,87 @@
|
|||
$ git clone https://github.com/Lemmih/reanimate.git
|
||||
$ cd reanimate/
|
||||
$ stack build
|
||||
$ stack ./examples/raster.hs
|
||||
$ stack ./examples/doc_andThen.hs
|
||||
```
|
||||
|
||||
Running the above code should open a browser window containing this animation:
|
||||
|
||||
<details>
|
||||
<summary>Toggle doc_andThen.hs source code.</summary>
|
||||
<pre><code class="haskell">
|
||||
{!examples/doc_andThen.hs!}
|
||||
</code></pre>
|
||||
</details>
|
||||
<br/>
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/bvLqalX.mp4">
|
||||
</video>
|
||||
|
||||
|
||||
## Checking dependencies
|
||||
|
||||
Reanimate includes a run-time for optional dependencies.
|
||||
If something doesn't work, you can run a check for run-time dependencies like
|
||||
this:
|
||||
|
||||
```console
|
||||
$ stack ./examples/raster.hs check
|
||||
$ stack ./examples/doc_andThen.hs check
|
||||
reanimate checks:
|
||||
Has ffmpeg: 4.1.3-0ubuntu1
|
||||
Has dvisvgm: /usr/bin/dvisvgm
|
||||
Has povray: /usr/bin/povray
|
||||
Has blender: 2.82
|
||||
Has rsvg-convert: 2.44.10
|
||||
Has inkscape: 0.92.4
|
||||
Has convert: 6.9.10-14
|
||||
Has LaTeX: /usr/bin/latex
|
||||
Has LaTeX package 'babel': OK
|
||||
Has LaTeX package 'preview': OK
|
||||
Has LaTeX package 'amsmath': OK
|
||||
Has XeLaTeX: /usr/bin/xelatex
|
||||
Has XeLaTeX package 'ctex': OK
|
||||
```
|
||||
|
||||
## Rendering movies
|
||||
None of these dependencies are vital but the functionality of **reanimate**
|
||||
will be reduced if they are missing. For example, without 'ffmpeg', you won't
|
||||
be able to generate stand-alone video files (but can still view animations
|
||||
in a browser window). Without 'LaTeX' and 'dvisvgm', you won't be able use
|
||||
LaTeX for typesetting.
|
||||
|
||||
## Rendering animations
|
||||
|
||||
**reanimate** has builtin support for rendering animations to video files and
|
||||
gifs. To render an animation with default options, use the 'render' command:
|
||||
|
||||
```console
|
||||
$ stack ./examples/raster.hs render
|
||||
$ stack ./examples/doc_andThen.hs render
|
||||
```
|
||||
|
||||
For computationally intense animations, use the `--compile` flag to compile
|
||||
your animation script and run it using all available cores:
|
||||
|
||||
```console
|
||||
$ stack ./examples/doc_andThen.hs render --compile
|
||||
```
|
||||
|
||||
If the defaults are too aggressive for you tastes, change them directly or use
|
||||
a different preset:
|
||||
|
||||
```console
|
||||
$ stack ./examples/doc_andThen.hs render --preset high --fps 10
|
||||
```
|
||||
|
||||
Rendering to gifs automatically does two passes and uses a color palette to
|
||||
improve the quality and size of the gif. GIFs are, by default, rendered at
|
||||
24 fps with a resolution of 320x180:
|
||||
|
||||
```console
|
||||
$ stack ./examples/doc_andThen.hs render --format gif
|
||||
```
|
||||
|
||||
# Learning resources
|
||||
|
||||
* [API documentation](http://hackage.haskell.org/package/reanimate/docs/Reanimate.html)
|
||||
* [Core concepts](introduction)
|
||||
* [Gallery](gallery)
|
||||
* [Examples](https://github.com/Lemmih/reanimate/tree/master/examples)
|
||||
* [Design overview](glue_tut)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Core concepts
|
||||
# Introduction
|
||||
|
||||
This document will introduce the foundamental concepts used in **reanimate**.
|
||||
It is assumed that you are already familiar with Haskell. After reading, you will be able to understand all the concepts used in the [API reference documentation](http://hackage.haskell.org/package/reanimate/docs/Reanimate.html). This is not a tutorial, though, and you may have to look at the examples before you can turn these concepts into beautiful animations.
|
||||
|
|
|
|||
200
docs/morphology.md
Normal file
200
docs/morphology.md
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
# 2D morphology
|
||||
|
||||
Morphing 2D shapes is conceptually simple. We can imagine it as a function that
|
||||
takes two SVG images and produces the intermediate steps that smoothly
|
||||
transforms the source image to the target image. If the SVG images are simple
|
||||
shapes then it looks like this:
|
||||
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/4Uwayst.mp4">
|
||||
</video>
|
||||
|
||||
Often it is quite easy for a human to tell if a morph looks right or not. This,
|
||||
unfortunately, does not mean the problem is easy to solve for a computer. As
|
||||
it turns out, there is not a single, perfect algorithm for morphing between 2D
|
||||
shapes. Instead, there are many different algorithms that each have their own
|
||||
benefits and trade-offs. This article will specify the details of the
|
||||
morphology-problem and show a handful of different solutions.
|
||||
|
||||
# Breaking it down
|
||||
|
||||
Conceptually, a morphing function should take two SVG images and give a new
|
||||
SVG image over time. In Haskell, it would look like this:
|
||||
|
||||
```haskell
|
||||
morph :: SVG → SVG → (Time → SVG)
|
||||
```
|
||||
|
||||
However, morphing has several sub-problems, each of which that can be solved
|
||||
indepently in a variety of ways. These solutions have different benefits and
|
||||
drawbacks, and wildly different performance. Since no single morphing algorithm
|
||||
is obviously superior in all cases, let's identify the othogonal sub-problems
|
||||
and use their solutions as parameters to our morphing function.
|
||||
|
||||
## 1. Point correspondence
|
||||
```haskell
|
||||
type PointCorrespondence = Polygon → Polygon → (Polygon, Polygon)
|
||||
```
|
||||
|
||||
SVG images can be simplified to polygons which are made out of points. To morph
|
||||
between two shapes, we somehow have to transform the points in a source
|
||||
polygon into the points in a target polygon. Finding a good point correspondence
|
||||
between two polygons is a tricky thing, though, and it can have a huge impact
|
||||
on the quality of the morph as seen in this illustration:
|
||||
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/9WJ6mC6.mp4">
|
||||
</video>
|
||||
|
||||
Point correspondence algorithms take two polygons (which may have a different
|
||||
number of points) and align their points so there's an exact one-to-one
|
||||
correspondence. This may require shuffling, adding, splitting, or merging points.
|
||||
|
||||
## 2. Point trajectory
|
||||
```haskell
|
||||
type Trajectory = (Polygon, Polygon) → (Double → Polygon)
|
||||
```
|
||||
|
||||
Once the points in the source and target polygons have been aligned, the next
|
||||
step is to figure out which path they should take. The simplest approach would
|
||||
be to move the points in a straight line but this doesn't always look right.
|
||||
The points in the leftmost figure of the following illustration moves in a
|
||||
straight line while the points in the rightmost figure take a curved path
|
||||
designed to avoid self-intersections:
|
||||
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/jXOR7Ij.mp4">
|
||||
</video>
|
||||
|
||||
The design space for trajectory algorithms is quite large and this article
|
||||
will cover five different approaches in addition to the simplest straight-line
|
||||
path.
|
||||
|
||||
|
||||
## 3. Object correspondence
|
||||
|
||||
```haskell
|
||||
type ObjectCorrespondence = [Polygon] → [Polygon] → [(Polygon, Polygon)]
|
||||
```
|
||||
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/GoiZxgo.mp4">
|
||||
</video>
|
||||
|
||||
## 4. Color interpolation
|
||||
|
||||
Morphing between colors is something many libraries don't pay a lot of attention
|
||||
but there is a fair amount of complexity here and it does deserve consideration.
|
||||
In short, humans (usually) have three types light sensitive cells that respond
|
||||
to different ranges of light wavelengths. So, even though the light we see may
|
||||
contain hundreds of different wavelengths, the eye only notices how much each
|
||||
of the three types of cells are stimulated and will therefore always reduce
|
||||
colors to just three numbers.
|
||||
|
||||
So far, so good. With three coordinates necessary to specify a color, each color
|
||||
has a position in a three dimensional space and we can morph between two colors
|
||||
by going in a straight line. This is where things get tricky, though, because
|
||||
going in a straight line in the XYZ space (the 3D colorspace defined by the
|
||||
sensitivity of a typical human's eye) is physically correct but often a bit
|
||||
counter intuitive. For example, morphing between **blue** and **yellow** will go
|
||||
through **grey**. Also, the XYZ doesn't take into account how humans perceive
|
||||
colors (ie. what happens in the brain rather than the eye) and
|
||||
morphing between two colors with the same perceived brightness will not keep the
|
||||
brightness a constant. These concerns, and many more, have lead to the
|
||||
development of different 3D colorspaces, such as CIE LAB, which take human color perception into
|
||||
account. **Reanimate** has built-in support for several spaces and this
|
||||
illustration shows how they stack up against each other:
|
||||
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/AUqFi7L.mp4">
|
||||
</video>
|
||||
|
||||
By default, **reanimate** uses the LAB colorspace for morphing colors.
|
||||
|
||||
# Linear interpolation
|
||||
|
||||
TBD.
|
||||
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/ZBh1ena.mp4">
|
||||
</video>
|
||||
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/2jWIXLL.mp4">
|
||||
</video>
|
||||
|
||||
# Rotational interpolation
|
||||
|
||||
```haskell
|
||||
rotationalTrajectory :: Origin -> Trajectory
|
||||
```
|
||||
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/dFY0IZz.mp4">
|
||||
</video>
|
||||
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/nAk8xJ1.mp4">
|
||||
</video>
|
||||
|
||||
# Edge+angle interpolation
|
||||
|
||||
```haskell
|
||||
lineBend :: Trajectory
|
||||
```
|
||||
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/cmG1Wwr.mp4">
|
||||
</video>
|
||||
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/hoSncAC.mp4">
|
||||
</video>
|
||||
|
||||
|
||||
TBD.
|
||||
|
||||
# Least-work correspondence
|
||||
|
||||
<video width="640" height="360" muted autoplay loop>
|
||||
<source src="https://i.imgur.com/C8bCnea.mp4">
|
||||
</video>
|
||||
|
||||
$$
|
||||
work_{stretch} = k_s\frac{\delta^2}{2L_0}
|
||||
$$
|
||||
|
||||
$$
|
||||
work_{bend} =
|
||||
\left\{
|
||||
\begin{array}{ll}
|
||||
k_b(\Delta \theta + m_b \Delta \theta^\star)^{e_b}
|
||||
& \text{if $\theta(t)$ never goes to zero} \\
|
||||
k_b(\Delta \theta + m_b \Delta \theta^\star)^{e_b} + p_b
|
||||
& \text{if $\theta(t)$ does go to zero}
|
||||
\end{array}
|
||||
\right.
|
||||
$$
|
||||
|
||||
TBD.
|
||||
|
||||
# Skeleton
|
||||
|
||||
TBD.
|
||||
|
||||
# As-rigid-as-possible
|
||||
|
||||
TBD.
|
||||
|
||||
# Guaranteed intersection-free
|
||||
|
||||
TBD.
|
||||
|
||||
# Related work
|
||||
|
||||
* Flubber
|
||||
* Polymorph
|
||||
* raphael
|
||||
* Kute
|
||||
* svg_morph
|
||||
* D3
|
||||
|
|
@ -1 +1,2 @@
|
|||
markdown_include==0.5.1
|
||||
pymdown-extensions==7.0
|
||||
|
|
|
|||
Loading…
Reference in a new issue