Newer
Older
fractals / src / FractalSVG.ts
import { Polygon, Point } from "./Polygon";

function inwardSpiral(input:Polygon, scalar:number, randomizeShrinkRate:number): Polygon
{
    const output = new Polygon();

    let a:Point;
    let b:Point;
    let cx: number;
    let cy: number;
    let rate:number;
    for(let i=0;i<input.length;i++)
    {
        rate = scalar;
        if(randomizeShrinkRate !== 0)
        {
            rate += randomizeShrinkRate * 2 * (Math.random() -0.5);
            rate = Math.min(0.9, Math.max(0, rate));
        }
        a = input[i];
        b = input[(i+1)%input.length];

        cx = rate * (b.x - a.x) + a.x;
        cy = rate * (b.y - a.y) + a.y;

        output.push(new Point(cx, cy));
    }

    return output;
}

function createInwardSpiral(
    input:Polygon,
    maxiterations:number,
    shrinkRate:number,
    randomizeShrinkRate:number,
): Polygon[]
{
    const cycle:Polygon[] = [input];
    let pNext:Polygon;
    for(let i=0;i<maxiterations;i++)
    {
        pNext = inwardSpiral(input, shrinkRate, randomizeShrinkRate);
        cycle.push(pNext);
        input = pNext;
    }

    return cycle;
}

function polygonToSvg(
    svgRootElement:SVGSVGElement,
    polygon:Polygon,
    { fill }:{fill:string | null},
): SVGPolygonElement
{
    // Note: it is not possible to create any svg element without the namespace.
    const polygonSvg = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
    polygonSvg.style.fill = fill;
    for(const corner of polygon)
    {
        // This is the only way to create an SVGPoint:
        const point = svgRootElement.createSVGPoint();
        point.x = corner.x;
        point.y = corner.y;
        polygonSvg.points.appendItem(point);
    }

    return polygonSvg;
}

function polygonStyle(strength:number)
{
    return {
        fill: `rgb(0,0,${Math.floor(strength)})`,
    };
}

export function fractalToSvg(fractal:Polygon[][], sideLength:number): SVGSVGElement
{
    // Note: it is not possible to create any svg element without the namespace.
    const el = document.createElementNS("http://www.w3.org/2000/svg", "svg") as SVGSVGElement;
    el.width.baseVal.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX, sideLength);
    el.height.baseVal.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX, sideLength);
    el.style.fill = "none";
    el.style.stroke = "purple";
    el.style.strokeWidth = "1";

    let colorStrength = 0;
    const initialColorStrength = 20;
    for(const segment of fractal)
    {
        colorStrength = initialColorStrength;
        for(const iteration of segment)
        {
            const p = polygonToSvg(el, iteration, polygonStyle(colorStrength));
            el.appendChild(p);
            colorStrength += (255-initialColorStrength)/segment.length;
        }
    }

    return el;
}

export function createSquare(side:number): Polygon
{
    const square = new Polygon();
    square.push(new Point(0,0));
    square.push(new Point(0,side));
    square.push(new Point(side,side));
    square.push(new Point(side,0));
    return square;
}

export function inwardSpiralFractal(shape:Polygon, iterations:number, shrinkRate:number, randomize:number): Polygon[][]
{
    const fractal = [
        createInwardSpiral(shape, iterations, shrinkRate, randomize),
    ];
    return fractal;
}