You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
9.4 KiB
211 lines
9.4 KiB
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.computeHullPath = computeHullPath;
|
|
const line_1 = require("../../utils/line");
|
|
const path_1 = require("../../utils/path");
|
|
const point_1 = require("../../utils/point");
|
|
const vector_1 = require("../../utils/vector");
|
|
/**
|
|
* <zh/> 计算 Hull 路径
|
|
*
|
|
* <en/> Compute Hull Path
|
|
* @param points - <zh/> 顶点列表 | <en/> Vertices of Hull
|
|
* @param padding - <zh/> 内边距 | <en/> padding
|
|
* @param corner - <zh/> 拐角类型,目前支持 'sharp'、'rounded' 和 'smooth' | <en/> Corner type, currently supports 'sharp', 'rounded' and 'smooth'
|
|
* @returns <zh/> Hull 路径 | <en/> Hull Path
|
|
*/
|
|
function computeHullPath(points, padding, corner) {
|
|
if (points.length === 1)
|
|
return genSinglePointHullPath(points[0], padding, corner);
|
|
if (points.length === 2)
|
|
return genTwoPointsHullPath(points, padding, corner);
|
|
if (points.length === 3) {
|
|
const [p1, p2, p3] = (0, point_1.sortByClockwise)(points);
|
|
if ((0, point_1.isCollinear)(p1, p2, p3))
|
|
return genTwoPointsHullPath([p1, p3], padding, corner);
|
|
}
|
|
switch (corner) {
|
|
case 'smooth':
|
|
return genMultiPointsSmoothHull(points, padding);
|
|
case 'sharp':
|
|
return genMultiPointsSharpHull(points, padding);
|
|
case 'rounded':
|
|
default:
|
|
return genMultiPointsRoundedHull(points, padding);
|
|
}
|
|
}
|
|
/**
|
|
* <zh/> 生成单点 Hull 路径
|
|
*
|
|
* <en/> Generate Hull Path for a single point
|
|
* @param point - <zh/> 单点 | <en/> Single point
|
|
* @param padding - <zh/> 内边距 | <en/> Padding
|
|
* @param corner - <zh/> 拐角类型 | <en/> Corner type
|
|
* @returns <zh/> 单点 Hull 路径 | <en/> Single point Hull Path
|
|
*/
|
|
const genSinglePointHullPath = (point, padding, corner) => {
|
|
if (corner === 'sharp')
|
|
return [
|
|
['M', point[0] - padding, point[1] - padding],
|
|
['L', point[0] + padding, point[1] - padding],
|
|
['L', point[0] + padding, point[1] + padding],
|
|
['L', point[0] - padding, point[1] + padding],
|
|
['Z'],
|
|
];
|
|
const arcData = [padding, padding, 0, 0, 0];
|
|
return [
|
|
['M', point[0], point[1] - padding],
|
|
['A', ...arcData, point[0], point[1] + padding],
|
|
['A', ...arcData, point[0], point[1] - padding],
|
|
];
|
|
};
|
|
/**
|
|
* <zh/> 生成两点 Hull 路径
|
|
*
|
|
* <en/> Generate Hull Path for two points
|
|
* @param points - <zh/> 两点 | <en/> Two points
|
|
* @param padding - <zh/> 内边距 | <en/> Padding
|
|
* @param corner - <zh/> 拐角类型 | <en/> Corner type
|
|
* @returns <zh/> 两点 Hull 路径 | <en/> Two points Hull Path
|
|
*/
|
|
const genTwoPointsHullPath = (points, padding, corner) => {
|
|
const arcData = [padding, padding, 0, 0, 0];
|
|
const point1 = corner === 'sharp' ? (0, vector_1.add)(points[0], (0, vector_1.scale)((0, vector_1.normalize)((0, vector_1.subtract)(points[0], points[1])), padding)) : points[0];
|
|
const point2 = corner === 'sharp' ? (0, vector_1.add)(points[1], (0, vector_1.scale)((0, vector_1.normalize)((0, vector_1.subtract)(points[1], points[0])), padding)) : points[1];
|
|
const offsetVector = (0, vector_1.scale)((0, vector_1.normalize)((0, vector_1.perpendicular)((0, vector_1.subtract)(point1, point2), false)), padding);
|
|
const invOffsetVector = (0, vector_1.scale)(offsetVector, -1);
|
|
const prev = (0, vector_1.add)(point1, offsetVector);
|
|
const current = (0, vector_1.add)(point2, offsetVector);
|
|
const p2 = (0, vector_1.add)(point2, invOffsetVector);
|
|
const p3 = (0, vector_1.add)(point1, invOffsetVector);
|
|
if (corner === 'sharp') {
|
|
return [['M', prev[0], prev[1]], ['L', current[0], current[1]], ['L', p2[0], p2[1]], ['L', p3[0], p3[1]], ['Z']];
|
|
}
|
|
return [
|
|
['M', prev[0], prev[1]],
|
|
['L', current[0], current[1]],
|
|
['A', ...arcData, p2[0], p2[1]],
|
|
['L', p3[0], p3[1]],
|
|
['A', ...arcData, prev[0], prev[1]],
|
|
];
|
|
};
|
|
/**
|
|
* <zh/> 生成多点 Hull 路径且拐角为圆角
|
|
*
|
|
* <en/> Generate Hull Path for multiple points with rounded corners
|
|
* @param points - <zh/> 形成 Hull 的点集 | <en/> Points that form the Hull
|
|
* @param padding - <zh/> 内边距 | <en/> Padding
|
|
* @returns <zh/> 圆角外壳路径 | <en/> Rounded hull path
|
|
*/
|
|
const genMultiPointsRoundedHull = (points, padding) => {
|
|
const segments = (0, point_1.sortByClockwise)(points).map((current, i) => {
|
|
const prev2Index = (i - 2 + points.length) % points.length;
|
|
const prevIndex = (i - 1 + points.length) % points.length;
|
|
const nextIndex = (i + 1) % points.length;
|
|
const prev2 = points[prev2Index];
|
|
const prev = points[prevIndex];
|
|
const next = points[nextIndex];
|
|
const v0 = (0, vector_1.subtract)(prev2, prev);
|
|
const v1 = (0, vector_1.subtract)(prev, current);
|
|
const v2 = (0, vector_1.subtract)(current, next);
|
|
// 判断是否为凹角 | Determine if it is a concave angle
|
|
const isConcave = (v1, v2) => {
|
|
return (0, vector_1.angle)(v1, v2, true) < Math.PI;
|
|
};
|
|
const concavePrev = isConcave(v0, v1);
|
|
const concaveNext = isConcave(v1, v2);
|
|
const offsetVector = (v) => (0, vector_1.scale)((0, vector_1.normalize)((0, vector_1.perpendicular)(v, false)), padding);
|
|
const offset = offsetVector(v1);
|
|
return [
|
|
{
|
|
p: (0, vector_1.toVector2)(concavePrev ? (0, vector_1.add)(prev, offsetVector(v0)) : (0, vector_1.add)(prev, offset)),
|
|
concave: concavePrev && prev,
|
|
},
|
|
{
|
|
p: (0, vector_1.toVector2)(concaveNext ? (0, vector_1.add)(current, offsetVector(v2)) : (0, vector_1.add)(current, offset)),
|
|
concave: concaveNext && current,
|
|
},
|
|
];
|
|
});
|
|
const arcData = [padding, padding, 0, 0, 0];
|
|
const startIndex = segments.findIndex((segment, i) => !segments[(i - 1 + segments.length) % segments.length][0].concave &&
|
|
!segments[(i - 1 + segments.length) % segments.length][1].concave &&
|
|
!segment[0].concave &&
|
|
!segment[0].concave &&
|
|
!segment[1].concave);
|
|
const sortedSegments = segments.slice(startIndex).concat(segments.slice(0, startIndex));
|
|
let concavePoints = [];
|
|
return sortedSegments.flatMap((segment, i) => {
|
|
const pathFragment = [];
|
|
const lastSegment = sortedSegments[segments.length - 1];
|
|
if (i === 0)
|
|
pathFragment.push(['M', ...lastSegment[1].p]);
|
|
if (!segment[0].concave) {
|
|
pathFragment.push(['A', ...arcData, ...segment[0].p]);
|
|
}
|
|
else {
|
|
concavePoints.push(segment[0].p, segment[1].p);
|
|
}
|
|
if (!segment[1].concave) {
|
|
pathFragment.push(['L', ...segment[1].p]);
|
|
}
|
|
else {
|
|
concavePoints.unshift(segment[1].p);
|
|
}
|
|
if (concavePoints.length === 3) {
|
|
pathFragment.pop();
|
|
pathFragment.push(['C', ...concavePoints.flat()]);
|
|
concavePoints = [];
|
|
}
|
|
return pathFragment;
|
|
});
|
|
};
|
|
/**
|
|
* <zh/> 生成多点 Hull 路径且拐角为平滑
|
|
*
|
|
* <en/> Generate Hull Path for multiple points with smooth corners
|
|
* @param points - <zh/> 形成 Hull 的点集 | <en/> Points that form the Hull
|
|
* @param padding - <zh/> 内边距 | <en/> Padding
|
|
* @returns <zh/> 平滑外壳路径 | <en/> Smooth hull path
|
|
*/
|
|
const genMultiPointsSmoothHull = (points, padding) => {
|
|
const hullPoints = (0, point_1.sortByClockwise)(points).map((p, i) => {
|
|
const pNext = points[(i + 1) % points.length];
|
|
return { p, v: (0, vector_1.normalize)((0, vector_1.subtract)(pNext, p)) };
|
|
});
|
|
// Compute the expanded hull points, and the nearest prior control point for each.
|
|
hullPoints.forEach((hp, i) => {
|
|
const priorIndex = i > 0 ? i - 1 : points.length - 1;
|
|
const prevV = hullPoints[priorIndex].v;
|
|
const extensionVec = (0, vector_1.normalize)((0, vector_1.add)(prevV, (0, vector_1.scale)(hp.v, (0, vector_1.angle)(prevV, hp.v, true) < Math.PI ? 1 : -1)));
|
|
hp.p = (0, vector_1.add)(hp.p, (0, vector_1.scale)(extensionVec, padding));
|
|
});
|
|
return (0, path_1.getClosedSpline)(hullPoints.map((obj) => obj.p));
|
|
};
|
|
/**
|
|
* <zh/> 生成多点 Hull 路径且拐角为尖锐
|
|
*
|
|
* <en/> Generate Hull Path for multiple points with sharp corners
|
|
* @param points - <zh/> 形成 Hull 的点集 | <en/> Points that form the Hull
|
|
* @param padding - <zh/> 内边距 | <en/> Padding
|
|
* @returns <zh/> 锐角外壳路径 | <en/> Sharp hull path
|
|
*/
|
|
const genMultiPointsSharpHull = (points, padding) => {
|
|
const segments = points.map((current, i) => {
|
|
const prev = points[i === 0 ? points.length - 1 : i - 1];
|
|
const offset = (0, vector_1.toVector3)((0, vector_1.scale)((0, vector_1.normalize)((0, vector_1.perpendicular)((0, vector_1.subtract)(prev, current), false)), padding));
|
|
return [(0, vector_1.add)(prev, offset), (0, vector_1.add)(current, offset)];
|
|
});
|
|
const arr = segments.flat();
|
|
const vertices = arr
|
|
.map((_, i) => {
|
|
if (i % 2 === 0)
|
|
return null;
|
|
const l1 = [arr[(i - 1) % arr.length], arr[i % arr.length]];
|
|
const l2 = [arr[(i + 1) % arr.length], arr[(i + 2) % arr.length]];
|
|
return (0, line_1.getLinesIntersection)(l1, l2, true);
|
|
})
|
|
.filter(Boolean);
|
|
return vertices.map((point, i) => [i === 0 ? 'M' : 'L', point[0], point[1]]).concat([['Z']]);
|
|
};
|
|
//# sourceMappingURL=util.js.map
|