mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
Post/final transform implementation
This commit is contained in:
parent
1aa45e3f59
commit
6c4d73f081
@ -28,8 +28,8 @@ export default function GasketWeighted() {
|
|||||||
|
|
||||||
const weightInput = (title, weight, setWeight) => (
|
const weightInput = (title, weight, setWeight) => (
|
||||||
<>
|
<>
|
||||||
<div className={styles.inputDiv}>
|
<div className={styles.inputElement}>
|
||||||
<p><TeX>{title}</TeX> weight:<span style={{float: 'right'}}>{weight}</span></p>
|
<p><TeX>{title}</TeX> weight: {weight}</p>
|
||||||
<input type={'range'} min={0} max={1} step={.01} style={{width: '100%'}} value={weight}
|
<input type={'range'} min={0} max={1} step={.01} style={{width: '100%'}} value={weight}
|
||||||
onInput={e => setWeight(Number(e.currentTarget.value))}/>
|
onInput={e => setWeight(Number(e.currentTarget.value))}/>
|
||||||
</div>
|
</div>
|
||||||
@ -38,7 +38,7 @@ export default function GasketWeighted() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{paddingTop: '1em', display: 'grid', gridTemplateColumns: 'auto auto auto'}}>
|
<div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: 'auto auto auto'}}>
|
||||||
{weightInput("F_0", f0Weight, setF0Weight)}
|
{weightInput("F_0", f0Weight, setF0Weight)}
|
||||||
{weightInput("F_1", f1Weight, setF1Weight)}
|
{weightInput("F_1", f1Weight, setF1Weight)}
|
||||||
{weightInput("F_2", f2Weight, setF2Weight)}
|
{weightInput("F_2", f2Weight, setF2Weight)}
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
import TeX from "@matejmazur/react-katex";
|
||||||
|
import {Coefs} from "../src/coefs";
|
||||||
|
|
||||||
|
import styles from "../src/css/styles.module.css";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
title: String;
|
||||||
|
isPost: boolean;
|
||||||
|
coefs: Coefs;
|
||||||
|
setCoefs: (coefs: Coefs) => void;
|
||||||
|
}
|
||||||
|
export const CoefEditor = ({title, isPost, coefs, setCoefs}: Props) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: 'auto auto auto'}}>
|
||||||
|
<p className={styles.inputTitle} style={{gridColumn: '1/-1'}}>{title}</p>
|
||||||
|
<div className={styles.inputElement}>
|
||||||
|
<p>{isPost ? <TeX>\alpha</TeX> : 'a'}: {coefs.a}</p>
|
||||||
|
<input type={'range'} min={0} max={2} step={0.01} style={{width: '100%'}} value={coefs.a}
|
||||||
|
onInput={e => setCoefs({...coefs, a: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputElement}>
|
||||||
|
<p>{isPost ? <TeX>\beta</TeX> : 'b'}: {coefs.b}</p>
|
||||||
|
<input type={'range'} min={0} max={2} step={0.01} style={{width: '100%'}} value={coefs.b}
|
||||||
|
onInput={e => setCoefs({...coefs, b: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputElement}>
|
||||||
|
<p>{isPost ? <TeX>\gamma</TeX> : 'c'}: {coefs.c}</p>
|
||||||
|
<input type={'range'} min={0} max={2} step={0.01} style={{width: '100%'}} value={coefs.c}
|
||||||
|
onInput={e => setCoefs({...coefs, c: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputElement}>
|
||||||
|
<p>{isPost ? <TeX>\delta</TeX> : 'd'}: {coefs.d}</p>
|
||||||
|
<input type={'range'} min={0} max={2} step={0.01} style={{width: '100%'}} value={coefs.d}
|
||||||
|
onInput={e => setCoefs({...coefs, d: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputElement}>
|
||||||
|
<p>{isPost ? <TeX>\epsilon</TeX> : 'e'}: {coefs.e}</p>
|
||||||
|
<input type={'range'} min={0} max={2} step={0.01} style={{width: '100%'}} value={coefs.e}
|
||||||
|
onInput={e => setCoefs({...coefs, e: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputElement}>
|
||||||
|
<p>{isPost ? <TeX>\zeta</TeX> : 'f'}: {coefs.f}</p>
|
||||||
|
<input type={'range'} min={0} max={2} step={0.01} style={{width: '100%'}} value={coefs.f}
|
||||||
|
onInput={e => setCoefs({...coefs, f: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,15 +1,7 @@
|
|||||||
import {useContext, useEffect, useState} from "react";
|
import {useContext, useEffect, useState} from "react";
|
||||||
import { blend } from "./blend";
|
|
||||||
import { applyCoefs, Coefs } from "../src/coefs"
|
|
||||||
import {randomBiUnit} from "../src/randomBiUnit";
|
|
||||||
import {linear} from "../src/linear";
|
|
||||||
import {julia} from "../src/julia";
|
|
||||||
import {popcorn} from "../src/popcorn";
|
|
||||||
import {pdj} from "../src/pdj";
|
|
||||||
import {Variation} from "../src/variation";
|
|
||||||
import {Transform} from "../src/transform";
|
import {Transform} from "../src/transform";
|
||||||
import {
|
import {
|
||||||
pdjParams,
|
identityCoefs,
|
||||||
xform1Coefs,
|
xform1Coefs,
|
||||||
xform1Weight,
|
xform1Weight,
|
||||||
xform2Coefs,
|
xform2Coefs,
|
||||||
@ -17,26 +9,15 @@ import {
|
|||||||
xform3Coefs,
|
xform3Coefs,
|
||||||
xform3Weight
|
xform3Weight
|
||||||
} from "../src/params";
|
} from "../src/params";
|
||||||
import {randomChoice} from "../src/randomChoice";
|
|
||||||
import {plotBinary} from "../src/plotBinary"
|
|
||||||
import {PainterContext} from "../src/Canvas"
|
import {PainterContext} from "../src/Canvas"
|
||||||
|
import {buildBlend, buildTransform} from "./buildTransform"
|
||||||
import styles from "../src/css/styles.module.css"
|
import {chaosGameFinal} from "./chaosGameFinal"
|
||||||
|
import {VariationEditor, VariationProps} from "./VariationEditor"
|
||||||
type VariationBlend = {
|
|
||||||
linear: number,
|
|
||||||
julia: number,
|
|
||||||
popcorn: number,
|
|
||||||
pdj: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function FlameBlend() {
|
export default function FlameBlend() {
|
||||||
const quality = 2;
|
|
||||||
const step = 5000;
|
|
||||||
|
|
||||||
const {width, height, setPainter} = useContext(PainterContext);
|
const {width, height, setPainter} = useContext(PainterContext);
|
||||||
|
|
||||||
const xform1Default: VariationBlend = {
|
const xform1Default: VariationProps = {
|
||||||
linear: 0,
|
linear: 0,
|
||||||
julia: 1,
|
julia: 1,
|
||||||
popcorn: 0,
|
popcorn: 0,
|
||||||
@ -44,7 +25,7 @@ export default function FlameBlend() {
|
|||||||
}
|
}
|
||||||
const [xform1Variations, setXform1Variations] = useState(xform1Default)
|
const [xform1Variations, setXform1Variations] = useState(xform1Default)
|
||||||
|
|
||||||
const xform2Default: VariationBlend = {
|
const xform2Default: VariationProps = {
|
||||||
linear: 1,
|
linear: 1,
|
||||||
julia: 0,
|
julia: 0,
|
||||||
popcorn: 1,
|
popcorn: 1,
|
||||||
@ -52,7 +33,7 @@ export default function FlameBlend() {
|
|||||||
}
|
}
|
||||||
const [xform2Variations, setXform2Variations] = useState(xform2Default)
|
const [xform2Variations, setXform2Variations] = useState(xform2Default)
|
||||||
|
|
||||||
const xform3Default: VariationBlend = {
|
const xform3Default: VariationProps = {
|
||||||
linear: 0,
|
linear: 0,
|
||||||
julia: 0,
|
julia: 0,
|
||||||
popcorn: 0,
|
popcorn: 0,
|
||||||
@ -60,80 +41,21 @@ export default function FlameBlend() {
|
|||||||
}
|
}
|
||||||
const [xform3Variations, setXform3Variations] = useState(xform3Default)
|
const [xform3Variations, setXform3Variations] = useState(xform3Default)
|
||||||
|
|
||||||
function buildTransform(coefs: Coefs, variations: VariationBlend): Transform {
|
// Cheating a bit here; for purposes of code re-use, use the post- and final-transform-enabled chaos game,
|
||||||
return (x: number, y: number) => {
|
// and swap in identity components for each
|
||||||
const [varX, varY] = applyCoefs(x, y, coefs);
|
const identityXform: Transform = (x, y) => [x, y];
|
||||||
const varFunctions: [number, Variation][] = [
|
|
||||||
[variations.linear, linear],
|
|
||||||
[variations.julia, julia],
|
|
||||||
[variations.popcorn, popcorn(coefs)],
|
|
||||||
[variations.pdj, pdj(pdjParams)]
|
|
||||||
]
|
|
||||||
|
|
||||||
return blend(varX, varY, varFunctions);
|
useEffect(() => setPainter(chaosGameFinal(width, height, [
|
||||||
}
|
[xform1Weight, buildTransform(xform1Coefs, buildBlend(xform1Coefs, xform1Variations))],
|
||||||
}
|
[xform2Weight, buildTransform(xform2Coefs, buildBlend(xform2Coefs, xform2Variations))],
|
||||||
|
[xform3Weight, buildTransform(xform3Coefs, buildBlend(xform3Coefs, xform3Variations))]
|
||||||
const image = new ImageData(width, height);
|
], identityXform)), [xform1Variations, xform2Variations, xform3Variations]);
|
||||||
function* chaosGame() {
|
|
||||||
let [x, y] = [randomBiUnit(), randomBiUnit()];
|
|
||||||
const transforms: [number, Transform][] = [
|
|
||||||
[xform1Weight, buildTransform(xform1Coefs, xform1Variations)],
|
|
||||||
[xform2Weight, buildTransform(xform2Coefs, xform2Variations)],
|
|
||||||
[xform3Weight, buildTransform(xform3Coefs, xform3Variations)]
|
|
||||||
]
|
|
||||||
|
|
||||||
const iterations = quality * image.width * image.height;
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
let [_, transform] = randomChoice(transforms);
|
|
||||||
[x, y] = transform(x, y);
|
|
||||||
|
|
||||||
if (i > 20)
|
|
||||||
plotBinary(x, y, image);
|
|
||||||
|
|
||||||
if (i % step === 0) {
|
|
||||||
console.log(`Checking in; iterations=${i}`)
|
|
||||||
yield image;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yield image;
|
|
||||||
}
|
|
||||||
useEffect(() => setPainter(chaosGame()), [xform1Variations, xform2Variations, xform3Variations]);
|
|
||||||
|
|
||||||
const variationEditor = (title, variations, setVariations) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p style={{gridColumn: '1/-1'}}>{title}:</p>
|
|
||||||
<div className={styles.inputDiv}>
|
|
||||||
<p>Linear: {variations.linear}</p>
|
|
||||||
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.linear}
|
|
||||||
onInput={e => setVariations({...variations, linear: Number(e.currentTarget.value)})}/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.inputDiv}>
|
|
||||||
<p>Julia: {variations.julia}</p>
|
|
||||||
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.julia}
|
|
||||||
onInput={e => setVariations({...variations, julia: Number(e.currentTarget.value)})}/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.inputDiv}>
|
|
||||||
<p>Popcorn: {variations.popcorn}</p>
|
|
||||||
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.popcorn}
|
|
||||||
onInput={e => setVariations({...variations, popcorn: Number(e.currentTarget.value)})}/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.inputDiv}>
|
|
||||||
<p>PDJ: {variations.pdj}</p>
|
|
||||||
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.pdj}
|
|
||||||
onInput={e => setVariations({...variations, pdj: Number(e.currentTarget.value)})}/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{paddingTop: '1em', display: 'grid', gridTemplateColumns: 'auto auto auto auto'}}>
|
<>
|
||||||
{variationEditor("Transform 1", xform1Variations, setXform1Variations)}
|
<VariationEditor title={"Transform 1"} variations={xform1Variations} setVariations={setXform1Variations}/>
|
||||||
{variationEditor("Transform 2", xform2Variations, setXform2Variations)}
|
<VariationEditor title={"Transform 2"} variations={xform2Variations} setVariations={setXform2Variations}/>
|
||||||
{variationEditor("Transform 3", xform3Variations, setXform3Variations)}
|
<VariationEditor title={"Transform 3"} variations={xform3Variations} setVariations={setXform3Variations}/>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
import {useContext, useEffect, useState} from "react";
|
||||||
|
import {Coefs} from "../src/coefs"
|
||||||
|
import {
|
||||||
|
xform1Coefs,
|
||||||
|
xform1Weight,
|
||||||
|
xform1Variations,
|
||||||
|
xform1CoefsPost,
|
||||||
|
xform2Coefs,
|
||||||
|
xform2Weight,
|
||||||
|
xform2Variations,
|
||||||
|
xform2CoefsPost,
|
||||||
|
xform3Coefs,
|
||||||
|
xform3Weight,
|
||||||
|
xform3Variations,
|
||||||
|
xform3CoefsPost,
|
||||||
|
xformFinalCoefs as xformFinalCoefsDefault,
|
||||||
|
xformFinalCoefsPost as xformFinalCoefsPostDefault,
|
||||||
|
} from "../src/params";
|
||||||
|
import {PainterContext} from "../src/Canvas"
|
||||||
|
import {buildBlend, buildTransform} from "./buildTransform";
|
||||||
|
import {transformPost} from "./post";
|
||||||
|
import {chaosGameFinal} from "./chaosGameFinal"
|
||||||
|
import {VariationEditor, VariationProps} from "./VariationEditor";
|
||||||
|
import {CoefEditor} from "./CoefEditor";
|
||||||
|
import {Transform} from "../src/transform";
|
||||||
|
|
||||||
|
export default function FlameFinal() {
|
||||||
|
const {width, height, setPainter} = useContext(PainterContext);
|
||||||
|
|
||||||
|
const [xformFinalCoefs, setXformFinalCoefs] = useState<Coefs>(xformFinalCoefsDefault);
|
||||||
|
|
||||||
|
const xformFinalVariationsDefault: VariationProps = {
|
||||||
|
linear: 0,
|
||||||
|
julia: 1,
|
||||||
|
popcorn: 0,
|
||||||
|
pdj: 0
|
||||||
|
}
|
||||||
|
const [xformFinalVariations, setXformFinalVariations] = useState<VariationProps>(xformFinalVariationsDefault);
|
||||||
|
|
||||||
|
const [xformFinalCoefsPost, setXformFinalCoefsPost] = useState<Coefs>(xformFinalCoefsPostDefault);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const transforms: [number, Transform][] = [
|
||||||
|
[xform1Weight, transformPost(buildTransform(xform1Coefs, xform1Variations), xform1CoefsPost)],
|
||||||
|
[xform2Weight, transformPost(buildTransform(xform2Coefs, xform2Variations), xform2CoefsPost)],
|
||||||
|
[xform3Weight, transformPost(buildTransform(xform3Coefs, xform3Variations), xform3CoefsPost)]
|
||||||
|
];
|
||||||
|
|
||||||
|
const finalBlend = buildBlend(xformFinalCoefs, xformFinalVariations);
|
||||||
|
const finalTransform = buildTransform(xformFinalCoefs, finalBlend);
|
||||||
|
const finalPost = transformPost(finalTransform, xformFinalCoefsPost);
|
||||||
|
|
||||||
|
setPainter(chaosGameFinal(width, height, transforms, finalPost));
|
||||||
|
}, [xformFinalCoefs, xformFinalVariations, xformFinalCoefsPost]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CoefEditor title={"Final Transform"} isPost={false} coefs={xformFinalCoefs} setCoefs={setXformFinalCoefs}/>
|
||||||
|
<VariationEditor title={"Final Transform Variations"} variations={xformFinalVariations}
|
||||||
|
setVariations={setXformFinalVariations}/>
|
||||||
|
<CoefEditor title={"Final Transform Post"} isPost={true} coefs={xformFinalCoefsPost} setCoefs={setXformFinalCoefsPost}/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
46
blog/2024-11-15-playing-with-fire/2-transforms/FlamePost.tsx
Normal file
46
blog/2024-11-15-playing-with-fire/2-transforms/FlamePost.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import {useContext, useEffect, useState} from "react";
|
||||||
|
import {Coefs} from "../src/coefs"
|
||||||
|
import {Transform} from "../src/transform";
|
||||||
|
import {
|
||||||
|
xform1Coefs,
|
||||||
|
xform1Weight,
|
||||||
|
xform1Variations,
|
||||||
|
xform1CoefsPost as xform1CoefsPostDefault,
|
||||||
|
xform2Coefs,
|
||||||
|
xform2Weight,
|
||||||
|
xform2Variations,
|
||||||
|
xform2CoefsPost as xform2CoefsPostDefault,
|
||||||
|
xform3Coefs,
|
||||||
|
xform3Weight,
|
||||||
|
xform3Variations,
|
||||||
|
xform3CoefsPost as xform3CoefsPostDefault
|
||||||
|
} from "../src/params";
|
||||||
|
import {PainterContext} from "../src/Canvas"
|
||||||
|
import {chaosGameFinal} from "./chaosGameFinal"
|
||||||
|
import {CoefEditor} from "./CoefEditor"
|
||||||
|
import {transformPost} from "./post";
|
||||||
|
import {buildTransform} from "./buildTransform";
|
||||||
|
|
||||||
|
export default function FlamePost() {
|
||||||
|
const {width, height, setPainter} = useContext(PainterContext);
|
||||||
|
|
||||||
|
const [xform1CoefsPost, setXform1PostCoefs] = useState<Coefs>(xform1CoefsPostDefault);
|
||||||
|
const [xform2CoefsPost, setXform2PostCoefs] = useState<Coefs>(xform2CoefsPostDefault);
|
||||||
|
const [xform3CoefsPost, setXform3PostCoefs] = useState<Coefs>(xform3CoefsPostDefault);
|
||||||
|
|
||||||
|
const identityXform: Transform = (x, y) => [x, y];
|
||||||
|
|
||||||
|
useEffect(() => setPainter(chaosGameFinal(width, height, [
|
||||||
|
[xform1Weight, transformPost(buildTransform(xform1Coefs, xform1Variations), xform1CoefsPost)],
|
||||||
|
[xform2Weight, transformPost(buildTransform(xform2Coefs, xform2Variations), xform2CoefsPost)],
|
||||||
|
[xform3Weight, transformPost(buildTransform(xform3Coefs, xform3Variations), xform3CoefsPost)]
|
||||||
|
], identityXform)), [xform1CoefsPost, xform2CoefsPost, xform3CoefsPost]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CoefEditor title={"Transform 1 Post"} isPost={true} coefs={xform1CoefsPost} setCoefs={setXform1PostCoefs}/>
|
||||||
|
<CoefEditor title={"Transform 2 Post"} isPost={true} coefs={xform2CoefsPost} setCoefs={setXform2PostCoefs}/>
|
||||||
|
<CoefEditor title={"Transform 3 Post"} isPost={true} coefs={xform3CoefsPost} setCoefs={setXform3PostCoefs}/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
import styles from "../src/css/styles.module.css"
|
||||||
|
|
||||||
|
export interface VariationProps {
|
||||||
|
linear: number;
|
||||||
|
julia: number;
|
||||||
|
popcorn: number;
|
||||||
|
pdj: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
title: String;
|
||||||
|
variations: VariationProps;
|
||||||
|
setVariations: (variations: VariationProps) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VariationEditor = ({title, variations, setVariations}: Props) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: 'auto auto auto auto'}}>
|
||||||
|
<p className={styles.inputTitle} style={{gridColumn: '1/-1'}}>{title}</p>
|
||||||
|
<div className={styles.inputElement}>
|
||||||
|
<span>Linear: {variations.linear}</span>
|
||||||
|
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.linear}
|
||||||
|
onInput={e => setVariations({...variations, linear: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputElement}>
|
||||||
|
<span>Julia: {variations.julia}</span>
|
||||||
|
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.julia}
|
||||||
|
onInput={e => setVariations({...variations, julia: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputElement}>
|
||||||
|
<span>Popcorn: {variations.popcorn}</span>
|
||||||
|
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.popcorn}
|
||||||
|
onInput={e => setVariations({...variations, popcorn: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputElement}>
|
||||||
|
<span>PDJ: {variations.pdj}</span>
|
||||||
|
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.pdj}
|
||||||
|
onInput={e => setVariations({...variations, pdj: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,7 +1,10 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import { Variation } from "../src/variation"
|
import {VariationBlend} from "../src/variationBlend";
|
||||||
// hidden-end
|
// hidden-end
|
||||||
export function blend(x: number, y: number, variations: [number, Variation][]): [number, number] {
|
export function blend(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
variations: VariationBlend): [number, number] {
|
||||||
let [finalX, finalY] = [0, 0];
|
let [finalX, finalY] = [0, 0];
|
||||||
|
|
||||||
for (const [weight, variation] of variations) {
|
for (const [weight, variation] of variations) {
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import {applyCoefs, Coefs} from "../src/coefs";
|
||||||
|
import {VariationProps} from "./VariationEditor";
|
||||||
|
import {Transform} from "../src/transform";
|
||||||
|
import {linear} from "../src/linear";
|
||||||
|
import {julia} from "../src/julia";
|
||||||
|
import {popcorn} from "../src/popcorn";
|
||||||
|
import {pdj} from "../src/pdj";
|
||||||
|
import {pdjParams} from "../src/params";
|
||||||
|
import {blend} from "./blend";
|
||||||
|
import {VariationBlend} from "../src/variationBlend";
|
||||||
|
|
||||||
|
export function buildBlend(coefs: Coefs, variations: VariationProps): VariationBlend {
|
||||||
|
return [
|
||||||
|
[variations.linear, linear],
|
||||||
|
[variations.julia, julia],
|
||||||
|
[variations.popcorn, popcorn(coefs)],
|
||||||
|
[variations.pdj, pdj(pdjParams)]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildTransform(coefs: Coefs, variations: VariationBlend): Transform {
|
||||||
|
return (x: number, y: number) => {
|
||||||
|
[x, y] = applyCoefs(x, y, coefs);
|
||||||
|
return blend(x, y, variations);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
// hidden-start
|
||||||
|
import { randomBiUnit } from "../src/randomBiUnit";
|
||||||
|
import { randomChoice } from "../src/randomChoice";
|
||||||
|
import { plotBinary as plot } from "../src/plotBinary"
|
||||||
|
import {Transform} from "../src/transform";
|
||||||
|
const iterations = 500_000;
|
||||||
|
const step = 1000;
|
||||||
|
// hidden-end
|
||||||
|
export function* chaosGameFinal(width: number, height: number, transforms: [number, Transform][], final: Transform) {
|
||||||
|
let image = new ImageData(width, height);
|
||||||
|
let [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||||
|
|
||||||
|
for (let i = 0; i < iterations; i++) {
|
||||||
|
const [_, transform] = randomChoice(transforms);
|
||||||
|
[x, y] = transform(x, y);
|
||||||
|
|
||||||
|
// highlight-start
|
||||||
|
[x, y] = final(x, y);
|
||||||
|
// highlight-end
|
||||||
|
|
||||||
|
if (i > 20)
|
||||||
|
plot(x, y, image);
|
||||||
|
|
||||||
|
if (i % step === 0)
|
||||||
|
yield image;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield image;
|
||||||
|
}
|
@ -37,12 +37,14 @@ the general format. For example:
|
|||||||
$$
|
$$
|
||||||
\begin{align*}
|
\begin{align*}
|
||||||
F_0(x,y) &= \left({x \over 2}, {y \over 2}\right) \\
|
F_0(x,y) &= \left({x \over 2}, {y \over 2}\right) \\
|
||||||
&= (a_0 \cdot x + b_0 \cdot y + c_o, d_0 \cdot x + e_0 \cdot y + f_0) \\
|
&= (a_0 \cdot x + b_0 \cdot y + c_0, d_0 \cdot x + e_0 \cdot y + f_0) \\
|
||||||
& a_0 = 0.5 \hspace{0.2cm} b_0 = 0 \hspace{0.2cm} c_0 = 0 \\
|
& a_0 = 0.5 \hspace{0.2cm} b_0 = 0 \hspace{0.2cm} c_0 = 0 \\
|
||||||
& d_0 = 0 \hspace{0.2cm} e_0 = 0.5 \hspace{0.2cm} f_0 = 0
|
& d_0 = 0 \hspace{0.2cm} e_0 = 0.5 \hspace{0.2cm} f_0 = 0
|
||||||
\end{align*}
|
\end{align*}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
|
TODO: Explain the applyCoefs function
|
||||||
|
|
||||||
However, these transforms are pretty boring. We can build more exciting images by using additional functions
|
However, these transforms are pretty boring. We can build more exciting images by using additional functions
|
||||||
within the transform. These "sub-functions" are called "variations":
|
within the transform. These "sub-functions" are called "variations":
|
||||||
|
|
||||||
@ -79,10 +81,10 @@ $$
|
|||||||
r &= \sqrt{x^2 + y^2} \\
|
r &= \sqrt{x^2 + y^2} \\
|
||||||
\theta &= \text{arctan}(x / y) \\
|
\theta &= \text{arctan}(x / y) \\
|
||||||
\Omega &= \left\{
|
\Omega &= \left\{
|
||||||
\begin{array}{lr}
|
\begin{array}{lr}
|
||||||
0 \hspace{0.4cm} \text{w.p. } 0.5 \\
|
0 \hspace{0.4cm} \text{w.p. } 0.5 \\
|
||||||
\pi \hspace{0.4cm} \text{w.p. } 0.5 \\
|
\pi \hspace{0.4cm} \text{w.p. } 0.5 \\
|
||||||
\end{array}
|
\end{array}
|
||||||
\right\} \\
|
\right\} \\
|
||||||
|
|
||||||
V_{13}(x, y) &= \sqrt{r} \cdot (\text{cos} ( \theta / 2 + \Omega ), \text{sin} ( \theta / 2 + \Omega ))
|
V_{13}(x, y) &= \sqrt{r} \cdot (\text{cos} ( \theta / 2 + \Omega ), \text{sin} ( \theta / 2 + \Omega ))
|
||||||
@ -123,8 +125,7 @@ import pdjSrc from '!!raw-loader!../src/pdj'
|
|||||||
|
|
||||||
Now, one variation is fun, but we can also combine variations in a single transform by "blending."
|
Now, one variation is fun, but we can also combine variations in a single transform by "blending."
|
||||||
Each variation receives the same $x$ and $y$ inputs, and we add together each variation's $x$ and $y$ outputs.
|
Each variation receives the same $x$ and $y$ inputs, and we add together each variation's $x$ and $y$ outputs.
|
||||||
We'll also give each variation a weight ($v_j$) that scales the output, which allows us to control
|
We'll also give each variation a weight ($v_{ij}$) to control how much it contributes to the transform:
|
||||||
how much each variation contributes to the transform:
|
|
||||||
|
|
||||||
$$
|
$$
|
||||||
F_i(x,y) = \sum_{j} v_{ij} V_j(a_i \cdot x + b_i \cdot y + c_i, \hspace{0.2cm} d_i \cdot x + e_i \cdot y + f_i)
|
F_i(x,y) = \sum_{j} v_{ij} V_j(a_i \cdot x + b_i \cdot y + c_i, \hspace{0.2cm} d_i \cdot x + e_i \cdot y + f_i)
|
||||||
@ -136,7 +137,10 @@ import blendSource from "!!raw-loader!./blend";
|
|||||||
|
|
||||||
<CodeBlock language={'typescript'}>{blendSource}</CodeBlock>
|
<CodeBlock language={'typescript'}>{blendSource}</CodeBlock>
|
||||||
|
|
||||||
And with that in place, we have enough to render a first full fractal flame:
|
And with that in place, we have enough to render a first full fractal flame.
|
||||||
|
The sliders below change the variation weights for each transform (the $v_{ij}$ parameters);
|
||||||
|
try changing them around to see which parts of the image are controlled by
|
||||||
|
each transform.
|
||||||
|
|
||||||
import BrowserOnly from "@docusaurus/BrowserOnly";
|
import BrowserOnly from "@docusaurus/BrowserOnly";
|
||||||
import Canvas from "../src/Canvas";
|
import Canvas from "../src/Canvas";
|
||||||
@ -144,4 +148,29 @@ import FlameBlend from "./FlameBlend";
|
|||||||
|
|
||||||
<Canvas width={500} height={500}>
|
<Canvas width={500} height={500}>
|
||||||
<BrowserOnly>{() => <FlameBlend/>}</BrowserOnly>
|
<BrowserOnly>{() => <FlameBlend/>}</BrowserOnly>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
## Post transforms
|
||||||
|
|
||||||
|
After variation blending, we apply a second set of transform coordinates.
|
||||||
|
|
||||||
|
The fractal flame below starts with the same initial transforms/variations as the previous fractal flame,
|
||||||
|
but allows modifying the post-transform coefficients.
|
||||||
|
|
||||||
|
$$
|
||||||
|
P_i(x, y) = (\alpha_i x + \beta_i y + \gamma_i, \delta_i x + \epsilon_i y + \zeta_i)
|
||||||
|
$$
|
||||||
|
|
||||||
|
import FlamePost from "./FlamePost";
|
||||||
|
|
||||||
|
<Canvas width={500} height={500}>
|
||||||
|
<BrowserOnly>{() => <FlamePost/>}</BrowserOnly>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
## Final transform
|
||||||
|
|
||||||
|
import FlameFinal from "./FlameFinal";
|
||||||
|
|
||||||
|
<Canvas width={500} height={500}>
|
||||||
|
<BrowserOnly>{() => <FlameFinal/>}</BrowserOnly>
|
||||||
</Canvas>
|
</Canvas>
|
7
blog/2024-11-15-playing-with-fire/2-transforms/post.ts
Normal file
7
blog/2024-11-15-playing-with-fire/2-transforms/post.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// hidden-start
|
||||||
|
import {Coefs} from "../src/coefs";
|
||||||
|
import {Transform} from "../src/transform";
|
||||||
|
import {applyCoefs} from "../src/coefs";
|
||||||
|
// hidden-end
|
||||||
|
export const transformPost = (transform: Transform, coefs: Coefs): Transform =>
|
||||||
|
(x, y): [number, number] => applyCoefs(...transform(x, y), coefs)
|
0
blog/2024-11-15-playing-with-fire/src/blend.ts
Normal file
0
blog/2024-11-15-playing-with-fire/src/blend.ts
Normal file
@ -3,7 +3,7 @@ export interface Coefs {
|
|||||||
d: number, e: number, f: number
|
d: number, e: number, f: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyCoefs(x: number, y: number, coefs: Coefs) {
|
export function applyCoefs(x: number, y: number, coefs: Coefs): [number, number] {
|
||||||
return [
|
return [
|
||||||
(x * coefs.a + y * coefs.b + coefs.c),
|
(x * coefs.a + y * coefs.b + coefs.c),
|
||||||
(x * coefs.d + y * coefs.e + coefs.f)
|
(x * coefs.d + y * coefs.e + coefs.f)
|
||||||
|
@ -1,4 +1,23 @@
|
|||||||
.inputDiv {
|
.inputGroup {
|
||||||
|
padding: .5em;
|
||||||
|
margin: .5em;
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: var(--ifm-global-radius);
|
||||||
|
border-color: var(--ifm-color-emphasis-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputTitle {
|
||||||
|
border: 0 solid;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
border-color: var(--ifm-color-emphasis-500);
|
||||||
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputElement {
|
||||||
padding-left: .5em;
|
padding-left: .5em;
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputElement > p {
|
||||||
|
margin: 0
|
||||||
}
|
}
|
@ -3,11 +3,14 @@ import { Variation } from './variation'
|
|||||||
// hidden-end
|
// hidden-end
|
||||||
export const julia: Variation = (x, y) => {
|
export const julia: Variation = (x, y) => {
|
||||||
const r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
const r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
||||||
|
|
||||||
const theta = Math.atan2(x, y);
|
const theta = Math.atan2(x, y);
|
||||||
const omega = Math.random() > 0.5 ? 0 : Math.PI;
|
const omega = Math.random() > 0.5 ? 0 : Math.PI;
|
||||||
|
|
||||||
|
const sqrtR = Math.sqrt(r);
|
||||||
|
const thetaVal = theta / 2 + omega;
|
||||||
return [
|
return [
|
||||||
r * Math.cos(theta / 2 + omega),
|
sqrtR * Math.cos(thetaVal),
|
||||||
r * Math.sin(theta / 2 + omega)
|
sqrtR * Math.sin(thetaVal)
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -4,11 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Coefs } from './coefs';
|
import { Coefs } from './coefs';
|
||||||
|
import {VariationBlend} from "./variationBlend";
|
||||||
import { linear } from './linear'
|
import { linear } from './linear'
|
||||||
import { julia } from './julia'
|
import { julia } from './julia'
|
||||||
import { popcorn } from './popcorn'
|
import { popcorn } from './popcorn'
|
||||||
import {pdj, PdjParams} from './pdj'
|
import {pdj, PdjParams} from './pdj'
|
||||||
import {Variation} from "./variation"
|
|
||||||
|
|
||||||
export const identityCoefs: Coefs = {
|
export const identityCoefs: Coefs = {
|
||||||
a: 1, b: 0, c: 0,
|
a: 1, b: 0, c: 0,
|
||||||
@ -25,7 +25,7 @@ export const xform1Coefs = {
|
|||||||
d: 1.381068, e: -1.381068, f: 0,
|
d: 1.381068, e: -1.381068, f: 0,
|
||||||
}
|
}
|
||||||
export const xform1CoefsPost = identityCoefs;
|
export const xform1CoefsPost = identityCoefs;
|
||||||
export const xform1Variations = [
|
export const xform1Variations: VariationBlend = [
|
||||||
[1, julia]
|
[1, julia]
|
||||||
]
|
]
|
||||||
export const xform1Color = 0;
|
export const xform1Color = 0;
|
||||||
@ -39,7 +39,7 @@ export const xform2CoefsPost = {
|
|||||||
a: 1, b: 0, c: 0.241352,
|
a: 1, b: 0, c: 0.241352,
|
||||||
d: 0, e: 1, f: 0.271521,
|
d: 0, e: 1, f: 0.271521,
|
||||||
}
|
}
|
||||||
export const xform2Variations = [
|
export const xform2Variations: VariationBlend = [
|
||||||
[1, linear],
|
[1, linear],
|
||||||
[1, popcorn(xform2Coefs)]
|
[1, popcorn(xform2Coefs)]
|
||||||
]
|
]
|
||||||
@ -51,7 +51,7 @@ export const xform3Coefs = {
|
|||||||
d: 0.740356, e: -1.455964, f: -0.362059,
|
d: 0.740356, e: -1.455964, f: -0.362059,
|
||||||
}
|
}
|
||||||
export const xform3CoefsPost = identityCoefs;
|
export const xform3CoefsPost = identityCoefs;
|
||||||
export const xform3Variations = [
|
export const xform3Variations: VariationBlend = [
|
||||||
[1, pdj(pdjParams)]
|
[1, pdj(pdjParams)]
|
||||||
];
|
];
|
||||||
export const xform3Color = 0.349;
|
export const xform3Color = 0.349;
|
||||||
@ -61,7 +61,7 @@ export const xformFinalCoefs = {
|
|||||||
d: 0, e: 2, f: 0
|
d: 0, e: 2, f: 0
|
||||||
}
|
}
|
||||||
export const xformFinalCoefsPost = identityCoefs;
|
export const xformFinalCoefsPost = identityCoefs;
|
||||||
export const xformFinalVariations = [
|
export const xformFinalVariations: VariationBlend = [
|
||||||
[1, julia]
|
[1, julia]
|
||||||
]
|
]
|
||||||
export const xformFinalColor = 0;
|
export const xformFinalColor = 0;
|
||||||
|
2
blog/2024-11-15-playing-with-fire/src/variationBlend.ts
Normal file
2
blog/2024-11-15-playing-with-fire/src/variationBlend.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import {Variation} from "./variation";
|
||||||
|
export type VariationBlend = [number, Variation][];
|
Loading…
Reference in New Issue
Block a user