mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
Convert baseline renderer to animation
This commit is contained in:
parent
782b00320b
commit
7f44243cd0
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { CanvasParams } from "./0-canvas";
|
||||
import {
|
||||
randomBiUnit,
|
||||
randomInteger,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user