Reorganize a bit, write some more

This commit is contained in:
Bradlee Speice 2024-11-19 21:42:03 -05:00
parent 431ba2d0f4
commit aba3c9f988
17 changed files with 181 additions and 156 deletions

View File

@ -51,7 +51,7 @@ First, $S$. We're generating images, so everything is in two dimensions: $S \in
all points that are "in the system." To generate our final image, we just plot every point in the system all points that are "in the system." To generate our final image, we just plot every point in the system
like a coordinate chart. like a coordinate chart.
TODO: What is a stationary point? How does it relate to the chaos game? TODO: What is a stationary point? How does it relate to the chaos game? Why does the chaos game work?
For example, if we say $S = \{(0,0), (1, 1), (2, 2)\}$, there are three points to plot: For example, if we say $S = \{(0,0), (1, 1), (2, 2)\}$, there are three points to plot:
@ -187,4 +187,6 @@ import Gasket from '!!raw-loader!./Gasket'
<small> <small>
Note: The image our chaos game generates is different than the fractal flame paper, but I think the version displayed Note: The image our chaos game generates is different than the fractal flame paper, but I think the version displayed
here is correct. As confirmation, the next post will re-create the same image using a different method. here is correct. As confirmation, the next post will re-create the same image using a different method.
</small> </small>
TODO: Explanation of function weights $w_i$

View File

@ -0,0 +1,5 @@
import {useColorMode, useThemeConfig} from "@docusaurus/theme-common";
export default function BaselineRender({children}) {
const {colorMode, setColorMode} = useColorMode();
}

View File

@ -0,0 +1,22 @@
// hidden-start
import { Coefs } from './coefs'
import { Variation } from './variations'
// hidden-end
export function applyTransform(
x: number,
y: number,
coefs: Coefs,
variations: [number, Variation][])
{
const transformX = coefs.a * x + coefs.b * y + coefs.c;
const transformY = coefs.d * x + coefs.e * y + coefs.f;
var finalX = 0;
var finalY = 0;
for (const [blend, variation] of variations) {
const [variationX, variationY] = variation(transformX, transformY);
finalX += blend * variationX;
finalY += blend * variationY;
}
return [finalX, finalY];
}

View File

@ -25,6 +25,10 @@ $$
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) 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)
$$ $$
import coefsSrc from '!!raw-loader!../src/coefs'
<CodeBlock language={'typescript'}>{coefsSrc}</CodeBlock>
We also introduced the Sierpinski Gasket functions ($F_0$, $F_1$, and $F_2$), demonstrating how they are related to 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: the general format. For example:
@ -37,7 +41,7 @@ F_0(x,y) &= \left({x \over 2}, {y \over 2}\right) \\
\end{align*} \end{align*}
$$ $$
However, these transforms are pretty boring. We can build more exciting images by using some additional functions However, these transforms are pretty boring. We can build more exciting images by using additional functions
within the transform. These "sub-functions" are called "variations": within the transform. These "sub-functions" are called "variations":
$$ $$
@ -52,18 +56,18 @@ Our reference image will focus on just four variations:
### Linear (variation 0) ### Linear (variation 0)
This variation returns the $x$ and $y$ coordinates as-is. As mentioned, the Sierpinski Gasket is This variation returns the $x$ and $y$ coordinates as-is:
a fractal flame using only the linear variation:
$$ $$
V_0(x,y) = (x,y) V_0(x,y) = (x,y)
$$ $$
```typescript import linearSrc from '!!raw-loader!../src/linear'
function linear(x: number, y: number) {
return [x, y]; <CodeBlock language={'typescript'}>{linearSrc}</CodeBlock>
}
``` Before we move on, it's worth mentioning the relationship between this variation and the Sierpinski Gasket.
Specifically, we can think of the Gasket as a fractal flame that uses only the linear variation.
### Julia (variation 13) ### Julia (variation 13)
@ -86,18 +90,9 @@ V_{13}(x, y) &= \sqrt{r} \cdot (\text{cos} ( \theta / 2 + \Omega ), \text{sin} (
\end{align*} \end{align*}
$$ $$
```typescript import juliaSrc from '!!raw-loader!../src/julia'
function julia(x: number, y: number) {
const r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
const theta = Math.atan2(x, y);
const omega = Math.random() > 0.5 ? 0 : Math.PI;
return [ <CodeBlock language={'typescript'}>{juliaSrc}</CodeBlock>
r * Math.cos(theta / 2 + omega),
r * Math.sin(theta / 2 + omega)
]
}
```
### Popcorn (variation 17) ### Popcorn (variation 17)
@ -108,14 +103,9 @@ $$
V_{17}(x,y) = (x + c \cdot \text{sin}(\text{tan }3y), y + f \cdot \text{sin}(\text{tan }3x)) V_{17}(x,y) = (x + c \cdot \text{sin}(\text{tan }3y), y + f \cdot \text{sin}(\text{tan }3x))
$$ $$
```typescript import popcornSrc from '!!raw-loader!../src/popcorn'
function popcorn(coefs: {c: number, f: number}) {
return (x: number, y: number) => [ <CodeBlock language={'typescript'}>{popcornSrc}</CodeBlock>
x + coefs.c * Math.sin(Math.tan(3 * y)),
y + coefs.f * Math.sin(Math.tan(3 * x))
]
}
```
### PDJ (variation 24) ### PDJ (variation 24)
@ -126,11 +116,27 @@ p_1 = \text{pdj.a} \hspace{0.2cm} p_2 = \text{pdj.b} \hspace{0.2cm} p_3 = \text{
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)) 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))
$$ $$
```typescript import pdjSrc from '!!raw-loader!../src/pdj'
function pdj(a: number, b: number, c: number, d: number) {
return (x: number, y: number) => [ <CodeBlock language={'typescript'}>{pdjSrc}</CodeBlock>
Math.sin(a * y) - Math.cos(b * x),
Math.sin(c * x) - Math.cos(d * y) ### Blending
]
} Now, one variation is fun, but we can also combine variations in a single transform by "blending."
``` First, each variation is assigned a value that describes how much it affects the transform function ($v_j$).
Afterward, sum up the $x$ and $y$ values respectively:
$$
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:
import baselineSrc from '!!raw-loader!./baseline'
<CodeBlock language={'typescript'}>{baselineSrc}</CodeBlock>
TODO: Mention that the Sierpinski Gasket is just a blend with linear weight 1, all others 0. Maybe replace comment above about Sierpinski Gasket and linear transform?
And with that in place, we have enough to render a first full fractal flame:

