From 4bcc321c4b139a55bc6cba635c99704283b25fb7 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sat, 1 Jul 2023 04:38:15 +0000 Subject: [PATCH] Get full post/final xform working --- posts/2023/06/flam3/2-variations.ts | 142 -------------------- posts/2023/06/flam3/2a-variations.ts | 189 +++++++++++++++++++++++++++ posts/2023/06/flam3/2b-post.ts | 79 +++++++++++ posts/2023/06/flam3/2c-final.ts | 86 ++++++++++++ posts/2023/06/flam3/index.tsx | 7 +- posts/2023/06/flam3/variations.bak | 2 +- posts/2023/06/flam3/variations.flame | 78 +++++------ 7 files changed, 399 insertions(+), 184 deletions(-) delete mode 100644 posts/2023/06/flam3/2-variations.ts create mode 100644 posts/2023/06/flam3/2a-variations.ts create mode 100644 posts/2023/06/flam3/2b-post.ts create mode 100644 posts/2023/06/flam3/2c-final.ts diff --git a/posts/2023/06/flam3/2-variations.ts b/posts/2023/06/flam3/2-variations.ts deleted file mode 100644 index 685c8fd..0000000 --- a/posts/2023/06/flam3/2-variations.ts +++ /dev/null @@ -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 - ); -} diff --git a/posts/2023/06/flam3/2a-variations.ts b/posts/2023/06/flam3/2a-variations.ts new file mode 100644 index 0000000..b0753c9 --- /dev/null +++ b/posts/2023/06/flam3/2a-variations.ts @@ -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(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); +} diff --git a/posts/2023/06/flam3/2b-post.ts b/posts/2023/06/flam3/2b-post.ts new file mode 100644 index 0000000..41f639b --- /dev/null +++ b/posts/2023/06/flam3/2b-post.ts @@ -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); +} diff --git a/posts/2023/06/flam3/2c-final.ts b/posts/2023/06/flam3/2c-final.ts new file mode 100644 index 0000000..5c68f9c --- /dev/null +++ b/posts/2023/06/flam3/2c-final.ts @@ -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); +} diff --git a/posts/2023/06/flam3/index.tsx b/posts/2023/06/flam3/index.tsx index 7651ac2..bc23633 100644 --- a/posts/2023/06/flam3/index.tsx +++ b/posts/2023/06/flam3/index.tsx @@ -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 () { + + ); } diff --git a/posts/2023/06/flam3/variations.bak b/posts/2023/06/flam3/variations.bak index bb2f8cb..ed2e58d 100644 --- a/posts/2023/06/flam3/variations.bak +++ b/posts/2023/06/flam3/variations.bak @@ -81,6 +81,7 @@ + 3A78875998AA5E9DAC78B1C2599BAB36798A2252601B3438 1823270D1215080705010101000000000002080A090A0809 @@ -116,5 +117,4 @@ 6C303A56213D3033381C343619343B15383E193A431A4E5C - diff --git a/posts/2023/06/flam3/variations.flame b/posts/2023/06/flam3/variations.flame index ed2e58d..5d37782 100644 --- a/posts/2023/06/flam3/variations.flame +++ b/posts/2023/06/flam3/variations.flame @@ -1,43 +1,4 @@ - - - - - - 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 - - @@ -117,4 +78,43 @@ 6C303A56213D3033381C343619343B15383E193A431A4E5C + + + + + + 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 + +