speice.io/posts/2023/06/flam3/2-variations.ts

143 lines
3.4 KiB
TypeScript

import { randomInteger } from "./0-utility";
type Variation = (x: number, y: number) => [number, number];
const r = (x: number, y: number) => {
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
};
const theta = (x: number, y: number) => {
return Math.atan2(x, y);
};
const linear: Variation = (x, y) => {
return [x, y];
};
const swirl: Variation = (x, y) => {
const r2 = Math.pow(r(x, y), 2);
const sinR2 = Math.sin(r2);
const cosR2 = Math.cos(r2);
return [x * sinR2 - y * cosR2, x * cosR2 + y * sinR2];
};
const polar: Variation = (x, y) => {
return [theta(x, y) / Math.PI, r(x, y) - 1];
};
const disc: Variation = (x, y) => {
const thetaOverPi = theta(x, y) / Math.PI;
const piR = Math.PI * r(x, y);
return [thetaOverPi * Math.sin(piR), thetaOverPi * Math.cos(piR)];
};
const variations = [linear, swirl, polar, disc];
class Coefs {
constructor(
public readonly a: number,
public readonly b: number,
public readonly c: number,
public readonly d: number,
public readonly e: number,
public readonly f: number
) {}
}
class Transform {
constructor(
public readonly weight: number,
public readonly coefs: Coefs,
// Assumes that we have a blend for each variation
public readonly blend: number[]
) {}
apply(x: number, y: number) {
const variationX = this.coefs.a * x + this.coefs.b * y + this.coefs.c;
const variationY = this.coefs.d * x + this.coefs.e * y + this.coefs.f;
var transformX = 0;
var transformY = 0;
this.blend.forEach((blend, i) => {
const [perVarX, perVarY] = variations[i](variationX, variationY);
transformX += blend * perVarX;
transformY += blend * perVarY;
});
return [transformX, transformY];
}
}
function plot(x: number, y: number, image: ImageData) {
const pixelX = Math.floor(((x + 2) * image.width) / 4);
const pixelY = Math.floor(((y + 2) * image.height) / 4);
if (
pixelX < 0 ||
pixelX > image.width ||
pixelY < 0 ||
pixelY > image.height
) {
return;
}
const index = pixelY * (image.width * 4) + pixelX * 4;
image.data[index + 0] = 0;
image.data[index + 1] = 0;
image.data[index + 2] = 0;
image.data[index + 3] = 0xff;
}
function render(transforms: Transform[], image: ImageData) {
const weightSum = transforms.reduce((val, xform) => val + xform.weight, 0);
var x = Math.random() * 2 - 1;
var y = Math.random() * 2 - 1;
const iter = 100_000;
for (var i = 0; i < iter; i++) {
// Find the tranform we're operating on
/*
var f = Math.random() * weightSum;
var transform = transforms[0];
transforms.forEach((xform, j) => {
if (f > 0 && f < xform.weight) {
console.log(`xform=${j}`);
transform = xform;
f -= xform.weight;
}
});
*/
// HACK: Currently assuming weights are equal
const transform = transforms[randomInteger(0, transforms.length)];
// Play the chaos game
[x, y] = transform.apply(x, y);
if (i > 20) {
plot(x, y, image);
}
}
}
export function renderBaseline(image: ImageData) {
return render(
[
new Transform(
0.5,
new Coefs(0.982996, 0, -0.219512, 0, 0.982996, -0.1875),
[1, 0, 0, -0.934]
),
new Transform(
0.5,
new Coefs(0.966511, -0.256624, 0.050305, 0.256624, 0.966511, -0.235518),
[0, 0.156, 0.778, 0]
),
],
image
);
}