diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/Gasket.jsx b/blog/2024-11-15-playing-with-fire/1-introduction/Gasket.jsx index 36b32ea..5dcc0e5 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/Gasket.jsx +++ b/blog/2024-11-15-playing-with-fire/1-introduction/Gasket.jsx @@ -9,7 +9,6 @@ const functions = [ ] function chaosGame(image) { - var plotter = new Plotter(image.width, image.height); var [x, y] = [randomBiUnit(), randomBiUnit()]; for (var count = 0; count < iterations; count++) { @@ -17,11 +16,9 @@ function chaosGame(image) { [x, y] = functions[i](x, y); if (count > 20) { - plotter.plot(x, y); + plot(x, y, image); } } - - plotter.paint(image); } render() diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/biunit.ts b/blog/2024-11-15-playing-with-fire/1-introduction/biunit.ts new file mode 100644 index 0000000..21bf035 --- /dev/null +++ b/blog/2024-11-15-playing-with-fire/1-introduction/biunit.ts @@ -0,0 +1,3 @@ +export default function randomBiUnit() { + return Math.random() * 2 - 1; +} \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx b/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx index 74c34a3..9e6758c 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx +++ b/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx @@ -157,15 +157,15 @@ 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 randomBiunitSource from '!!raw-loader!../src/randomBiunit' +import biunitSource from '!!raw-loader!./biunit' -{randomBiunitSource} +{biunitSource} Next, we need to choose a random integer from $0$ to $n - 1$: -import randomIntegerSource from '!!raw-loader!../src/randomInteger' +import randintSource from '!!raw-loader!./randint' -{randomIntegerSource} +{randintSource} 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 diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/plot.ts b/blog/2024-11-15-playing-with-fire/1-introduction/plot.ts index 523635c..3bf9f03 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/plot.ts +++ b/blog/2024-11-15-playing-with-fire/1-introduction/plot.ts @@ -1,40 +1,32 @@ -export function imageIndex(x: number, y: number, width: number, stride: number): number { - return y * (width * stride) + x * 4; -} +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); -export class Plotter { - private readonly pixels: Array; - - constructor(private readonly width: number, private readonly height: number) { - this.pixels = new Array(width * height).fill(false); + // 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; } - public plot(x: number, y: number) { - // 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 * this.width); - const pixelY = Math.floor(y * this.height); + // 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; - // Translate the pixel coordinates into an index - const pixelIndex = imageIndex(pixelX, pixelY, this.width, 1); - - // If the index is valid, enable that pixel - if (0 <= pixelIndex && pixelIndex < this.pixels.length) { - this.pixels[pixelIndex] = true; - } - } - - public paint(image: ImageData) { - // "Paint" all our pixels by setting their value to black - for (var pixelIndex = 0; pixelIndex < this.pixels.length; pixelIndex++) { - if (this.pixels[pixelIndex]) { - const imageIndex = pixelIndex * 4; - image.data[imageIndex] = 0; // red - image.data[imageIndex + 1] = 0; // green - image.data[imageIndex + 2] = 0; // blue - image.data[imageIndex + 3] = 0xff; // alpha - } - } - } + // 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; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/randint.ts b/blog/2024-11-15-playing-with-fire/1-introduction/randint.ts new file mode 100644 index 0000000..07d79a3 --- /dev/null +++ b/blog/2024-11-15-playing-with-fire/1-introduction/randint.ts @@ -0,0 +1,3 @@ +export default function randomInteger(min: number, max: number) { + return Math.floor(Math.random() * (max - min)) + min; +} \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/scope.ts b/blog/2024-11-15-playing-with-fire/1-introduction/scope.ts index ff3fcbf..f020b05 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/scope.ts +++ b/blog/2024-11-15-playing-with-fire/1-introduction/scope.ts @@ -1,13 +1,13 @@ import React from 'react'; -import { randomBiUnit } from '../src/randomBiunit'; -import { Plotter } from './plot'; -import { randomInteger } from '../src/randomInteger'; +import randomBiUnit from './biunit'; +import plot from './plot'; +import randomInteger from './randint'; import Canvas from './Canvas'; const Scope = { React, - Plotter, + plot, randomBiUnit, randomInteger, Canvas diff --git a/blog/2024-11-15-playing-with-fire/src/FlameCanvas.tsx b/blog/2024-11-15-playing-with-fire/src/FlameCanvas.tsx deleted file mode 100644 index 4b9563c..0000000 --- a/blog/2024-11-15-playing-with-fire/src/FlameCanvas.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import {createContext, useEffect, useRef, useState} from "react"; -import {Transform} from "./transform"; -import {randomBiUnit} from "./randomBiunit"; -import {useColorMode} from "@docusaurus/theme-common"; -import {randomChoice} from "./randomChoice"; - -export interface Flame { - transforms: [number, Transform][]; - final: Transform; - palette: Uint8Array; -} - -interface IFlameContext { - setQuality?: (quality: number) => void; - setFlame?: (flame: Flame) => void; -} - -export const FlameContext = createContext({}); - -abstract class Renderer { - protected readonly _size: number; - protected _x: number = randomBiUnit(); - protected _y: number = randomBiUnit(); - protected _iterations: number = 0; - - protected _flame?: Flame; - - protected constructor(size: number) { - this._size = size; - } - - abstract plot(x: number, y: number, c: number): void; - abstract run(iterations: number): void; - abstract paint(image: ImageData): void; - - public get size(): number { - return this._size; - } - - public get iterations(): number { - return this._iterations; - } - - public set flame(value: Flame) { - [this._x, this._y] = [randomBiUnit(), randomBiUnit()]; - this._iterations = 0; - } -} - -export interface Props { - renderer: Renderer; - children?: React.ReactNode; -} - -export const FlameCanvas: React.FC = ({renderer, children}) => { - const ITER_STEP = 10000; - const canvasRef = useRef(null); - - const colorMode = useColorMode(); - const [quality, setQuality] = useState(); - const [flame, setFlame] = useState(null); - - var image: ImageData = null; - - function animate() { - renderer.run(ITER_STEP); - paint(); - - if (renderer.iterations < quality * renderer.size * renderer.size) { - requestAnimationFrame(this); - } - } - - function paint() { - - } - - useEffect(() => { - renderer.flame = flame; - requestAnimationFrame(animate); - }, [quality, flame]); - - useEffect(() => { paint(); }, [colorMode]); - - return ( - - - {children} - - ) -} \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/randomBiunit.ts b/blog/2024-11-15-playing-with-fire/src/randomBiunit.ts deleted file mode 100644 index 7e5f389..0000000 --- a/blog/2024-11-15-playing-with-fire/src/randomBiunit.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function randomBiUnit() { - return Math.random() * 2 - 1; -} \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/randomChoice.ts b/blog/2024-11-15-playing-with-fire/src/randomChoice.ts deleted file mode 100644 index 4920f87..0000000 --- a/blog/2024-11-15-playing-with-fire/src/randomChoice.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @param choices array of [weight, value] pairs - * @returns pair of [index, value] - */ -export function randomChoice(choices: [number, T][]): [number, T] { - const weightSum = choices.reduce( - (current, [weight, _]) => current + weight, - 0 - ); - var choice = Math.random() * weightSum; - - for (var i = 0; i < choices.length; i++) { - const [weight, t] = choices[i]; - if (choice < weight) { - return [i, t]; - } - - choice -= weight; - } - - throw "unreachable"; -} \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/randomInteger.ts b/blog/2024-11-15-playing-with-fire/src/randomInteger.ts deleted file mode 100644 index c3a6719..0000000 --- a/blog/2024-11-15-playing-with-fire/src/randomInteger.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function randomInteger(min: number, max: number) { - return Math.floor(Math.random() * (max - min)) + min; -} \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/transform.ts b/blog/2024-11-15-playing-with-fire/src/transform.ts index 3caf1fa..e719c57 100644 --- a/blog/2024-11-15-playing-with-fire/src/transform.ts +++ b/blog/2024-11-15-playing-with-fire/src/transform.ts @@ -4,7 +4,6 @@ import { Variation } from './variations' export interface Transform { coefs: Coefs, variations: [number, Variation][], - enabled: boolean, - coefsPost?: Coefs, - color?: number + coefsPost: Coefs, + color: number } \ No newline at end of file