.
This commit is contained in:
parent
d8f2407ddf
commit
9689bfcc18
23 changed files with 1815 additions and 955 deletions
|
|
@ -10,6 +10,142 @@ export const exactMm = (value: number) =>
|
|||
new Intl.NumberFormat('en-GB', { maximumFractionDigits: 3 }).format(value);
|
||||
export const viewBox = (box: PreviewBox) => `${box.x} ${box.y} ${box.width} ${box.height}`;
|
||||
|
||||
export interface EdgeDimension {
|
||||
edge: number;
|
||||
length: number;
|
||||
text: string;
|
||||
/** Acute angle to the horizontal board edge; the diagram supplies its slope direction. */
|
||||
angle?: number;
|
||||
start: Point;
|
||||
end: Point;
|
||||
lineStart: Point;
|
||||
lineEnd: Point;
|
||||
normal: Point;
|
||||
label: Point;
|
||||
labelBox: PreviewBox;
|
||||
}
|
||||
|
||||
const moved = (point: Point, direction: Point, distance: number): Point => ({
|
||||
x: point.x + direction.x * distance,
|
||||
y: point.y + direction.y * distance,
|
||||
});
|
||||
const overlaps = (a: PreviewBox, b: PreviewBox, gap: number) =>
|
||||
a.x < b.x + b.width + gap &&
|
||||
a.x + a.width + gap > b.x &&
|
||||
a.y < b.y + b.height + gap &&
|
||||
a.y + a.height + gap > b.y;
|
||||
|
||||
/** Dimensions stay outside the outline. Short adjacent edges share a lane with staggered text. */
|
||||
export function dimensionedOutline(points: Point[], edges?: number[]) {
|
||||
if (points.length < 3)
|
||||
return { dimensions: [] as EdgeDimension[], font: 1, box: fullPreviewBox(1, 1) };
|
||||
const span = Math.max(
|
||||
1,
|
||||
Math.max(...points.map((point) => point.x)) - Math.min(...points.map((point) => point.x)),
|
||||
Math.max(...points.map((point) => point.y)) - Math.min(...points.map((point) => point.y)),
|
||||
);
|
||||
const font = span * 0.065;
|
||||
const orientation =
|
||||
Math.sign(
|
||||
points.reduce((sum, point, i) => {
|
||||
const next = points[(i + 1) % points.length];
|
||||
return sum + point.x * next.y - next.x * point.y;
|
||||
}, 0),
|
||||
) || 1;
|
||||
const dimensions: EdgeDimension[] = [];
|
||||
const selected = (edges ?? points.map((_, i) => i))
|
||||
.map((edge) => ({
|
||||
edge,
|
||||
start: points[edge],
|
||||
end: points[(edge + 1) % points.length],
|
||||
}))
|
||||
.map((edge) => ({
|
||||
...edge,
|
||||
length: Math.hypot(edge.end.x - edge.start.x, edge.end.y - edge.start.y),
|
||||
}))
|
||||
.filter((edge) => edge.length > 0.000001)
|
||||
.sort((a, b) => b.length - a.length || a.edge - b.edge);
|
||||
|
||||
for (const edge of selected) {
|
||||
const direction = {
|
||||
x: (edge.end.x - edge.start.x) / edge.length,
|
||||
y: (edge.end.y - edge.start.y) / edge.length,
|
||||
};
|
||||
const normal = { x: orientation * direction.y, y: -orientation * direction.x };
|
||||
const center = { x: (edge.start.x + edge.end.x) / 2, y: (edge.start.y + edge.end.y) / 2 };
|
||||
const text = exactMm(edge.length);
|
||||
const angle =
|
||||
Math.abs(direction.x) > 0.000001 && Math.abs(direction.y) > 0.000001
|
||||
? (Math.atan2(Math.abs(direction.y), Math.abs(direction.x)) * 180) / Math.PI
|
||||
: undefined;
|
||||
const angleText = angle === undefined ? '' : `${exactMm(angle)}°`;
|
||||
const width = Math.max(font * 1.2, Math.max(text.length, angleText.length) * font * 0.64),
|
||||
height = font * (angle === undefined ? 1.3 : 2.5);
|
||||
const outside = Math.max(
|
||||
0,
|
||||
...points.map((point) => (point.x - center.x) * normal.x + (point.y - center.y) * normal.y),
|
||||
);
|
||||
let offset = outside + font * 0.65;
|
||||
let label = center,
|
||||
labelBox: PreviewBox = { x: 0, y: 0, width, height };
|
||||
let placed = false;
|
||||
for (let lane = 0; lane < 30 && !placed; lane++) {
|
||||
const base = moved(
|
||||
center,
|
||||
normal,
|
||||
offset + (Math.abs(normal.x) * width + Math.abs(normal.y) * height) / 2 + font * 0.18,
|
||||
);
|
||||
for (let step = 0; step < 25; step++) {
|
||||
const shift = step === 0 ? 0 : Math.ceil(step / 2) * (step % 2 ? -1 : 1) * font * 1.55;
|
||||
label = moved(base, direction, shift);
|
||||
labelBox = { x: label.x - width / 2, y: label.y - height / 2, width, height };
|
||||
if (!dimensions.some((dimension) => overlaps(labelBox, dimension.labelBox, font * 0.25))) {
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!placed) offset += font * 1.8;
|
||||
}
|
||||
dimensions.push({
|
||||
...edge,
|
||||
text,
|
||||
angle,
|
||||
normal,
|
||||
lineStart: moved(edge.start, normal, offset),
|
||||
lineEnd: moved(edge.end, normal, offset),
|
||||
label,
|
||||
labelBox,
|
||||
});
|
||||
}
|
||||
const xs = points.map((point) => point.x),
|
||||
ys = points.map((point) => point.y);
|
||||
for (const dimension of dimensions) {
|
||||
xs.push(
|
||||
dimension.lineStart.x,
|
||||
dimension.lineEnd.x,
|
||||
dimension.labelBox.x,
|
||||
dimension.labelBox.x + dimension.labelBox.width,
|
||||
);
|
||||
ys.push(
|
||||
dimension.lineStart.y,
|
||||
dimension.lineEnd.y,
|
||||
dimension.labelBox.y,
|
||||
dimension.labelBox.y + dimension.labelBox.height,
|
||||
);
|
||||
}
|
||||
const padding = font * 0.4;
|
||||
return {
|
||||
dimensions: dimensions.sort((a, b) => a.edge - b.edge),
|
||||
font,
|
||||
box: {
|
||||
x: Math.min(...xs) - padding,
|
||||
y: Math.min(...ys) - padding,
|
||||
width: Math.max(...xs) - Math.min(...xs) + padding * 2,
|
||||
height: Math.max(...ys) - Math.min(...ys) + padding * 2,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function flooringTemplate(piece: FlooringPiece): Point[] {
|
||||
if (piece.template.length) return piece.template;
|
||||
return piece.shaped
|
||||
|
|
|
|||
Loading…
Reference in a new issue