docs: doxygen annotations for curves + mesh + sketch
CI / Build & Test (push) Failing after 29s
CI / Release Build (push) Failing after 38s

This commit is contained in:
茂之钳
2026-07-24 11:04:04 +00:00
parent b97b0415f5
commit 4c9ee4f760
53 changed files with 5307 additions and 506 deletions
+382 -52
View File
@@ -1,4 +1,28 @@
#pragma once
/**
* @file sdf_gradient.h
* @brief SDF 梯度计算(数值微分、解析梯度、链式法则)
*
* 提供两类梯度计算:
*
* 1. **空间梯度** ∇f(p) — SDF 在场中的方向导数(法向量方向)
* - 有限差分近似
* - 各图元的解析梯度
* - CSG 操作的链式法则
*
* 2. **参数梯度** ∂f/∂θ — SDF 对形状参数的敏感度
* - 有限差分计算 ∂f/∂radius, ∂f/∂height 等
* - 用于基于梯度的形状优化
*
* ## 梯度约定
*
* 对于 SDF,梯度指向距离增加最快的方向。
* 在表面上 (f=0),∇f 等于外法向量(单位长度)。
* 在外部 (f>0),∇f 指向远离表面的方向。
* 在内部 (f<0),∇f 指向最近的表面。
*
* @ingroup sdf
*/
#include "vde/sdf/sdf_primitives.h"
#include "vde/sdf/sdf_operations.h"
#include "vde/core/point.h"
@@ -14,11 +38,30 @@ using core::Vector3D;
// Finite-Difference Gradient
// ═══════════════════════════════════════════════
/// Gradient of SDF at point p (normal direction)
/// Computed via central finite differences: (f(p+h) - f(p-h)) / (2h)
/// @param f SDF function (primitives or evaluate from tree)
/// @param p evaluation point
/// @param h step size (default 1e-6)
/**
* @brief SDF 的空间梯度(中心差分近似)
*
* 使用三轴中心有限差分:
* ∂f/∂x ≈ (f(x+h, y, z) - f(x-h, y, z)) / (2h)
*
* 二阶精度,适用于任意黑盒 SDF 函数。
*
* @param f SDF 函数(接受 Point3D 参数)
* @param p 求值点
* @param h 差分步长(默认 1e-6
* @return 梯度向量 ∇f(p)
*
* @note 步长太小会导致舍入误差主导,太大导致截断误差主导。
* 默认值 1e-6 在单精度场景下是较好的平衡。
* @note 对于含不连续性的 SDF(如 sharp CSG),有限差分可能在边界产生错误梯度。
*
* @code{.cpp}
* auto f = [](const Point3D& p) { return sphere(p, 1.0); };
* Vector3D n = gradient(f, Point3D(1, 0, 0)); // ≈ (1, 0, 0)
* @endcode
*
* @see evaluate_with_gradient 一次性获取值和梯度
*/
[[nodiscard]] inline Vector3D gradient(
const std::function<double(const Point3D&)>& f,
const Point3D& p, double h = 1e-6)
@@ -35,13 +78,27 @@ using core::Vector3D;
// Gradient Result Struct
// ═══════════════════════════════════════════════
/// Forward-mode AD: compute SDF value AND gradient in one pass
/// Returns {sdf_value, gradient_vector}
/**
* @brief 前向模梯度结果: SDF 值 + 梯度
*
* 在一次计算中返回 SDF 值和空间梯度,避免重复求值。
* 梯度向量在表面上即为单位外法向量的近似。
*/
struct GradResult {
double value;
Vector3D grad; // df/dx, df/dy, df/dz
double value; ///< SDF 值 f(p)
Vector3D grad; ///< 空间梯度 ∇f(p) = (df/dx, df/dy, df/dz)
};
/**
* @brief 同时计算 SDF 值和梯度的便捷函数
*
* @param f SDF 函数
* @param p 求值点
* @param h 差分步长
* @return value + grad 结构
*
* @see gradient 仅计算梯度
*/
[[nodiscard]] inline GradResult evaluate_with_gradient(
const std::function<double(const Point3D&)>& f,
const Point3D& p, double h = 1e-6)
@@ -53,13 +110,36 @@ struct GradResult {
// Parameter Gradients (Finite Difference)
// ═══════════════════════════════════════════════
/// Compute df/dradius for a sphere at point p
/**
* @brief 球体 SDF 对半径的参数梯度 ∂f/∂r
*
* 用中心差分计算: (sphere(p, r+h) - sphere(p, r-h)) / (2h)
*
* @param p 采样点
* @param radius 球体半径
* @param h 差分步长(默认 1e-6
* @return ∂f/∂radius
*
* @note 对于球体,解析梯度为 -1(球面上),但此函数提供与实现无关的数值验证。
*
* @see sphere_param_grad 封装版本
*/
[[nodiscard]] inline double sphere_radius_gradient(const Point3D& p, double radius, double h = 1e-6)
{
return (sphere(p, radius + h) - sphere(p, radius - h)) / (2.0 * h);
}
/// Compute df/d(extents[i]) for a box (axis 0=x, 1=y, 2=z)
/**
* @brief 长方体 SDF 对各轴半尺寸的参数梯度 ∂f/∂extent[i]
*
* @param p 采样点
* @param extents 半边长
* @param axis 轴索引: 0=x, 1=y, 2=z
* @param h 差分步长
* @return ∂f/∂extent[axis]
*
* @see box_param_grad 封装版本
*/
[[nodiscard]] inline double box_extent_gradient(const Point3D& p, const Point3D& extents,
int axis, double h = 1e-6)
{
@@ -70,28 +150,52 @@ struct GradResult {
return (box(p, ext_plus) - box(p, ext_minus)) / (2.0 * h);
}
/// Compute df/d(major_radius) for a torus
/**
* @brief 圆环 SDF 对主半径的参数梯度 ∂f/∂major_r
*
* @param p 采样点
* @param major_r 主半径
* @param minor_r 副半径
* @param h 差分步长
* @return 参数梯度
*/
[[nodiscard]] inline double torus_major_gradient(const Point3D& p, double major_r, double minor_r,
double h = 1e-6)
{
return (torus(p, major_r + h, minor_r) - torus(p, major_r - h, minor_r)) / (2.0 * h);
}
/// Compute df/d(minor_radius) for a torus
/**
* @brief 圆环 SDF 对副半径的参数梯度 ∂f/∂minor_r
*
* @return 参数梯度
*/
[[nodiscard]] inline double torus_minor_gradient(const Point3D& p, double major_r, double minor_r,
double h = 1e-6)
{
return (torus(p, major_r, minor_r + h) - torus(p, major_r, minor_r - h)) / (2.0 * h);
}
/// Compute df/dheight for cylinder
/**
* @brief 圆柱 SDF 对高度的参数梯度 ∂f/∂height
*
* @param p 采样点
* @param radius 圆柱半径
* @param height 圆柱高度
* @param h 差分步长
* @return 参数梯度
*/
[[nodiscard]] inline double cylinder_height_gradient(const Point3D& p, double radius, double height,
double h = 1e-6)
{
return (cylinder(p, radius, height + h) - cylinder(p, radius, height - h)) / (2.0 * h);
}
/// Compute df/dradius for cylinder
/**
* @brief 圆柱 SDF 对半径的参数梯度 ∂f/∂radius
*
* @return 参数梯度
*/
[[nodiscard]] inline double cylinder_radius_gradient(const Point3D& p, double radius, double height,
double h = 1e-6)
{
@@ -102,8 +206,20 @@ struct GradResult {
// Analytical Gradients (Exact)
// ═══════════════════════════════════════════════
/// Exact gradient of sphere: radial unit vector (p - center) / |p - center|
/// For sphere centered at origin: p / |p|
/**
* @brief 球体 SDF 的解析梯度
*
* **公式:** ∇f = p / |p|
*
* 即从球心指向采样点的单位径向向量。
* 注意:球心处 p = (0,0,0),梯度退化,返回零向量。
*
* @param p 采样点
* @param radius 球半径(当前未使用,保留以保持接口一致)
* @return 解析梯度(单位向量)
*
* @note 在表面上 (|p| = radius),梯度即为外法向量。
*/
[[nodiscard]] inline Vector3D sphere_gradient_analytic(const Point3D& p, double /*radius*/)
{
double n = p.norm();
@@ -111,8 +227,19 @@ struct GradResult {
return p / n;
}
/// Exact gradient of box: sign(p) component-wise for exterior points
/// For interior points, the gradient is the unit normal of the nearest face
/**
* @brief 长方体 SDF 的解析梯度
*
* **算法:**
* - 外部: 梯度分量非零当且仅当对应坐标超出 semi-extent,方向向外
* - 内部: 梯度指向最近的表面(单分量)
*
* 注意:长方体 SDF 使用平滑近似,边缘处的梯度在内部/外部是分段常数。
*
* @param p 采样点
* @param half_extents 半边长
* @return 解析梯度
*/
[[nodiscard]] inline Vector3D box_gradient_analytic(const Point3D& p, const Point3D& half_extents)
{
Vector3D g = Vector3D::Zero();
@@ -136,8 +263,20 @@ struct GradResult {
return g;
}
/// Exact gradient of torus
/// SDF: sqrt((sqrt(px^2 + pz^2) - major)^2 + py^2) - minor
/**
* @brief 圆环 SDF 的解析梯度
*
* **SDF:** f = √((√(pₓ²+p𝓏²) - R_major)² + p_y²) - R_minor
*
* 梯度通过链式法则推导,将环向距离和 Y 方向分别求导。
*
* **特殊情况:** 在 Y 轴上 (pₓ=p𝓏=0),x 和 z 梯度分量退化为 0。
*
* @param p 采样点
* @param major_r 主半径
* @param minor_r 副半径
* @return 解析梯度
*/
[[nodiscard]] inline Vector3D torus_gradient_analytic(const Point3D& p, double major_r, double minor_r)
{
double rho = std::sqrt(p.x() * p.x() + p.z() * p.z());
@@ -158,7 +297,18 @@ struct GradResult {
);
}
/// Exact gradient of cylinder (infinite along Y): normalize(p.x, 0, p.z)
/**
* @brief 无限圆柱(沿 Y 轴)的解析梯度
*
* **公式:** ∇f = (pₓ, 0, p𝓏) / √(pₓ² + p𝓏²)
*
* 梯度仅含水平分量,方向为从轴线径向向外。
* 在轴线上退化返回 (0,1,0)。
*
* @param p 采样点
* @param radius 圆柱半径(未使用,保留接口一致)
* @return 解析梯度
*/
[[nodiscard]] inline Vector3D cylinder_gradient_analytic(const Point3D& p, double /*radius*/)
{
double rho = std::sqrt(p.x() * p.x() + p.z() * p.z());
@@ -166,13 +316,41 @@ struct GradResult {
return Vector3D(p.x() / rho, 0.0, p.z() / rho);
}
/// Exact gradient of plane: normal
/**
* @brief 平面的解析梯度
*
* **公式:** ∇f = normal
*
* 平面的梯度为常向量(法向量),与位置无关。
*
* @param normal 单位法向量
* @return 梯度(等于 normal
*
* @note 这是最简单的解析梯度,因为平面 SDF 是线性的。
*/
[[nodiscard]] inline Vector3D plane_gradient_analytic(const Vector3D& normal)
{
return normal;
}
/// Exact gradient of capsule: gradient of segment distance
/**
* @brief 胶囊体 SDF 的解析梯度
*
* 梯度指向点 p 到最近线段上的投影方向。
*
* **算法:**
* 1. 找到 p 在线段 ab 上的投影点 closest
* 2. 若 |p-closest| ≈ 0(点在轴上),返回零向量
* 3. 否则梯度 = (p - closest) / |p - closest|
*
* 退化情况: 当 a ≈ b(线段退化为点)时,退化为球体的径向梯度。
*
* @param p 采样点
* @param a 线段起点
* @param b 线段终点
* @param radius 扫掠球半径
* @return 解析梯度
*/
[[nodiscard]] inline Vector3D capsule_gradient_analytic(const Point3D& p,
const Point3D& a, const Point3D& b,
double radius)
@@ -198,29 +376,79 @@ struct GradResult {
// Chain Rule for CSG Operations
// ═══════════════════════════════════════════════
/// Gradient after union: gradient of the child that is closer
/**
* @brief 并集操作后的梯度传递
*
* 取两个子对象中较近者的梯度(因为距离值来自较近者)。
*
* @param grad_a 子对象 A 的梯度
* @param grad_b 子对象 B 的梯度
* @param d1 A 的 SDF 值
* @param d2 B 的 SDF 值
* @return 合并后的梯度
*
* @warning 当 d1 ≈ d2 时,梯度可能在两个子对象间不连续跳变。
*
* @see chain_smooth_union 避免不连续的平滑版本
*/
[[nodiscard]] inline Vector3D chain_union(const Vector3D& grad_a, const Vector3D& grad_b,
double d1, double d2)
{
return (d1 < d2) ? grad_a : grad_b;
}
/// Gradient after intersection: gradient of the child that is farther
/**
* @brief 交集操作后的梯度传递
*
* 取两个子对象中较远者的梯度(因为距离值来自较远者)。
*
* @return 合并后的梯度
*/
[[nodiscard]] inline Vector3D chain_intersection(const Vector3D& grad_a, const Vector3D& grad_b,
double d1, double d2)
{
return (d1 > d2) ? grad_a : grad_b;
}
/// Gradient after difference: max(d1, -d2)
/**
* @brief 差集操作后的梯度传递
*
* 若 A 决定结果 (d1 > -d2),使用 ∇A;否则使用 -∇B(B 的内部变成外部)。
*
* @param grad_a 子对象 A 的梯度
* @param grad_b 子对象 B 的梯度
* @param d1 f_A
* @param d2 f_B
* @return 合并后的梯度
*/
[[nodiscard]] inline Vector3D chain_difference(const Vector3D& grad_a, const Vector3D& grad_b,
double d1, double d2)
{
return (d1 > -d2) ? grad_a : Vector3D(-grad_b.x(), -grad_b.y(), -grad_b.z());
}
/// Gradient after smooth union with full chain rule
/// f = d1*(1-h) + d2*h - k*h*(1-h) where h = clamp(0.5+0.5*(d2-d1)/k, 0, 1)
/**
* @brief 平滑并集操作的梯度传递(含完整链式法则)
*
* 与普通并集的硬切换不同,本函数使用多项式混合函数的导数来平滑插值两个子对象的梯度。
*
* **推导:**
* f = d₁(1-h) + d₂h - k·h(1-h)h = clamp(0.5+0.5(d₂-d₁)/k, 0, 1)
* ∂f/∂d₁ = 2 - 3h
* ∂f/∂d₂ = 3h - 1
*
* 在混合区域 (0 < h < 1) 内,权重平滑地从 A 过渡到 B。
*
* @param grad_a 子对象 A 的梯度
* @param grad_b 子对象 B 的梯度
* @param d1 f_A
* @param d2 f_B
* @param k 混合参数
* @return 平滑合并后的梯度
*
* @see op_smooth_union 对应的平滑并集 SDF 操作
* @see chain_union 硬切换版本
*/
[[nodiscard]] inline Vector3D chain_smooth_union(const Vector3D& grad_a, const Vector3D& grad_b,
double d1, double d2, double k)
{
@@ -240,14 +468,33 @@ struct GradResult {
// Chain Rule for Domain Transforms
// ═══════════════════════════════════════════════
/// Chain through translation: gradient unchanged (isometric)
/**
* @brief 平移变换后的梯度传递(梯度不变)
*
* 平移是等距变换(isometry),函数值不变,
* 因此子对象的梯度直接穿透。
*
* @param child_result 变换空间中的子对象求值结果
* @param offset 平移量(仅接口保留)
* @return 相同梯度
*/
[[nodiscard]] inline GradResult chain_translate(const GradResult& child_result, const Point3D& /*offset*/)
{
return child_result;
}
/// Chain through rotation around Y axis (inverse rotation applied to gradient)
/// op_rotate applies R(-θ) to the point, so chain applies R(θ) to the gradient
/**
* @brief 绕 Y 轴旋转后的梯度传递
*
* 由于 op_rotate 对采样点施加 R(-θ),链式法则要求对梯度施加 R(θ)。
* 即:∇f_world = R(θ) · ∇f_local
*
* @param child_result 旋转后坐标系中的子对象求值
* @param angle_rad 旋转角度
* @return 世界坐标系的梯度
*
* @see op_rotate 对应的域变形操作
*/
[[nodiscard]] inline GradResult chain_rotate(const GradResult& child_result, double angle_rad)
{
GradResult result = child_result;
@@ -262,7 +509,19 @@ struct GradResult {
return result;
}
/// Chain through non-uniform scale: divide gradient components by scale factors
/**
* @brief 非均匀缩放后的梯度传递
*
* **公式:** ∇f_world = (∇f_child.x / sx, ∇f_child.y / sy, ∇f_child.z / sz)
*
* 因为 f_world(p) = f_child(p / s),由链式法则得 ∂f_world/∂p = ∇f_child · diag(1/s)。
*
* @note 缩放后梯度不再是单位长度,除非是均匀缩放。
*
* @param child_result 缩放后坐标系中的子对象求值
* @param factors 缩放因子
* @return 世界坐标系的梯度
*/
[[nodiscard]] inline GradResult chain_scale(const GradResult& child_result, const Point3D& factors)
{
GradResult result = child_result;
@@ -274,9 +533,28 @@ struct GradResult {
return result;
}
/// Chain through twist around Y axis
/// Twist: T(p) rotates (x,z) by +amount*p.y
/// Chain: J_T^T * child_grad where J_T includes y-dependence of the rotation angle
/**
* @brief 扭曲变换后的梯度传递(含完整雅可比)
*
* 扭曲 T(p) 绕 Y 轴旋转角度 amount·p_y。
* 梯度传递需要对雅可比矩阵的转置做链式法则:
*
* **雅可比 J:**
* ```
* ∂T_x/∂p_x = c, ∂T_x/∂p_y = -a·(p_x·s + p_z·c), ∂T_x/∂p_z = -s
* ∂T_y/∂p_x = 0, ∂T_y/∂p_y = 1, ∂T_y/∂p_z = 0
* ∂T_z/∂p_x = s, ∂T_z/∂p_y = a·(p_x·c - p_z·s), ∂T_z/∂p_z = c
* ```
* 其中 c = cos(a·p_y), s = sin(a·p_y), a = amount。
*
* @param child_result 扭曲空间中的子对象求值
* @param amount 单位高度的旋转量
* @param p 原始采样点
* @return 世界坐标系的梯度
*
* @see op_twist 对应的域变形操作
* @see chain_rotate 不含 Y 依赖的简化旋转梯度传递
*/
[[nodiscard]] inline GradResult chain_twist(const GradResult& child_result,
double amount, const Point3D& p)
{
@@ -303,7 +581,19 @@ struct GradResult {
return result;
}
/// Chain through repeat: gradient unchanged (periodic domain)
/**
* @brief 周期重复后的梯度传递(梯度不变)
*
* 重复映射是平移变换的叠加,每个晶格内梯度相同(周期域)。
* 在晶格边界处存在不连续性。
*
* @param child_result 中心晶格内的子对象求值
* @param cell 晶格尺寸(仅接口保留)
* @param p 原始采样点(仅接口保留)
* @return 相同梯度
*
* @warning 在晶格边界处梯度不连续,可能影响基于梯度的优化。
*/
[[nodiscard]] inline GradResult chain_repeat(const GradResult& child_result,
const Point3D& /*cell*/, const Point3D& /*p*/)
{
@@ -314,22 +604,41 @@ struct GradResult {
// Parameter Gradient Vector (All Shape Params)
// ═══════════════════════════════════════════════
/// Parameter gradient struct: holds all possible shape-parameter sensitivities
/**
* @brief 参数梯度结构体
*
* 存储 SDF 对所有形状参数的偏导数:
* ∂f/∂radius, ∂f/∂height, ∂f/∂extent[3], ∂f/∂major, ∂f/∂minor,
* ∂f/∂angle, ∂f/∂translate[3], ∂f/∂rotate, ∂f/∂scale[3]
*
* 用于基于梯度的形状优化和参数拟合。
*
* @see sphere_param_grad, box_param_grad, cylinder_param_grad
*/
struct ParamGrad {
double d_radius = 0.0;
double d_height = 0.0;
Point3D d_extents = Point3D::Zero();
double d_major = 0.0;
double d_minor = 0.0;
double d_angle = 0.0;
Vector3D d_normal = Vector3D::Zero();
double d_offset = 0.0;
Point3D d_translate = Point3D::Zero();
double d_rotate = 0.0;
Point3D d_scale = Point3D::Zero();
double d_radius = 0.0; ///< ∂f/∂radius
double d_height = 0.0; ///< ∂f/∂height
Point3D d_extents = Point3D::Zero(); ///< ∂f/∂(extent.x, extent.y, extent.z)
double d_major = 0.0; ///< ∂f/∂major_radius
double d_minor = 0.0; ///< ∂f/∂minor_radius
double d_angle = 0.0; ///< ∂f/∂angle_rad
Vector3D d_normal = Vector3D::Zero(); ///< ∂f/∂normal (无效分量清零)
double d_offset = 0.0; ///< ∂f/∂offset
Point3D d_translate = Point3D::Zero(); ///< ∂f/∂translate_offset
double d_rotate = 0.0; ///< ∂f/∂rotation_angle
Point3D d_scale = Point3D::Zero(); ///< ∂f/∂scale_factors
};
/// Compute parameter gradients for a sphere primitive
/**
* @brief 球体所有参数的梯度(当前仅 radius 有意义)
*
* @param p 采样点
* @param radius 球体半径
* @param h 差分步长
* @return 参数梯度(仅 d_radius 非零)
*
* @see sphere_radius_gradient 底层数值差分
*/
[[nodiscard]] inline ParamGrad sphere_param_grad(const Point3D& p, double radius, double h = 1e-6)
{
ParamGrad pg;
@@ -337,7 +646,18 @@ struct ParamGrad {
return pg;
}
/// Compute parameter gradients for a box primitive
/**
* @brief 长方体所有参数的梯度
*
* 分别计算三个轴的半尺寸对 SDF 值的影响。
*
* @param p 采样点
* @param half_extents 半边长
* @param h 差分步长
* @return 参数梯度(d_extents 分量填充)
*
* @see box_extent_gradient 底层数值差分
*/
[[nodiscard]] inline ParamGrad box_param_grad(const Point3D& p, const Point3D& half_extents,
double h = 1e-6)
{
@@ -348,7 +668,17 @@ struct ParamGrad {
return pg;
}
/// Compute parameter gradients for a cylinder primitive
/**
* @brief 圆柱所有参数的梯度
*
* 分别计算半径和高度对 SDF 值的影响。
*
* @param p 采样点
* @param radius 圆柱半径
* @param height 圆柱高度
* @param h 差分步长
* @return 参数梯度(d_radius, d_height 填充)
*/
[[nodiscard]] inline ParamGrad cylinder_param_grad(const Point3D& p, double radius, double height,
double h = 1e-6)
{