mirror of
				https://github.com/bspeice/speice.io
				synced 2025-11-03 18:10:32 -05:00 
			
		
		
		
	Render the gasket
Need to get rid of Plotly, unfortuantely - causes issues with hydration. Seems like Victory is better able to handle what I need.
This commit is contained in:
		@ -1,6 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
slug: 2024/11/playing-with-fire
 | 
			
		||||
title: "Playing with fire: Introduction"
 | 
			
		||||
title: "Playing with fire: The fractal flame algorithm"
 | 
			
		||||
date: 2024-11-15 12:00:00
 | 
			
		||||
authors: [bspeice]
 | 
			
		||||
tags: []
 | 
			
		||||
@ -12,9 +12,9 @@ Wikipedia [describes](https://en.wikipedia.org/wiki/Fractal_flame) fractal flame
 | 
			
		||||
 | 
			
		||||
I think of them a different way: beauty in mathematics.
 | 
			
		||||
 | 
			
		||||
import isDarkMode from "@site/src/isDarkMode";
 | 
			
		||||
import bannerDark from "./banner-dark.png"
 | 
			
		||||
import bannerLight from "./banner-light.png"
 | 
			
		||||
import isDarkMode from '@site/src/isDarkMode'
 | 
			
		||||
import bannerDark from '../banner-dark.png'
 | 
			
		||||
import bannerLight from '../banner-light.png'
 | 
			
		||||
 | 
			
		||||
<center>
 | 
			
		||||
    <!-- Why are these backwards? -->
 | 
			
		||||
@ -201,6 +201,8 @@ $$
 | 
			
		||||
 | 
			
		||||
### The chaos game
 | 
			
		||||
 | 
			
		||||
import CodeBlock from '@theme/CodeBlock'
 | 
			
		||||
 | 
			
		||||
Next, how do we find out all the points in $S$? The paper lays out an algorithm called the "chaos game":
 | 
			
		||||
 | 
			
		||||
$$
 | 
			
		||||
@ -212,4 +214,33 @@ $$
 | 
			
		||||
&\hspace{1cm} \text{plot}(x,y) \text{ except during the first 20 iterations} \\
 | 
			
		||||
\}
 | 
			
		||||
\end{align*}
 | 
			
		||||
$$
 | 
			
		||||
$$
 | 
			
		||||
 | 
			
		||||
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'
 | 
			
		||||
 | 
			
		||||
<CodeBlock language="typescript">{biunitSource}</CodeBlock>
 | 
			
		||||
 | 
			
		||||
Next, we need to choose a random integer from $0$ to $n - 1$:
 | 
			
		||||
 | 
			
		||||
import randintSource from '!!raw-loader!./randint'
 | 
			
		||||
 | 
			
		||||
<CodeBlock language="typescript">{randintSource}</CodeBlock>
 | 
			
		||||
 | 
			
		||||
Finally, implementing the `plot` function. Web browsers have a [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)
 | 
			
		||||
we can use for 2D graphics. In our case, the plot function will take an $(x,y)$ coordinate and plot it by
 | 
			
		||||
