Get full post/final xform working

flam3.js
Bradlee Speice 2023-07-01 04:38:15 +00:00
parent 68c0b26015
commit 4bcc321c4b
7 changed files with 399 additions and 184 deletions

View File

@ -1,142 +0,0 @@
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
);
}

View File

@ -0,0 +1,189 @@
export type Variation = (
x: number,
y: number,
transformCoefs: Coefs
) => [number, number];
export type Coefs = {
a: number;
b: number;
c: number;
d: number;
e: number;
f: number;
};
function r(x: number, y: number) {
return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
}
function theta(x: number, y: number) {
return Math.atan2(x, y);
}
function omega(): number {
return Math.random() > 0.5 ? Math.PI : 0;
}
export const linear: Variation = (x, y) => [x, y];
export const julia: Variation = (x, y) => {
const sqrtR = Math.sqrt(r(x, y));
return [
sqrtR * Math.cos(theta(x, y) / 2 + omega()),
sqrtR * Math.sin(theta(x, y) / 2 + omega()),
];
};
export const popcorn: Variation = (x, y, transformCoefs) => {
return [
x + transformCoefs.c * Math.sin(Math.tan(3 * y)),
y + transformCoefs.f * Math.sin(Math.tan(3 * x)),
];
};
export const pdj: (
pdjA: number,
pdjB: number,
pdjC: number,
pdjD: number
) => Variation = (pdjA, pdjB, pdjC, pdjD) => {
return (x, y) => [
Math.sin(pdjA * y) - Math.cos(pdjB * x),
Math.sin(pdjC * x) - Math.cos(pdjD * y),
];
};
export class Transform {
constructor(
public readonly coefs: Coefs,
public readonly variations: [number, Variation][]
) {}
apply(x: number, y: number) {
const xformX = this.coefs.a * x + this.coefs.b * y + this.coefs.c;
const xformY = this.coefs.d * x + this.coefs.e * y + this.coefs.f;
var [curX, curY] = [0, 0];
this.variations.forEach(([blend, variation]) => {
const [varX, varY] = variation(xformX, xformY, this.coefs);
curX += blend * varX;
curY += blend * varY;
});
return [curX, curY];
}
}
export function weightedChoice<T>(choices: [number, T][]) {
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) {
return t;
}
choice -= weight;
}
throw "unreachable";
}
export 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;
}
export class Flame {
constructor(public readonly transforms: [number, Transform][]) {}
render(quality: number, image: ImageData) {
var x = Math.random() * 2 - 1;
var y = Math.random() * 2 - 1;
const iter = quality * (image.width * image.height);
for (var i = 0; i < iter; i++) {
const transform = weightedChoice(this.transforms);
// Play the chaos game
[x, y] = transform.apply(x, y);
if (i > 20) {
plot(x, y, image);
}
}
}
}
export const transform1Coefs: Coefs = {
a: -1.381068,
b: -1.381068,
c: 0,
d: 1.381068,
e: -1.381068,
f: 0,
};
export const transform1Weight = 0.56453495;
export const transform2Coefs: Coefs = {
a: 0.031393,
b: 0.031367,
c: 0,
d: -0.031367,
e: 0.031393,
f: 0,
};
export const transform2Weight = 0.013135;
export const transform3Coefs: Coefs = {
a: 1.51523,
b: -3.048677,
c: 0.724135,
d: 0.740356,
e: -1.455964,
f: -0.362059,
};
export const transform3Pdj = [1.09358, 2.13048, 2.54127, 2.37267];
export const transform3Weight = 0.42233;
export function renderBaseline(image: ImageData) {
const transform1 = new Transform(transform1Coefs, [[1, julia]]);
const transform2 = new Transform(transform2Coefs, [
[1, linear],
[1, popcorn],
]);
const [pdjA, pdjB, pdjC, pdjD] = transform3Pdj;
const transform3 = new Transform(transform3Coefs, [
[1, pdj(pdjA, pdjB, pdjC, pdjD)],
]);
const flame = new Flame([
[transform1Weight, transform1],
[transform2Weight, transform2],
[transform3Weight, transform3],
]);
flame.render(1, image);
}

