Basic function weights

This commit is contained in:
2024-11-24 22:37:53 -05:00
parent 4c3f4246a4
commit 7759b58dbe
24 changed files with 172 additions and 62 deletions

View File

@ -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>
</>
)
}

View File

@ -1,3 +0,0 @@
export default function randomBiUnit() {
return Math.random() * 2 - 1;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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/>

View File

@ -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:

View File

@ -1,3 +0,0 @@
export default function randomInteger(min: number, max: number) {
return Math.floor(Math.random() * (max - min)) + min;
}

View File

@ -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 = {