View File

@ -0,0 +1,13 @@
// hidden-start
import { Variation } from './variations'
// hidden-end
export const julia: Variation = (x, y) => {
const r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
const theta = Math.atan2(x, y);
const omega = Math.random() > 0.5 ? 0 : Math.PI;
return [
r * Math.cos(theta / 2 + omega),
r * Math.sin(theta / 2 + omega)
]
}

View File

@ -0,0 +1,4 @@
// hidden-start
import {Variation} from "./variations"
//hidden-end
export const linear: Variation = (x, y) => [x, y];

View File

@ -3,8 +3,12 @@
* translated into something that's easier to work with. * translated into something that's easier to work with.
*/ */
import {Coefs, Transform} from "./types"; import { Coefs } from './coefs';
import { julia, linear, pdj, popcorn } from "./variation"; import { Transform } from './transform';
import { linear } from './linear'
import { julia } from './julia'
import { popcorn } from './popcorn'
import { pdj } from './pdj'
export const identityCoefs: Coefs = { export const identityCoefs: Coefs = {
a: 1, b: 0, c: 0, a: 1, b: 0, c: 0,
@ -12,53 +16,51 @@ export const identityCoefs: Coefs = {
} }
export const xform1Weight = 0.56453495; export const xform1Weight = 0.56453495;
export const xform1: Transform = { export const xform1Coefs = {
coefs: { a: -1.381068, b: -1.381068, c: 0,
a: -1.381068, b: -1.381068, c: 0, d: 1.381068, e: -1.381068, f: 0,
d: 1.381068, e: -1.381068, f: 0,
},
coefsPost: identityCoefs,
variations: [[1, julia]],
color: 0
} }
export const xform1CoefsPost = identityCoefs;
export const xform1Variations = [
[1, julia]
]
export const xform1Color = 0;
const xform2Weight = 0.013135; export const xform2Weight = 0.013135;
export const xform2: Transform = { export const xform2Coefs = {
coefs: { a: 0.031393, b: 0.031367, c: 0,
a: 0.031393, b: 0.031367, c: 0, d: -0.031367, e: 0.031393, f: 0,
d: -0.031367, e: 0.031393, f: 0,
},
coefsPost: {
a: 1, b: 0, c: 0.241352,
d: 0, e: 1, f: 0.271521,
},
variations: [
[1, linear],
[1, popcorn]
],
color: 0.844
} }
export const xform2CoefsPost = {
a: 1, b: 0, c: 0.241352,
d: 0, e: 1, f: 0.271521,
}
export const xform2Variations = [
[1, linear],
[1, popcorn(xform2Coefs)]
]
export const xform2Color = 0.844;
export const xform3Weight = 0.42233; export const xform3Weight = 0.42233;
export const xform3: Transform = { export const xform3Coefs = {
coefs: { a: 1.51523, b: -3.048677, c: 0.724135,
a: 1.51523, b: -3.048677, c: 0.724135, d: 0.740356, e: -1.455964, f: -0.362059,
d: 0.740356, e: -1.455964, f: -0.362059,
},
coefsPost: identityCoefs,
variations: [[1, pdj(1.09358, 2.13048, 2.54127, 2.37267)]],
color: 0.349
} }
export const xform3CoefsPost = identityCoefs;
export const xform3Variations = [
[1, pdj(1.09358, 2.13048, 2.54127, 2.37267)]
];
export const xform3Color = 0.349;
export const xformFinal: Transform = { export const xformFinalCoefs = {
coefs: { a: 2, b: 0, c: 0,
a: 2, b: 0, c: 0, d: 0, e: 2, f: 0
d: 0, e: 2, f: 0
},
coefsPost: identityCoefs,
variations: [[1, julia]],
color: 0
} }
export const xformFinalCoefsPost = identityCoefs;
export const xformFinalVariations = [
[1, julia]
]
export const xformFinalColor = 0;
export const palette = export const palette =
"7E3037762C45722B496E2A4E6A2950672853652754632656" + "7E3037762C45722B496E2A4E6A2950672853652754632656" +

View File

@ -0,0 +1,9 @@
// hidden-start
import { Variation } from './variations'
//hidden-end
export function pdj(a: number, b: number, c: number, d: number): Variation {
return (x, y) => [
Math.sin(a * y) - Math.cos(b * x),
Math.sin(c * x) - Math.cos(d * y)
]
}

View File

@ -0,0 +1,10 @@
// hidden-start
import {Coefs} from './coefs'
import {Variation} from './variations'
// hidden-end
export function popcorn({c, f}: Coefs): Variation {
return (x, y) => [
x + c * Math.sin(Math.tan(3 * y)),
y + f * Math.sin(Math.tan(3 * x))
];
}

View File

@ -0,0 +1,9 @@
import { Coefs } from './coefs'
import { Variation } from './variations'
export interface Transform {
coefs: Coefs,
variations: [number, Variation][],
coefsPost: Coefs,
color: number
}

View File

@ -0,0 +1 @@
export type Variation = (x: number, y: number) => [number, number];

View File

@ -1,24 +0,0 @@
/**
* Affine transformation coefficients
*/
export type Coefs = {
a: number;
b: number;
c: number;
d: number;
e: number;
f: number;
};
export type Variation = (
x: number,
y: number,
coefs: Coefs
) => [number, number];
export type Transform = {
coefs: Coefs,
coefsPost: Coefs,
variations: [number, Variation][],
color: number
}

View File

@ -1,40 +0,0 @@
import { Variation } from "./types";
export const linear: Variation = (x, y) => [x, y];
function r(x: number, y: number) {
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
}
function theta(x: number, y: number) {
return Math.atan2(x, y);
}
function omega(): number {
return Math.random() > 0.5 ? Math.PI : 0;
}
export const julia: Variation = (x, y) => {
const sqrtR = Math.sqrt(r(x, y));
const thetaVal = theta(x, y) / 2 + omega();
return [sqrtR * Math.cos(thetaVal), sqrtR * Math.sin(thetaVal)];
};
export const popcorn: Variation = (x, y, transformCoefs) => {
return [
x + transformCoefs.c * Math.sin(Math.tan(3 * y)),
y + transformCoefs.f * Math.sin(Math.tan(3 * x)),
];
};
export const pdj: (
pdjA: number,
pdjB: number,
pdjC: number,
pdjD: number
) => Variation = (pdjA, pdjB, pdjC, pdjD) => {
return (x, y) => [
Math.sin(pdjA * y) - Math.cos(pdjB * x),
Math.sin(pdjC * x) - Math.cos(pdjD * y),
];
};

View File

@ -81,7 +81,19 @@ const config: Config = {
prism: { prism: {
theme: prismThemes.oneLight, theme: prismThemes.oneLight,
darkTheme: prismThemes.oneDark, darkTheme: prismThemes.oneDark,
additionalLanguages: ['bash', 'java', 'julia', 'nasm'] additionalLanguages: ['bash', 'java', 'julia', 'nasm'],
magicComments: [
// Remember to extend the default highlight class name as well!
{
className: 'theme-code-block-highlighted-line',
line: 'highlight-next-line',
block: {start: 'highlight-start', end: 'highlight-end'},
},
{
className: 'code-block-hidden',
block: {start: 'hidden-start', end: 'hidden-end'}
}
]
}, },
} satisfies Preset.ThemeConfig, } satisfies Preset.ThemeConfig,
plugins: [require.resolve('docusaurus-lunr-search')], plugins: [require.resolve('docusaurus-lunr-search')],

View File

@ -1,13 +0,0 @@
import {useColorMode} from "@docusaurus/theme-common";
import React from "react";
interface Props {
srcLight: string;
srcDark: string;
alt: string;
}
const DualImage = ({srcLight, srcDark, alt}: Props) => {
const {colorMode} = useColorMode();
return <img src={colorMode === "dark" ? srcDark : srcLight} alt={alt} />
}
export default DualImage;

View File

@ -29,4 +29,11 @@ adapted for Victory charts
*/ */
[data-theme='dark'] .VictoryContainer { [data-theme='dark'] .VictoryContainer {
filter: invert(75%) hue-rotate(180deg); filter: invert(75%) hue-rotate(180deg);
}
/*
Custom magic comment for Prism - hide parts of the code in display
*/
.code-block-hidden {
display: none;
} }