148 lines
4.2 KiB
Svelte
148 lines
4.2 KiB
Svelte
<!--
|
||
Simulate a chaos game on an iterated function set (IFS)
|
||
and plot the result on a canvas of given dimensions.
|
||
-->
|
||
|
||
<script>
|
||
import { onMount, onDestroy, tick, afterUpdate } from 'svelte';
|
||
import { Matrix } from 'ml-matrix';
|
||
import { choose } from './util.js';
|
||
|
||
/** Width of the canvas on which the chaos figure is rendered. */
|
||
export let width = 500;
|
||
|
||
/** Height of the canvas on which the chaos figure is rendered. */
|
||
export let height = 500;
|
||
|
||
/** Total number of iterations to perform. */
|
||
export let totalIterations = 100000;
|
||
|
||
/** Number of iterations to perform in one frame. */
|
||
export let iterationsPerFrame = 1000;
|
||
|
||
/**
|
||
* Iterated function set to use for rendering the figure as a list of
|
||
* pairs, each containing the weight of the function and the function
|
||
* as a 3×3 matrix.
|
||
*/
|
||
export let ifs = [[1, Matrix.identity(3)]];
|
||
|
||
/** Initial point for the iteration, in homogeneous coordinates. */
|
||
export let start = Matrix.columnVector([0, 0, 1]);
|
||
|
||
// Canvas and drawing context on which the figure is rendered
|
||
let canvas;
|
||
let ctx;
|
||
|
||
// Handle to the next scheduled frame
|
||
let nextFrame = null;
|
||
|
||
/**
|
||
* Perform random chaos iteration on the given point, leaving traces
|
||
* behind as the iteration goes.
|
||
*
|
||
* @param point Starting point for the iteration.
|
||
* @param ifs Iterated function set to use.
|
||
* @param iterations Number of iterations to perform.
|
||
*/
|
||
const randomIterate = (point, ifs, iterations) =>
|
||
{
|
||
// Only perform a limited amount of iterations and
|
||
// defer the remaining ones to the next frame
|
||
for (let i = 0; i < iterationsPerFrame; ++i)
|
||
{
|
||
const [_, matrix] = choose(ifs);
|
||
point = matrix.mmul(point);
|
||
|
||
const x = Math.floor(point.get(0, 0));
|
||
const y = Math.floor(point.get(1, 0));
|
||
|
||
if (x >= 0 && x < width && y >= 0 && y < height)
|
||
{
|
||
ctx.fillRect(x, y, 1, 1);
|
||
}
|
||
}
|
||
|
||
if (iterations > iterationsPerFrame)
|
||
{
|
||
nextFrame = window.requestAnimationFrame(() => randomIterate(
|
||
point, ifs,
|
||
iterations - iterationsPerFrame
|
||
));
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Perform deterministic chaos iteration on each of the given points,
|
||
* leaving traces behind as the iteration goes.
|
||
*
|
||
* @param points Seed points, modified in-place.
|
||
* @param index Index from which to iterate in the points array.
|
||
* @param ifs Iterated function set to use.
|
||
* @param iterations Number of iterations to perform.
|
||
*/
|
||
const deterministicIterate = (points, index, ifs, iterations) =>
|
||
{
|
||
let i = index;
|
||
|
||
for (
|
||
let frameIterations = 0;
|
||
frameIterations < iterationsPerFrame;
|
||
frameIterations += ifs.length,
|
||
i = (i + ifs.length) % points.length
|
||
)
|
||
{
|
||
points.splice(
|
||
i, 1,
|
||
...ifs.map(([_, matrix]) =>
|
||
{
|
||
const nextPoint = matrix.mmul(points[i]);
|
||
const x = Math.floor(nextPoint.get(0, 0));
|
||
const y = Math.floor(nextPoint.get(1, 0));
|
||
|
||
if (x >= 0 && x < width && y >= 0 && y < height)
|
||
{
|
||
ctx.fillRect(x, y, 1, 1);
|
||
}
|
||
|
||
return nextPoint;
|
||
})
|
||
);
|
||
}
|
||
|
||
if (iterations > iterationsPerFrame)
|
||
{
|
||
nextFrame = window.requestAnimationFrame(() => deterministicIterate(
|
||
points, i, ifs,
|
||
iterations - iterationsPerFrame
|
||
));
|
||
}
|
||
};
|
||
|
||
/** Interrupt the iteration process. */
|
||
const stopLoop = () =>
|
||
{
|
||
window.cancelAnimationFrame(nextFrame);
|
||
};
|
||
|
||
$: {
|
||
stopLoop();
|
||
|
||
if (ctx && ifs.length)
|
||
{
|
||
ctx.clearRect(0, 0, width, height);
|
||
ctx.fillStyle = 'black';
|
||
deterministicIterate([start], 0, ifs, totalIterations);
|
||
}
|
||
}
|
||
|
||
onMount(() => ctx = canvas.getContext('2d'));
|
||
onDestroy(() => stopLoop());
|
||
</script>
|
||
|
||
<canvas
|
||
bind:this={canvas}
|
||
draggable=false
|
||
width={width} height={height}
|
||
/>
|