2024-11-18 22:01:31 -05:00
|
|
|
---
|
|
|
|
slug: 2024/11/playing-with-fire-transforms
|
|
|
|
title: "Playing with fire: Transforms and variations"
|
|
|
|
date: 2024-11-15 13:00:00
|
|
|
|
authors: [bspeice]
|
|
|
|
tags: []
|
|
|
|
---
|
|
|
|
|
|
|
|
Now that we have a basic chaos game in place, it's time to spice things up. Transforms and variations create the
|
|
|
|
interesting patterns that fractal flames are known for.
|
|
|
|
|
|
|
|
<!-- truncate -->
|
|
|
|
|
|
|
|
This blog post uses a set of reference parameters ([available here](../params.flame)) to demonstrate a practical
|
|
|
|
implementation of the fractal flame algorithm. If you're interested in tweaking the parameters, or generating
|
|
|
|
your own art, [Apophysis](https://sourceforge.net/projects/apophysis/) is a good introductory tool.
|
|
|
|
|
2024-11-29 19:25:29 -05:00
|
|
|
TODO: Include the reference image here
|
|
|
|
|
2024-11-18 22:01:31 -05:00
|
|
|
## Transforms and variations
|
|
|
|
|
|
|
|
import CodeBlock from '@theme/CodeBlock'
|
|
|
|
|
|
|
|
We previously introduced "transforms" as the "functions" of an "iterated function system." Their general format is:
|
|
|
|
|
|
|
|
$$
|
|
|
|
F_i(x,y) = (a_i \cdot x + b_i \cdot y + c_i, \hspace{0.2cm} d_i \cdot x + e_i \cdot y + f_i)
|
|
|
|
$$
|
|
|
|
|
2024-11-19 21:42:03 -05:00
|
|
|
import coefsSrc from '!!raw-loader!../src/coefs'
|
|
|
|
|
|
|
|
<CodeBlock language={'typescript'}>{coefsSrc}</CodeBlock>
|
|
|
|
|
2024-11-18 22:01:31 -05:00
|
|
|
We also introduced the Sierpinski Gasket functions ($F_0$, $F_1$, and $F_2$), demonstrating how they are related to
|
|
|
|
the general format. For example:
|
|
|
|
|
|
|
|
$$
|
|
|
|
\begin{align*}
|
|
|
|
F_0(x,y) &= \left({x \over 2}, {y \over 2}\right) \\
|
2024-11-30 17:35:42 -05:00
|
|
|
&= (a_0 \cdot x + b_0 \cdot y + c_0, d_0 \cdot x + e_0 \cdot y + f_0) \\
|
2024-11-18 22:01:31 -05:00
|
|
|
& a_0 = 0.5 \hspace{0.2cm} b_0 = 0 \hspace{0.2cm} c_0 = 0 \\
|
|
|
|
& d_0 = 0 \hspace{0.2cm} e_0 = 0.5 \hspace{0.2cm} f_0 = 0
|
|
|
|
\end{align*}
|
|
|
|
$$
|
|
|
|
|
2024-11-30 17:35:42 -05:00
|
|
|
TODO: Explain the applyCoefs function
|
|
|
|
|
2024-11-19 21:42:03 -05:00
|
|
|
However, these transforms are pretty boring. We can build more exciting images by using additional functions
|
2024-11-18 22:01:31 -05:00
|
|
|
within the transform. These "sub-functions" are called "variations":
|
|
|
|
|
|
|
|
$$
|
|
|
|
F_i(x, y) = V_j(a_i \cdot x + b_i \cdot y + c_i, \hspace{0.2cm} d_i \cdot x + e_i \cdot y + f_i)
|
|
|
|
$$
|
|
|
|
|
|
|
|
The fractal flame paper lists 49 variation functions ($V_j$ above), but the sky's the limit here.
|
|
|
|
For example, the official `flam3` implementation supports
|
|
|
|
[98 variations](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/variations.c).
|
|
|
|
|
|
|
|
Our reference image will focus on just four variations:
|
|
|
|
|
|
|
|
### Linear (variation 0)
|
|
|
|
|
2024-11-19 21:42:03 -05:00
|
|
|
This variation returns the $x$ and $y$ coordinates as-is:
|
2024-11-18 22:01:31 -05:00
|
|
|
|
|
|
|
$$
|
|
|
|
V_0(x,y) = (x,y)
|
|
|
|
$$
|
|
|
|
|
2024-11-19 21:42:03 -05:00
|
|
|
import linearSrc from '!!raw-loader!../src/linear'
|
|
|
|
|
|
|
|
<CodeBlock language={'typescript'}>{linearSrc}</CodeBlock>
|
|
|
|
|
2024-11-18 22:01:31 -05:00
|
|
|
### Julia (variation 13)
|
|
|
|
|
2024-11-18 22:08:23 -05:00
|
|
|
This variation still uses just the $x$ and $y$ coordinates, but does crazy things with them:
|
2024-11-18 22:01:31 -05:00
|
|
|
|
|
|
|
<small>TODO: Is this related to the Julia set?</small>
|
|
|
|
|
|
|
|
$$
|
|
|
|
\begin{align*}
|
|
|
|
r &= \sqrt{x^2 + y^2} \\
|
|
|
|
\theta &= \text{arctan}(x / y) \\
|
|
|
|
\Omega &= \left\{
|
2024-11-30 17:35:42 -05:00
|
|
|
\begin{array}{lr}
|
|
|
|
0 \hspace{0.4cm} \text{w.p. } 0.5 \\
|
|
|
|
\pi \hspace{0.4cm} \text{w.p. } 0.5 \\
|
|
|
|
\end{array}
|
2024-11-18 22:01:31 -05:00
|
|
|
\right\} \\
|
|
|
|
|
|
|
|
V_{13}(x, y) &= \sqrt{r} \cdot (\text{cos} ( \theta / 2 + \Omega ), \text{sin} ( \theta / 2 + \Omega ))
|
|
|
|
\end{align*}
|
|
|
|
$$
|
|
|
|
|
2024-11-19 21:42:03 -05:00
|
|
|
import juliaSrc from '!!raw-loader!../src/julia'
|
2024-11-18 22:01:31 -05:00
|
|
|
|
2024-11-19 21:42:03 -05:00
|
|
|
<CodeBlock language={'typescript'}>{juliaSrc}</CodeBlock>
|
2024-11-18 22:01:31 -05:00
|
|
|
|
|
|
|
### Popcorn (variation 17)
|
|
|
|
|
2024-11-18 22:08:23 -05:00
|
|
|
This is known as a "dependent variation" because it depends on knowing the transform coefficients
|
|
|
|
(specifically, $c$ and $f$):
|
2024-11-18 22:01:31 -05:00
|
|
|
|
|
|
|
$$
|
|
|
|
V_{17}(x,y) = (x + c \cdot \text{sin}(\text{tan }3y), y + f \cdot \text{sin}(\text{tan }3x))
|
|
|
|
$$
|
|
|
|
|
2024-11-19 21:42:03 -05:00
|
|
|
import popcornSrc from '!!raw-loader!../src/popcorn'
|
|
|
|
|
|
|
|
<CodeBlock language={'typescript'}>{popcornSrc}</CodeBlock>
|
2024-11-18 22:01:31 -05:00
|
|
|
|
|
|
|
### PDJ (variation 24)
|
|
|
|
|
|
|
|
This is known as a "parametric" variation because it has additional parameters given to it:
|
|
|
|
|
|
|
|
$$
|
|
|
|
p_1 = \text{pdj.a} \hspace{0.2cm} p_2 = \text{pdj.b} \hspace{0.2cm} p_3 = \text{pdj.c} \hspace{0.2cm} p_4 = \text{pdj.d} \\
|
|
|
|
V_{24} = (\text{sin}(p_1 \cdot y) - \text{cos}(p_2 \cdot x), \text{sin}(p_3 \cdot x) - \text{cos}(p_4 \cdot y))
|
|
|
|
$$
|
|
|
|
|
2024-11-19 21:42:03 -05:00
|
|
|
import pdjSrc from '!!raw-loader!../src/pdj'
|
|
|
|
|
|
|
|
<CodeBlock language={'typescript'}>{pdjSrc}</CodeBlock>
|
|
|
|
|
2024-11-29 23:08:47 -05:00
|
|
|
## Blending
|
2024-11-19 21:42:03 -05:00
|
|
|
|
|
|
|
Now, one variation is fun, but we can also combine variations in a single transform by "blending."
|
2024-11-29 19:25:29 -05:00
|
|
|
Each variation receives the same $x$ and $y$ inputs, and we add together each variation's $x$ and $y$ outputs.
|
2024-11-30 17:35:42 -05:00
|
|
|
We'll also give each variation a weight ($v_{ij}$) to control how much it contributes to the transform:
|
2024-11-19 21:42:03 -05:00
|
|
|
|
|
|
|
$$
|
|
|
|
F_i(x,y) = \sum_{j} v_{ij} V_j(a_i \cdot x + b_i \cdot y + c_i, \hspace{0.2cm} d_i \cdot x + e_i \cdot y + f_i)
|
|
|
|
$$
|
|
|
|
|
|
|
|
The formula looks intimidating, but it's not hard to implement:
|
|
|
|
|
2024-11-29 23:08:47 -05:00
|
|
|
import blendSource from "!!raw-loader!./blend";
|
|
|
|
|
|
|
|
<CodeBlock language={'typescript'}>{blendSource}</CodeBlock>
|
2024-11-19 21:42:03 -05:00
|
|
|
|
2024-11-30 17:35:42 -05:00
|
|
|
And with that in place, we have enough to render a first full fractal flame.
|
|
|
|
The sliders below change the variation weights for each transform (the $v_{ij}$ parameters);
|
|
|
|
try changing them around to see which parts of the image are controlled by
|
|
|
|
each transform.
|
2024-11-29 19:25:29 -05:00
|
|
|
|
2024-11-29 23:08:47 -05:00
|
|
|
import Canvas from "../src/Canvas";
|
2024-11-29 19:25:29 -05:00
|
|
|
import FlameBlend from "./FlameBlend";
|
|
|
|
|
2024-12-01 18:17:36 -05:00
|
|
|
<Canvas><FlameBlend/></Canvas>
|
2024-11-30 17:35:42 -05:00
|
|
|
|
|
|
|
## Post transforms
|
|
|
|
|
|
|
|
After variation blending, we apply a second set of transform coordinates.
|
|
|
|
|
|
|
|
The fractal flame below starts with the same initial transforms/variations as the previous fractal flame,
|
|
|
|
but allows modifying the post-transform coefficients.
|
|
|
|
|
|
|
|
$$
|
|
|
|
P_i(x, y) = (\alpha_i x + \beta_i y + \gamma_i, \delta_i x + \epsilon_i y + \zeta_i)
|
|
|
|
$$
|
|
|
|
|
|
|
|
import FlamePost from "./FlamePost";
|
|
|
|
|
2024-12-01 18:17:36 -05:00
|
|
|
<Canvas><FlamePost/></Canvas>
|
2024-11-30 17:35:42 -05:00
|
|
|
|
|
|
|
## Final transform
|
|
|
|
|
|
|
|
import FlameFinal from "./FlameFinal";
|
|
|
|
|
2024-12-01 18:17:36 -05:00
|
|
|
<Canvas><FlameFinal/></Canvas>
|