speice.io/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx

222 lines
7.9 KiB
Plaintext
Raw Normal View History

2024-11-16 18:20:32 -05:00
---
slug: 2024/11/playing-with-fire
title: "Playing with fire: The fractal flame algorithm"
2024-11-16 18:20:32 -05:00
date: 2024-11-15 12:00:00
authors: [bspeice]
tags: []
---
2024-12-08 22:50:46 -05:00
Wikipedia describes fractal flames fractal flames as:
2024-11-16 18:20:32 -05:00
> a member of the iterated function system class of fractals
2024-12-08 22:50:46 -05:00
It's a bit tedious, but technically correct. I choose to think of them a different way: beauty in mathematics.
2024-11-16 18:20:32 -05:00
import isDarkMode from '@site/src/isDarkMode'
2024-11-29 19:25:29 -05:00
import banner from '../banner.png'
2024-11-16 18:20:32 -05:00
<center>
2024-11-29 19:25:29 -05:00
<img src={banner} style={{filter: isDarkMode() ? '' : 'invert(1)'}}/>
2024-11-16 18:20:32 -05:00
</center>
<!-- truncate -->
2024-12-08 22:50:46 -05:00
I don't remember exactly when I first learned about fractal flames, but I do remember becoming entranced by the images they created.
I also remember their unique appeal to my young engineering mind; this was an art form I could participate in.
2024-11-16 18:20:32 -05:00
2024-12-09 22:18:13 -05:00
The original [Fractal Flame Algorithm paper](https://flam3.com/flame_draves.pdf) describing their structure was too much
2024-12-08 22:50:46 -05:00
for me to handle at the time (I was ~12 years old), so I was content to play around and enjoy the pictures.
But the desire to understand it stuck around. Now, with a graduate degree under my belt, maybe I can make some progress.
This guide is my attempt to explain fractal flames in a way that younger me &mdash; and others interested in the art &mdash;
can understand without too much prior knowledge.
2024-11-16 18:20:32 -05:00
---
## Iterated function systems
2024-12-09 22:18:13 -05:00
:::note
This post covers section 2 of the Fractal Flame Algorithm paper
:::
As mentioned, fractal flames are a type of "[iterated function system](https://en.wikipedia.org/wiki/Iterated_function_system),"
2024-12-08 22:50:46 -05:00
or IFS. Their mathematical foundations come from a paper written by [John E. Hutchinson](https://maths-people.anu.edu.au/~john/Assets/Research%20Papers/fractals_self-similarity.pdf),
but reading that paper isn't critical for our purposes. Instead, we'll focus on building a practical understanding
of how they work. The formula for an IFS is short, but will take some time to unpack:
2024-11-16 18:20:32 -05:00
$$
2024-12-08 22:50:46 -05:00
S = \bigcup_{i=0}^{n-1} F_i(S)
2024-11-16 18:20:32 -05:00
$$
2024-12-08 22:50:46 -05:00
### Fixed set
2024-11-16 18:20:32 -05:00
2024-12-08 22:50:46 -05:00
First, $S$. $S$ is the set of points in two dimensions (in math terms, $S \in \mathbb{R}^2$) that represent
a "solution" of some kind. Our goal is to find all points in the set $S$, plot them, and display that image.
2024-11-18 22:01:31 -05:00
2024-11-16 18:20:32 -05:00
For example, if we say $S = \{(0,0), (1, 1), (2, 2)\}$, there are three points to plot:
2024-12-08 22:50:46 -05:00
<!-- TODO: What is a stationary point? How does it relate to the chaos game? Why does the chaos game work? -->
2024-11-17 20:42:42 -05:00
import {VictoryChart, VictoryTheme, VictoryScatter, VictoryLegend} from "victory";
export const simpleData = [
{x: 0, y: 0},
{x: 1, y: 1},
{x: 2, y: 2}
]
2024-11-16 18:20:32 -05:00
2024-11-17 20:42:42 -05:00
<VictoryChart theme={VictoryTheme.clean}>
<VictoryScatter data={simpleData} size={5} style={{data: {fill: "blue"}}}/>
</VictoryChart>
2024-11-16 18:20:32 -05:00
2024-12-08 22:50:46 -05:00
However, this is a pretty boring image. With fractal flames, rather than listing individual points,
2024-12-09 22:18:13 -05:00
we use functions to describe which points are part of the solution.
TODO: Explain characteristics of the solution - fixed set
This means there are an infinite
2024-12-08 22:50:46 -05:00
number of points, but if we find _enough_ points to plot, we'll end up with a nice picture.
And if we choose different functions to start with, our solution set changes, and we'll end up
with a new picture.
However, it's not clear which points belong in the solution just by staring at the functions.
We'll need a computer to figure it out.
2024-11-16 18:20:32 -05:00
### Transformation functions
Second, $F_i(S)$. At their most basic, each $F_i$ is a function that takes in a 2-dimensional point and transforms
it into a new 2-dimensional point: $F_i \in \mathbb{R}^2 \rightarrow \mathbb{R}^2$. It's worth discussing
these functions, but not critical, so **this section is optional**.
In mathematical terms, each $F_i$ is a special kind of function called an [affine transformation](https://en.wikipedia.org/wiki/Affine_transformation).
We can think of them like mapping from one coordinate system to another. For example, we can define a coordinate system
where everything is shifted over:
$$
F_{shift}(x, y) = (x + 1, y)
$$
That is, for an input point $(x, y)$, the output point will be $(x + 1, y)$:
2024-11-17 20:42:42 -05:00
export const shiftData = simpleData.map(({x, y}) => { return {x: x + 1, y} })
<VictoryChart theme={VictoryTheme.clean}>
<VictoryScatter data={simpleData} size={5} style={{data: {fill: "blue"}}}/>
<VictoryScatter data={shiftData} size={5} style={{data: {fill: "orange"}}}/>
<VictoryLegend
2024-11-16 18:20:32 -05:00
data={[
2024-11-17 20:42:42 -05:00
{name: "(x,y)", symbol: {fill: "blue"}},
{name: "F(x,y)", symbol: {fill: "orange"}}
2024-11-16 18:20:32 -05:00
]}
2024-11-17 20:42:42 -05:00
orientation={"vertical"}
x={75}
y={10}
2024-11-16 18:20:32 -05:00
/>
2024-11-17 20:42:42 -05:00
</VictoryChart>
2024-11-16 18:20:32 -05:00
This is a simple example designed to illustrate the principle. In general, $F_i$ functions have the form:
$$
F_i(x,y) = (a_i \cdot x + b_i \cdot y + c_i, d_i \cdot x + e_i \cdot y + f_i)
2024-11-16 18:20:32 -05:00
$$
The parameters ($a_i$, $b_i$, etc.) are values we get to choose. In the example above, we can represent our shift
function using these parameters:
$$
a_i = 1 \hspace{0.5cm} b_i = 0 \hspace{0.5cm} c_i = 1 \\
d_i = 0 \hspace{0.5cm} e_i = 1 \hspace{0.5cm} f_i = 0 \\
$$
$$
\begin{align*}
F_{shift}(x,y) &= (1 \cdot x + 0 \cdot y + 1, 0 \cdot x + 1 \cdot y + 0) \\
F_{shift}(x,y) &= (x + 1, y)
\end{align*}
$$
Fractal flames use more complex functions to produce a wide variety of images, but all follow this same format.
## Sierpinski's gasket
2024-11-24 22:37:53 -05:00
Using these definitions, we can build the first image. The paper defines a function system for us:
2024-11-16 18:20:32 -05:00
$$
F_0(x, y) = \left({x \over 2}, {y \over 2} \right) \\
~\\
F_1(x, y) = \left({{x + 1} \over 2}, {y \over 2} \right) \\
~\\
2024-11-16 18:20:32 -05:00
F_2(x, y) = \left({x \over 2}, {{y + 1} \over 2} \right)
$$
### The chaos game
import CodeBlock from '@theme/CodeBlock'
2024-11-16 18:20:32 -05:00
Next, how do we find out all the points in $S$? The paper lays out an algorithm called the "chaos game":
$$
\begin{align*}
&(x, y) = \text{random point in the bi-unit square} \\
2024-11-16 18:20:32 -05:00
&\text{iterate } \{ \\
&\hspace{1cm} i = \text{random integer from 0 to } n - 1 \\
2024-11-16 18:20:32 -05:00
&\hspace{1cm} (x,y) = F_i(x,y) \\
&\hspace{1cm} \text{plot}(x,y) \text{ if iterations} > 20 \\
2024-11-16 18:20:32 -05:00
\}
\end{align*}
$$
Let's turn this into code, one piece at a time.
First, the "bi-unit square" is the range $[-1, 1]$. We can pick a random point like this:
2024-11-24 22:37:53 -05:00
import biunitSource from '!!raw-loader!../src/randomBiUnit'
<CodeBlock language="typescript">{biunitSource}</CodeBlock>
Next, we need to choose a random integer from $0$ to $n - 1$:
2024-11-24 22:37:53 -05:00
import randintSource from '!!raw-loader!../src/randomInteger'
<CodeBlock language="typescript">{randintSource}</CodeBlock>
Finally, implementing the `plot` function. Web browsers have a [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)
we can use for 2D graphics. In our case, the plot function will take an $(x,y)$ coordinate and plot it by
coloring the corresponding pixel in an [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData):
import plotSource from '!!raw-loader!./plot'
<CodeBlock language="typescript">{plotSource}</CodeBlock>
import Playground from '@theme/Playground'
import Scope from './scope'
import chaosGameSource from '!!raw-loader!./chaosGame'
<Playground scope={Scope} noInline={true}>{chaosGameSource}</Playground>
2024-11-17 20:42:42 -05:00
<hr/>
<small>
2024-12-08 22:50:46 -05:00
Note: The image here is slightly different than the fractal flame paper; I think the paper has an error,
so I'm choosing to plot the image in a way that's consistent with [`flam3` itself](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L440-L441).
2024-11-19 21:42:03 -05:00
</small>
2024-11-24 22:37:53 -05:00
## Weights
Finally, we'll introduce a "weight" parameter ($w_i$) assigned to each function, which controls
how often that function is used:
import randomChoiceSource from '!!raw-loader!../src/randomChoice'
<CodeBlock language={'typescript'}>{randomChoiceSource}</CodeBlock>
import chaosGameWeightedSource from "!!raw-loader!./chaosGameWeighted";
<CodeBlock language={'typescript'}>{chaosGameWeightedSource}</CodeBlock>
2024-11-29 23:22:31 -05:00
import GasketWeighted from "./GasketWeighted";
import {SquareCanvas} from "../src/Canvas";
2024-11-24 22:37:53 -05:00
<SquareCanvas><GasketWeighted/></SquareCanvas>