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.
 
 
 

147 lines
4.2 KiB

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