mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 00:28:10 -05:00
Basic function weights
This commit is contained in:
parent
4c3f4246a4
commit
7759b58dbe
@ -0,0 +1,61 @@
|
|||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import Canvas from "../src/Canvas";
|
||||||
|
import { Params, chaosGameWeighted } from "./chaosGameWeighted";
|
||||||
|
import TeX from '@matejmazur/react-katex';
|
||||||
|
|
||||||
|
type Transform = (x: number, y: number) => [number, number];
|
||||||
|
|
||||||
|
function WeightInput({value, setValue, children}) {
|
||||||
|
return (
|
||||||
|
<div style={{paddingLeft: '1.5em', paddingRight: '1.5em'}}>
|
||||||
|
{children}
|
||||||
|
<input type={'range'} min={0} max={1} step={.01} style={{width: '100%'}} value={value} onInput={e => setValue(Number(e.currentTarget.value))}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GasketWeighted() {
|
||||||
|
const image = new ImageData(600, 600);
|
||||||
|
const iterations = 100_000;
|
||||||
|
const step = 1000;
|
||||||
|
|
||||||
|
const [f0Weight, setF0Weight] = useState<number>(1);
|
||||||
|
const [f1Weight, setF1Weight] = useState<number>(1);
|
||||||
|
const [f2Weight, setF2Weight] = useState<number>(1);
|
||||||
|
|
||||||
|
const f0: Transform = (x, y) => [x / 2, y / 2];
|
||||||
|
const f1: Transform = (x, y) => [(x + 1) / 2, y / 2];
|
||||||
|
const f2: Transform = (x, y) => [x / 2, (y + 1) / 2];
|
||||||
|
|
||||||
|
const [game, setGame] = useState<Generator<ImageData>>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
const params: Params = {
|
||||||
|
transforms: [
|
||||||
|
[f0Weight, f0],
|
||||||
|
[f1Weight, f1],
|
||||||
|
[f2Weight, f2]
|
||||||
|
],
|
||||||
|
image,
|
||||||
|
iterations,
|
||||||
|
step
|
||||||
|
}
|
||||||
|
setGame(chaosGameWeighted(params))
|
||||||
|
}, [f0Weight, f1Weight, f2Weight]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Canvas width={image.width} height={image.height} painter={game}/>
|
||||||
|
<div style={{paddingTop: '1em', display: 'grid', gridTemplateColumns: 'auto auto auto'}}>
|
||||||
|
<WeightInput value={f0Weight} setValue={setF0Weight}>
|
||||||
|
<p><TeX>F_0</TeX> weight:<span style={{float: 'right'}}>{f0Weight}</span></p>
|
||||||
|
</WeightInput>
|
||||||
|
<WeightInput value={f1Weight} setValue={setF1Weight}>
|
||||||
|
<p><TeX>F_1</TeX> weight:<span style={{float: 'right'}}>{f1Weight}</span></p>
|
||||||
|
</WeightInput>
|
||||||
|
<WeightInput value={f2Weight} setValue={setF2Weight}>
|
||||||
|
<p><TeX>F_2</TeX> weight:<span style={{float: 'right'}}>{f2Weight}</span></p>
|
||||||
|
</WeightInput>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
export default function randomBiUnit() {
|
|
||||||
return Math.random() * 2 - 1;
|
|
||||||
}
|
|
@ -2,20 +2,24 @@ function Gasket() {
|
|||||||
// Hint: try increasing the iteration count
|
// Hint: try increasing the iteration count
|
||||||
const iterations = 10000;
|
const iterations = 10000;
|
||||||
|
|
||||||
|
// Display the progress every `step` iterations
|
||||||
|
const step = 1000;
|
||||||
|
|
||||||
// Hint: negating `x` and `y` creates some interesting images
|
// Hint: negating `x` and `y` creates some interesting images
|
||||||
const functions = [
|
const transforms = [
|
||||||
(x, y) => [x / 2, y / 2],
|
(x, y) => [x / 2, y / 2],
|
||||||
(x, y) => [(x + 1) / 2, y / 2],
|
(x, y) => [(x + 1) / 2, y / 2],
|
||||||
(x, y) => [x / 2, (y + 1) / 2]
|
(x, y) => [x / 2, (y + 1) / 2]
|
||||||
]
|
]
|
||||||
|
|
||||||
const image = new ImageData(600, 600);
|
const image = new ImageData(600, 600);
|
||||||
|
|
||||||
function* chaosGame() {
|
function* chaosGame() {
|
||||||
var [x, y] = [randomBiUnit(), randomBiUnit()];
|
var [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||||
|
|
||||||
for (var count = 0; count < iterations; count++) {
|
for (var count = 0; count < iterations; count++) {
|
||||||
const i = randomInteger(0, functions.length);
|
const i = randomInteger(0, transforms.length);
|
||||||
[x, y] = functions[i](x, y);
|
[x, y] = transforms[i](x, y);
|
||||||
|
|
||||||
if (count > 20) {
|
if (count > 20) {
|
||||||
plot(x, y, image);
|
plot(x, y, image);
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
// hidden-start
|
||||||
|
import { randomBiUnit } from "../src/randomBiUnit";
|
||||||
|
import { randomChoice } from "../src/randomChoice";
|
||||||
|
import { plot } from "./plot"
|
||||||
|
export type Transform = (x: number, y: number) => [number, number];
|
||||||
|
export type Params = {
|
||||||
|
transforms: [number, Transform][],
|
||||||
|
image: ImageData,
|
||||||
|
iterations: number,
|
||||||
|
step: number
|
||||||
|
}
|
||||||
|
// hidden-end
|
||||||
|
export function* chaosGameWeighted({transforms, image, iterations, step}: Params) {
|
||||||
|
var [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||||
|
|
||||||
|
for (let i = 0; i < iterations; i++) {
|
||||||
|
// highlight-start
|
||||||
|
const [_, transform] = randomChoice(transforms);
|
||||||
|
// highlight-end
|
||||||
|
[x, y] = transform(x, y);
|
||||||
|
|
||||||
|
if (i > 20) {
|
||||||
|
plot(x, y, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i % step === 0) {
|
||||||
|
yield image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yield image;
|
||||||
|
}
|
@ -126,7 +126,7 @@ Fractal flames use more complex functions to produce a wide variety of images, b
|
|||||||
|
|
||||||
## Sierpinski's gasket
|
## Sierpinski's gasket
|
||||||
|
|
||||||
Using these definitions, we can build the first image. The paper defines a function system we can use as-is:
|
Using these definitions, we can build the first image. The paper defines a function system for us:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
F_0(x, y) = \left({x \over 2}, {y \over 2} \right)
|
F_0(x, y) = \left({x \over 2}, {y \over 2} \right)
|
||||||
@ -157,13 +157,13 @@ 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:
|
First, the "bi-unit square" is the range $[-1, 1]$. We can pick a random point like this:
|
||||||
|
|
||||||
import biunitSource from '!!raw-loader!./biunit'
|
import biunitSource from '!!raw-loader!../src/randomBiUnit'
|
||||||
|
|
||||||
<CodeBlock language="typescript">{biunitSource}</CodeBlock>
|
<CodeBlock language="typescript">{biunitSource}</CodeBlock>
|
||||||
|
|
||||||
Next, we need to choose a random integer from $0$ to $n - 1$:
|
Next, we need to choose a random integer from $0$ to $n - 1$:
|
||||||
|
|
||||||
import randintSource from '!!raw-loader!./randint'
|
import randintSource from '!!raw-loader!../src/randomInteger'
|
||||||
|
|
||||||
<CodeBlock language="typescript">{randintSource}</CodeBlock>
|
<CodeBlock language="typescript">{randintSource}</CodeBlock>
|
||||||
|
|
||||||
@ -185,8 +185,22 @@ import chaosGameSource from '!!raw-loader!./chaosGame'
|
|||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
<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 here is different than the fractal flame paper, but I think the paper has an error.
|
||||||
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$
|
## 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>
|
||||||
|
|
||||||
|
import GasketWeighted from "./GasketWeighted"
|
||||||
|
|
||||||
|
<GasketWeighted/>
|
@ -1,4 +1,4 @@
|
|||||||
export default function plot(x: number, y: number, image: ImageData) {
|
export function plot(x: number, y: number, image: ImageData) {
|
||||||
// Translate (x,y) coordinates to pixel coordinates.
|
// Translate (x,y) coordinates to pixel coordinates.
|
||||||
// The display range we care about is x=[0, 1], y=[0, 1],
|
// The display range we care about is x=[0, 1], y=[0, 1],
|
||||||
// so our pixelX and pixelY coordinates are easy to calculate:
|
// so our pixelX and pixelY coordinates are easy to calculate:
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
export default function randomInteger(min: number, max: number) {
|
|
||||||
return Math.floor(Math.random() * (max - min)) + min;
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
import randomBiUnit from './biunit';
|
import { plot } from './plot';
|
||||||
import plot from './plot';
|
import { randomBiUnit } from '../src/randomBiUnit';
|
||||||
import randomInteger from './randint';
|
import { randomInteger } from '../src/randomInteger';
|
||||||
import Canvas from "../src/Canvas";
|
import Canvas from "../src/Canvas";
|
||||||
|
|
||||||
const Scope = {
|
const Scope = {
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import {useColorMode, useThemeConfig} from "@docusaurus/theme-common";
|
|
||||||
|
|
||||||
export default function BaselineRender({children}) {
|
|
||||||
const {colorMode, setColorMode} = useColorMode();
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
// 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];
|
|
||||||
}
|
|
@ -66,9 +66,6 @@ import linearSrc from '!!raw-loader!../src/linear'
|
|||||||
|
|
||||||
<CodeBlock language={'typescript'}>{linearSrc}</CodeBlock>
|
<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)
|
||||||
|
|
||||||
This variation still uses just the $x$ and $y$ coordinates, but does crazy things with them:
|
This variation still uses just the $x$ and $y$ coordinates, but does crazy things with them:
|
||||||
@ -132,11 +129,6 @@ $$
|
|||||||
|
|
||||||
The formula looks intimidating, but it's not hard to implement:
|
The formula looks intimidating, but it's not hard to implement:
|
||||||
|
|
||||||
import baselineSrc from '!!raw-loader!./baseline'
|
TODO: Blending implementation?
|
||||||
|
|
||||||
<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:
|
And with that in place, we have enough to render a first full fractal flame:
|
||||||
|
|
||||||
|
@ -39,13 +39,18 @@ export default function Canvas({width, height, painter, children}: Props) {
|
|||||||
useEffect(paint, [colorMode, image]);
|
useEffect(paint, [colorMode, image]);
|
||||||
|
|
||||||
const animate = () => {
|
const animate = () => {
|
||||||
|
if (!painter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Animating");
|
||||||
const nextImage = painter.next().value;
|
const nextImage = painter.next().value;
|
||||||
if (nextImage) {
|
if (nextImage) {
|
||||||
setImage([nextImage])
|
setImage([nextImage])
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
useEffect(animate, [canvasCtx]);
|
useEffect(animate, [painter, canvasCtx]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import { Variation } from './variations'
|
import { Variation } from './variation'
|
||||||
// hidden-end
|
// hidden-end
|
||||||
export const julia: Variation = (x, y) => {
|
export const julia: Variation = (x, y) => {
|
||||||
const r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
const r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import {Variation} from "./variations"
|
import {Variation} from "./variation"
|
||||||
//hidden-end
|
//hidden-end
|
||||||
export const linear: Variation = (x, y) => [x, y];
|
export const linear: Variation = (x, y) => [x, y];
|
@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Coefs } from './coefs';
|
import { Coefs } from './coefs';
|
||||||
import { Transform } from './transform';
|
|
||||||
import { linear } from './linear'
|
import { linear } from './linear'
|
||||||
import { julia } from './julia'
|
import { julia } from './julia'
|
||||||
import { popcorn } from './popcorn'
|
import { popcorn } from './popcorn'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import { Variation } from './variations'
|
import { Variation } from './variation'
|
||||||
//hidden-end
|
//hidden-end
|
||||||
export function pdj(a: number, b: number, c: number, d: number): Variation {
|
export function pdj(a: number, b: number, c: number, d: number): Variation {
|
||||||
return (x, y) => [
|
return (x, y) => [
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import {Coefs} from './coefs'
|
import {Coefs} from './coefs'
|
||||||
import {Variation} from './variations'
|
import {Variation} from './variation'
|
||||||
// hidden-end
|
// hidden-end
|
||||||
export function popcorn({c, f}: Coefs): Variation {
|
export function popcorn({c, f}: Coefs): Variation {
|
||||||
return (x, y) => [
|
return (x, y) => [
|
||||||
|
3
blog/2024-11-15-playing-with-fire/src/randomBiUnit.ts
Normal file
3
blog/2024-11-15-playing-with-fire/src/randomBiUnit.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function randomBiUnit() {
|
||||||
|
return Math.random() * 2 - 1;
|
||||||
|
}
|
15
blog/2024-11-15-playing-with-fire/src/randomChoice.ts
Normal file
15
blog/2024-11-15-playing-with-fire/src/randomChoice.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export function randomChoice<T>(choices: [number, T][]): [number, T] {
|
||||||
|
const weightSum = choices.reduce((sum, [weight, _]) => sum + weight, 0);
|
||||||
|
let choice = Math.random() * weightSum;
|
||||||
|
|
||||||
|
for (const [index, element] of choices.entries()) {
|
||||||
|
const [weight, t] = element;
|
||||||
|
if (choice < weight) {
|
||||||
|
return [index, t];
|
||||||
|
}
|
||||||
|
choice -= weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = choices.length - 1;
|
||||||
|
return [index, choices[index][1]];
|
||||||
|
}
|
3
blog/2024-11-15-playing-with-fire/src/randomInteger.ts
Normal file
3
blog/2024-11-15-playing-with-fire/src/randomInteger.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function randomInteger(min: number, max: number) {
|
||||||
|
return Math.floor(Math.random() * (max - min)) + min;
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { Coefs } from './coefs'
|
import { Coefs } from './coefs'
|
||||||
import { Variation } from './variations'
|
import { Variation } from './variation'
|
||||||
|
|
||||||
export interface Transform {
|
export interface Transform {
|
||||||
coefs: Coefs,
|
coefs: Coefs,
|
||||||
|
14
package-lock.json
generated
14
package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"@docusaurus/faster": "^3.6.1",
|
"@docusaurus/faster": "^3.6.1",
|
||||||
"@docusaurus/preset-classic": "^3.6.1",
|
"@docusaurus/preset-classic": "^3.6.1",
|
||||||
"@docusaurus/theme-live-codeblock": "^3.6.1",
|
"@docusaurus/theme-live-codeblock": "^3.6.1",
|
||||||
|
"@matejmazur/react-katex": "^3.1.3",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"docusaurus-lunr-search": "^3.5.0",
|
"docusaurus-lunr-search": "^3.5.0",
|
||||||
@ -3031,6 +3032,19 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz",
|
||||||
"integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g=="
|
"integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@matejmazur/react-katex": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@matejmazur/react-katex/-/react-katex-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-rBp7mJ9An7ktNoU653BWOYdO4FoR4YNwofHZi+vaytX/nWbIlmHVIF+X8VFOn6c3WYmrLT5FFBjKqCZ1sjR5uQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12",
|
||||||
|
"yarn": ">=1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"katex": ">=0.9",
|
||||||
|
"react": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@mdx-js/mdx": {
|
"node_modules/@mdx-js/mdx": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz",
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"@docusaurus/faster": "^3.6.1",
|
"@docusaurus/faster": "^3.6.1",
|
||||||
"@docusaurus/preset-classic": "^3.6.1",
|
"@docusaurus/preset-classic": "^3.6.1",
|
||||||
"@docusaurus/theme-live-codeblock": "^3.6.1",
|
"@docusaurus/theme-live-codeblock": "^3.6.1",
|
||||||
|
"@matejmazur/react-katex": "^3.1.3",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"docusaurus-lunr-search": "^3.5.0",
|
"docusaurus-lunr-search": "^3.5.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user