Creating fractals with the chaos game
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

224 lines
5.9 KiB

<script>
import { onMount } from 'svelte';
import { writable, derived } from 'svelte/store';
import { Matrix } from 'ml-matrix';
import Chaos from './Chaos.svelte';
import Point from './Point.svelte';
// Page dimensions
let pageWidth;
let pageHeight;
// Controls dimensions
const controlsWidth = 200;
// Render dimensions
$: renderWidth = pageWidth - controlsWidth;
$: renderHeight = pageHeight;
// Number of points
let count = 3;
// Whether controls are shown
let controls = true;
// Fraction
const ratios = writable([]);
// Bounding points of the fractal
const points = writable([]);
// Center of the fractal
const center = derived(points, ($points, set) =>
{
set(Matrix.div(
$points.reduce(
(prev, point) => Matrix.add(prev, point),
Matrix.zeros(3, 1)
),
$points.length
));
});
// Derived IFS
const ifs = derived([ratios, points], ([$ratios, $points], set) =>
{
set($points.map((point, index) => [
1,
new Matrix([
[$ratios[index], 0, point.get(0, 0) * (1 - $ratios[index])],
[0, $ratios[index], point.get(1, 0) * (1 - $ratios[index])],
[0, 0, 1]
])
]))
});
const myifs = [
[1, new Matrix([
[0, 0, 0],
[0, 0.16, 0],
[0, 0, 1]
])],
[85, new Matrix([
[0.85, 0.04, 0],
[-0.04, 0.85, 160],
[0, 0, 1]
])],
[7, new Matrix([
[0.20, -0.26, 0],
[0.23, 0.22, 160],
[0, 0, 1]
])],
[7, new Matrix([
[-0.15, 0.28, 0],
[0.26, 0.24, 44],
[0, 0, 1]
])]
];
for (let f of myifs)
{
/* f[1] = new Matrix([ */
/* [1, 0, 100], */
/* [0, 1, 100], */
/* [0, 0, 1] */
/* ]).mmul(f[1]); */
}
console.log(myifs);
$: {
// Generate a regular polygon with the given number of vertices
// and half ratio for each vertex
const pageCenter = [renderWidth / 2, renderHeight / 2];
const radius = Math.min(renderWidth, renderHeight) * 0.4;
points.set(
Array.from({length: count}).map(
(_, index) => index * 2 * Math.PI / count - Math.PI / 2
).map(angle => Matrix.columnVector([
pageCenter[0] + Math.cos(angle) * radius,
pageCenter[1] + Math.sin(angle) * radius,
1
]))
);
ratios.set(
Array.from({length: count}).fill(0.5)
);
}
</script>
<style>
.app
{
display: flex;
width: 100%;
height: 100%;
}
.controls
{
flex: 1;
}
.render
{
flex: 1;
position: relative;
}
.render .overlay
{
position: absolute;
top: 0;
left: 0;
}
</style>
<div class="app" bind:clientWidth={pageWidth} bind:clientHeight={pageHeight}>
<aside class="controls" style="width: {controlsWidth}px">
<p>
<button on:click={() => ++count}>Add point</button>
<button on:click={() => --count}>Remove point</button>
</p>
<p>
{#if controls}
Controls visible.
<button on:click={() => controls = false}>Hide</button>
{:else}
Controls hidden.
<button on:click={() => controls = true}>Show</button>
{/if}
</p>
</aside>
<main class="render" style="width: {renderWidth}px">
<Chaos
width={renderWidth} height={renderHeight}
start={$center} ifs={$ifs}
/>
{#if controls}
<svg
class="overlay"
width={renderWidth} height={renderHeight}
viewBox="0 0 {renderWidth} {renderHeight}"
>
{#each $points as point}
<line
stroke="#333"
x1={$center.get(0, 0)}
y1={$center.get(1, 0)}
x2={point.get(0, 0)}
y2={point.get(1, 0)}
/>
{/each}
</svg>
{#each $points as point, index}
<Point
x={point.get(0, 0)}
y={point.get(1, 0)}
on:move={evt => points.set([
...$points.slice(0, index),
Matrix.columnVector([evt.detail.x, evt.detail.y, 1]),
...$points.slice(index + 1)
])}
/>
{/each}
{#each $ifs as current, index}
<Point
x={current[1].mmul($center).get(0, 0)}
y={current[1].mmul($center).get(1, 0)}
on:move={evt => {
const point = Matrix.columnVector([evt.detail.x, evt.detail.y, 1]);
const vec1 = Matrix.sub($points[index], $center);
const vec2 = Matrix.sub(point, $center);
const nextRatio = 1 - vec1.dot(vec2) / vec1.dot(vec1);
if (evt.detail.shift)
{
ratios.set(
Array.from({length: $ratios.length}).fill(nextRatio)
);
}
else
{
ratios.set([
...$ratios.slice(0, index),
nextRatio,
...$ratios.slice(index + 1)
]);
}
}}
/>
{/each}
{/if}
</main>
</div>