// NURBS 离散:节点插入分解为有理 Bézier 段,再按控制凸包细分。
// 使用标准 Boehm 节点插入、de Casteljau 算法,未移植第三方实现。
// 正权重使曲线位于投影控制点凸包内,避免只测中点漏掉 S 形曲线。
///|
fn finite_number(x : Double) -> Bool {
!x.is_nan() && !x.is_inf()
}
///|
fn positive_tolerance(tol : Double) -> Bool {
finite_number(tol) && tol > 0.0
}
///|
fn mix_h(a : Vec3, b : Vec3, t : Double) -> Vec3 {
Vec3::new(
(1.0 - t) * a.x + t * b.x,
(1.0 - t) * a.y + t * b.y,
(1.0 - t) * a.z + t * b.z,
)
}
///|
fn project_h(p : Vec3) -> Vec2 {
Vec2::new(p.x / p.z, p.y / p.z)
}
///|
fn insert_spline_knot(
ctrl : Array[Vec3],
knots : Array[Double],
degree : Int,
value : Double,
) -> (Array[Vec3], Array[Double]) {
// 在包括活动参数域右端点的最后一个等值节点后插入。
let mut span = degree
while span + 1 < knots.length() && knots[span + 1] <= value {
span = span + 1
}
let mut multiplicity = 0
for u in knots {
if u == value {
multiplicity = multiplicity + 1
}
}
let result : Array[Vec3] = []
for i = 0; i <= ctrl.length(); i = i + 1 {
if i <= span - degree {
result.push(ctrl[i])
} else if i >= span - multiplicity + 1 {
result.push(ctrl[i - 1])
} else {
let alpha = (value - knots[i]) / (knots[i + degree] - knots[i])
result.push(mix_h(ctrl[i - 1], ctrl[i], alpha))
}
}
let new_knots : Array[Double] = []
for i = 0; i < knots.length(); i = i + 1 {
new_knots.push(knots[i])
if i == span {
new_knots.push(value)
}
}
(result, new_knots)
}
///|
fn sample_bezier(
ctrl : Array[Vec3],
tol : Double,
depth : Int,
out : Array[Vec2],
) -> Bool {
if out.length() >= 65536 {
return false
}
let a = project_h(ctrl[0])
let b = project_h(ctrl[ctrl.length() - 1])
let mut flat = true
for h in ctrl {
let p = project_h(h)
if !finite_number(p.x) || !finite_number(p.y) {
return false
}
let distance = dist_to_segment(p, a, b)
if !finite_number(distance) {
return false
}
if distance > tol {
flat = false
}
}
if flat {
out.push(b)
return true
}
// 有限资源下达不到容差要明确失败,不能悄悄当成合格折线。
if depth == 0 {
return false
}
let work = ctrl.copy()
let left : Array[Vec3] = [work[0]]
let right : Array[Vec3] = [work[work.length() - 1]]
for level = 1; level < ctrl.length(); level = level + 1 {
for i = 0; i < ctrl.length() - level; i = i + 1 {
work[i] = mix_h(work[i], work[i + 1], 0.5)
}
left.push(work[0])
right.push(work[ctrl.length() - level - 1])
}
let reversed : Array[Vec3] = []
for i = right.length() - 1; i >= 0; i = i - 1 {
reversed.push(right[i])
}
sample_bezier(left, tol, depth - 1, out) &&
sample_bezier(reversed, tol, depth - 1, out)
}
///|
/// 将连续、正权重 NURBS 曲线投影到 XY 并离散。
/// 无效节点、非正权重、非有限坐标、断裂曲线或资源上限返回错误。
pub fn spline_points_checked(
degree : Int,
knots : Array[Double],
control_points : Array[Vec3],
weights : Array[Double],
tol : Double,
) -> Result[Array[Vec2], String] {
let count = control_points.length()
if !positive_tolerance(tol) {
return Err("离散容差必须是正的有限数字")
}
if degree < 1 ||
degree > 32 ||
count <= degree ||
count > 4096 ||
knots.length() != count + degree + 1 {
return Err(
"样条次数、控制点数或节点数无效(次数上限32,控制点上限4096)",
)
}
if weights.length() != 0 && weights.length() != count {
return Err("样条权重数必须为零或等于控制点数")
}
for i = 0; i < knots.length(); i = i + 1 {
if !finite_number(knots[i]) || (i > 0 && knots[i] < knots[i - 1]) {
return Err("样条节点必须有限且不递减")
}
}
let start = knots[degree]
let end = knots[count]
if end <= start {
return Err("样条没有非空参数域")
}
let mut ctrl : Array[Vec3] = []
for i = 0; i < count; i = i + 1 {
let p = control_points[i]
let w = weight_at(weights, i)
if !finite_number(p.x) ||
!finite_number(p.y) ||
!finite_number(p.z) ||
!finite_number(w) ||
w <= 0.0 ||
!finite_number(p.x * w) ||
!finite_number(p.y * w) {
return Err("样条需要有限坐标与正的有限权重")
}
ctrl.push(Vec3::new(p.x * w, p.y * w, w))
}
let breaks : Array[Double] = []
for u in knots {
if u >= start &&
u <= end &&
(breaks.length() == 0 || breaks[breaks.length() - 1] != u) {
breaks.push(u)
}
}
let mut refined = knots.copy()
for u in breaks {
let mut multiplicity = 0
for v in refined {
if u == v {
multiplicity = multiplicity + 1
}
}
if multiplicity > degree + 1 {
return Err("节点重复次数超过次数加一")
}
if u > start && u < end && multiplicity > degree {
return Err("内部重节点使样条断裂,不能连接为单条折线")
}
while multiplicity < degree {
let (next_ctrl, next_knots) = insert_spline_knot(ctrl, refined, degree, u)
ctrl = next_ctrl
refined = next_knots
multiplicity = multiplicity + 1
}
}
let out : Array[Vec2] = []
for k = degree; k < ctrl.length(); k = k + 1 {
if refined[k] < start ||
refined[k + 1] > end ||
refined[k + 1] <= refined[k] {
continue
}
let segment : Array[Vec3] = []
for i = k - degree; i <= k; i = i + 1 {
segment.push(ctrl[i])
}
if out.length() == 0 {
out.push(project_h(segment[0]))
}
if !sample_bezier(segment, tol, 16, out) {
return Err(
"样条离散超过深度或点数上限,或遇到数值溢出;未满足指定容差",
)
}
}
if out.length() == 0 {
Err("样条没有可离散的节点区间")
} else {
Ok(out)
}
}