2023-07-21 23:26:54 -04:00
|
|
|
export const DEFAULT_SIZE: number = 400;
|
|
|
|
|
2023-07-16 22:27:45 -04:00
|
|
|
/**
|
|
|
|
* Image render manager
|
|
|
|
*
|
|
|
|
* This class tracks the chaos game state so we can periodically
|
|
|
|
* get an image.
|
|
|
|
*/
|
|
|
|
export abstract class Renderer {
|
|
|
|
/**
|
|
|
|
* Build a render manager. For simplicity, this class assumes
|
|
|
|
* we're working with a square image.
|
|
|
|
*
|
|
|
|
* @param size Image width and height
|
|
|
|
*/
|
|
|
|
constructor(public readonly size: number) {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Run the chaos game
|
|
|
|
*
|
|
|
|
* @param quality iteration count
|
|
|
|
*/
|
|
|
|
abstract run(quality: number): void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Output the current chaos game state to image
|
|
|
|
*
|
|
|
|
* @param image output pixel buffer
|
|
|
|
*/
|
|
|
|
abstract render(image: ImageData): void;
|
|
|
|
}
|
|
|
|
|
2023-07-21 23:26:54 -04:00
|
|
|
export type RenderParams = {
|
|
|
|
quality: number;
|
|
|
|
renderer: (size: number) => Renderer;
|
|
|
|
};
|
2023-06-25 21:56:33 -04:00
|
|
|
|
2023-07-16 22:27:45 -04:00
|
|
|
/**
|
|
|
|
* @returns random number in the bi-unit square (-1, 1)
|
|
|
|
*/
|
2023-07-02 19:34:34 -04:00
|
|
|
export function randomBiUnit() {
|
|
|
|
// Math.random() produces a number in the range [0, 1),
|
|
|
|
// scale to (-1, 1)
|
|
|
|
return Math.random() * 2 - 1;
|
|
|
|
}
|
|
|
|
|
2023-07-16 22:27:45 -04:00
|
|
|
/**
|
|
|
|
* @returns random integer (with equal weight) in the range [min, max)
|
|
|
|
*/
|
2023-06-25 21:56:33 -04:00
|
|
|
export function randomInteger(min: number, max: number) {
|
|
|
|
return Math.floor(Math.random() * (max - min)) + min;
|
|
|
|
}
|
2023-07-02 19:34:34 -04:00
|
|
|
|
2023-07-16 22:27:45 -04:00
|
|
|
/**
|
|
|
|
* @param choices array of [weight, value] pairs
|
|
|
|
* @returns pair of [index, value]
|
|
|
|
*/
|
2023-07-04 14:25:31 -04:00
|
|
|
export function weightedChoice<T>(choices: [number, T][]): [number, T] {
|
2023-07-02 19:34:34 -04:00
|
|
|
const weightSum = choices.reduce(
|
|
|
|
(current, [weight, _t]) => current + weight,
|
|
|
|
0
|
|
|
|
);
|
|
|
|
var choice = Math.random() * weightSum;
|
|
|
|
|
|
|
|
for (var i = 0; i < choices.length; i++) {
|
|
|
|
const [weight, t] = choices[i];
|
|
|
|
if (choice < weight) {
|
2023-07-04 14:25:31 -04:00
|
|
|
return [i, t];
|
2023-07-02 19:34:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
choice -= weight;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw "unreachable";
|
|
|
|
}
|
|
|
|
|
2023-07-16 22:27:45 -04:00
|
|
|
/**
|
|
|
|
* @param x pixel coordinate
|
|
|
|
* @param y pixel coordinate
|
|
|
|
* @param width image width
|
|
|
|
* @returns index into ImageData buffer for a specific pixel
|
|
|
|
*/
|
2023-07-02 19:34:34 -04:00
|
|
|
export function imageIndex(x: number, y: number, width: number) {
|
|
|
|
// Taken from: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas
|
|
|
|
return y * (width * 4) + x * 4;
|
|
|
|
}
|
|
|
|
|
2023-07-16 22:27:45 -04:00
|
|
|
/**
|
|
|
|
* @param x pixel coordinate
|
|
|
|
* @param y pixel coordinate
|
|
|
|
* @param width image width
|
|
|
|
* @returns index into a histogram for a specific pixel
|
|
|
|
*/
|
2023-07-02 19:34:34 -04:00
|
|
|
export function histIndex(x: number, y: number, width: number) {
|
|
|
|
return y * width + x;
|
|
|
|
}
|