我试图以编程方式确定贝塞尔曲线的多个方面。我希望能够找到曲线的弧长、沿曲线定位的点在曲率较高的区域具有较高的密度,以及定位的点彼此等距。目前,我将控件列表存储在数组中,并使用 de Casteljau 的递归算法来查找点和导数。我在 Luau 中的实现片段如下:
function Bezier:Hodograph(t: number): Curve<Bezier> -- A single step in the Casteljau algorithm. Yields a curve 1 degree lower than itself.
local hodograph = Bezier.new()
for i = 1, #self.Controls - 1 do
hodograph.Controls[i] = self.Controls[i]:Lerp(self.Controls[i+1], t)
end
return hodograph
end
function Bezier:Casteljau(t: number, derivative: number?): Curve<Bezier> -- Reduces control points and returns a new curve containing the control points.
derivative = derivative or 0
if #self.Controls <= derivative+1 then
return self
end
return self:Hodograph(t):Casteljau(t, derivative)
end
function Bezier:Split(t: number): (Curve<Bezier>, Curve<Bezier>) -- Cuts the curve at point `t` and returns the slices in a tuple, left before right.
local left = Bezier.new()
local right = Bezier.new()
local hodograph = self
for i = 1, #self.Controls do
left.Controls[i] = hodograph.Controls[1]
right.Controls[(#self.Controls) - (i-1)] = hodograph.Controls[#hodograph.Controls]
hodograph = hodograph:Hodograph(t)
end
return left, right
end
function Bezier:GetPoint(t: number): Vector3
return self:Casteljau(t).Controls[1]
end
function Bezier:GetTangent(t: number): Vector3 -- Gets the 1st derivative, or rate of change (tangent) of point t.
local controls: {Vector3} = self:Casteljau(t, 1).Controls
if #controls < 2 then
return Vector3.zero -- Not enough control points to calculate first derivative (tangent).
end
return ((controls[2] - controls[1]) * (#self.Controls - 1))
end
function Bezier:GetAcceleration(t: number): Vector3 -- Gets the 2nd derivative, or rate of change in tangent (acceleration) of point t.
local controls: {Vector3} = self:Casteljau(t, 2).Controls
if #controls < 3 then
return Vector3.zero -- Not enough control points to calculate second derivative (acceleration).
end
return ((controls[3] - 2 * controls[2] + controls[1]) * (#self.Controls - 1) * (#self.Controls - 2))
end
function Bezier:GetCurvature(t: number): number
local secondDerivative = self:Casteljau(t, 2)
local tangent: Vector3 = secondDerivative:GetTangent(t)
local acceleration: Vector3 = secondDerivative:GetAcceleration(t)
local cross = tangent:Cross(acceleration)
local speed = tangent.Magnitude^3
if speed == 0 then
return 0
else
return cross.Magnitude / speed
end
end
如您所见,我已经创建了一阶和二阶导数(切线和加速度)的方法,以及查找曲率的方法。我需要某种方法可以自适应地参数化曲线的弧长,强调曲线并确保曲率很小的区域不会获得太多密度。
我尝试过用不同的时间值迭代曲线,根据曲率改变步长的大小,细分直到点之间的距离达到一定的 epsilon,但我的方法都不是非常“科学”。它们都需要将某种值传递到函数中或对下一步执行的操作进行粗略估计。我需要一个可靠的函数,并在最短的时间内返回质量参数化,同时仅使用曲线本身的具体方面,没有任意加权或错误检查。
我认为您寻找“科学”解决方案的决心阻碍了您找到/确定实用的解决方案。从硬编码细分开始;如果需要,添加自适应中点细分(即,在样本之间递归地添加样本,如果添加样本处的导数不连续性高于极限,则继续递归);如果仍然需要(可能不需要),请使用梯度下降来细化解决方案,移动样本的参数以最大化二阶导数,并在样本点彼此相遇时合并样本点。
还有更复杂的方法,但老实说,它们对应用程序质量的影响似乎不太值得实施的难度。