js:使用规划算法思想生成平滑贝塞尔曲线
2023-12-17 本文已影响0人
时间煮鱼
最终效果路线(红色线)
ps: 这样做出来的,并不一定是最优的,姑且能用吧
1702890718753.png先说问题:
当前有:两条带方向的直线和一条贝塞尔曲线,贝塞尔曲线的两端连接两条直线的各一端;
那么:有一辆车沿着这组成的线段行驶,如何将贝塞尔修改成最优的曲线使车最少进行原地旋转
解法思虑:
1、找到两条带方向直线的方向(航向)角度
2、沿起点线方向延长出n(我这边是12)个点&沿终止线反方向延长出n个点
将这12 * 12个点,两两组合再加上起点线的末端和终止线的首端生成144条贝塞尔:(这12 * 12是两个控制点)
3、将这144条贝塞尔,每一条切分m(我这边是100)份,计算出每一份的曲率,然后计算这m个曲率的方差
4、找到方差最小的一条就是最平滑的贝塞尔
部分核心代码
// 计算平滑的贝塞尔曲线
export function getSmoothBezier(startPoint, startAngle, endPoint, endAngle) {
const len = Math.sqrt((startPoint.x - endPoint.x) ** 2 + (startPoint.y - endPoint.y) ** 2);
const segs = 12; // 将起点和终点之间的距离分成12段
const step = parseInt(len / segs);
const allBeziers = []; // 存放所有生成的贝塞尔曲线
// 起始线段&终点线段延长
for (let i = 0; i < segs; i++) {
for (let j = 0; j < segs; j++) {
allBeziers.push({
startPoint: { ...startPoint },
startControlPoint: {
x: startPoint.x + step * Math.cos(startAngle) * i,
y: startPoint.y + step * Math.sin(startAngle) * i
},
endControlPoint: {
x: endPoint.x - step * Math.cos(endAngle) * j,
y: endPoint.y - step * Math.sin(endAngle) * j
},
endPoint: { ...endPoint }
});
}
}
const minBezier = getMinCurvatureBezier(allBeziers);
return minBezier;
}
// 计算最小曲率的贝塞尔
export function getMinCurvatureBezier(allBeziers) {
let minBezier = calculateCurvature(allBeziers[0]);
let minBezierIndex = allBeziers[0];
allBeziers.forEach(bezier => {
const curvature = calculateCurvature(bezier);
if (curvature < minBezier) {
minBezierIndex = bezier;
minBezier = curvature;
}
});
return minBezierIndex;
}
// 贝塞尔曲线计算函数
function bezier(t, p0, p1, p2, p3) {
const u = 1 - t;
const tt = t * t;
const uu = u * u;
const uuu = uu * u;
const ttt = tt * t;
const p = {};
p.x = uuu * p0.x + 3 * uu * t * p1.x + 3 * u * tt * p2.x + ttt * p3.x;
p.y = uuu * p0.y + 3 * uu * t * p1.y + 3 * u * tt * p2.y + ttt * p3.y;
return p;
}
// 计算贝塞尔曲线的切线向量
function tangentVector(t, p0, p1, p2, p3) {
const dt = 0.001; // 微小的增量
const p1n = bezier(t - dt, p0, p1, p2, p3);
const p1p = bezier(t + dt, p0, p1, p2, p3);
// 切线向量
const tangent = {
x: p1p.x - p1n.x,
y: p1p.y - p1n.y
};
return tangent;
}
// 计算曲率
function curvature(t, p0, p1, p2, p3) {
const tangent = tangentVector(t, p0, p1, p2, p3);
const speed = Math.sqrt(tangent.x ** 2 + tangent.y ** 2);
// 曲率公式
const curvature = speed / Math.pow((1 + tangent.x ** 2 + tangent.y ** 2), 1.5);
return curvature;
}
// 将贝塞尔曲线平分为100段并计算曲率
function calculateCurvature(bezier) {
const { startPoint, startControlPoint, endControlPoint, endPoint } = bezier;
const numOfSegments = 100;
const result = [];
for (let i = 0; i < numOfSegments; i++) {
const t = i / numOfSegments;
const curv = curvature(t, startPoint, startControlPoint, endControlPoint, endPoint);
result.push(curv);
}
// 计算方差
const variance = calculateVariance(result);
return variance;
}
// 计算方差
function calculateVariance(result) {
let sum = 0;
let sumSquare = 0;
result.forEach(v => {
sum += v;
sumSquare += v ** 2;
});
const mean = sum / result.length;
const variance = sumSquare / result.length - mean ** 2;
return variance;
}
代码引用
// 计算最优贝塞尔曲线的控制点
export function getOptimalBezier(startLine, endLine) {
const startLineStartPoint = convertPointInfo(startLine.startPointPosition);
const startLineEndPoint = convertPointInfo(startLine.endPointPosition);
const endLineStartPoint = convertPointInfo(endLine.startPointPosition);
const endLineEndPoint = convertPointInfo(endLine.endPointPosition);
let startLineAngle = Math.atan2(startLineEndPoint.y - startLineStartPoint.y, startLineEndPoint.x - startLineStartPoint.x);
let endLineAngle = Math.atan2(endLineEndPoint.y - endLineStartPoint.y, endLineEndPoint.x - endLineStartPoint.x);
const smoothBezier = getSmoothBezier(startLineEndPoint, startLineAngle, endLineStartPoint, endLineAngle);
return {
startControlPoint: {
x: toFixed1(smoothBezier.startControlPoint.x),
y: toFixed1(smoothBezier.startControlPoint.y)
},
endControlPoint: {
x: toFixed1(smoothBezier.endControlPoint.x),
y: toFixed1(smoothBezier.endControlPoint.y)
}
};
}