tileSvg = (pairs, options) => {
const { n, winged, fgColor, bgColor, debug } = {
n: 4,
fgColor: 'black',
bgColor: 'white',
debug: true,
...options
};
const { modDistance } = modFunctions(options.n || 4);
const distance = ([x0, y0], [x1, y1]) =>
Math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2);
const midpoint = ([x1, y1], [x2, y2]) => [(x1 + x2) / 2, (y1 + y2) / 2];
const reflect = ([x, y], [cx, cy]) => [cx - 2 * (x - cx), cy - 2 * (y - cy)];
const rawSideLength = distance(
[1, 0],
[Math.cos((2 * Math.PI) / n), Math.sin((2 * Math.PI) / n)]
);
let { edgeLength } = {
edgeLength: ((100 / rawSideLength) * 4) / n,
...options
};
let { size } = { size: edgeLength * rawSideLength, ...options };
let raw = arange(n)
.map(i => ((i + .5) / n) * 2 * Math.PI)
.map(θ => [Math.cos(θ), Math.sin(θ)]);
let rawBounds = [
Math.min(...raw.map(([x]) => x)),
Math.min(...raw.map(([_, y]) => y)),
Math.max(...raw.map(([x]) => x)),
Math.max(...raw.map(([_, y]) => y))
];
if (winged) {
const d = 1 / 3; // FIXME
rawBounds[0] -= d;
rawBounds[1] -= d;
rawBounds[2] += d;
rawBounds[3] += d;
}
const scale = size / rawSideLength;
const mapX = x => (x - rawBounds[0]) * scale;
const mapY = y => (y - rawBounds[1]) * scale;
const pa = θ => [mapX(Math.cos(θ)), mapY(Math.sin(θ))]; // angle => point
const v2a = i => (2 * Math.PI * (i + .5)) / n; // vertex index => angle
const pv = i => pa(v2a(i)); // vector index => point
// midpoint of side between vertices i and i+1
const edgeCenter = i => midpoint(pv(i), pv(i + 1));
let sideLength = distance(pv(0), pv(1));
const lineWidth = sideLength / 3;
const polyPath = arange(n)
.map(pv)
.join(' ');
const clipPathId = DOM.uid("clip");
return svg`<svg width=${mapX(rawBounds[2]) + Number(debug)} height=${mapY(
rawBounds[3]
)}
fill=none stroke=none>
<defs>
<clipPath id="${clipPathId.id}">
<polygon points="${polyPath}" />
</clipPath>
</defs>
<polygon points="${polyPath}" fill="${bgColor}"/>
<g stroke="${fgColor}" stroke-width="${lineWidth}" clip-path="${clipPathId}">
${pairs.map(([i, j, fill]) => {
if (i === j) throw "edge indices in pair must be distinct";
const m1 = edgeCenter(i);
const m2 = edgeCenter(j);
const v1 = pv(i);
const v2 = pv(j);
// could instead test whether the intersection is defined
if (2 * Math.abs(j - i) === n) {
const [x1, y1] = m1;
const [x2, y2] = m2;
const pts = [];
if (fill) {
for (let k = i; k != j; ) {
k = (k + 1) % n;
pts.push(pv(k));
}
return svg`<polygon points="${[m1, ...pts, m2].join(
' '
)}" fill="${fgColor}" stroke-width="${lineWidth}"/>`;
}
return svg`<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}"/>`;
}
let c = math.intersect(m1, v1, m2, v2);
if (fill && (j - i + n) % n > n / 2) {
const center = [mapX(0), mapY(0)];
c = reflect(c, center);
}
const [cx, cy] = c;
const r = distance(m1, c);
return svg`<circle cx="${cx}" cy="${cy}" r="${r}" fill="${
fill ? fgColor : 'none'
}"/>`;
})}
</g>
${winged &&
arange(n)
.map(pv)
.map(
([x, y]) =>
svg`<circle cx="${x}" cy="${y}" r="${lineWidth}" fill="${bgColor}"/>`
)}
${winged &&
arange(n)
.map(edgeCenter)
.map(
([x, y]) =>
svg`<circle cx="${x}" cy="${y}" r="${lineWidth /
2}" fill="${fgColor}" />`
)}
${debug &&
svg`<polygon points="${polyPath}" stroke="blue" stroke-dasharray="4"/>`}
</svg>`;
}