效果图:
同样50均分,红色(梯形法则)和绿色(积分表达式)是本文章算法实现的效果(为了对比,有一定偏移),蓝色是简单的均分x得到的效果
产生差异的原因
蓝色的均分x因为在0~1时斜率大,就会让点的间距变大,红色和绿色的是通过均分曲线长度实现的,每个点之间的距离基本相差无几
实现方法
1、梯形法则
- totalArcLength(a, b, n): 计算区间
[a, b]
上的总弧长。 - arcLength(x): 计算给定点
x
处的弧长微分。曲线积分公式y=ln(x)求导得1/x,带入公式得到Math.sqrt(1 + 1 / (x * x))
- integrate(f, a, b, n): 使用梯形法则近似计算函数
f
在区间[a, b]
上的定积分,n
为区间分割数。function integrate(f, a, b, n) { const h = (b - a) / n; let sum = 0.5 * (f(a) + f(b)); for (let i = 1; i < n; i++) { sum += f(a + i * h); } return sum * h; } function arcLength(x) { return Math.sqrt(1 + 1 / (x * x)); } function totalArcLength(a, b, n) { return integrate(arcLength, a, b, n); }
- findXForArcLength(targetLength, a, b, n): 使用二分查找法找到使得弧长等于
targetLength
的x
值。 - divideCurve(a, b, n, segments): 将曲线
y = ln(x)
在区间[a, b]
上按曲线总长度均匀分割成segments
段,并返回每个分割点的坐标。function findXForArcLength(targetLength, a, b, n) { const tolerance = 1e-6; let low = a; let high = b; while (high - low > tolerance) { const mid = (low + high) / 2; const length = totalArcLength(a, mid, n); if (length < targetLength) { low = mid; } else { high = mid; } } return (low + high) / 2; } export function divideCurve(a, b, n, segments) { const totalLength = totalArcLength(a, b, n); const segmentLength = totalLength / segments; const points = []; for (let i = 1; i <= segments; i++) { const targetLength = i * segmentLength; const x = findXForArcLength(targetLength, a, b, n); const y = Math.log(x); points.push({ x, y }); } return points; }
2、积分表达式
- arcLength(x): 计算给定点x处的弧长积分值,先得到弧长积分∫√(1+1/x∧2)dx,然后对其积分,具体推导:
。
- totalArcLength(a, b): 计算从a到b的总弧长。
- findXForArcLength(targetLength, a, b): 使用二分查找法找到使得弧长等于目标长度的x值。
- divideCurve2(a, b, n): 将从a到b的曲线分成n段,每段的弧长相等,并返回每个分割点的坐标。
function arcLength(x) { return x * Math.sqrt(1 + 1 / (x * x)) - Math.log(Math.sqrt(1 + 1 / (x * x)) + 1 / x); } function totalArcLength(a, b) { return arcLength(b) - arcLength(a); } function findXForArcLength(targetLength, a, b) { const tolerance = 1e-6; let low = a; let high = b; while (high - low > tolerance) { const mid = (low + high) / 2; const length = arcLength(mid) - arcLength(a); if (length < targetLength) { low = mid; } else { high = mid; } } return (low + high) / 2; } export function divideCurve2(a, b, n) { const totalLength = totalArcLength(a, b); const segmentLength = totalLength / n; const points = []; for (let i = 1; i <= n; i++) { const targetLength = i * segmentLength; const x = findXForArcLength(targetLength, a, b); const y = Math.log(x); points.push({ x, y }); } return points; }
3、对比
- 均分长度视觉效果优于均分x
- 梯形法则原理简单,不需要积分运算,任何曲线都适用,但是运算量大,而且只是近似值,积分表达式相反,不过需要具体曲线具体计算,不是所有曲线都能积分