View File

@ -0,0 +1,79 @@
import {
Coefs,
Variation,
Flame,
Transform,
linear,
julia,
popcorn,
pdj,
transform1Coefs,
transform1Weight,
transform2Coefs,
transform2Weight,
transform3Coefs,
transform3Pdj,
transform3Weight,
} from "./2a-variations";
export class TransformPost extends Transform {
constructor(
coefs: Coefs,
variations: [number, Variation][],
public readonly post: Coefs
) {
super(coefs, variations);
}
apply(x: number, y: number): [number, number] {
const [transformX, transformY] = super.apply(x, y);
return [
transformX * this.post.a + transformY * this.post.b + this.post.c,
transformX * this.post.d + transformY * this.post.e + this.post.f,
];
}
}
export function variationPost(coefs: Coefs, variation: Variation): Variation {
return (x, y, transformCoefs) => {
const [varX, varY] = variation(x, y, transformCoefs);
return [
varX * coefs.a + varY * coefs.b + coefs.c,
varX * coefs.d + varY * coefs.e + coefs.f,
];
};
}
export const transform2Post: Coefs = {
a: 1,
b: 0,
c: 0.241352,
d: 0,
e: 1,
f: 0.271521,
};
export function renderPost(image: ImageData) {
const transform1 = new Transform(transform1Coefs, [[1, julia]]);
const transform2 = new TransformPost(
transform2Coefs,
[
[1, linear],
[1, popcorn],
],
transform2Post
);
const [pdjA, pdjB, pdjC, pdjD] = transform3Pdj;
const transform3 = new Transform(transform3Coefs, [
[1, pdj(pdjA, pdjB, pdjC, pdjD)],
]);
const flame = new Flame([
[transform1Weight, transform1],
[transform2Weight, transform2],
[transform3Weight, transform3],
]);
flame.render(1, image);
}

View File

@ -0,0 +1,86 @@
import {
Coefs,
Flame,
Transform,
linear,
julia,
popcorn,
pdj,
transform1Coefs,
transform1Weight,
transform2Coefs,
transform2Weight,
transform3Coefs,
transform3Pdj,
transform3Weight,
weightedChoice,
plot,
} from "./2a-variations";
import { TransformPost, transform2Post } from "./2b-post";
export class FlameFinal extends Flame {
constructor(
transforms: [number, Transform][],
public readonly final: Transform
) {
super(transforms);
}
render(quality: number, image: ImageData) {
var x = Math.random() * 2 - 1;
var y = Math.random() * 2 - 1;
const iter = quality * (image.width * image.height);
for (var i = 0; i < iter; i++) {
const transform = weightedChoice(this.transforms);
[x, y] = transform.apply(x, y);
// This line is the only thing that changes:
[x, y] = this.final.apply(x, y);
if (i > 20) {
plot(x, y, image);
}
}
}
}
export const finalCoefs: Coefs = {
a: 2,
b: 0,
c: 0,
d: 0,
e: 2,
f: 0,
};
export function renderFinal(image: ImageData) {
const transform1 = new Transform(transform1Coefs, [[1, julia]]);
const transform2 = new TransformPost(
transform2Coefs,
[
[1, linear],
[1, popcorn],
],
transform2Post
);
const [pdjA, pdjB, pdjC, pdjD] = transform3Pdj;
const transform3 = new Transform(transform3Coefs, [
[1, pdj(pdjA, pdjB, pdjC, pdjD)],
]);
const transformFinal = new Transform(finalCoefs, [[1, julia]]);
const flame = new FlameFinal(
[
[transform1Weight, transform1],
[transform2Weight, transform2],
[transform3Weight, transform3],
],
transformFinal
);
flame.render(1, image);
}

View File

