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)
{
+428 -12
View File
@@ -1,4 +1,19 @@
#pragma once
/**
* @file sdf_operations.h
* @brief SDF 布尔运算、修饰算子与域变形
*
* 本文件提供三类操作:
*
* 1. **布尔运算(CSG)** — 在距离值上执行组合逻辑
* 2. **修饰算子(Modifiers)** — 对距离值做数值变换
* 3. **域变形(Domain Deformation** — 对采样点空间做几何变换
*
* 所有操作遵循 SDF 惯例:对采样点或其距离值做变换后,保持 f<0=内部、f>0=外部的语义。
* 域变形算子应用到采样点(而非对象),因此位移/旋转方向与直觉相反(逆变换)。
*
* @ingroup sdf
*/
#include "vde/core/point.h"
#include <cmath>
#include <algorithm>
@@ -10,30 +25,150 @@ using core::Vector3D;
// ── Boolean operations on distance values ──
/**
* @brief 布尔并集: A B
*
* 取两个距离值的最小值。在两个子对象的距离场中选择较近者。
*
* **数学公式:** f = min(d1, d2)
*
* 最常见的 CSG 组合操作,用于合并两个几何体。
*
* @param d1 第一个对象的 SDF 值
* @param d2 第二个对象的 SDF 值
* @return 并集的 SDF 值
*
* @code{.cpp}
* double d = op_union(sphere(p, 1.0), box(p, Point3D(0.5, 0.5, 0.5)));
* @endcode
*
* @see op_smooth_union 带平滑过渡的并集
* @see op_intersection 交集
*/
[[nodiscard]] inline double op_union(double d1, double d2) {
return std::min(d1, d2);
}
/**
* @brief 布尔交集: A ∩ B
*
* 取两个距离值的最大值。保留同时属于两个对象的区域。
*
* **数学公式:** f = max(d1, d2)
*
* @param d1 第一个对象的 SDF 值
* @param d2 第二个对象的 SDF 值
* @return 交集的 SDF 值
*
* @code{.cpp}
* // 球体与盒子的交集(取共同区域)
* double d = op_intersection(sphere(p, 1.0), box(p, Point3D(0.5, 0.5, 0.5)));
* @endcode
*
* @see op_smooth_intersection 带平滑过渡的交集
*/
[[nodiscard]] inline double op_intersection(double d1, double d2) {
return std::max(d1, d2);
}
/**
* @brief 布尔差集: A \ B
*
* 从对象 A 中减去对象 B 所占区域。
*
* **数学公式:** f = max(d1, -d2)
*
* 将第二个距离值取反后取最大值:B 的外部变成内部,内部变成外部。
*
* @param d1 被减对象 A 的 SDF 值
* @param d2 减去对象 B 的 SDF 值
* @return 差集的 SDF 值
*
* @code{.cpp}
* // 从球体中减去盒子
* double d = op_difference(sphere(p, 1.0), box(p, Point3D(0.3, 0.3, 0.3)));
* @endcode
*
* @see op_smooth_difference 带平滑过渡的差集
*/
[[nodiscard]] inline double op_difference(double d1, double d2) {
return std::max(d1, -d2);
}
/**
* @brief 平滑并集(带混合过渡)
*
* 通过多项式混合函数在两个对象接合处产生平滑圆角过渡。
*
* **数学公式:**
* h = clamp(0.5 + 0.5·(d1-d2)/k, 0, 1)
* f = d1·(1-h) + d2·h - k·h·(1-h)
*
* 混合参数 k 控制过渡区域的宽度。k 越大,过渡越平滑但越偏离原始几何。
*
* @param d1 第一个对象的 SDF 值
* @param d2 第二个对象的 SDF 值
* @param k 混合强度参数(> 0 时平滑过渡;≤ 0 时退化为普通并集)
* @return 平滑混合后的 SDF 值
*
* @note 多项式混合是函数 f(x)=x 在 [0,k] 区间的平滑近似。在 d1=d2 处,f 向下偏移 k/4。
* @warning k 值过大(超过对象尺寸的 10%)会严重改变几何形态。
*
* @code{.cpp}
* // 两个球体平滑融合,过渡区宽度 0.3
* double d = op_smooth_union(sphere(p, 1.0), sphere(p - Point3D(1.2,0,0), 1.0), 0.3);
* @endcode
*
* @see op_union 无平滑的标准并集
* @see op_smooth_intersection 平滑交集
* @see op_smooth_difference 平滑差集
*/
[[nodiscard]] inline double op_smooth_union(double d1, double d2, double k) {
if (k <= 0.0) return std::min(d1, d2);
double h = std::clamp(0.5 + 0.5 * (d1 - d2) / k, 0.0, 1.0);
return d1 * (1.0 - h) + d2 * h - k * h * (1.0 - h);
}
/**
* @brief 平滑交集(带混合过渡)
*
* 与平滑并集类似,但应用于交集操作。
*
* **数学公式:**
* h = clamp(0.5 - 0.5·(d1-d2)/k, 0, 1)
* f = d1·(1-h) + d2·h + k·h·(1-h)
*
* @param d1 第一个对象的 SDF 值
* @param d2 第二个对象的 SDF 值
* @param k 混合强度参数
* @return 平滑混合后的 SDF 值
*
* @see op_intersection 无平滑的标准交集
* @see op_smooth_union 平滑并集
*/
[[nodiscard]] inline double op_smooth_intersection(double d1, double d2, double k) {
if (k <= 0.0) return std::max(d1, d2);
double h = std::clamp(0.5 - 0.5 * (d1 - d2) / k, 0.0, 1.0);
return d1 * (1.0 - h) + d2 * h + k * h * (1.0 - h);
}
/**
* @brief 平滑差集(带混合过渡)
*
* 与平滑并集类似,但应用于差集操作。
*
* **数学公式:**
* h = clamp(0.5 - 0.5·(d2+d1)/k, 0, 1)
* f = d1·(1-h) + (-d2)·h + k·h·(1-h)
*
* @param d1 被减对象 A 的 SDF 值
* @param d2 减去对象 B 的 SDF 值
* @param k 混合强度参数
* @return 平滑混合后的 SDF 值
*
* @see op_difference 无平滑的标准差集
* @see op_smooth_union 平滑并集
*/
[[nodiscard]] inline double op_smooth_difference(double d1, double d2, double k) {
if (k <= 0.0) return std::max(d1, -d2);
double h = std::clamp(0.5 - 0.5 * (d2 + d1) / k, 0.0, 1.0);
@@ -42,17 +177,77 @@ using core::Vector3D;
// ── Modifiers (operate on a distance value) ──
/**
* @brief 圆角偏移: 将等值面向外扩展 r 单位
*
* **数学公式:** f = d - r
*
* 相当于对隐式曲面做正向膨胀。配合其他运算可实现倒圆角效果。
* 例如 box SDF + op_round 即等价于 round_box。
*
* @param d SDF 值
* @param r 偏移距离(正值 = 膨胀,负值 = 收缩)
* @return 偏移后的 SDF 值
*
* @code{.cpp}
* // 给任意形状加 0.1 的圆角
* double rounded = op_round(my_sdf(p), 0.1);
* @endcode
*
* @see op_onion 壳层修饰
* @see round_box 特例化的圆角立方体
*/
[[nodiscard]] inline double op_round(double d, double r) {
return d - r;
}
/**
* @brief 洋葱皮/壳层修饰: 取绝对值后减去厚度
*
* **数学公式:** f = |d| - thickness
*
* 将 SDF 的等值面变为一个厚度为 2·thickness 的壳层。
* 原始表面变成壳层的内外边界。
*
* @param d SDF 值
* @param thickness 壳层半厚度,必须 > 0
* @return 壳层的 SDF 值
*
* @code{.cpp}
* // 0.05 厚的球壳
* double shell = op_onion(sphere(p, 1.0), 0.05);
* @endcode
*
* @see op_round 圆角偏移
*/
[[nodiscard]] inline double op_onion(double d, double thickness) {
return std::abs(d) - thickness;
}
// ── Domain deformation operators (transform point space) ──
/// Infinite repetition in a cell
/**
* @brief 无限重复(周期复制)
*
* 将空间映射到以 cell 为周期的单位晶格中。
* 每个晶格是边长为 cell 的轴对齐长方体。
*
* **变换:** p' = p - cell·round(p / cell)
*
* 配合任何 SDF 使用,可生成无限周期阵列。
*
* @param p 原始采样点
* @param cell 晶格尺寸 (cx, cy, cz),各分量必须 > 0 才生效
* @return 映射到中心晶格内的点
*
* @code{.cpp}
* // 在空间中无限重复的球体阵列(间距 2)
* Point3D q = op_repeat(p, Point3D(2, 2, 2));
* double d = sphere(q, 0.5);
* @endcode
*
* @note 边界处 SDF 可能不连续,需要确保各晶格间映射是连续的。
*/
[[nodiscard]] inline Point3D op_repeat(const Point3D& p, const Point3D& cell) {
auto wrap = [](double v, double c) {
return c > 0.0 ? v - c * std::round(v / c) : v;
@@ -62,53 +257,232 @@ using core::Vector3D;
wrap(p.z(), cell.z()));
}
/// Mirror across X=0 plane, with optional offset
/**
* @brief 镜像对称(X=0 平面),可选偏移
*
* 将空间关于 x = offset 平面取镜像对称。
*
* **变换:** p'_x = offset + |p_x - offset|
*
* @param p 原始采样点
* @param offset 镜像平面沿 X 轴的偏移量(默认 0 = X=0 平面)
* @return 映射到镜像半空间的点
*
* @code{.cpp}
* // 关于 X=1 平面对称
* Point3D q = op_mirror_x(p, 1.0);
* @endcode
*
* @see op_mirror_y YZ 平面镜像
* @see op_mirror_z XY 平面镜像
*/
[[nodiscard]] inline Point3D op_mirror_x(const Point3D& p, double offset = 0.0) {
return Point3D(offset + std::abs(p.x() - offset), p.y(), p.z());
}
/// Mirror across Y=0
/**
* @brief 镜像对称(Y=0 平面)
*
* 将空间关于 XZ 平面取镜像对称。
*
* **变换:** p'_y = |p_y|
*
* @param p 原始采样点
* @return 映射到上半空间的点
*
* @code{.cpp}
* // 关于 Y=0 对称(上下对称)
* Point3D q = op_mirror_y(p);
* double d = sphere(q - Point3D(0, 1.0, 0), 0.5); // 上下各一个球
* @endcode
*/
[[nodiscard]] inline Point3D op_mirror_y(const Point3D& p) {
return Point3D(p.x(), std::abs(p.y()), p.z());
}
/// Mirror across Z=0
/**
* @brief 镜像对称(Z=0 平面)
*
* 将空间关于 XY 平面取镜像对称。
*
* **变换:** p'_z = |p_z|
*
* @param p 原始采样点
* @return 映射到前半空间的点
*/
[[nodiscard]] inline Point3D op_mirror_z(const Point3D& p) {
return Point3D(p.x(), p.y(), std::abs(p.z()));
}
/// Translate space (inverse for SDF: move the world, not the object)
/**
* @brief 空间平移(逆变换)
*
* 将采样点向反方向平移,使后续 SDF 求值表现出正向移动效果。
*
* **变换:** p' = p - offset
*
* 例如 offset = (2, 0, 0) 会使 SDF 在空间中表现为向右移动 2 个单位。
*
* @param p 原始采样点
* @param offset 平移量(对象正向移动的方向)
* @return 逆平移后的采样点
*
* @code{.cpp}
* // 球体向右平移 2 个单位
* Point3D q = op_translate(p, Point3D(2, 0, 0));
* double d = sphere(q, 1.0);
* @endcode
*
* @see op_rotate 旋转变换
* @see op_scale 缩放变换
*/
[[nodiscard]] inline Point3D op_translate(const Point3D& p, const Point3D& offset) {
return p - offset;
}
/// Rotate around Y axis by angle_rad (inverse for SDF)
/**
* @brief 绕 Y 轴旋转(逆变换)
*
* 将采样点绕 Y 轴旋转 -angle_rad,使后续 SDF 表现出正向旋转效果。
*
* **变换矩阵:**
* ```
* p'_x = p_x·cos(θ) + p_z·sin(θ)
* p'_y = p_y
* p'_z = -p_x·sin(θ) + p_z·cos(θ)
* ```
*
* @param p 原始采样点
* @param angle_rad 旋转角度(弧度),正值 = 逆时针(从上往下看)
* @return 逆旋转后的采样点
*
* @code{.cpp}
* // 将盒子绕 Y 轴旋转 45°
* Point3D q = op_rotate(p, M_PI / 4);
* double d = box(q, Point3D(1, 1, 1));
* @endcode
*
* @see op_twist 绕 Y 轴的扭曲变换
* @see op_bend 弯曲变换
*/
[[nodiscard]] inline Point3D op_rotate(const Point3D& p, double angle_rad) {
double c = std::cos(-angle_rad);
double s = std::sin(-angle_rad);
return Point3D(p.x() * c - p.z() * s, p.y(), p.x() * s + p.z() * c);
}
/// Non-uniform scale (inverse for SDF)
/**
* @brief 非均匀缩放(逆变换)
*
* 将采样点各分量分别除以缩放因子。
*
* **变换:** p'_i = p_i / s_i
*
* @param p 原始采样点
* @param s 各轴缩放因子 (sx, sy, sz),均必须 ≠ 0
* @return 缩放后的采样点
*
* @warning 非均匀缩放会破坏 SDF 的距离性质(梯度不再是单位长度)。
* 只有均匀缩放(sx=sy=sz)才能保持距离场精确性。
*
* @code{.cpp}
* // 均匀缩小为一半
* Point3D q = op_scale(p, Point3D(2, 2, 2));
* double d = sphere(q, 1.0); // 实际半径变为 0.5
* @endcode
*
* @see op_translate 平移(等距变换)
* @see op_rotate 旋转(等距变换)
*/
[[nodiscard]] inline Point3D op_scale(const Point3D& p, const Point3D& s) {
return Point3D(p.x() / s.x(), p.y() / s.y(), p.z() / s.z());
}
/// Twist space around Y axis
/**
* @brief 绕 Y 轴的扭曲变换
*
* 旋转角度随 Y 坐标线性变化: angle = amount · p_y。
*
* **变换:**
* ```
* p'_x = p_x·cos(amount·p_y) - p_z·sin(amount·p_y)
* p'_y = p_y
* p'_z = p_x·sin(amount·p_y) + p_z·cos(amount·p_y)
* ```
*
* @param p 原始采样点
* @param amount 单位高度上的旋转量(弧度/单位长度)
* @return 扭曲后的采样点
*
* @code{.cpp}
* // 在上方 2 个单位处旋转 90 度的扭曲
* Point3D q = op_twist(p, M_PI / 4);
* double d = box(q, Point3D(0.5, 2, 0.5));
* @endcode
*
* @see op_rotate 恒定角度旋转
* @see op_bend 弯曲变换
*/
[[nodiscard]] inline Point3D op_twist(const Point3D& p, double amount) {
double c = std::cos(amount * p.y());
double s = std::sin(amount * p.y());
return Point3D(p.x() * c - p.z() * s, p.y(), p.x() * s + p.z() * c);
}
/// Bend space
/**
* @brief 弯曲变换(绕 Z 轴弯曲 XZ 平面)
*
* 将空间沿 X 轴弯曲,弯曲量由参数 k 控制。
*
* **变换:**
* ```
* p'_x = cos(k·p_x)·p_x - sin(k·p_x)·p_y
* p'_y = sin(k·p_x)·p_x + cos(k·p_x)·p_y
* p'_z = p_z
* ```
*
* @param p 原始采样点
* @param k 弯曲曲率参数(k 越大弯曲越剧烈)
* @return 弯曲后的采样点
*
* @code{.cpp}
* // 轻度弯曲的长方体
* Point3D q = op_bend(p, 0.2);
* double d = box(q, Point3D(3, 0.3, 0.3));
* @endcode
*
* @see op_cheap_bend 简化版弯曲(仅绕 X 轴)
* @see op_twist 扭曲变换
*/
[[nodiscard]] inline Point3D op_bend(const Point3D& p, double k) {
double c = std::cos(k * p.x());
double s = std::sin(k * p.x());
return Point3D(c * p.x() - s * p.y(), s * p.x() + c * p.y(), p.z());
}
/// Elongate (stretch space, eliminating interior along major axes)
/**
* @brief 拉伸/压扁域变形(消除拉伸方向上的内部体积)
*
* 将点沿各主轴方向向原点压缩,消除 |p_i| ≤ h_i 区域的内部信息。
* 这在组合建模中用于从对象中挖去沿坐标轴的孔道。
*
* **变换:**
* 对每个分量 i
* - 若 |p_i| > h_i: p'_i = p_i - sign(p_i)·h_i
* - 否则: p'_i = 0
*
* @param p 原始采样点
* @param h 各轴的拉伸阈值 (hx, hy, hz),均 ≥ 0
* @return 变形后的采样点
*
* @code{.cpp}
* // 将球体沿 X 轴拉伸,挖去|x|≤1 的区域
* Point3D q = op_elongate(p, Point3D(1, 0, 0));
* double d = sphere(q, 1.5);
* @endcode
*
* @see op_repeat 周期重复
*/
[[nodiscard]] inline Point3D op_elongate(const Point3D& p, const Point3D& h) {
Point3D q = p.cwiseAbs() - h;
return Point3D(
@@ -118,14 +492,56 @@ using core::Vector3D;
);
}
/// Cheap bend (simplified bending)
/**
* @brief 简化弯曲变换(绕 X 轴弯曲 YZ 平面)
*
* 比 op_bend 更快但精度较低的弯曲,仅在 YZ 方向产生偏移。
*
* **变换:**
* ```
* p'_x = p_x
* p'_y = p_y·cos(k·p_x) - p_z·sin(k·p_x)
* p'_z = p_y·sin(k·p_x) + p_z·cos(k·p_x)
* ```
*
* @param p 原始采样点
* @param k 弯曲曲率参数
* @return 弯曲后的采样点
*
* @note 使用场景:不需要精确物理弯曲,仅需视觉效果时优先用此函数以节省计算。
*
* @see op_bend 完整弯曲变换
*/
[[nodiscard]] inline Point3D op_cheap_bend(const Point3D& p, double k) {
double c = std::cos(k * p.x());
double s = std::sin(k * p.x());
return Point3D(p.x(), p.y() * c - p.z() * s, p.y() * s + p.z() * c);
}
/// Displace distance field with sinusoidal noise
/**
* @brief 正弦波位移扰动
*
* 在距离场上叠加一个三维正弦波的微小扰动。
*
* **数学公式:**
* f = d + amplitude · ∏_i sin(p_i · frequency)
*
* 三个方向的 sin 函数乘积产生体纹理效果。
*
* @param d 原始 SDF 值
* @param p 采样点(用于扰动计算)
* @param amplitude 扰动幅值
* @param frequency 空间频率
* @return 扰动后的 SDF 值
*
* @warning 扰动能轻易破坏距离场的 Lipschitz 性质(|∇f| ≤ 1),
* 导致 ray-marching 效率下降。需控制 amplitude/frequency 在合理范围内。
*
* @code{.cpp}
* // 给球体添加微小波纹
* double d = op_displace(sphere(p, 1.0), p, 0.05, 4.0);
* @endcode
*/
[[nodiscard]] inline double op_displace(double d, const Point3D& p,
double amplitude, double frequency) {
double noise = std::sin(p.x() * frequency) *
+449 -43
View File
@@ -1,4 +1,29 @@
#pragma once
/**
* @file sdf_primitives.h
* @brief 有符号距离函数(SDF)基础图元
*
* 提供常见几何体到三维空间中各点的有符号距离计算。
* 所有图元定义在 vde::sdf 命名空间下,使用 vde::core::Point3D 作为输入。
*
* ## SDF 约定
*
* - f(p) < 0: 点 p 在几何体**内部**
* - f(p) = 0: 点 p 在几何体**表面**上
* - f(p) > 0: 点 p 在几何体**外部**
* - |f(p)| 的绝对值近似为 p 到最近表面的欧几里得距离
*
* ## 数值特性
*
* - 所有内联函数标记为 `[[nodiscard]]`,强制调用方使用返回值
* - 距离函数在表面附近满足 |∇f| ≈ 1(Lipschitz 连续,步进可靠)
* - 部分复杂图元(cone, triangular_prism, link, infinite_cone)为非内联实现,定义在 src/sdf/ 中
*
* @note 所有图元假设未施加变换——坐标系原点即图元中心/基点。
* 需要平移/旋转/缩放时,使用 sdf_operations.h 中的域变形算子对采样点预先变换。
*
* @ingroup sdf
*/
#include "vde/core/point.h"
#include <cmath>
#include <algorithm>
@@ -11,21 +36,93 @@ using core::Vector3D;
// Basic Primitives
// ═══════════════════════════════════════════════
/// Signed distance to sphere centered at origin
/**
* @brief 球体的 SDF
*
* 计算三维空间中一点到球面的有符号距离。
*
* **数学公式:** f(p) = |p| - r
*
* ```
* f < 0: 点在球内
* f = 0: 点在球面上
* f > 0: 点在球外
* ```
*
* |∇f| = 1 在球外和球面上严格成立,球内在原点处退化(梯度为零)。
*
* @param p 采样点(三维坐标)
* @param radius 球半径,必须 > 0
* @return 有符号距离
*
* @note 球位于原点。如需其他位置,使用 op_translate 对采样点做反向平移。
*
* @code{.cpp}
* double d = sphere(Point3D(1.0, 0.0, 0.0), 0.5); // d = 0.5 (外部)
* double inside = sphere(Point3D(0.0, 0.0, 0.0), 1.0); // d = -1.0 (球心)
* @endcode
*
* @see ellipsoid 各轴半径不同的椭球
* @see cylinder 沿 Y 轴的圆柱
*/
[[nodiscard]] inline double sphere(const Point3D& p, double radius) {
return p.norm() - radius;
}
/// Signed distance to axis-aligned box centered at origin
/// half_extents = (hx, hy, hz) — half-dimensions in each axis
/**
* @brief 轴对齐立方体的 SDF
*
* 计算点到以原点为中心的轴对齐长方体表面的有符号距离。
*
* **算法概要:**
* 1. 将点的各分量取绝对值,得到第一卦限对称映射
* 2. 减去半边长得到相对于面的距离 qᵢ = |pᵢ| - halfExtentᵢ
* 3. 外部距离 = ||max(q,0)||,内部惩罚 = min(max(qₓ,q_y,q_z), 0)
*
* 结果在面外为真实欧氏距离,边/角区域使用 L2 距离组合保证连续性。
*
* @param p 采样点
* @param half_extents 半边长 (hx, hy, hz),均必须 ≥ 0
* @return 有符号距离
*
* @code{.cpp}
* // 2×3×4 的盒子
* double d = box(Point3D(0.0, 0.0, 0.0), Point3D(1.0, 1.5, 2.0));
* @endcode
*
* @see round_box 带圆角的盒子
* @see wedge 对角切割的半盒子
*/
[[nodiscard]] inline double box(const Point3D& p, const Point3D& half_extents) {
Point3D q = p.cwiseAbs() - half_extents;
return q.cwiseMax(0.0).norm()
+ std::min(std::max({q.x(), q.y(), q.z()}), 0.0);
}
/// Signed distance to rounded box centered at origin
/// Chamfer radius r is subtracted from the box distance
/**
* @brief 圆角立方体的 SDF
*
* 与 box() 使用相同的核心算法,但在最后减去圆角半径 r。
* 相当于先对 box SDF 做膨胀(offset),再缩回,等价于对边角做半径为 r 的倒圆处理。
*
* **数学公式:** f(p) = box(p, half_extents) - r
*
* @param p 采样点
* @param half_extents 半边长 (hx, hy, hz)
* @param r 圆角半径,必须 < min(half_extents) 否则整体形态改变
* @return 有符号距离
*
* @warning 当 r 超过某轴半边长时,该维度的边会被完全圆化,表面不再平坦。
* 实际应用中限制 r ≤ 0.5 * min(hx, hy, hz) 可保证形态可控。
*
* @code{.cpp}
* // 带 0.2 圆角的 2×2×2 立方体
* double d = round_box(p, Point3D(1, 1, 1), 0.2);
* @endcode
*
* @see box 无圆角的长方体
* @see op_round 对任意 SDF 做圆角偏移
*/
[[nodiscard]] inline double round_box(const Point3D& p, const Point3D& half_extents, double r) {
Point3D q = p.cwiseAbs() - half_extents;
return q.cwiseMax(0.0).norm()
@@ -33,16 +130,62 @@ using core::Vector3D;
- r;
}
/// Signed distance to torus in XZ plane, Y-axis symmetry
/// major_radius = distance from origin to tube center
/// minor_radius = tube thickness radius
/**
* @brief 圆环面的 SDF
*
* 圆环位于 XZ 平面内,绕 Y 轴对称。
*
* **数学公式:**
* 1. 计算环向半径: q = √(pₓ² + p𝓏²) - R_major
* 2. 管向距离: f = √(q² + p_y²) - R_minor
*
* R_major = 环心到管心的距离(主半径)
* R_minor = 管的截面半径(副半径)
*
* @param p 采样点
* @param major_radius 主半径(环的半径),必须 > 0
* @param minor_radius 副半径(管的粗细),必须 > 0
* @return 有符号距离
*
* @warning 当 major_radius ≤ minor_radius 时,环孔消失形成苹果状(self-intersecting torus)。
*
* @code{.cpp}
* // 主半径 2.0、管半径 0.5 的圆环
* double d = torus(Point3D(2.0, 0.0, 0.0), 2.0, 0.5); // d ≈ 0.0(表面上)
* @endcode
*
* @see link 双环连接体(两个并行圆环的组合)
*/
[[nodiscard]] inline double torus(const Point3D& p, double major_radius, double minor_radius) {
double qx = std::sqrt(p.x() * p.x() + p.z() * p.z()) - major_radius;
return std::sqrt(qx * qx + p.y() * p.y()) - minor_radius;
}
/// Signed distance to capsule (line segment swept with sphere of given radius)
/// a, b = segment endpoints
/**
* @brief 胶囊体的 SDF
*
* 以线段 ab 为骨架、半径为 radius 的扫掠球体。
* 等价于线段上最近的点到 p 的距离减去 radius。
*
* **算法概要:**
* 1. 将 p 投影到线段 ab 上,投影参数 h = clamp(pa·ba / ba·ba, 0, 1)
* 2. 计算 p 到投影点的距离减去 radius
*
* 退化情况: 当 |ba| ≈ 0(a ≈ b),退化为以 a 为球心的球体。
*
* @param p 采样点
* @param a 线段起点
* @param b 线段终点
* @param radius 扫掠球半径,必须 > 0
* @return 有符号距离
*
* @code{.cpp}
* // 从 (0,-2,0) 到 (0,2,0) 的胶囊、半径 0.5
* double d = capsule(p, Point3D(0,-2,0), Point3D(0,2,0), 0.5);
* @endcode
*
* @see cylinder 两端平头的圆柱(非球形端盖)
*/
[[nodiscard]] inline double capsule(const Point3D& p, const Point3D& a, const Point3D& b, double radius) {
Point3D pa = p - a;
Point3D ba = b - a;
@@ -55,8 +198,32 @@ using core::Vector3D;
return (pa - ba * h).norm() - radius;
}
/// Signed distance to capped cylinder along Y axis, centered at origin
/// radius = cylinder radius, height = full height (from -h/2 to +h/2)
/**
* @brief 带平端盖的圆柱体 SDF
*
* 沿 Y 轴、以原点为中心的有限长圆柱体。两端为平切面(非球形端盖)。
*
* **算法概要:**
* 1. 水平距离: d_xz = √(pₓ² + p𝓏²) - radius
* 2. 垂直距离: d_y = |p_y| - height/2
* 3. 用二维 min/max L2 组合计算矩形区域到圆角的 SDF
*
* 结果在柱面上为真实距离,在端盖边缘有连续过渡。
*
* @param p 采样点
* @param radius 圆柱截面半径,必须 > 0
* @param height 圆柱总高度(从 -h/2 到 +h/2),必须 > 0
* @return 有符号距离
*
* @code{.cpp}
* // 半径 1.0、高度 3.0 的圆柱
* double d = cylinder(Point3D(1.0, 0.0, 0.0), 1.0, 3.0);
* @endcode
*
* @see capsule 两端球形端盖的圆柱
* @see infinite_cylinder 无限长圆柱
* @see cone 圆锥台
*/
[[nodiscard]] inline double cylinder(const Point3D& p, double radius, double height) {
double d_xz = std::sqrt(p.x() * p.x() + p.z() * p.z()) - radius;
double d_y = std::abs(p.y()) - height * 0.5;
@@ -65,15 +232,65 @@ using core::Vector3D;
+ std::max(d_y, 0.0) * std::max(d_y, 0.0));
}
/// Signed distance to plane through origin with given normal
/// Normal must be unit length; offset shifts plane along normal
/**
* @brief 平面的 SDF
*
* 通过原点的平面,法向量为 normal,偏移量为 offset。
*
* **数学公式:** f(p) = p·n - offset
*
* 其中 n 为单位法向量。f > 0 表示点在法向量指向的一侧。
* 偏移量沿法向量正方向移动平面。
*
* @param p 采样点
* @param normal 单位法向量(必须已归一化)
* @param offset 沿法向量正方向的偏移量
* @return 有符号距离
*
* @pre normal.norm() ≈ 1.0(调用方负责归一化)
*
* @code{.cpp}
* // 通过原点、法向量朝上的平面
* double d = plane(p, Vector3D(0,1,0), 0.0);
* // 向上偏移 2 个单位的平面
* double d2 = plane(p, Vector3D(0,1,0), 2.0);
* @endcode
*
* @see wedge 两个平面+盒子的组合(对角切割)
*/
[[nodiscard]] inline double plane(const Point3D& p, const Vector3D& normal, double offset) {
return p.dot(normal) - offset;
}
/// Signed distance to ellipsoid centered at origin
/// radii = semi-axis lengths (rx, ry, rz)
/// Uses the standard bounded-gradient approximation: (|p/radii| - 1) * min(radii)
/**
* @brief 椭球体的 SDF(有界梯度近似)
*
* 以原点为中心的椭球,半轴长为 radii = (rx, ry, rz)。
*
* **算法概要:**
*
* 使用 bound-normalized 近似方法:
* 1. 缩放坐标: pr = p / radii
* 2. k0 = |pr|k1 = |p / radii²|
* 3. f = k0 * (k0 - 1) / k1
*
* 该方法保证在表面上 f = 0,但内部/远处的距离为近似值。
* 相比精确椭球距离,避开了六次方程的昂贵求根。
*
* @param p 采样点
* @param radii 半轴长 (rx, ry, rz),均必须 > 0
* @return 有符号距离(近似)
*
* @note 当三个半径相等时退化为球体,此时使用 sphere() 更精确高效。
* @note 当点到原点距离远大于最大半径时,近似精度下降。
*
* @code{.cpp}
* // 扁椭球: rx=2, ry=1, rz=1
* double d = ellipsoid(Point3D(2.0, 0.0, 0.0), Point3D(2, 1, 1));
* @endcode
*
* @see sphere 精确球体(rx=ry=rz 时的特例)
*/
[[nodiscard]] inline double ellipsoid(const Point3D& p, const Point3D& radii) {
Point3D pr(p.x() / radii.x(), p.y() / radii.y(), p.z() / radii.z());
double k0 = pr.norm();
@@ -91,9 +308,28 @@ using core::Vector3D;
// Parametric Primitives
// ═══════════════════════════════════════════════
/// Signed distance to regular hexagonal prism in XZ plane, extruded along Y
/// radius = circumradius of hexagon (center to vertex)
/// height = full extrusion height (±h/2 along Y)
/**
* @brief 正六棱柱的 SDF
*
* 六边形位于 XZ 平面,尖顶沿 X 轴方向,沿 Y 轴挤出的正六棱柱。
*
* **算法概要:**
* 1. 将点对称映射到第一象限后,计算二维六边形的 SDF
* 2. 六边形 SDF 通过两组约束平面表示: max(px + 0.577*pz, 1.155*pz) - radius
* 3. 沿 Y 轴做 bounded extrusion
*
* @param p 采样点
* @param radius 六边形外接圆半径(中心到顶点),必须 > 0
* @param height 挤出总高度(±h/2 沿 Y 轴),必须 > 0
* @return 有符号距离
*
* @code{.cpp}
* // 外接圆半径 1.0、高度 2.0 的六棱柱
* double d = hex_prism(Point3D(0.0, 0.0, 0.0), 1.0, 2.0);
* @endcode
*
* @see extrusion_bounded 通用 bounded extrusion 算子
*/
[[nodiscard]] inline double hex_prism(const Point3D& p, double radius, double height) {
double px = std::abs(p.x());
double pz = std::abs(p.z());
@@ -103,15 +339,57 @@ using core::Vector3D;
return std::max(d_hex, std::abs(p.y()) - height * 0.5);
}
/// Signed distance to infinite cylinder through origin along given axis
/// Axis must be unit length
/**
* @brief 无限长圆柱的 SDF
*
* 沿任意方向的无限长圆柱(无端盖),轴线过原点。
*
* **数学公式:** f(p) = |p × axis| - radius
*
* 叉积给出点到轴线的垂直距离。
*
* @param p 采样点
* @param axis 圆柱轴线方向(必须归一化,|axis| = 1)
* @param radius 截面半径,必须 > 0
* @return 有符号距离
*
* @pre axis.norm() ≈ 1.0
*
* @code{.cpp}
* // 沿 Z 轴的无限圆柱
* double d = infinite_cylinder(p, Vector3D(0,0,1), 0.5);
* @endcode
*
* @see cylinder 有限长圆柱(带平端盖)
*/
[[nodiscard]] inline double infinite_cylinder(const Point3D& p, const Vector3D& axis, double radius) {
return p.cross(axis).norm() - radius;
}
/// Signed distance to wedge — half of a box cut diagonally in XZ plane
/// w = width (X), h = height (Y), d = depth (Z)
/// The wedge occupies the half where z ≥ x inside the box
/**
* @brief 楔形体的 SDF
*
* 立方体沿 XZ 平面对角线切割的一半。切割面为 z = x。
* 楔形体占据盒子内 z ≥ x 的区域。
*
* **几何形状:**
* - 盒子: [-w/2, w/2] × [-h/2, h/2] × [-d/2, d/2]
* - 保留区域: 盒子内 z ≥ x 的半空间
*
* @param p 采样点
* @param w 宽度(X 方向)
* @param h 高度(Y 方向)
* @param d 深度(Z 方向)
* @return 有符号距离
*
* @code{.cpp}
* // 2×2×2 的楔形体(保留 z ≥ x 的一半)
* double d = wedge(Point3D(-0.5, 0.0, 0.5), 2.0, 2.0, 2.0);
* @endcode
*
* @see box 完整立方体
* @see plane 独立平面 SDF
*/
[[nodiscard]] inline double wedge(const Point3D& p, double w, double h, double d) {
Point3D q = p.cwiseAbs() - Point3D(w * 0.5, h * 0.5, d * 0.5);
double d_box = q.cwiseMax(0.0).norm()
@@ -125,21 +403,79 @@ using core::Vector3D;
// 2D Extrusions & Revolution
// ═══════════════════════════════════════════════
/// Extrude a 2D SDF infinitely along Z axis
/// sdf_2d = pre-computed 2D SDF value at (p.x, p.y)
/**
* @brief 二维 SDF 沿 Z 轴的无限挤出
*
* 将二维 SDF 沿 Z 轴无限延伸。三维点的 SDF 值等于其 (x,y) 投影处的二维 SDF 值。
*
* **使用模式:** 先计算二维 SDF 值,再传入此函数:
*
* @code{.cpp}
* double sdf_2d = circle_sdf(p.x(), p.y()); // 用户自定义二维 SDF
* double sdf_3d = extrusion(p, sdf_2d);
* @endcode
*
* @param p 三维采样点(仅 x,y 分量被隐式使用——通过 sdf_2d 参数间接)
* @param sdf_2d 预计算的二维 SDF 值(在 (p.x, p.y) 处求值)
* @return 三维有符号距离(等于 sdf_2d)
*
* @note p 参数仅保持接口一致,实际不影响结果。
* @note 对于有限长度的挤出,使用 extrusion_bounded()。
*
* @see extrusion_bounded 有限长度的挤出
* @see revolution 二维轮廓绕 Y 轴旋转
*/
[[nodiscard]] inline double extrusion(const Point3D& /*p*/, double sdf_2d) {
return sdf_2d;
}
/// Bounded extrusion: 2D SDF intersected with Z slab ±half_height
/// sdf_2d = pre-computed 2D SDF value at (p.x, p.y)
/**
* @brief 二维 SDF 的有限长度挤出
*
* 将二维 SDF 沿 Z 轴挤出并通过 |pz| ≤ half_height 做 bounded 限制。
* 相当于对二维 SDF 与 Z 轴平板做交集。
*
* @param p 三维采样点
* @param sdf_2d 预计算的二维 SDF 值(在 (p.x, p.y) 处求值)
* @param half_height 挤出半高度(Z 方向),必须 ≥ 0
* @return 有符号距离
*
* @code{.cpp}
* // 圆形截面、总高 4.0 的短柱
* double d2d = std::sqrt(p.x()*p.x() + p.y()*p.y()) - 1.0;
* double d3d = extrusion_bounded(p, d2d, 2.0);
* @endcode
*
* @see extrusion 无限挤出
* @see hex_prism 六棱柱(特例化 bounded extrusion 实现)
*/
[[nodiscard]] inline double extrusion_bounded(const Point3D& p, double sdf_2d, double half_height) {
return std::max(sdf_2d, std::abs(p.z()) - half_height);
}
/// Revolve a 2D profile around Y axis
/// sdf_2d = pre-computed 2D SDF value at (|p.xz|-offset, p.y)
/// offset = radial offset from Y axis
/**
* @brief 二维轮廓绕 Y 轴的旋转体 SDF
*
* 将二维 SDF 绕 Y 轴旋转,生成旋转体的三维 SDF。
*
* **使用模式:**
* 二维 SDF 定义在 (r, y) 坐标中,其中 r 为水平距离 = √(pₓ² + p𝓏²) - offset。
* 用户需要在调用前将 (r, y) 转换为二维采样并计算 SDF 值。
*
* @param p 三维采样点(接口保留,不直接使用)
* @param sdf_2d 预计算的二维 SDF(在 (√(pₓ²+p𝓏²)-offset, p.y) 处求值)
* @param offset 径向偏移量(从 Y 轴的起点距离)
* @return 有符号距离
*
* @code{.cpp}
* // 在距 Y 轴 2.0 处、截面半径 0.5 的圆环
* double r = std::sqrt(p.x()*p.x() + p.z()*p.z()) - 2.0;
* double sdf_2d = std::sqrt(r*r + p.y()*p.y()) - 0.5;
* double d3d = revolution(p, sdf_2d, 2.0);
* @endcode
*
* @see torus 圆环(revolution 的特例:截面为圆)
*/
[[nodiscard]] inline double revolution(const Point3D& /*p*/, double sdf_2d, double /*offset*/) {
return sdf_2d;
}
@@ -148,24 +484,94 @@ using core::Vector3D;
// Complex Primitives (implemented in src/sdf/)
// ═══════════════════════════════════════════════
/// Signed distance to capped cone, centered at origin along Y axis
/// Apex at y = +height/2, base at y = -height/2
/// angle_rad = half-angle at apex, height = full height
/**
* @brief 圆锥台的 SDF(非内联实现)
*
* 沿 Y 轴、以原点为中心的有限长圆锥台。
* 顶点在 y = +height/2(半径 0),底面在 y = -height/2(半径 height·tan(angle_rad))。
*
* **算法概要:**
* 将三维问题分解为二维 (r, y) 平面内点到三角形区域的距离计算,
* 其中 r = √(pₓ² + p𝓏²),三角形由底边和高决定。
*
* @param p 采样点
* @param angle_rad 半锥角(弧度),决定底面半径
* @param height 锥体总高度,必须 > 0
* @return 有符号距离
*
* @warning 当 angle_rad 接近 π/2 时底面半径极大,可能导致数值不稳定。
*
* @see cylinder 半锥角为 0 时的退化情况
* @see infinite_cone 无限圆锥
*/
[[nodiscard]] double cone(const Point3D& p, double angle_rad, double height);
/// Signed distance to triangular prism with arbitrary triangle cross-section
/// Triangle defined by vertices a,b,c in XY plane, extruded ±height/2 along Z
/**
* @brief 三角棱柱的 SDF(非内联实现)
*
* 以 XY 平面中任意三角形为截面、沿 Z 轴 ±height/2 挤出的棱柱。
*
* @param p 采样点
* @param a 三角形顶点 AXY 平面内)
* @param b 三角形顶点 BXY 平面内)
* @param c 三角形顶点 CXY 平面内)
* @param height 挤出总高度(沿 Z 轴),必须 > 0
* @return 有符号距离
*
* @pre a, b, c 三点不共线(构成有效三角形)
*
* @code{.cpp}
* // 顶点为 (0,0), (2,0), (0,2)、高度 1.0 的三棱柱
* double d = triangular_prism(p, Point3D(0,0,0), Point3D(2,0,0), Point3D(0,2,0), 1.0);
* @endcode
*
* @see wedge 直角三角形楔形体
*/
[[nodiscard]] double triangular_prism(const Point3D& p, const Point3D& a, const Point3D& b,
const Point3D& c, double height);
/// Signed distance to link (two parallel toruses connected along X)
/// length = spacing between torus centers along X
/// major_r = major radius of each torus
/// minor_r = minor (tube) radius of each torus
/**
* @brief 双环链接体的 SDF(非内联实现)
*
* 两个并列的圆环面沿 X 轴排列,等效于经典链环结构。
* 常用于链条、连杆等机械连接件的快速建模。
*
* @param p 采样点
* @param length 两个环心沿 X 轴的距离
* @param major_r 每个环的主半径(环的半径)
* @param minor_r 每个环的管半径(截面的粗细)
* @return 有符号距离
*
* @code{.cpp}
* // 环间距 2.0、主半径 1.0、管半径 0.2 的双环
* double d = link(Point3D(1.0, 0.0, 0.0), 2.0, 1.0, 0.2);
* @endcode
*
* @see torus 单个圆环
*/
[[nodiscard]] double link(const Point3D& p, double length, double major_r, double minor_r);
/// Signed distance to infinite cone from apex along axis
/// axis must be unit length, angle_rad = half-angle
/**
* @brief 无限圆锥的 SDF(非内联实现)
*
* 从顶点出发沿轴方向无限延伸的圆锥,半锥角为 angle_rad。
* 与 cone() 不同,本函数无高度限制,锥体向轴的正方向无限扩展。
*
* @param p 采样点
* @param apex 锥顶位置
* @param axis 锥轴方向(必须归一化),锥体沿此方向扩展
* @param angle_rad 半锥角(弧度)
* @return 有符号距离
*
* @pre axis.norm() ≈ 1.0
*
* @code{.cpp}
* // 顶点在原点、沿 Y 轴、半锥角 30° 的无限锥
* double d = infinite_cone(p, Point3D(0,0,0), Vector3D(0,1,0), M_PI/6);
* @endcode
*
* @see cone 有限高圆锥台
*/
[[nodiscard]] double infinite_cone(const Point3D& p, const Point3D& apex,
const Vector3D& axis, double angle_rad);
+77 -12
View File
@@ -1,26 +1,91 @@
#pragma once
/**
* @file sdf_to_mesh.h
* @brief SDF 到三角网格的转换
*
* 使用 Marching Cubes 算法将隐式曲面(SDF)转换为显式三角网格。
*
* ## Marching Cubes 算法
*
* 在指定的包围盒内以固定分辨率采样 SDF 场,通过查表确定每个体素内的等值面三角形配置。
*
* **流程:**
* 1. 在 `[bmin, bmax]` 范围内构建分辨率 `res³` 的三维规则网格
* 2. 在每个网格点求值 SDF
* 3. 利用 Marching Cubes 查找表在等值面处生成三角形
* 4. 返回包含顶点和三角形索引的 MCMesh
*
* ## 注意事项
*
* - 分辨率越高网格越精细,但计算量按 O(res³) 增长
* - 推荐分辨率 64~256,取决于几何复杂度
* - 结果网格需在 Marching Cubes 模块中定义(vde::mesh::MCMesh
*
* @ingroup sdf
*/
#include "vde/sdf/sdf_tree.h"
#include "vde/mesh/marching_cubes.h"
#include <functional>
namespace vde::sdf {
/// Convert an SDF expression tree to a triangle mesh using marching cubes
/// @param root SDF expression tree root node
/// @param resolution Grid resolution per axis (e.g. 64 → 64³ grid)
/// @param iso_level Isosurface level (0 = surface, default)
/// @return Mesh with vertices and triangle indices
/**
* @brief 将 SDF 表达式树转换为三角网格
*
* 使用 Marching Cubes 算法在 SDF 树的包围盒内采样并生成网格。
* 包围盒由 estimate_bbox() 自动确定。
*
* **算法步骤:**
* 1. 调用 estimate_bbox(root) 确定采样范围
* 2. 构建分辨率 `resolution³` 体素网格
* 3. 在每个体素角点求值 SDF
* 4. 用 Marching Cubes 提取 iso_level 等值面
*
* @param root SDF 表达式树的根节点
* @param resolution 每轴的网格分辨率(例如 64 表示 64³ 体素网格)
* @param iso_level 等值面水平(0.0 = 隐式表面)
* @return 包含顶点和三角形索引的 MCMesh
*
* @note 分辨率推荐为 2 的幂(32, 64, 128, 256)以获得最佳缓存性能。
* @note 对于大尺寸但细节少的几何体,使用较低的 resolution 即可获得光滑结果。
*
* @code{.cpp}
* auto root = SdfNode::sphere(2.0);
* auto mesh = sdf_to_mesh(root, 128); // 128³ 体素网格
* // mesh.vertices 和 mesh.indices 可直接用于渲染或导出
* @endcode
*
* @see sdf_to_mesh_lambda Lambda 版本的 SDF 转网格(Python 友好)
* @see estimate_bbox 包围盒估算
*/
[[nodiscard]] mesh::MCMesh sdf_to_mesh(const SdfNodePtr& root,
int resolution = 64,
double iso_level = 0.0);
/// Convert a lambda SDF f(x,y,z) to a triangle mesh
/// Convenience overload for direct function binding (useful from Python)
/// @param f SDF function f(x,y,z) → signed distance
/// @param bmin Lower corner of bounding box
/// @param bmax Upper corner of bounding box
/// @param resolution Grid resolution per axis
/// @param iso_level Isosurface level
/**
* @brief 将 Lambda SDF 函数转换为三角网格
*
* 接受 C++ lambda 或 std::function 作为 SDF 定义,适合 Python 绑定场景。
* 与 sdf_to_mesh() 不同,本函数需要调用方显式提供包围盒。
*
* @param f SDF 函数,签名为 double(double x, double y, double z) → 有符号距离
* @param bmin 采样包围盒最小角点
* @param bmax 采样包围盒最大角点
* @param resolution 每轴分辨率
* @param iso_level 等值面水平(默认 0.0
* @return MCMesh 三角网格
*
* @code{.cpp}
* // 球体 lambda
* auto sphere_lambda = [](double x, double y, double z) {
* return std::sqrt(x*x + y*y + z*z) - 1.0;
* };
* auto mesh = sdf_to_mesh_lambda(sphere_lambda,
* Point3D(-1.5, -1.5, -1.5), Point3D(1.5, 1.5, 1.5), 64);
* @endcode
*
* @see sdf_to_mesh 基于 SDF 树的网格转换
*/
[[nodiscard]] mesh::MCMesh sdf_to_mesh_lambda(
const std::function<double(double, double, double)>& f,
const Point3D& bmin, const Point3D& bmax,
+255 -36
View File
@@ -1,4 +1,37 @@
#pragma once
/**
* @file sdf_tree.h
* @brief SDF 表达式树结构
*
* 将 SDF 原语和 CSG 操作以树形数据结构表示,支持树遍历、求值和包围盒估算。
*
* ## 架构层级
*
* ```
* SdfNode (树节点) — 单节点,含操作类型+参数+子节点
* ├─ enum SdfOp — 操作枚举(图元/CSG/修饰/变形)
* ├─ struct SdfParams — 参数联合体
* ├─ 静态工厂方法 — 创建各类型节点
* └─ visit() — 前序遍历
*
* evaluate() — 递归求值
* estimate_bounds() / estimate_bbox() — 包围盒估算
* ```
*
* ## 使用模式
*
* 通过静态工厂方法构建节点树,然后调用 evaluate() 进行求值:
*
* @code{.cpp}
* auto root = SdfNode::op_union(
* SdfNode::sphere(1.0),
* SdfNode::box(Point3D(0.5, 0.5, 0.5))
* );
* double d = evaluate(root, Point3D(1.0, 0.0, 0.0));
* @endcode
*
* @ingroup sdf
*/
#include "vde/sdf/sdf_primitives.h"
#include "vde/sdf/sdf_operations.h"
#include "vde/core/point.h"
@@ -14,7 +47,16 @@ using core::Vector3D;
class SdfNode;
using SdfNodePtr = std::shared_ptr<SdfNode>;
/// All node types in the CSG tree
/**
* @brief CSG 树的所有节点类型
*
* 枚举涵盖三类操作:
* - **图元**Sphere, Box, ... — 叶子节点,产生基本几何体
* - **CSG 布尔**Union, Intersection, Difference — 二叉树组合
* - **修饰/变形**Round, Translate, Twist, ... — 单子节点变换
*
* @see SdfParams 对应的参数结构
*/
enum class SdfOp : uint8_t {
// Primitives
Sphere, Box, RoundBox, Torus, Capsule, Cylinder, Cone, Plane,
@@ -31,87 +73,208 @@ enum class SdfOp : uint8_t {
Displace
};
/// Parameter payload — one struct to rule them all
/**
* @brief SDF 节点参数联合体
*
* 一个结构体囊括所有可能的参数。不同操作类型使用不同字段子集:
*
* | 操作类型 | 使用字段 |
* |-------------------|--------------------------------------------|
* | Sphere | radius |
* | Box / RoundBox | extents |
* | Cylinder / Cone | radius, height, angle_rad |
* | Plane | normal, offset |
* | Torus | major_radius, minor_radius |
* | Capsule | pt_a, pt_b, radius |
* | Smooth CSG | blend_k |
* | Repeat | repeat_cell |
* | Translate | translate_offset |
* | Scale | scale_factors |
* | Displace | amplitude, frequency |
* | Twist/Bend | amount |
* | Onion | thickness |
* | Round | offset |
*
* @warning 不使用的字段保持默认值即可,但不要依赖未使用字段的默认值来做逻辑判断。
*/
struct SdfParams {
// Primitives
double radius = 1.0;
Point3D extents = Point3D(1, 1, 1);
Point3D pt_a = Point3D(0, -1, 0);
Point3D pt_b = Point3D(0, 1, 0);
double major_radius = 1.0;
double minor_radius = 0.3;
double angle_rad = 0.5;
double height = 2.0;
double thickness = 0.1;
Vector3D normal = Vector3D(0, 1, 0);
double offset = 0.0;
// Operations
double blend_k = 0.2;
double amount = 0.5;
double amplitude = 0.1;
double frequency = 1.0;
Point3D repeat_cell = Point3D(2, 2, 2);
Point3D translate_offset = Point3D(0, 0, 0);
Point3D scale_factors = Point3D(1, 1, 1);
// ── Primitive parameters ──
double radius = 1.0; ///< 球体/圆柱/胶囊/圆角半径
Point3D extents = Point3D(1, 1, 1); ///< 盒子的半边长
Point3D pt_a = Point3D(0, -1, 0); ///< 胶囊端点 A
Point3D pt_b = Point3D(0, 1, 0); ///< 胶囊端点 B
double major_radius = 1.0; ///< 圆环主半径
double minor_radius = 0.3; ///< 圆环副半径
double angle_rad = 0.5; ///< 锥体半角(弧度)
double height = 2.0; ///< 柱体/锥体/挤出高度
double thickness = 0.1; ///< 壳层厚度(Onion
Vector3D normal = Vector3D(0, 1, 0); ///< 平面法向量
double offset = 0.0; ///< 平面偏移 / 通用偏移
// ── Operation parameters ──
double blend_k = 0.2; ///< 平滑 CSG 混合参数
double amount = 0.5; ///< 扭曲/弯曲量
double amplitude = 0.1; ///< 位移扰动幅值
double frequency = 1.0; ///< 位移扰动频率
Point3D repeat_cell = Point3D(2, 2, 2); ///< 重复晶格尺寸
Point3D translate_offset = Point3D(0, 0, 0); ///< 平移量
Point3D scale_factors = Point3D(1, 1, 1); ///< 缩放因子
};
/// A single node in the SDF expression tree
/**
* @brief SDF 表达式树节点
*
* 每个节点包含:
* - `op`: 操作类型
* - `params`: 参数值
* - `children`: 子节点列表(CSG 布尔 = 2 个子节点,修饰/变形 = 1 个,图元 = 0 个)
* - `name`: 可选名称(调试用)
*
* 使用 make_shared<SdfNode>(op) 或静态工厂方法创建节点。
* 所有工厂方法返回 SdfNodePtrshared_ptr),自动管理生命周期。
*
* @note 树是不可变的——节点创建后 op 和子节点关系不变,但 params 可被外部修改以支持参数优化。
*/
class SdfNode {
public:
SdfOp op;
SdfParams params;
std::vector<SdfNodePtr> children;
std::string name;
SdfOp op; ///< 操作类型
SdfParams params; ///< 参数值
std::vector<SdfNodePtr> children; ///< 子节点
std::string name; ///< 节点名(调试用)
// ── Primitive factories ──
/** @brief 创建球体节点 */
static SdfNodePtr sphere(double r);
/** @brief 创建轴对齐长方体节点 */
static SdfNodePtr box(const Point3D& half_extents);
/** @brief 创建圆角长方体节点 */
static SdfNodePtr round_box(const Point3D& half_extents, double r);
/** @brief 创建圆柱体节点 */
static SdfNodePtr cylinder(double r, double h);
/** @brief 创建圆环体节点 */
static SdfNodePtr torus(double major_r, double minor_r);
/** @brief 创建胶囊体节点 */
static SdfNodePtr capsule(const Point3D& a, const Point3D& b, double r);
/** @brief 创建圆锥台节点 */
static SdfNodePtr cone(double angle_rad, double h);
/** @brief 创建平面节点 */
static SdfNodePtr plane(const Vector3D& normal, double offset);
/** @brief 创建椭球体节点 */
static SdfNodePtr ellipsoid(const Point3D& radii);
/** @brief 创建三角棱柱节点 */
static SdfNodePtr triangular_prism(double h);
/** @brief 创建六棱柱节点 */
static SdfNodePtr hex_prism(double h);
/** @brief 创建双环链接节点 */
static SdfNodePtr link(double r, double length, double thickness);
/** @brief 创建楔形体节点 */
static SdfNodePtr wedge(const Point3D& extents);
// ── Boolean CSG factories (two children) ──
/** @brief 创建并集节点: A B */
static SdfNodePtr op_union(SdfNodePtr a, SdfNodePtr b);
/** @brief 创建交集节点: A ∩ B */
static SdfNodePtr op_intersection(SdfNodePtr a, SdfNodePtr b);
/** @brief 创建差集节点: A \ B */
static SdfNodePtr op_difference(SdfNodePtr a, SdfNodePtr b);
/** @brief 创建平滑并集节点 */
static SdfNodePtr smooth_union(SdfNodePtr a, SdfNodePtr b, double k);
/** @brief 创建平滑交集节点 */
static SdfNodePtr smooth_intersection(SdfNodePtr a, SdfNodePtr b, double k);
/** @brief 创建平滑差集节点 */
static SdfNodePtr smooth_difference(SdfNodePtr a, SdfNodePtr b, double k);
// ── Modifier factories (one child) ──
/** @brief 创建圆角偏移节点 */
static SdfNodePtr round(SdfNodePtr child, double r);
/** @brief 创建壳层修饰节点 */
static SdfNodePtr onion(SdfNodePtr child, double thickness);
// ── Domain transform factories (one child) ──
/** @brief 创建周期重复节点 */
static SdfNodePtr repeat(SdfNodePtr child, const Point3D& cell);
/** @brief 创建 X 镜像节点 */
static SdfNodePtr mirror_x(SdfNodePtr child);
/** @brief 创建 Y 镜像节点 */
static SdfNodePtr mirror_y(SdfNodePtr child);
/** @brief 创建 Z 镜像节点 */
static SdfNodePtr mirror_z(SdfNodePtr child);
/** @brief 创建平移节点 */
static SdfNodePtr translate(SdfNodePtr child, const Point3D& offset);
/** @brief 创建旋转节点(绕 Y 轴) */
static SdfNodePtr rotate(SdfNodePtr child, double angle_rad);
/** @brief 创建缩放节点 */
static SdfNodePtr scale(SdfNodePtr child, const Point3D& factors);
/** @brief 创建扭曲节点 */
static SdfNodePtr twist(SdfNodePtr child, double amount);
/** @brief 创建弯曲节点 */
static SdfNodePtr bend(SdfNodePtr child, double amount);
/** @brief 创建拉伸节点 */
static SdfNodePtr elongate(SdfNodePtr child, const Point3D& h);
/** @brief 创建简化弯曲节点 */
static SdfNodePtr cheap_bend(SdfNodePtr child, double amount);
/** @brief 创建位移扰动节点 */
static SdfNodePtr displace(SdfNodePtr child, double amplitude, double frequency);
/// Visit all nodes in the tree (pre-order)
/**
* @brief 前序遍历所有节点
*
* 对树中每个节点调用回调函数 fn,遍历顺序为前序(父节点先于子节点)。
*
* @tparam Fn 可调用对象,签名为 void(SdfNode&)
*
* @code{.cpp}
* int leaf_count = 0;
* root->visit([&](const SdfNode& n) {
* if (n.children.empty()) ++leaf_count;
* });
* @endcode
*/
template <typename Fn>
void visit(Fn&& fn) {
fn(*this);
for (auto& c : children) c->visit(std::forward<Fn>(fn));
}
/// Visit all nodes in the tree (const pre-order)
/**
* @brief 前序遍历(const 版本)
*
* @tparam Fn 可调用对象,签名为 void(const SdfNode&)
* @see visit(Fn&&)
*/
template <typename Fn>
void visit(Fn&& fn) const {
fn(*this);
@@ -119,22 +282,78 @@ public:
}
public:
/**
* @brief 构造 SDF 节点
* @param o 操作类型
*/
explicit SdfNode(SdfOp o) : op(o) {}
};
/// Evaluate the SDF tree at a point in world space
/// Returns signed distance: negative = inside, positive = outside
/**
* @brief 在空间点 p 处求值 SDF 树
*
* 递归遍历表达式树,在叶节点调用对应图元的 SDF 函数,
* 在内部节点执行 CSG 操作或域变形。
*
* **算法概要:**
* 1. 域变形节点先对 p 做变换,将变换后的点传入子节点
* 2. CSG 布尔节点分别求值两个子节点后组合
* 3. 图元节点直接调用内联 SDF 函数
*
* @param root SDF 表达式树的根节点
* @param p 世界空间中的采样点
* @return 有符号距离(负 = 内部,零 = 表面,正 = 外部)
*
* @code{.cpp}
* auto root = SdfNode::sphere(1.0);
* double d = evaluate(root, Point3D(0.5, 0.0, 0.0)); // ≈ -0.5
* @endcode
*
* @see estimate_bounds 估计树的包围盒
*/
[[nodiscard]] double evaluate(const SdfNodePtr& root, const Point3D& p);
/// Estimate bounding box of the SDF tree
/// Returns the half-extent from center to corner (bounding radius)
/**
* @brief 估算 SDF 树的包围半尺寸
*
* 通过对叶节点包围盒的递归合并,估算从树根到最远角落的半径。
* 结果是包围盒的半边长(从中心到面的距离)。
*
* @param root SDF 树的根节点
* @param margin 额外安全边距(默认 1.0)
* @return 包围半边长 (hx, hy, hz)
*
* @note 变形节点(旋转/弯曲/扭曲等)会使包围盒放大,以保守估计覆盖变形后区域。
*
* @see estimate_bbox 完整 AABB 结果
*/
[[nodiscard]] Point3D estimate_bounds(const SdfNodePtr& root, double margin = 1.0);
/// Compute full AABB from estimate_bounds result
/**
* @brief SDF 树的包围盒(AABB
*
* min/max 分别为最小和最大角点坐标,包围盒以原点对称时 min = -max。
*/
struct SdfBBox {
Point3D min;
Point3D max;
Point3D min; ///< 最小角点
Point3D max; ///< 最大角点
};
/**
* @brief 估算 SDF 树的完整轴对齐包围盒
*
* 在 estimate_bounds 基础上计算对称 AABB。
*
* @param root SDF 树的根节点
* @param margin 额外安全边距
* @return AABB 包围盒
*
* @code{.cpp}
* auto bbox = estimate_bbox(root, 0.5);
* // bbox.min = (-hx-0.5, -hy-0.5, -hz-0.5)
* // bbox.max = ( hx+0.5, hy+0.5, hz+0.5)
* @endcode
*/
[[nodiscard]] SdfBBox estimate_bbox(const SdfNodePtr& root, double margin = 1.0);
} // namespace vde::sdf