mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
parent
e2a0ee1d72
commit
0a45267ea1
@ -9,7 +9,6 @@ const functions = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
function chaosGame(image) {
|
function chaosGame(image) {
|
||||||
var plotter = new Plotter(image.width, image.height);
|
|
||||||
var [x, y] = [randomBiUnit(), randomBiUnit()];
|
var [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||||
|
|
||||||
for (var count = 0; count < iterations; count++) {
|
for (var count = 0; count < iterations; count++) {
|
||||||
@ -17,11 +16,9 @@ function chaosGame(image) {
|
|||||||
[x, y] = functions[i](x, y);
|
[x, y] = functions[i](x, y);
|
||||||
|
|
||||||
if (count > 20) {
|
if (count > 20) {
|
||||||
plotter.plot(x, y);
|
plot(x, y, image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plotter.paint(image);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(<Canvas renderFn={chaosGame}/>)
|
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:
|
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$:
|
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)
|
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
|
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 {
|
export default function plot(x: number, y: number, image: ImageData) {
|
||||||
return y * (width * stride) + x * 4;
|
// 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 {
|
// If we have an (x,y) coordinate outside the display range,
|
||||||
private readonly pixels: Array<boolean>;
|
// skip it
|
||||||
|
if (
|
||||||
constructor(private readonly width: number, private readonly height: number) {
|
pixelX < 0 ||
|
||||||
this.pixels = new Array(width * height).fill(false);
|
pixelX > image.width ||
|
||||||
|
pixelY < 0 ||
|
||||||
|
pixelY > image.height
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public plot(x: number, y: number) {
|
// ImageData is an array that contains four bytes per pixel
|
||||||
// Translate (x,y) coordinates to pixel coordinates.
|
// (one for each of the red, green, blue, and alpha values).
|
||||||
// The display range we care about is x=[0, 1], y=[0, 1],
|
// The (pixelX, pixelY) coordinates are used to find where
|
||||||
// so our pixelX and pixelY coordinates are easy to calculate:
|
// in the image we need to write.
|
||||||
const pixelX = Math.floor(x * this.width);
|
const index = pixelY * (image.width * 4) + pixelX * 4;
|
||||||
const pixelY = Math.floor(y * this.height);
|
|
||||||
|
|
||||||
// Translate the pixel coordinates into an index
|
// Set the pixel to black by writing a 0 to the first three
|
||||||
const pixelIndex = imageIndex(pixelX, pixelY, this.width, 1);
|
// bytes (red, green, blue), and 256 to the last byte (alpha),
|
||||||
|
// starting at our index:
|
||||||
// If the index is valid, enable that pixel
|
image.data[index] = 0;
|
||||||
if (0 <= pixelIndex && pixelIndex < this.pixels.length) {
|
image.data[index + 1] = 0;
|
||||||
this.pixels[pixelIndex] = true;
|
image.data[index + 2] = 0;
|
||||||
}
|
image.data[index + 3] = 0xff;
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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 React from 'react';
|
||||||
|
|
||||||
import { randomBiUnit } from '../src/randomBiunit';
|
import randomBiUnit from './biunit';
|
||||||
import { Plotter } from './plot';
|
import plot from './plot';
|
||||||
import { randomInteger } from '../src/randomInteger';
|
import randomInteger from './randint';
|
||||||
import Canvas from './Canvas';
|
import Canvas from './Canvas';
|
||||||
|
|
||||||
const Scope = {
|
const Scope = {
|
||||||
React,
|
React,
|
||||||
Plotter,
|
plot,
|
||||||
randomBiUnit,
|
randomBiUnit,
|
||||||
randomInteger,
|
randomInteger,
|
||||||
Canvas
|
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 {
|
export interface Transform {
|
||||||
coefs: Coefs,
|
coefs: Coefs,
|
||||||
variations: [number, Variation][],
|
variations: [number, Variation][],
|
||||||
enabled: boolean,
|
coefsPost: Coefs,
|
||||||
coefsPost?: Coefs,
|
color: number
|
||||||
color?: number
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user