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
|
||||
const iterations = 10000;
|
||||
|
||||
// Display the progress every `step` iterations
|
||||
const step = 1000;
|
||||
|
||||
// Hint: negating `x` and `y` creates some interesting images
|
||||
const functions = [
|
||||
const transforms = [
|
||||
(x, y) => [x / 2, y / 2],
|
||||
(x, y) => [(x + 1) / 2, y / 2],
|
||||
(x, y) => [x / 2, (y + 1) / 2]
|
||||
]
|
||||
|
||||
const image = new ImageData(600, 600);
|
||||
|
||||
function* chaosGame() {
|
||||
var [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||
|
||||
for (var count = 0; count < iterations; count++) {
|
||||
const i = randomInteger(0, functions.length);
|
||||
[x, y] = functions[i](x, y);
|
||||
const i = randomInteger(0, transforms.length);
|
||||
[x, y] = transforms[i](x, y);
|
||||
|
||||
if (count > 20) {
|
||||
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
|
||||
|
||||
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)
|
||||
@ -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:
|
||||
|
||||
import biunitSource from '!!raw-loader!./biunit'
|
||||
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$:
|
||||
|
||||
import randintSource from '!!raw-loader!./randint'
|
||||
import randintSource from '!!raw-loader!../src/randomInteger'
|
||||
|
||||
<CodeBlock language="typescript">{randintSource}</CodeBlock>
|
||||
|
||||
@ -185,8 +185,22 @@ import chaosGameSource from '!!raw-loader!./chaosGame'
|
||||
<hr/>
|
||||
|
||||
<small>
|
||||
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.
|
||||
Note: The image here is different than the fractal flame paper, but I think the paper has an error.
|
||||
</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.
|
||||
// The display range we care about is x=[0, 1], y=[0, 1],
|
||||
// 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 randomInteger from './randint';
|
||||
import { plot } from './plot';
|
||||
import { randomBiUnit } from '../src/randomBiUnit';
|
||||
import { randomInteger } from '../src/randomInteger';
|
||||
import Canvas from "../src/Canvas";
|
||||
|
||||
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>
|
||||
|
||||
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)
|
||||
|
||||
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:
|
||||
|
||||
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?
|
||||
TODO: Blending implementation?
|
||||
|
||||
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]);
|
||||
|
||||
const animate = () => {
|
||||
if (!painter) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Animating");
|
||||
const nextImage = painter.next().value;
|
||||
if (nextImage) {
|
||||
setImage([nextImage])
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
}
|
||||
useEffect(animate, [canvasCtx]);
|
||||
useEffect(animate, [painter, canvasCtx]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,5 +1,5 @@
|
||||
// hidden-start
|
||||
import { Variation } from './variations'
|
||||
import { Variation } from './variation'
|
||||
// hidden-end
|
||||
export const julia: Variation = (x, y) => {
|
||||
const r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
||||
|
@ -1,4 +1,4 @@
|
||||
// hidden-start
|
||||
import {Variation} from "./variations"
|
||||
import {Variation} from "./variation"
|
||||
//hidden-end
|
||||
export const linear: Variation = (x, y) => [x, y];
|
@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { Coefs } from './coefs';
|
||||
import { Transform } from './transform';
|
||||
import { linear } from './linear'
|
||||
import { julia } from './julia'
|
||||
import { popcorn } from './popcorn'
|
||||
|
@ -1,5 +1,5 @@
|
||||
// hidden-start
|
||||
import { Variation } from './variations'
|
||||
import { Variation } from './variation'
|
||||
//hidden-end
|
||||
export function pdj(a: number, b: number, c: number, d: number): Variation {
|
||||
return (x, y) => [
|
||||
|
@ -1,6 +1,6 @@
|
||||
// hidden-start
|
||||
import {Coefs} from './coefs'
|
||||
import {Variation} from './variations'
|
||||
import {Variation} from './variation'
|
||||
// hidden-end
|
||||
export function popcorn({c, f}: Coefs): Variation {
|
||||
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 { Variation } from './variations'
|
||||
import { Variation } from './variation'
|
||||
|
||||
export interface Transform {
|
||||
coefs: Coefs,
|
||||
|
14
package-lock.json
generated
14
package-lock.json
generated
@ -12,6 +12,7 @@
|
||||
"@docusaurus/faster": "^3.6.1",
|
||||
"@docusaurus/preset-classic": "^3.6.1",
|
||||
"@docusaurus/theme-live-codeblock": "^3.6.1",
|
||||
"@matejmazur/react-katex": "^3.1.3",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"docusaurus-lunr-search": "^3.5.0",
|
||||
@ -3031,6 +3032,19 @@
|
||||
"resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz",
|
||||
"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": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz",
|
||||
|
@ -19,6 +19,7 @@
|
||||
"@docusaurus/faster": "^3.6.1",
|
||||
"@docusaurus/preset-classic": "^3.6.1",
|
||||
"@docusaurus/theme-live-codeblock": "^3.6.1",
|
||||
"@matejmazur/react-katex": "^3.1.3",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"docusaurus-lunr-search": "^3.5.0",
|
||||
|
Loading…
Reference in New Issue
Block a user