mirror of
https://github.com/bspeice/speice.io
synced 2025-06-30 21:36:38 -04:00
Basic function weights
This commit is contained in:
@ -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 = {
|
||||
|
Reference in New Issue
Block a user