speice.io/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx

189 lines
6.4 KiB
Plaintext
Raw Normal View History

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
2024-12-09 22:18:13 -05:00
shapes and patterns that fractal flames are known for.
2024-11-18 22:01:31 -05:00
<!-- truncate -->
2024-12-09 22:18:13 -05:00
:::note
This post uses a set of [reference parameters](../params.flame) to demonstrate the fractal flame algorithm.
If you're interested in tweaking the parameters, or generating your own art, [Apophysis](https://sourceforge.net/projects/apophysis/)
can load that file and you can try tweaking things yourself!
2024-11-18 22:01:31 -05:00
This post covers section 3 of the Fractal Flame Algorithm paper
2024-12-09 22:18:13 -05:00
:::
2024-11-29 19:25:29 -05:00
2024-11-18 22:01:31 -05:00
## Transforms and variations
2024-12-09 22:18:13 -05:00
import CodeBlock from '@theme/CodeBlock'
2024-11-19 21:42:03 -05:00
2024-12-09 22:18:13 -05:00
We previously introduced transforms as the "functions" of an "iterated function system," and showed how
playing the chaos game leads to an image of Sierpinski's Gasket. Even though we used simple functions,
the image it generates is exciting. But it's still not nearly as exciting as the images the Fractal Flame
algorithm is known for.
2024-11-19 21:42:03 -05:00
2024-12-09 22:18:13 -05:00
This leads us to the first big innovation of the Fractal Flame algorithm: using non-linear functions
for the transforms. These functions are known as "variations":
2024-11-18 22:01:31 -05:00
$$
2024-12-09 22:18:13 -05:00
F_i(x, y) = V_j(a_i \cdot x + b_i \cdot y + c_i, d_i \cdot x + e_i \cdot y + f_i)
2024-11-18 22:01:31 -05:00
$$
2024-12-09 22:18:13 -05:00
import variationSource from '!!raw-loader!../src/variation'
2024-11-30 17:35:42 -05:00
2024-12-09 22:18:13 -05:00
<CodeBlock language="typescript">{variationSource}</CodeBlock>
2024-11-18 22:01:31 -05:00
2024-12-09 22:18:13 -05:00
Variations, labeled $V_j$ above, are functions just like transforms (we use $j$ to indicate a specific variation).
They take an input point $(x,y)$, and give an output point. However, the sky is the limit for what variation functions do in between
input to output. The Fractal Flame paper lists 49 different variation functions,
and the official `flam3` implementation supports [98 different functions](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/variations.c).
2024-11-18 22:01:31 -05:00
2024-12-09 22:18:13 -05:00
To draw our reference image, we'll focus on four variations:
2024-11-18 22:01:31 -05:00
### Linear (variation 0)
2024-12-09 22:18:13 -05:00
This variation is dead simple: just return 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-12-09 22:18:13 -05:00
:::tip
2024-11-18 22:01:31 -05:00
2024-12-09 22:18:13 -05:00
In a way, we've already been using this variation! The functions that define Sierpinski's Gasket
apply the affine coefficients to the input point, and use that as the output point.
2024-11-18 22:01:31 -05:00
2024-12-09 22:18:13 -05:00
:::
### Julia (variation 13)
This variation is a good example of the non-linear functions the Fractal Flame Algorithm introduces.
It still receives an input point $(x, y)$, but does some crazy things with it:
2024-11-18 22:01:31 -05:00
$$
\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-12-09 22:18:13 -05:00
Some variations rely on knowing the transform's affine coefficients; these are known as "dependent variations."
For the popcorn variation, we use the $c$ and $f$ coefficients:
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)
2024-12-09 22:18:13 -05:00
Some variations have extra parameters that the designer can choose; these are known as "parametric variations."
For the PDJ variation, there are four extra parameters we can choose:
2024-11-18 22:01:31 -05:00
$$
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>
## Blending
2024-11-19 21:42:03 -05:00
2024-12-09 22:18:13 -05:00
Now, one variation is fun, but we can also combine variations in a process called "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-12-09 22:18:13 -05:00
We'll also give each variation a weight (called $v_{ij}$) that changes 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-12-01 21:57:10 -05:00
import blendSource from "!!raw-loader!../src/blend";
<CodeBlock language={'typescript'}>{blendSource}</CodeBlock>
2024-11-19 21:42:03 -05:00
2024-12-09 22:18:13 -05:00
With that in place, we have enough to render a first full fractal flame. We'll use the same
chaos game as before, but use our new transforms and variations to produce a dramatically different image:
:::tip
This image is interactive! The sliders change the variation weights ($v_{ij}$ parameters)
so you can design your own image.
Try using the sliders to find which parts of the image each transform controls.
:::
2024-11-29 19:25:29 -05:00
2024-12-08 22:50:46 -05:00
import {SquareCanvas} from "../src/Canvas";
2024-11-29 19:25:29 -05:00
import FlameBlend from "./FlameBlend";
2024-12-08 22:50:46 -05:00
<SquareCanvas><FlameBlend/></SquareCanvas>
2024-11-30 17:35:42 -05:00
## Post transforms
2024-12-09 22:18:13 -05:00
Post transforms introduce a second affine transform, this time _after_ variation blending.
We'll use introduce some new variables, but the post transform function should look familiar by now:
2024-11-30 17:35:42 -05:00
$$
2024-12-09 22:18:13 -05:00
\begin{align*}
P_i(x, y) &= (\alpha_i x + \beta_i y + \gamma_i, \delta_i x + \epsilon_i y + \zeta_i) \\
F_i(x, y) &= P_i\left(\sum_{j} v_{ij} V_j(x, y)\right)
\end{align*}
2024-11-30 17:35:42 -05:00
$$
2024-12-09 22:18:13 -05:00
import postSource from '!!raw-loader!./post'
<CodeBlock language="typescript">{postSource}</CodeBlock>
The image below starts with the same initial transforms/variations as the previous fractal flame,
but allows modifying the post-transform coefficients.
<details>
<summary>If you want a challenge...</summary>
Challenge 1: What post-transform coefficients will give us the previous image?
Challenge 2: What post-transform coefficients will give us a _mirrored_ image?
</details>
2024-11-30 17:35:42 -05:00
import FlamePost from "./FlamePost";
2024-12-08 22:50:46 -05:00
<SquareCanvas><FlamePost/></SquareCanvas>
2024-11-30 17:35:42 -05:00
2024-12-09 22:18:13 -05:00
## Final transforms
import chaosGameFinalSource from "!!raw-loader!./chaosGameFinal"
<CodeBlock language="typescript">{chaosGameFinalSource}</CodeBlock>
2024-11-30 17:35:42 -05:00
import FlameFinal from "./FlameFinal";
2024-12-08 22:50:46 -05:00
<SquareCanvas><FlameFinal/></SquareCanvas>