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:
David Himmelstrup 2020-05-14 15:14:29 +08:00 committed by GitHub
commit 4356efbc7e
70 changed files with 6504 additions and 195 deletions

View file

@ -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)

View file

@ -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
View 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

View file

@ -1 +1,2 @@
markdown_include==0.5.1
pymdown-extensions==7.0