+ >
+ );
}
\ No newline at end of file
diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/cameraGasket.ts b/blog/2024-11-15-playing-with-fire/1-introduction/cameraGasket.ts
index 8f729cc..47d104c 100644
--- a/blog/2024-11-15-playing-with-fire/1-introduction/cameraGasket.ts
+++ b/blog/2024-11-15-playing-with-fire/1-introduction/cameraGasket.ts
@@ -1,10 +1,10 @@
export function camera(
- size: number,
- x: number,
- y: number
+ x: number,
+ y: number,
+ size: number
): [number, number] {
- return [
- Math.floor(x * size),
- Math.floor(y * size)
- ];
+ return [
+ Math.floor(x * size),
+ Math.floor(y * size)
+ ];
}
\ No newline at end of file
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 17f6962..777ccfd 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
@@ -6,28 +6,29 @@ const xforms = [
(x, y) => [x / 2, y / 2],
(x, y) => [(x + 1) / 2, y / 2],
(x, y) => [x / 2, (y + 1) / 2]
-]
+];
-function* chaosGame({width, height}) {
- const step = 1000;
- let img = new ImageData(width, height);
+function* chaosGame({ width, height }) {
+ let img =
+ new ImageData(width, height);
let [x, y] = [
randomBiUnit(),
randomBiUnit()
];
- for (let c = 0; c < iterations; c++) {
- const i = randomInteger(0, xforms.length);
- [x, y] = xforms[i](x, y);
+ for (let i = 0; i < iterations; i++) {
+ const index =
+ randomInteger(0, xforms.length);
+ [x, y] = xforms[index](x, y);
- if (c > 20)
+ if (i > 20)
plot(x, y, img);
- if (c % step === 0)
+ if (i % 1000 === 0)
yield img;
}
yield img;
}
-render()
+render();
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 ef1966a..41a9922 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
@@ -1,37 +1,41 @@
// hidden-start
import { randomBiUnit } from "../src/randomBiUnit";
import { randomChoice } from "../src/randomChoice";
-import { plot } from "./plot"
-import {Transform} from "../src/transform";
+import { plot } from "./plot";
+import { Transform } from "../src/transform";
const quality = 0.5;
const step = 1000;
// hidden-end
export type Props = {
- width: number,
- height: number,
- transforms: [number, Transform][]
+ width: number,
+ height: number,
+ transforms: [number, Transform][]
}
+
export function* chaosGameWeighted(
- {width, height, transforms}: Props
+ { width, height, transforms }: Props
) {
- let img = new ImageData(width, height);
+ let img =
+ new ImageData(width, height);
let [x, y] = [
- randomBiUnit(),
- randomBiUnit()
+ randomBiUnit(),
+ randomBiUnit()
];
- const iterations = width * height * quality;
- for (let c = 0; c < iterations; c++) {
+ const pixels = width * height;
+ const iterations = quality * pixels;
+ for (let i = 0; i < iterations; i++) {
// highlight-start
- const [_, xform] = randomChoice(transforms);
+ const [_, xform] =
+ randomChoice(transforms);
// highlight-end
[x, y] = xform(x, y);
- if (c > 20)
+ if (i > 20)
plot(x, y, img);
- if (c % step === 0)
+ if (i % step === 0)
yield img;
}
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 60a7645..dd9dacc 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
@@ -83,7 +83,7 @@ called an "[affine transformation](https://en.wikipedia.org/wiki/Affine_transfor
The general form of an affine transformation is:
$$
-F_i(a_i \cdot x + b_i \cdot y + c_i, d_i \cdot x + e_i \cdot y + f_i)
+F_i(a_i x + b_i y + c_i, d_i x + e_i y + f_i)
$$
import transformSource from "!!raw-loader!../src/transform"
@@ -102,8 +102,7 @@ c &= 0.5 \\
d &= 0 \\
e &= 1 \\
f &= 1.5 \\
-F_{shift}(x,y) &= (1 \cdot x + 0 \cdot y + 0.5, 0 \cdot x + 1 \cdot y + 0.5) \\
-F_{shift}(x, y) &= (x + 0.5, y + 1.5)
+F_{shift}(x, y) &= (1 \cdot x + 0.5, 1 \cdot y + 1.5)
\end{align*}
$$
@@ -155,7 +154,7 @@ explaining the mathematics of iterated function systems:
> of finite compositions $F_{i_1...i_p}$ of members of $F$.
:::note
-I've tweaked the wording slightly to match the conventions of the Fractal Flame paper
+I've tweaked the conventions of that paper slightly to match the Fractal Flame paper
:::
Before your eyes glaze over, let's unpack this:
@@ -174,15 +173,15 @@ Thus, by applying the functions to fixed points of our system, we will find the
...then there are some extra details I've glossed over so far.
First, the Hutchinson paper requires that the functions $F_i$ be _contractive_ for the solution set to exist.
- That is, applying the function to a point must bring it closer to other points. However, as the Fractal Flame
+ That is, applying the function to a point must bring it closer to other points. However, as the fractal flame
algorithm demonstrates, we only need functions to be contractive _on average_. At worst, the system will
degenerate and produce a bad image.
- Second, we're focused $\mathbb{R}^2$ because we're generating images, but the Hutchinson paper
+ Second, we're focused on $\mathbb{R}^2$ because we're generating images, but the Hutchinson paper
allows for arbitrary dimensions; you could also have 3-dimensional fractal flames.
Finally, there's a close relationship between fractal flames and [attractors](https://en.wikipedia.org/wiki/Attractor).
- Specifically, the fixed points of $S$ act as attractors for the chaos game (explained in detail below).
+ Specifically, the fixed points of $S$ act as attractors for the chaos game (explained in more detail below).
This is still a bit vague, so let's work through an example.
@@ -272,8 +271,8 @@ import chaosGameSource from '!!raw-loader!./chaosGame'
The image here is slightly different than in the paper.
- I think the paper has an error, so I'm choosing to plot the image
- the same way as the [reference implementation](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L440-L441).
+ I think the paper has an error, so I'm plotting the image
+ like the [reference implementation](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L440-L441).
### Weights
@@ -292,7 +291,10 @@ import chaosGameWeightedSource from "!!raw-loader!./chaosGameWeighted";
For Sierpinski's Gasket, we start with equal weighting,
but changing the transform weights affects how often
-the chaos game "visits" parts of the image:
+the chaos game "visits" parts of the image. If the chaos
+were to run forever, we'd get the same image;
+but because the iteration count is limited,
+some parts may be missing below.
:::tip
Double-click the image if you want to save a copy!
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 ef386f5..6db267d 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
@@ -1,10 +1,11 @@
// hidden-start
-import {camera} from "./cameraGasket"
+import { camera } from "./cameraGasket";
+
// hidden-end
function imageIndex(
- width: number,
x: number,
- y: number
+ y: number,
+ width: number
) {
return y * (width * 4) + x * 4;
}
@@ -14,24 +15,26 @@ export function plot(
y: number,
img: ImageData
) {
- let [pixelX, pixelY] = camera(img.width, x, y);
+ let [pixelX, pixelY] =
+ camera(x, y, img.width);
+
+ // Skip coordinates outside the display
+ if (
+ pixelX < 0 ||
+ pixelX >= img.width ||
+ pixelY < 0 ||
+ pixelY >= img.height
+ )
+ return;
const i = imageIndex(
- img.width,
pixelX,
- pixelY
+ pixelY,
+ img.width
);
- // Skip pixels outside the display range
- if (
- i < 0 ||
- i > img.data.length
- ) {
- return;
- }
-
- // Set the pixel to black by writing 0
- // to the first three elements at the index
+ // Set the pixel to black by setting
+ // the first three elements to 0
// (red, green, and blue, respectively),
// and 255 to the last element (alpha)
img.data[i] = 0;
diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/scope.tsx b/blog/2024-11-15-playing-with-fire/1-introduction/scope.tsx
index dc05043..4adee1d 100644
--- a/blog/2024-11-15-playing-with-fire/1-introduction/scope.tsx
+++ b/blog/2024-11-15-playing-with-fire/1-introduction/scope.tsx
@@ -1,12 +1,12 @@
import Gasket from "./Gasket";
-import { plot } from './plot';
-import { randomBiUnit } from '../src/randomBiUnit';
-import { randomInteger } from '../src/randomInteger';
+import { plot } from "./plot";
+import { randomBiUnit } from "../src/randomBiUnit";
+import { randomInteger } from "../src/randomInteger";
const Scope = {
- Gasket,
- plot,
- randomBiUnit,
- randomInteger,
-}
+ Gasket,
+ plot,
+ randomBiUnit,
+ randomInteger
+};
export default Scope;
\ No newline at end of file
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 fc4d782..77e0abd 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
@@ -1,51 +1,52 @@
import TeX from "@matejmazur/react-katex";
-import {Coefs} from "../src/coefs";
+import { Coefs } from "../src/transform";
import styles from "../src/css/styles.module.css";
export interface Props {
- title: String;
- isPost: boolean;
- coefs: Coefs;
- setCoefs: (coefs: Coefs) => void;
- resetCoefs: () => void;
+ title: String;
+ isPost: boolean;
+ coefs: Coefs;
+ setCoefs: (coefs: Coefs) => void;
+ resetCoefs: () => void;
}
-export const CoefEditor = ({title, isPost, coefs, setCoefs, resetCoefs}: Props) => {
- const resetButton =
- return (
-