coloring the corresponding pixel in an [ImageData](https://developer.mozilla.org/en-US/docs/Web/API/ImageData):
 | 
			
		||||
 | 
			
		||||
import plotSource from '!!raw-loader!./plot'
 | 
			
		||||
 | 
			
		||||
<CodeBlock language="typescript">{plotSource}</CodeBlock>
 | 
			
		||||
 | 
			
		||||
import Playground from '@theme/Playground'
 | 
			
		||||
import Scope from './scope'
 | 
			
		||||
 | 
			
		||||
import Gasket from '!!raw-loader!./Gasket'
 | 
			
		||||
 | 
			
		||||
<Playground scope={Scope}>{Gasket}</Playground>
 | 
			
		||||
							
								
								
									
										43
									
								
								blog/2024-11-15-playing-with-fire/1-introduction/Gasket.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								blog/2024-11-15-playing-with-fire/1-introduction/Gasket.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
function Gasket(props) {
 | 
			
		||||
    const iterations = 1000;
 | 
			
		||||
    const functions = [
 | 
			
		||||
        (x, y) => [x / 2, y / 2],
 | 
			
		||||
        (x, y) => [(x + 1) / 2, y / 2],
 | 
			
		||||
        (x, y) => [x / 2, (y + 1) / 2]
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    function chaosGame(image) {
 | 
			
		||||
        var [x, y] = [randomBiUnit(), randomBiUnit()];
 | 
			
		||||
 | 
			
		||||
        for (var i = 0; i < iterations; i++) {
 | 
			
		||||
            const f = functions[randomInteger(0, functions.length)];
 | 
			
		||||
            [x, y] = f(x, y);
 | 
			
		||||
 | 
			
		||||
            if (i > 20) {
 | 
			
		||||
                plot(x, y, image);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function onClickRender() {
 | 
			
		||||
        /** @type{HTMLCanvasElement} */
 | 
			
		||||
        const canvas = document.getElementById('canvas-gasket');
 | 
			
		||||
        const context = canvas.getContext('2d');
 | 
			
		||||
        const image = context.createImageData(canvas.width, canvas.height);
 | 
			
		||||
        chaosGame(image);
 | 
			
		||||
        context.putImageData(image, 0, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return <div style={{width: '100%'}}>
 | 
			
		||||
        <center>
 | 
			
		||||
            <button onClick={onClickRender}>Play chaos game</button>
 | 
			
		||||
            <hr/>
 | 
			
		||||
        </center>
 | 
			
		||||
        <div>
 | 
			
		||||
            <canvas
 | 
			
		||||
                id={'canvas-gasket'}
 | 
			
		||||
                style={{width: '100%', aspectRatio: '1 / 1'}}
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
export default function randomBiUnit(): number {
 | 
			
		||||
    return Math.random() * 2 - 1;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								blog/2024-11-15-playing-with-fire/1-introduction/plot.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								blog/2024-11-15-playing-with-fire/1-introduction/plot.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
export default 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:
 | 
			
		||||
    const pixelX = Math.floor(x * image.width);
 | 
			
		||||
    const pixelY = Math.floor(y * image.height);
 | 
			
		||||
 | 
			
		||||
    // If we have an (x,y) coordinate outside the display range,
 | 
			
		||||
    // skip it
 | 
			
		||||
    if (
 | 
			
		||||
        pixelX < 0 ||
 | 
			
		||||
        pixelX > image.width ||
 | 
			
		||||
        pixelY < 0 ||
 | 
			
		||||
        pixelY > image.height
 | 
			
		||||
    ) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ImageData is an array that contains four bytes per pixel
 | 
			
		||||
    // (one for each of the red, green, blue, and alpha values).
 | 
			
		||||
    // The (pixelX, pixelY) coordinates are used to find where
 | 
			
		||||
    // in the image we need to write.
 | 
			
		||||
    const index = pixelY * (image.width * 4) + pixelX * 4;
 | 
			
		||||
 | 
			
		||||
    // Set the pixel to black by writing a 0 to the first three
 | 
			
		||||
    // bytes (red, green, blue), and 256 to the last byte (alpha),
 | 
			
		||||
    // starting at our index:
 | 
			
		||||
    image.data[index] = 0;
 | 
			
		||||
    image.data[index + 1] = 0;
 | 
			
		||||
    image.data[index + 2] = 0;
 | 
			
		||||
    image.data[index + 3] = 0xff;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
export default function randomInteger(min: number, max: number): number {
 | 
			
		||||
    return Math.floor(Math.random() * (max - min)) + min;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								blog/2024-11-15-playing-with-fire/1-introduction/scope.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								blog/2024-11-15-playing-with-fire/1-introduction/scope.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
import randomBiUnit from './biunit';
 | 
			
		||||
import plot from './plot';
 | 
			
		||||
import randomInteger from './randint';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const Scope = {
 | 
			
		||||
    React,
 | 
			
		||||
    plot,
 | 
			
		||||
    randomBiUnit,
 | 
			
		||||
    randomInteger
 | 
			
		||||
}
 | 
			
		||||
export default Scope;
 | 
			
		||||
@ -1,29 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Generate a uniform random number in the range (-1, 1)
 | 
			
		||||
 * 
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function randomBiUnit() {
 | 
			
		||||
  return Math.random() * 2 - 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generate a uniform random integer in the range [min, max)
 | 
			
		||||
 * 
 | 
			
		||||
 * @param min 
 | 
			
		||||
 * @param max 
 | 
			
		||||
 * @returns 
 | 
			
		||||
 */
 | 
			
		||||
export function randomInteger(min: number, max: number) {
 | 
			
		||||
  return Math.floor(Math.random() * (max - min)) + min;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://stackoverflow.com/a/34356351
 | 
			
		||||
export function hexToBytes(hex: string) {
 | 
			
		||||
  var bytes = [];
 | 
			
		||||
  for (var i = 0; i < hex.length; i += 2) {
 | 
			
		||||
    bytes.push(parseInt(hex.substring(i, i + 2), 16));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return bytes;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user