Convert baseline renderer to animation

flam3.js
Bradlee Speice 2023-07-18 02:21:01 +00:00
parent 782b00320b
commit 7f44243cd0
4 changed files with 104 additions and 14 deletions

View File

@ -35,14 +35,16 @@ export type CanvasParams = {
size: number;
qualityMax: number;
qualityStep: number;
renderer: Renderer;
renderer: (size: number) => Renderer;
};
export const CanvasRenderer: React.FC<{ params: CanvasParams }> = ({
params,
}) => {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
var qualityCurrent: number = 0;
var rendererCurrent: Renderer = params.renderer(params.size);
const animate = () => {
const ctx = canvasRef.current?.getContext("2d");
@ -51,21 +53,34 @@ export const CanvasRenderer: React.FC<{ params: CanvasParams }> = ({
}
const image = ctx.createImageData(params.size, params.size);
params.renderer.run(params.qualityStep);
params.renderer.render(image);
rendererCurrent.run(params.qualityStep);
rendererCurrent.render(image);
ctx.putImageData(image, 0, 0);
qualityCurrent += params.qualityStep;
if (qualityCurrent < params.qualityMax) {
qualityCurrent += params.qualityStep;
requestAnimationFrame(animate);
}
};
const reset = () => {
qualityCurrent = 0;
rendererCurrent = params.renderer(params.size);
requestAnimationFrame(animate);
};
useEffect(() => {
if (canvasRef.current) {
requestAnimationFrame(animate);
}
}, []);
return <canvas ref={canvasRef} width={params.size} height={params.size} />;
return (
<canvas
ref={canvasRef}
width={params.size}
height={params.size}
onClick={reset}
/>
);
};

View File

@ -1,3 +1,4 @@
import { CanvasParams } from "./0-canvas";
import {
randomBiUnit,
randomInteger,

View File

@ -1,10 +1,17 @@
import { randomBiUnit, weightedChoice } from "./0-utility";
import {
Renderer,
histIndex,
imageIndex,
randomBiUnit,
weightedChoice,
} from "./0-utility";
export type Variation = (
x: number,
y: number,
transformCoefs: Coefs
) => [number, number];
export type Coefs = {
a: number;
b: number;
@ -83,6 +90,66 @@ export class Transform {
}
}
export class RendererFlame extends Renderer {
private values = new Uint8Array(this.size * this.size);
constructor(size: number, public readonly transforms: [number, Transform][]) {
super(size);
}
plot(x: number, y: number) {
// By default, Apophysis uses a camera that covers the range [-2, 2]
// (specifically, the `scale` parameter becomes `pixels_per_unit` in flam3)
// Shift the coordinate system to [0, 4], then scale to pixel coordinates
const pixelX = Math.floor(((x + 2) * this.size) / 4);
const pixelY = Math.floor(((y + 2) * this.size) / 4);
if (
pixelX < 0 ||
pixelX >= this.size ||
pixelY < 0 ||
pixelY >= this.size
) {
return;
}
const hIndex = histIndex(pixelX, pixelY, this.size);
this.values[hIndex] = 1;
}
run(quality: number): void {
var x = randomBiUnit();
var y = randomBiUnit();
const iterations = quality * this.size * this.size;
for (var i = 0; i < iterations; i++) {
const [_, transform] = weightedChoice(this.transforms);
[x, y] = transform.apply(x, y);
if (i > 20) {
this.plot(x, y);
}
}
}
render(image: ImageData): void {
for (var x = 0; x < this.size; x++) {
for (var y = 0; y < this.size; y++) {
const hIndex = histIndex(x, y, this.size);
if (!this.values[hIndex]) {
continue;
}
const iIndex = imageIndex(x, y, this.size);
image.data[iIndex + 0] = 0;
image.data[iIndex + 1] = 0;
image.data[iIndex + 2] = 0;
image.data[iIndex + 3] = 0xff;
}
}
}
}
export class Flame {
protected x: number = randomBiUnit();
protected y: number = randomBiUnit();
@ -186,12 +253,10 @@ export const transform3 = new Transform(
[[1, pdj(1.09358, 2.13048, 2.54127, 2.37267)]]
);
export function renderBaseline(image: ImageData) {
const flame = new Flame([
export function rendererBaseline(size: number) {
return new RendererFlame(size, [
[transform1Weight, transform1],
[transform2Weight, transform2],
[transform3Weight, transform3],
]);
render(flame, 1, image);
}

View File

@ -2,7 +2,7 @@ import Blog from "../../../LayoutBlog";
import { Canvas, CanvasRenderer } from "./0-canvas";
import { RendererGasket } from "./1-gasket";
import { renderBaseline } from "./2a-variations";
import { rendererBaseline } from "./2a-variations";
import { renderPost } from "./2b-post";
import { renderFinal } from "./2c-final";
import { renderBinary } from "./3a-binary";
@ -23,13 +23,22 @@ export default function () {
});
return (
<Layout>
<CanvasRenderer
{/* <CanvasRenderer
params={{
defaultUrl: "",
size: 400,
renderer: new RendererGasket(400),
qualityMax: 0.25,
qualityStep: 0.25,
qualityMax: 0.3,
qualityStep: 0.1,
}}
/> */}
<CanvasRenderer
params={{
defaultUrl: "",
size: 400,
renderer: (size) => rendererBaseline(size),
qualityMax: 1,
qualityStep: 0.1,
}}
/>
{/* <div>