= (params:P, previous:T, index:number) => T;
+export type FractalSequenceElement = (params:P) => T;
+
+export function Fractal
(
+ startValue:T,
+ iterator:FractalIterator
,
+ length:number,
+): Array>
+{
+ const states = new Array(length+1);
+ states[0] = startValue;
+ const output:Array> = [
+ () => startValue,
+ ];
+ for(let i=0;i {
+ 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>
+{
+ return Fractal(
+ startShape,
+ inwardSpiralStep,
+ length,
+ );
}
diff --git a/public/index.html b/public/index.html
index 5288ba7..3f2f32c 100644
--- a/public/index.html
+++ b/public/index.html
@@ -39,12 +39,8 @@
Inward spiral
-
-
Randomized
-
+
Square
+
Triangle
diff --git a/src/BaseShapes.ts b/src/BaseShapes.ts
index dacf94c..e89d649 100644
--- a/src/BaseShapes.ts
+++ b/src/BaseShapes.ts
@@ -6,26 +6,36 @@
const center = new Point(max / 2, max / 2);
const hexagon = createHexagonInSquare(max);
const lines = zip(hexagon, hexagon, 1);
- const base = lines.map(([a, b]) => {
- return new Polygon(...[
- a, b, center,
- ]);
- });
+ const hexagonTriangles = lines.map(([a, b]) => [a, b, center]);
+
const corner1 = new Point(max, 0);
const corner2 = new Point(0, 0);
const corner3 = new Point(0, max);
const corner4 = new Point(max, max);
- base.push(new Polygon(...[hexagon[0], corner1, hexagon[1]]));
- base.push(new Polygon(...[hexagon[2], corner2, hexagon[3]]));
- base.push(new Polygon(...[hexagon[3], corner3, hexagon[4]]));
- base.push(new Polygon(...[hexagon[5], corner4, hexagon[0]]));
- return base;
+ const cornerTriangles = [
+ [hexagon[0], corner1, hexagon[1]],
+ [hexagon[2], corner2, hexagon[3]],
+ [hexagon[3], corner3, hexagon[4]],
+ [hexagon[5], corner4, hexagon[0]],
+ ];
+ return [
+ cornerTriangles[0],
+ hexagonTriangles[0],
+ hexagonTriangles[1],
+ hexagonTriangles[2],
+ cornerTriangles[1],
+ cornerTriangles[2],
+ hexagonTriangles[3],
+ hexagonTriangles[4],
+ hexagonTriangles[5],
+ cornerTriangles[3],
+ ];
}
export function createSquare(sideLength:number): Polygon
{
const max = sideLength - 1;
- const square = new Polygon();
+ const square:Polygon = [];
square.push(new Point(0,0));
square.push(new Point(0,max));
square.push(new Point(max,max));
@@ -33,24 +43,24 @@
return square;
}
-export function createHexagonInSquare(sideLength:number): Polygon
+function createHexagonInSquare(sideLength:number): Polygon
{
const max = sideLength - 1;
- return new Polygon(...[
+ return [
new Point(max, max/2),
new Point(3 * max / 4, 0),
new Point(max / 4, 0),
new Point(0, max / 2),
new Point(max/4, max),
new Point(3*max/4, max),
- ]);
+ ];
}
export function createTriangle(sideLength:number): Polygon
{
- return new Polygon(
+ return [
new Point(sideLength/2, 0),
new Point(0,sideLength),
new Point(sideLength, sideLength),
- );
+ ];
}
diff --git a/src/Polygon.ts b/src/Polygon.ts
index 85430f6..3e5a6b4 100644
--- a/src/Polygon.ts
+++ b/src/Polygon.ts
@@ -6,17 +6,4 @@
) {}
}
-export class Polygon extends Array
-{
- public translate(x:number, y:number): Polygon
- {
- const newCorners = new Polygon();
-
- for(const corner of this)
- {
- newCorners.push(new Point(corner.x + x, corner.y + y));
- }
-
- return newCorners;
- }
-}
+export type Polygon = Point[];
diff --git a/src/SpiralFractal.ts b/src/SpiralFractal.ts
index 3a3c477..1a42ef9 100644
--- a/src/SpiralFractal.ts
+++ b/src/SpiralFractal.ts
@@ -1,29 +1,72 @@
import { Polygon } from "./Polygon";
-import { zip, midPoint, randomizeScalar } from "./utils";
+import { zip, midPoint } from "./utils";
-function inwardSpiralStep(input:Polygon, scalar:number, randomizeShrinkRate:number): Polygon
+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, randomizeScalar(scalar, randomizeShrinkRate, 0, 0.9)));
- return new Polygon(...newPoints);
+ midPoint(a, b, factor));
+ return newPoints;
}
-export function createInwardSpiral(
- startShape:Polygon,
- maxiterations:number,
- shrinkRate:number,
- randomizeShrinkRate:number,
-): Polygon[]
-{
- const cycle:Polygon[] = [startShape];
- let pNext:Polygon;
- for(let i=0;i = (params:P, previous:T, index:number) => T;
+export type FractalSequenceElement = (params:P) => T;
+
+export function Fractal
(
+ startValue:T,
+ iterator:FractalIterator
,
+ length:number,
+): Array>
+{
+ const states = new Array(length+1);
+ states[0] = startValue;
+ const output:Array> = [
+ () => startValue,
+ ];
+ for(let i=0;i {
+ 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>
+{
+ return Fractal(
+ startShape,
+ inwardSpiralStep,
+ length,
+ );
}
diff --git a/src/SvgRenderer.ts b/src/SvgRenderer.ts
index e1aea8b..a03c236 100644
--- a/src/SvgRenderer.ts
+++ b/src/SvgRenderer.ts
@@ -1,23 +1,14 @@
import { Polygon } from "./Polygon";
+import { FractalSequenceElement, ISpiralParameters } from "./SpiralFractal";
+import { zip } from "./utils";
-function polygonToSvg(
- svgRootElement:SVGSVGElement,
- polygon:Polygon,
+function createSvgPolygon(
{ 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;
}
@@ -28,28 +19,91 @@
};
}
-export function fractalToSvg(fractal:Polygon[][], sideLength:number): SVGSVGElement
+export function createSvgRootElement(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";
+ // 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";
+ return el;
+}
- let colorStrength = 0;
+export function createBasePolygons(length:number): SVGPolygonElement[]
+{
const initialColorStrength = 20;
- for(const segment of fractal)
+ let colorStrength = initialColorStrength;
+ const polygons:SVGPolygonElement[] = [];
+ for(let i=0;i,
+ ) { }
+
+ public update(params:ISpiralParameters): void
+ {
+ const newState = this.fn(params);
+ for(let i=0;i this.polygon.points.length)
+ {
+ // add additional points
+ for(let i=this.polygon.points.length; i>,
+ ) {
+ if(this.polygons.length !== sequence.length)
+ {
+ throw new Error("SVGPolygons and sequence functions have a different count.");
+ }
+
+ this.elements =
+ zip(polygons, sequence, 0)
+ .map(([p,s]) => new SvgSpiralPolygon(svgRootElement, p, s));
+ }
+
+ public update(params:ISpiralParameters): void
+ {
+ this.elements.forEach(e => e.update(params));
+ }
}
diff --git a/public/index.html b/public/index.html
index 5288ba7..3f2f32c 100644
--- a/public/index.html
+++ b/public/index.html
@@ -39,12 +39,8 @@
Inward spiral
-
-
Randomized
-
+
Square
+
Triangle
diff --git a/src/BaseShapes.ts b/src/BaseShapes.ts
index dacf94c..e89d649 100644
--- a/src/BaseShapes.ts
+++ b/src/BaseShapes.ts
@@ -6,26 +6,36 @@
const center = new Point(max / 2, max / 2);
const hexagon = createHexagonInSquare(max);
const lines = zip(hexagon, hexagon, 1);
- const base = lines.map(([a, b]) => {
- return new Polygon(...[
- a, b, center,
- ]);
- });
+ const hexagonTriangles = lines.map(([a, b]) => [a, b, center]);
+
const corner1 = new Point(max, 0);
const corner2 = new Point(0, 0);
const corner3 = new Point(0, max);
const corner4 = new Point(max, max);
- base.push(new Polygon(...[hexagon[0], corner1, hexagon[1]]));
- base.push(new Polygon(...[hexagon[2], corner2, hexagon[3]]));
- base.push(new Polygon(...[hexagon[3], corner3, hexagon[4]]));
- base.push(new Polygon(...[hexagon[5], corner4, hexagon[0]]));
- return base;
+ const cornerTriangles = [
+ [hexagon[0], corner1, hexagon[1]],
+ [hexagon[2], corner2, hexagon[3]],
+ [hexagon[3], corner3, hexagon[4]],
+ [hexagon[5], corner4, hexagon[0]],
+ ];
+ return [
+ cornerTriangles[0],
+ hexagonTriangles[0],
+ hexagonTriangles[1],
+ hexagonTriangles[2],
+ cornerTriangles[1],
+ cornerTriangles[2],
+ hexagonTriangles[3],
+ hexagonTriangles[4],
+ hexagonTriangles[5],
+ cornerTriangles[3],
+ ];
}
export function createSquare(sideLength:number): Polygon
{
const max = sideLength - 1;
- const square = new Polygon();
+ const square:Polygon = [];
square.push(new Point(0,0));
square.push(new Point(0,max));
square.push(new Point(max,max));
@@ -33,24 +43,24 @@
return square;
}
-export function createHexagonInSquare(sideLength:number): Polygon
+function createHexagonInSquare(sideLength:number): Polygon
{
const max = sideLength - 1;
- return new Polygon(...[
+ return [
new Point(max, max/2),
new Point(3 * max / 4, 0),
new Point(max / 4, 0),
new Point(0, max / 2),
new Point(max/4, max),
new Point(3*max/4, max),
- ]);
+ ];
}
export function createTriangle(sideLength:number): Polygon
{
- return new Polygon(
+ return [
new Point(sideLength/2, 0),
new Point(0,sideLength),
new Point(sideLength, sideLength),
- );
+ ];
}
diff --git a/src/Polygon.ts b/src/Polygon.ts
index 85430f6..3e5a6b4 100644
--- a/src/Polygon.ts
+++ b/src/Polygon.ts
@@ -6,17 +6,4 @@
) {}
}
-export class Polygon extends Array
-{
- public translate(x:number, y:number): Polygon
- {
- const newCorners = new Polygon();
-
- for(const corner of this)
- {
- newCorners.push(new Point(corner.x + x, corner.y + y));
- }
-
- return newCorners;
- }
-}
+export type Polygon = Point[];
diff --git a/src/SpiralFractal.ts b/src/SpiralFractal.ts
index 3a3c477..1a42ef9 100644
--- a/src/SpiralFractal.ts
+++ b/src/SpiralFractal.ts
@@ -1,29 +1,72 @@
import { Polygon } from "./Polygon";
-import { zip, midPoint, randomizeScalar } from "./utils";
+import { zip, midPoint } from "./utils";
-function inwardSpiralStep(input:Polygon, scalar:number, randomizeShrinkRate:number): Polygon
+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, randomizeScalar(scalar, randomizeShrinkRate, 0, 0.9)));
- return new Polygon(...newPoints);
+ midPoint(a, b, factor));
+ return newPoints;
}
-export function createInwardSpiral(
- startShape:Polygon,
- maxiterations:number,
- shrinkRate:number,
- randomizeShrinkRate:number,
-): Polygon[]
-{
- const cycle:Polygon[] = [startShape];
- let pNext:Polygon;
- for(let i=0;i = (params:P, previous:T, index:number) => T;
+export type FractalSequenceElement = (params:P) => T;
+
+export function Fractal
(
+ startValue:T,
+ iterator:FractalIterator
,
+ length:number,
+): Array>
+{
+ const states = new Array(length+1);
+ states[0] = startValue;
+ const output:Array> = [
+ () => startValue,
+ ];
+ for(let i=0;i {
+ 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>
+{
+ return Fractal(
+ startShape,
+ inwardSpiralStep,
+ length,
+ );
}
diff --git a/src/SvgRenderer.ts b/src/SvgRenderer.ts
index e1aea8b..a03c236 100644
--- a/src/SvgRenderer.ts
+++ b/src/SvgRenderer.ts
@@ -1,23 +1,14 @@
import { Polygon } from "./Polygon";
+import { FractalSequenceElement, ISpiralParameters } from "./SpiralFractal";
+import { zip } from "./utils";
-function polygonToSvg(
- svgRootElement:SVGSVGElement,
- polygon:Polygon,
+function createSvgPolygon(
{ 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;
}
@@ -28,28 +19,91 @@
};
}
-export function fractalToSvg(fractal:Polygon[][], sideLength:number): SVGSVGElement
+export function createSvgRootElement(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";
+ // 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";
+ return el;
+}
- let colorStrength = 0;
+export function createBasePolygons(length:number): SVGPolygonElement[]
+{
const initialColorStrength = 20;
- for(const segment of fractal)
+ let colorStrength = initialColorStrength;
+ const polygons:SVGPolygonElement[] = [];
+ for(let i=0;i,
+ ) { }
+
+ public update(params:ISpiralParameters): void
+ {
+ const newState = this.fn(params);
+ for(let i=0;i this.polygon.points.length)
+ {
+ // add additional points
+ for(let i=this.polygon.points.length; i>,
+ ) {
+ if(this.polygons.length !== sequence.length)
+ {
+ throw new Error("SVGPolygons and sequence functions have a different count.");
+ }
+
+ this.elements =
+ zip(polygons, sequence, 0)
+ .map(([p,s]) => new SvgSpiralPolygon(svgRootElement, p, s));
+ }
+
+ public update(params:ISpiralParameters): void
+ {
+ this.elements.forEach(e => e.update(params));
+ }
}
diff --git a/src/index.ts b/src/index.ts
index 2120815..210a87e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,33 +1,87 @@
-import { createInwardSpiral } from "./SpiralFractal";
+import { InwardSpiral, FractalSequenceElement, ISpiralParameters } from "./SpiralFractal";
import { createSquare, createTriangle, createHexagonalBase } from "./BaseShapes";
-import { fractalToSvg } from "./SvgRenderer";
+import { createSvgRootElement, createBasePolygons, SvgSpiralBody } from "./SvgRenderer";
+import { Polygon } from "./Polygon";
+
+function makeSpirals(
+ sideLength:number,
+ iterations:number,
+ startShapes:Polygon[],
+)
+{
+ const root = createSvgRootElement(sideLength);
+ const segments:SvgSpiralBody[] = [];
+ for (const startShape of startShapes)
+ {
+ const spiral = InwardSpiral(startShape, iterations);
+ const svgPolygons = createBasePolygons(spiral.length);
+ svgPolygons.forEach(p => root.appendChild(p));
+ segments.push(new SvgSpiralBody(root, svgPolygons, spiral));
+ }
+
+ return segments;
+}
+
+function createAnimationStep(
+ shrinkRate:number,
+ svgSpiral:SvgSpiralBody,
+ speed:number = 0.0025,
+)
+{
+ const maxTime = shrinkRate;
+ let time = 0;
+ const animationStep = () => {
+ svgSpiral.update({shrinkRate, firstShrinkRate:time});
+ time = (time + speed) % maxTime;
+ if(time < 0)
+ {
+ time += maxTime;
+ }
+ };
+ return animationStep;
+}
+
+function animatedSpiralFractal(
+ sideLength:number,
+ iterations:number,
+ shrinkRate:number,
+ startShapes:Polygon[],
+ htmlDivId:string,
+)
+{
+ const animationSpeed = 0.005;
+ const svgSpirals = makeSpirals(sideLength, iterations, startShapes);
+ const divSpiral = document.getElementById(htmlDivId) as HTMLDivElement;
+ divSpiral.appendChild(svgSpirals[0].svgRootElement);
+ svgSpirals.forEach(s => s.update({shrinkRate, firstShrinkRate:shrinkRate}));
+
+ return svgSpirals.map((s,i) => createAnimationStep(shrinkRate, s, ((2*((i+1)%2))-1) * animationSpeed));
+}
function pageInit(): void
{
const sideLength = 400;
const iterations = 50;
const shrinkRate = 0.1;
- const randomizeShrinkRate = 0.3;
const square = createSquare(sideLength);
-
- const fractal = [createInwardSpiral(square, iterations, shrinkRate, 0)];
- const divSpiral = document.getElementById("spiral") as HTMLDivElement;
- divSpiral.appendChild(fractalToSvg(fractal, sideLength));
-
- const randomizedFractal = [createInwardSpiral(square, iterations, shrinkRate, randomizeShrinkRate)];
- const divRandomizedSpiral = document.getElementById("randomizedSpiral") as HTMLDivElement;
- divRandomizedSpiral.appendChild(fractalToSvg(randomizedFractal, sideLength));
+ const squareAnimation = animatedSpiralFractal(sideLength, iterations, shrinkRate, [square], "square");
const triangle = createTriangle(sideLength);
- const triangleFractal = [createInwardSpiral(triangle, iterations, 1-0.06, 0)];
- const divTriangle = document.getElementById("triangle") as HTMLDivElement;
- divTriangle.appendChild(fractalToSvg(triangleFractal, sideLength));
+ const triangleAnimation = animatedSpiralFractal(sideLength, iterations, shrinkRate, [triangle], "triangle");
const hexagonalBase = createHexagonalBase(sideLength);
- const hexagonalFractal = hexagonalBase.map(h => createInwardSpiral(h, iterations, 0.1, 0));
- const divHexagonal = document.getElementById("hexagonal") as HTMLDivElement;
- divHexagonal.appendChild(fractalToSvg(hexagonalFractal, sideLength));
+ const hexagonalAnimations =
+ animatedSpiralFractal(sideLength, iterations, shrinkRate, hexagonalBase, "hexagonal");
+
+ const animation = () => {
+ squareAnimation.forEach(a => a());
+ triangleAnimation.forEach(a => a());
+ hexagonalAnimations.forEach(a => a());
+ requestAnimationFrame(animation);
+ };
+ requestAnimationFrame(animation);
+
}
document.addEventListener("DOMContentLoaded", pageInit);