diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/chaosGame.js b/blog/2024-11-15-playing-with-fire/1-introduction/chaosGame.js
index 42ff413..75f0d5f 100644
--- a/blog/2024-11-15-playing-with-fire/1-introduction/chaosGame.js
+++ b/blog/2024-11-15-playing-with-fire/1-introduction/chaosGame.js
@@ -9,6 +9,7 @@ const xforms = [
]
function* chaosGame({width, height}) {
+ const step = 1000;
let img = new ImageData(width, height);
let [x, y] = [
randomBiUnit(),
@@ -22,7 +23,7 @@ function* chaosGame({width, height}) {
if (c > 20)
plot(x, y, img);
- if (c % 1000 === 0)
+ if (c % step === 0)
yield img;
}
diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/chaosGameWeighted.ts b/blog/2024-11-15-playing-with-fire/1-introduction/chaosGameWeighted.ts
index a8d4c7e..e219ed9 100644
--- a/blog/2024-11-15-playing-with-fire/1-introduction/chaosGameWeighted.ts
+++ b/blog/2024-11-15-playing-with-fire/1-introduction/chaosGameWeighted.ts
@@ -6,7 +6,7 @@ import {Transform} from "../src/transform";
const iterations = 50_000;
const step = 1000;
// hidden-end
-type Props = {
+export type Props = {
width: number,
height: number,
transforms: [number, Transform][]
@@ -20,6 +20,7 @@ export function* chaosGameWeighted(
randomBiUnit()
];
+ // TODO: Explain quality
const iterations = width * height * 0.5;
for (let c = 0; c < iterations; c++) {
// highlight-start
diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx b/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx
index e9c8ec9..de3897b 100644
--- a/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx
+++ b/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx
@@ -6,11 +6,11 @@ authors: [bspeice]
tags: []
---
-Wikipedia [describes](https://en.wikipedia.org/wiki/Fractal_flame) fractal flames as:
+Wikipedia describes fractal flames fractal flames as:
> a member of the iterated function system class of fractals
-I think of them a different way: beauty in mathematics.
+It's a bit tedious, but technically correct. I choose to think of them a different way: beauty in mathematics.
import isDarkMode from '@site/src/isDarkMode'
import banner from '../banner.png'
@@ -21,37 +21,38 @@ import banner from '../banner.png'
-I don't remember exactly when or how I originally came across 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 actively participate in.
+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.
-The [paper](https://flam3.com/flame_draves.pdf) describing their mathematical structure was too much
-for me to handle at the time (I was ~12 years old), and I was content to play around and enjoy the pictures.
-But the desire to understand it stuck with me, so I wanted to try again. With a graduate degree in Financial Engineering under my belt,
-maybe it would be easier this time.
+The original [Fractal Flame](https://flam3.com/flame_draves.pdf) describing their structure was too much
+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 — and others interested in the art —
+can understand without too much prior knowledge.
---
## Iterated function systems
-Let's begin by defining an "[iterated function system](https://en.wikipedia.org/wiki/Iterated_function_system)" (IFS).
-We'll start at the end and work backwards to build a practical understanding. In mathematical notation, an IFS is:
+As mentioned above, fractal flames are a type of "[iterated function system](https://en.wikipedia.org/wiki/Iterated_function_system),"
+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:
$$
-S = \bigcup_{i=0}^{n-1} F_i(S) \\[0.6cm]
-S \in \mathbb{R}^2 \\
-F_i(S) \in \mathbb{R}^2 \rightarrow \mathbb{R}^2
+S = \bigcup_{i=0}^{n-1} F_i(S)
$$
-### Stationary point
+### Fixed set
-First, $S$. We're generating images, so everything is in two dimensions: $S \in \mathbb{R}^2$. The set $S$ is
-all points that are "in the system." To generate our final image, we just plot every point in the system
-like a coordinate chart.
-
-TODO: What is a stationary point? How does it relate to the chaos game? Why does the chaos game work?
+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.
For example, if we say $S = \{(0,0), (1, 1), (2, 2)\}$, there are three points to plot:
+
+
import {VictoryChart, VictoryTheme, VictoryScatter, VictoryLegend} from "victory";
export const simpleData = [
{x: 0, y: 0},
@@ -63,8 +64,18 @@ export const simpleData = [
-For fractal flames, we just need to figure out which points are in $S$ and plot them. While there are
-technically an infinite number of points, if we find _enough_ points and plot them, we end up with a nice picture.
+However, this is a pretty boring image. With fractal flames, rather than listing individual points,
+we use functions to describe which points are part of the solution. This means there are an infinite
+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.
+
+TODO: Other topics worth covering in this section? Maybe in a `details` block?:
+- Fixed sets: https://en.wiktionary.org/wiki/fixed_set
+- Compact sets
### Transformation functions
@@ -182,7 +193,8 @@ import chaosGameSource from '!!raw-loader!./chaosGame'
-Note: The image here is different than the fractal flame paper, but I think the paper has an error.
+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).
## Weights
diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/plot.ts b/blog/2024-11-15-playing-with-fire/1-introduction/plot.ts
index a30eaf3..2364330 100644
--- a/blog/2024-11-15-playing-with-fire/1-introduction/plot.ts
+++ b/blog/2024-11-15-playing-with-fire/1-introduction/plot.ts
@@ -29,17 +29,17 @@ export function plot(
let pixelY = Math.floor(y * img.height);
const index = imageIndex(
- img.width,
- pixelX,
- pixelY
+ img.width,
+ pixelX,
+ pixelY
);
// Skip pixels outside the display range
if (
- index < 0 ||
- index > img.data.length
+ index < 0 ||
+ index > img.data.length
) {
- return;
+ return;
}
// Set the pixel to black by writing 0
diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/CoefEditor.tsx b/blog/2024-11-15-playing-with-fire/2-transforms/CoefEditor.tsx
index 53080c3..fc4d782 100644
--- a/blog/2024-11-15-playing-with-fire/2-transforms/CoefEditor.tsx
+++ b/blog/2024-11-15-playing-with-fire/2-transforms/CoefEditor.tsx
@@ -14,7 +14,7 @@ export const CoefEditor = ({title, isPost, coefs, setCoefs, resetCoefs}: Props)
const resetButton =
return (
-
+
{title} {resetButton}
{isPost ? \alpha : 'a'}: {coefs.a}
diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/FlameBlend.tsx b/blog/2024-11-15-playing-with-fire/2-transforms/FlameBlend.tsx
index b4792d4..a05dcf8 100644
--- a/blog/2024-11-15-playing-with-fire/2-transforms/FlameBlend.tsx
+++ b/blog/2024-11-15-playing-with-fire/2-transforms/FlameBlend.tsx
@@ -4,9 +4,8 @@ import * as params from "../src/params"
import {PainterContext} from "../src/Canvas"
import {chaosGameFinal} from "./chaosGameFinal"
import {VariationEditor, VariationProps} from "./VariationEditor"
-import {xform1Weight} from "../src/params";
-import {applyTransform} from "@site/blog/2024-11-15-playing-with-fire/src/applyTransform";
-import {buildBlend} from "@site/blog/2024-11-15-playing-with-fire/2-transforms/buildBlend";
+import {applyTransform} from "../src/applyTransform";
+import {buildBlend} from "./buildBlend";
export default function FlameBlend() {
const {width, height, setPainter} = useContext(PainterContext);
diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/FlamePost.tsx b/blog/2024-11-15-playing-with-fire/2-transforms/FlamePost.tsx
index 33e0664..539114e 100644
--- a/blog/2024-11-15-playing-with-fire/2-transforms/FlamePost.tsx
+++ b/blog/2024-11-15-playing-with-fire/2-transforms/FlamePost.tsx
@@ -3,7 +3,7 @@ import {Coefs} from "../src/coefs"
import {Transform} from "../src/transform";
import * as params from "../src/params";
import {PainterContext} from "../src/Canvas"
-import {chaosGameFinal, ChaosGameFinalProps} from "./chaosGameFinal"
+import {chaosGameFinal, Props as ChaosGameFinalProps} from "./chaosGameFinal"
import {CoefEditor} from "./CoefEditor"
import {applyPost, applyTransform} from "@site/blog/2024-11-15-playing-with-fire/src/applyTransform";
diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/VariationEditor.tsx b/blog/2024-11-15-playing-with-fire/2-transforms/VariationEditor.tsx
index 1785ea7..57a3159 100644
--- a/blog/2024-11-15-playing-with-fire/2-transforms/VariationEditor.tsx
+++ b/blog/2024-11-15-playing-with-fire/2-transforms/VariationEditor.tsx
@@ -18,7 +18,7 @@ export const VariationEditor = ({title, variations, setVariations, resetVariatio
const resetButton =
return (
-
+
{title} {resetButton}
Linear: {variations.linear}
diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/chaosGameFinal.ts b/blog/2024-11-15-playing-with-fire/2-transforms/chaosGameFinal.ts
index 638846d..147b02b 100644
--- a/blog/2024-11-15-playing-with-fire/2-transforms/chaosGameFinal.ts
+++ b/blog/2024-11-15-playing-with-fire/2-transforms/chaosGameFinal.ts
@@ -3,20 +3,19 @@ import { randomBiUnit } from "../src/randomBiUnit";
import { randomChoice } from "../src/randomChoice";
import { plotBinary as plot } from "../src/plotBinary"
import {Transform} from "../src/transform";
-import {ChaosGameWeightedProps} from "../1-introduction/chaosGameWeighted";
+import {Props as ChaosGameWeightedProps} from "../1-introduction/chaosGameWeighted";
+
+const quality = 0.5;
+const step = 1000;
// hidden-end
-export type ChaosGameFinalProps = ChaosGameWeightedProps & {
+export type Props = ChaosGameWeightedProps & {
final: Transform,
- quality?: number,
- step?: number,
}
-export function* chaosGameFinal({width, height, transforms, final, quality, step}: ChaosGameFinalProps) {
+export function* chaosGameFinal({width, height, transforms, final}: Props) {
let image = new ImageData(width, height);
let [x, y] = [randomBiUnit(), randomBiUnit()];
- const iterations = (quality ?? 0.5) * width * height;
- step = step ?? 1000;
-
+ const iterations = width * height * quality;
for (let i = 0; i < iterations; i++) {
const [_, transform] = randomChoice(transforms);
[x, y] = transform(x, y);
diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx b/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx
index 64f778f..47c60c2 100644
--- a/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx
+++ b/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx
@@ -142,10 +142,10 @@ The sliders below change the variation weights for each transform (the $v_{ij}$
try changing them around to see which parts of the image are controlled by
each transform.
-import Canvas from "../src/Canvas";
+import {SquareCanvas} from "../src/Canvas";
import FlameBlend from "./FlameBlend";
-
+
## Post transforms
@@ -160,10 +160,10 @@ $$
import FlamePost from "./FlamePost";
-
+
## Final transform
import FlameFinal from "./FlameFinal";
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/FlameColor.tsx b/blog/2024-11-15-playing-with-fire/3-log-density/FlameColor.tsx
index bf75f31..84a0db1 100644
--- a/blog/2024-11-15-playing-with-fire/3-log-density/FlameColor.tsx
+++ b/blog/2024-11-15-playing-with-fire/3-log-density/FlameColor.tsx
@@ -1,35 +1,12 @@
import React, {useContext, useEffect, useMemo, useRef, useState} from "react";
import * as params from "../src/params";
-import {InvertibleCanvas, PainterContext} from "../src/Canvas";
+import {PainterContext} from "../src/Canvas";
import {colorFromPalette} from "./paintColor";
import {chaosGameColor, ChaosGameColorProps, TransformColor} from "./chaosGameColor";
import styles from "../src/css/styles.module.css";
import {histIndex} from "../src/camera";
-
-type AutoSizingCanvasProps = {
- painter: (width: number, height: number) => ImageData;
-}
-const AutoSizingCanvas: React.FC = ({painter}) => {
- const sizingRef = useRef(null);
- const [width, setWidth] = useState(0);
- const [height, setHeight] = useState(0);
-
- useEffect(() => {
- if (sizingRef) {
- setWidth(sizingRef.current.offsetWidth);
- setHeight(sizingRef.current.offsetHeight)
- }
- }, [sizingRef]);
-
- const image: [ImageData] = useMemo(() => (width && height) ? [painter(width, height)] : null, [painter, width, height]);
-
- return (
-
{children}
>
diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/index.mdx b/blog/2024-11-15-playing-with-fire/3-log-density/index.mdx
index 69dbca5..7c96033 100644
--- a/blog/2024-11-15-playing-with-fire/3-log-density/index.mdx
+++ b/blog/2024-11-15-playing-with-fire/3-log-density/index.mdx
@@ -28,11 +28,11 @@ import paintLinearSource from "!!raw-loader!./paintLinear"
{paintLinearSource}
-import Canvas from "../src/Canvas";
+import {SquareCanvas} from "../src/Canvas";
import FlameHistogram from "./FlameHistogram";
import {paintLinear} from "./paintLinear";
-
+
## Log display
@@ -42,7 +42,7 @@ import paintLogarithmicSource from "!!raw-loader!./paintLogarithmic"
import {paintLogarithmic} from './paintLogarithmic'
-
+
## Color
@@ -52,4 +52,4 @@ import paintColorSource from "!!raw-loader!./paintColor"
import FlameColor from "./FlameColor";
-
\ No newline at end of file
+
\ No newline at end of file