225 lines
5.9 KiB
Svelte
225 lines
5.9 KiB
Svelte
|
<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>
|