gokul/flex-led-wristband
A flexible, rechargeable touch wristband PCB with MCP73831 Li-ion charging, switched 3.0 V regulation, ATtiny402 control, TTP223 capacitive touch sensing, and six charlieplexed red LEDs plus power/charge indicators.
- Version
- 0.0.3
- License
- unset
- Stars
- 0
lib/outline.ts
type Point = { x: number; y: number };
// Circular one-millimetre fillets, discretised to at most six degrees per chord.
// This includes the concave shoulder corners, where a flex outline can tear.
function roundCorners(vertices: Point[], radius: number): Point[] {
return vertices.flatMap((vertex, index) => {
const previous = vertices[(index + vertices.length - 1) % vertices.length];
const next = vertices[(index + 1) % vertices.length];
const previousLength = Math.hypot(
previous.x - vertex.x,
previous.y - vertex.y,
);
const nextLength = Math.hypot(next.x - vertex.x, next.y - vertex.y);
const previousDirection = {
x: (previous.x - vertex.x) / previousLength,
y: (previous.y - vertex.y) / previousLength,
};
const nextDirection = {
x: (next.x - vertex.x) / nextLength,
y: (next.y - vertex.y) / nextLength,
};
const angle = Math.acos(
previousDirection.x * nextDirection.x +
previousDirection.y * nextDirection.y,
);
const tangentDistance = Math.min(
radius / Math.tan(angle / 2),
previousLength / 3,
nextLength / 3,
);
const actualRadius = tangentDistance * Math.tan(angle / 2);
const bisectorLength = Math.hypot(
previousDirection.x + nextDirection.x,
previousDirection.y + nextDirection.y,
);
const centerDistance = actualRadius / Math.sin(angle / 2);
const center = {
x:
vertex.x +
((previousDirection.x + nextDirection.x) * centerDistance) /
bisectorLength,
y:
vertex.y +
((previousDirection.y + nextDirection.y) * centerDistance) /
bisectorLength,
};
const startAngle = Math.atan2(
vertex.y + previousDirection.y * tangentDistance - center.y,
vertex.x + previousDirection.x * tangentDistance - center.x,
);
const endAngle = Math.atan2(
vertex.y + nextDirection.y * tangentDistance - center.y,
vertex.x + nextDirection.x * tangentDistance - center.x,
);
let sweep = endAngle - startAngle;
while (sweep > Math.PI) sweep -= 2 * Math.PI;
while (sweep < -Math.PI) sweep += 2 * Math.PI;
const segments = Math.ceil(Math.abs(sweep) / (Math.PI / 30));
return Array.from({ length: segments + 1 }, (_, step) => ({
x:
center.x +
actualRadius * Math.cos(startAngle + (sweep * step) / segments),
y:
center.y +
actualRadius * Math.sin(startAngle + (sweep * step) / segments),
}));
});
}
export const boardOutline = roundCorners(
[
{ x: -45, y: -5 },
{ x: -17, y: -5 },
{ x: -15, y: -16 },
{ x: 15, y: -16 },
{ x: 17, y: -5 },
{ x: 45, y: -5 },
{ x: 45, y: 5 },
{ x: 17, y: 5 },
{ x: 15, y: 16 },
{ x: -15, y: 16 },
{ x: -17, y: 5 },
{ x: -45, y: 5 },
],
1,
);