chaos/src/Chaos.svelte

148 lines
4.2 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
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}
/>