@ -1,9 +1,10 @@
import React from "react";
import Blog from "../../../LayoutBlog";
import { Canvas } from "./0-utility";
import { gasket } from "./1-gasket";
import { renderBaseline } from "./2-variations";
import { renderBaseline } from "./2a-variations";
import { renderPost } from "./2b-post";
import { renderFinal } from "./2c-final";
export default function () {
const Layout = Blog({
@ -15,6 +16,8 @@ export default function () {
<Layout>
<Canvas f={gasket} />
<Canvas f={renderBaseline} />
<Canvas f={renderPost} />
<Canvas f={renderFinal} />
</Layout>
);
}

View File

@ -81,6 +81,7 @@
<xform weight="0.422330042096567" color="0" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
<xform weight="0.564534951145298" color="0" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" />
<xform weight="0.0131350067581356" color="0" linear="1" popcorn="1" coefs="0.031393 -0.031367 0.031367 0.031393 0 0" post="1 0 0 1 0.241352 0.271521" />
<finalxform color="0" symmetry="1" julia="1" coefs="2 0 0 2 0 0" />
<palette count="256" format="RGB">
3A78875998AA5E9DAC78B1C2599BAB36798A2252601B3438
1823270D1215080705010101000000000002080A090A0809
@ -116,5 +117,4 @@
6C303A56213D3033381C343619343B15383E193A431A4E5C
</palette>
</flame>
</Flames>

View File

@ -1,43 +1,4 @@
<Flames name="variations">
<flame name="baseline" version="Apophysis 2.08 beta" size="600 600" center="0 0" scale="150" oversample="1" filter="0.2" quality="1" background="0 0 0" brightness="4" gamma="4" >
<xform weight="0.422330042096567" color="0" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
<xform weight="0.564534951145298" color="0" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" />
<xform weight="0.0131350067581356" color="0" linear="1" popcorn="1" coefs="0.031393 -0.031367 0.031367 0.031393 0 0" />
<palette count="256" format="RGB">
3A78875998AA5E9DAC78B1C2599BAB36798A2252601B3438
1823270D1215080705010101000000000002080A090A0809
0C070D0B090A030406010101000000000000000000000000
0A00000B0A080E1213101B1F21202830243A6737357A3C31
864424643A22452F1838251427190E1C12080E0F110E1213
1014152110183720105D320FA0531F9144180409080A1312
0C13140E13160E15160E17160F16171015180B161C0A1225
0A0F2F101E37172E40294C5A3B6B7549798758879975A9BE
79A7BF7EA6C0949FA2AA9985B7A27BC4AB72AC965A867654
61574E4C4D48374343474141573F3F7C5C36B0914EC1DFF9
C4E4FAC8E9FCBEE1F4B5DAEDB2D8EDB0D6ED5398A7386D78
1D424A1B3B4219343B1B383E1D3C411D3B462155623D7C8B
46747F4F6C74636454785C3584663E917047BEA467CEA86A
DEAC6DC5975EAC834F916E41765A335F3D21431F21241625
1F202B1A2B321A2D321B30331B323A1628360E1D220E1D21
0F1D20101C1F111C1E111D1E121E1E2B21153B2B1B725432
85542C9854279B63369F7346AD7C3AB2763AB18F4FB39453
B69957B99B56BC9E56C19651CB9346AB6A2A9851254E341D
2F261B10181A0E15160C12120D11120A10100D0D0D0C0E0E
0B0F100B10120C11140F191A101F221829331A373B1E3D52
1A40551744591D556420424C1E3B431D3C41112C33102328
101B1D10191E111820101D2311242A1B33371B3A3F276476
3E637D556284545F7D7759355C41261B30290E16180B0F0F
0908060405030002010A0E0F12171A1C1B2B17343C3C7481
467F8F508A9E528FA23E81923769722E69772248512B545E
35616C688589807F85939FB5ABD6E6B3D6EA89B7CE5891A4
467E92356C81194A6B1A373F132C310E1C1F050409020205
0000020000000101010800000B0000170A000D0D0D0D1110
0F0E14100F141F11082619082F1904210F05111717101919
0F1B1B101F22182C2B252E2B282D311B2E321A2E2F162E30
1325270E191B0F1314190D0F2E1211461A27552227612723
6C303A56213D3033381C343619343B15383E193A431A4E5C
</palette>
</flame>
<flame name="post xform" version="Apophysis 2.08 beta" size="600 600" center="0 0" scale="150" oversample="1" filter="0.2" quality="1" background="0 0 0" brightness="4" gamma="4" >
<xform weight="0.422330042096567" color="0" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
<xform weight="0.564534951145298" color="0" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" />
@ -117,4 +78,43 @@
6C303A56213D3033381C343619343B15383E193A431A4E5C
</palette>
</flame>
<flame name="baseline" version="Apophysis 2.08 beta" size="600 600" center="0 0" scale="150" oversample="1" filter="0.2" quality="1" background="0 0 0" brightness="4" gamma="4" >
<xform weight="0.422330042096567" color="0" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
<xform weight="0.564534951145298" color="0.13" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" />
<xform weight="0.0131350067581356" color="0.844" linear="1" popcorn="1" coefs="0.031393 -0.031367 0.031367 0.031393 0 0" />
<palette count="256" format="RGB">
FF0000D31616BD2121A72C2C9137377C4242714747664D4D
3A63631D7171008080008B8B00969600A1A100ACAC00B1B1
00B7B700CCCC00D7D700E2E200EDED00F8F800FBFB00FFFF
2CF0FF42E8FF58E0FF6DD8FF83D1FF8ECDFF99C9FFAFC2FF
C5BAFFFFA6FFE9A2FFD39FFFBD9CFFA799FF9C97FF9196FF
668FFF508CFF3A89FF2485FF0E82FF0781FF0080FF0B80FF
1680FF2C80FF3780FF4280FF4D80FF5880FF5D80FF6380FF
7980FF7785F4758BE96A96D35FA1BD59A6B254ACA749B791
3EC17C28D7501DE23A12ED2409F61200FF0016E9002CD300
58A7006D9100837C00996600AF5000BA4500C53A00DB2400
F10E00E90B00D31600BD2100B22600A72C009137007C4200
5058003A6300246E001973000E79000080000A7500146A00
1E5F003249003C3E004633004B2D005028005A1D00651200
8100008C00009800009E0000A40000AF0000BB0000C70000
D20000EA0000F00000F60000FD0000F2160BE82C16DD4221
C76E37BC8342B2994DACA452A7AF589CC56392DB6E87F179
80FF8080E99680E39B80DEA180D3AC80C8B780BEC180B3CC
809DE2808EF08080FF7A80F47580E96A80D35F80BD5480A7
4980913380662D805B2880501D803A12802407800E008000
2C841A3784204285265887336E8940838B4D998D5AAF8E66
C59073FF9595FF9393FF9292FF9090FF8D8DFF8B8BFF8888
FF8383FF8181FF8080FF7E7EFF7B7BFF7979FF7777FF7783
FF768EFF769AFF75A6FF75B1FF74BDFF74C9FF73D4FF73E0
FF72F8FF71FBFF71FFFF6BEDFF65DBFF5FC9FF5AB7FF54A5
FF4E93FF4881FF426FFF3C5DFF374BFF2D2DFA293AF62548
F12155ED1E63E81A70E4167EDF128BDB0E99D60AA6D106B4
CD02C1CA00CACC00B9CE00A7CF0096D10085D30073D50062
D70050D8003FDA002EDC001CDE000BDF0000D90C06D4180C
CE2413C82F19C33B1FBD4725B7532BB25F32AC6B38A6773E
9D8A489D7E429E723C9E66359F5B2FA04F29A04323A1371D
A12B16A21F10A3130AA30804A40000A000009C0000980000
9400009000008C00008800008400008000007C0000750000
</palette>
</flame>
</Flames>