mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 08:38:09 -05:00
parent
e2a0ee1d72
commit
0a45267ea1
@ -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(<Canvas renderFn={chaosGame}/>)
|
||||
|
@ -0,0 +1,3 @@
|
||||
export default function randomBiUnit() {
|
||||
return Math.random() * 2 - 1;
|
||||
}
|
@ -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'
|
||||
|
||||
<CodeBlock language="typescript">{randomBiunitSource}</CodeBlock>
|
||||
<CodeBlock language="typescript">{biunitSource}</CodeBlock>
|
||||
|
||||
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'
|
||||
|
||||
<CodeBlock language="typescript">{randomIntegerSource}</CodeBlock>
|
||||
<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
|
||||
|
@ -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<boolean>;
|
||||
|
||||
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;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export default function randomInteger(min: number, max: number) {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
@ -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
|
||||
|
@ -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<IFlameContext>({});
|
||||
|
||||
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<Props> = ({renderer, children}) => {
|
||||
const ITER_STEP = 10000;
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
|
||||
const colorMode = useColorMode();
|
||||
const [quality, setQuality] = useState<number>();
|
||||
const [flame, setFlame] = useState<Flame>(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 (
|
||||
<FlameContext.Provider value={{setFlame, setQuality}}>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
width={renderer.size}
|
||||
height={renderer.size}
|
||||
style={{
|
||||
aspectRatio: '1 / 1',
|
||||
width: '100%',
|
||||
}}
|
||||
/>
|
||||
{children}
|
||||
</FlameContext.Provider>
|
||||
)
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export function randomBiUnit() {
|
||||
return Math.random() * 2 - 1;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
/**
|
||||
* @param choices array of [weight, value] pairs
|
||||
* @returns pair of [index, value]
|
||||
*/
|
||||
export function randomChoice<T>(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";
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export function randomInteger(min: number, max: number) {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user