Newer
Older
fractals / src / SpiralFractal.ts
import { Polygon } from "./Polygon";
import { zip, midPoint } from "./utils";

function inwardSpiralStep(
    {shrinkRate,firstShrinkRate}:ISpiralParameters,
    input:Polygon,
    index:number,
): Polygon
{
    const lines = zip(input, input, 1);
    let factor = shrinkRate;
    if(index === 0)
    {
        factor = firstShrinkRate;
    }
    const newPoints = lines.map(([a,b]) =>
        midPoint(a, b, factor));
    return newPoints;
}

/*
    The fractal is a function with very few input parameters
    and a lot of outputs. It makes the most sense to conceptualize
    the output as a sequence.
    But instead of a function, that produces a sequence of data,
    it would be better, to produce a sequence of functions.
    Each function produces one element of the function sequence.
    The function, that produces the sequence of functions,
    is the fractal itself.
    The SVG objects stores the state, which results from an
    execution of the function sequence.
*/

export type FractalIterator<P,T> = (params:P, previous:T, index:number) => T;
export type FractalSequenceElement<P,T> = (params:P) => T;

export function Fractal<P,T>(
    startValue:T,
    iterator:FractalIterator<P,T>,
    length:number,
): Array<FractalSequenceElement<P,T>>
{
    const states = new Array<T>(length+1);
    states[0] = startValue;
    const output:Array<FractalSequenceElement<P, T>> = [
        () => startValue,
    ];
    for(let i=0;i<length;i++)
    {
        output.push((p:P) => {
            states[i+1] = iterator(p, states[i], i);
            return states[i+1];
        });
    }
    return output;
}

export interface ISpiralParameters
{
    firstShrinkRate:number;
    shrinkRate:number;
}

export function InwardSpiral(startShape:Polygon, length:number):
    Array<FractalSequenceElement<ISpiralParameters, Polygon>>
{
    return Fractal<ISpiralParameters, Polygon>(
        startShape,
        inwardSpiralStep,
        length,
    );
}