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
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
|
||||
Loading…
Reference in a new issue