2026-07-24 07:04:55 +00:00
|
|
|
|
#pragma once
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
#include "vde/core/point.h"
|
|
|
|
|
|
#include <cmath>
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
|
|
|
|
namespace vde::sdf {
|
|
|
|
|
|
using core::Point3D;
|
|
|
|
|
|
using core::Vector3D;
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════
|
|
|
|
|
|
// Basic Primitives
|
|
|
|
|
|
// ═══════════════════════════════════════════════
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 轴的圆柱
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[nodiscard]] inline double sphere(const Point3D& p, double radius) {
|
|
|
|
|
|
return p.norm() - radius;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 对角切割的半盒子
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 做圆角偏移
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[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()
|
|
|
|
|
|
+ std::min(std::max({q.x(), q.y(), q.z()}), 0.0)
|
|
|
|
|
|
- r;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 双环连接体(两个并行圆环的组合)
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 两端平头的圆柱(非球形端盖)
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[nodiscard]] inline double capsule(const Point3D& p, const Point3D& a, const Point3D& b, double radius) {
|
|
|
|
|
|
Point3D pa = p - a;
|
|
|
|
|
|
Point3D ba = b - a;
|
2026-07-24 08:42:53 +00:00
|
|
|
|
double ba_sq = ba.squaredNorm();
|
|
|
|
|
|
if (ba_sq < 1e-30) {
|
|
|
|
|
|
// Degenerate to sphere
|
|
|
|
|
|
return pa.norm() - radius;
|
|
|
|
|
|
}
|
|
|
|
|
|
double h = std::clamp(pa.dot(ba) / ba_sq, 0.0, 1.0);
|
2026-07-24 07:04:55 +00:00
|
|
|
|
return (pa - ba * h).norm() - radius;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 圆锥台
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[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;
|
|
|
|
|
|
return std::min(std::max(d_xz, d_y), 0.0)
|
|
|
|
|
|
+ std::sqrt(std::max(d_xz, 0.0) * std::max(d_xz, 0.0)
|
|
|
|
|
|
+ std::max(d_y, 0.0) * std::max(d_y, 0.0));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 两个平面+盒子的组合(对角切割)
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[nodiscard]] inline double plane(const Point3D& p, const Vector3D& normal, double offset) {
|
|
|
|
|
|
return p.dot(normal) - offset;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 时的特例)
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[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();
|
|
|
|
|
|
double k1 = Point3D(p.x() / (radii.x() * radii.x()),
|
|
|
|
|
|
p.y() / (radii.y() * radii.y()),
|
|
|
|
|
|
p.z() / (radii.z() * radii.z())).norm();
|
2026-07-24 08:42:53 +00:00
|
|
|
|
if (k1 < 1e-30) {
|
|
|
|
|
|
// Degenerate: point at origin → distance = -min(radii)
|
|
|
|
|
|
return -std::min({radii.x(), radii.y(), radii.z()});
|
|
|
|
|
|
}
|
2026-07-24 07:04:55 +00:00
|
|
|
|
return k0 * (k0 - 1.0) / k1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════
|
|
|
|
|
|
// Parametric Primitives
|
|
|
|
|
|
// ═══════════════════════════════════════════════
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 算子
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[nodiscard]] inline double hex_prism(const Point3D& p, double radius, double height) {
|
|
|
|
|
|
double px = std::abs(p.x());
|
|
|
|
|
|
double pz = std::abs(p.z());
|
|
|
|
|
|
// 2D hexagon SDF (pointy-top in XZ plane)
|
|
|
|
|
|
double d_hex = std::max(px + pz * 0.577350269189626, pz * 1.154700538379252) - radius;
|
|
|
|
|
|
// Extrude along Y
|
|
|
|
|
|
return std::max(d_hex, std::abs(p.y()) - height * 0.5);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 有限长圆柱(带平端盖)
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[nodiscard]] inline double infinite_cylinder(const Point3D& p, const Vector3D& axis, double radius) {
|
|
|
|
|
|
return p.cross(axis).norm() - radius;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[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()
|
|
|
|
|
|
+ std::min(std::max({q.x(), q.y(), q.z()}), 0.0);
|
|
|
|
|
|
// Diagonal plane x - z ≤ 0 (i.e., z ≥ x)
|
|
|
|
|
|
double d_cut = (p.x() - p.z()) * 0.707106781186548; // 1/√2
|
|
|
|
|
|
return std::max(d_box, d_cut);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════
|
|
|
|
|
|
// 2D Extrusions & Revolution
|
|
|
|
|
|
// ═══════════════════════════════════════════════
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 轴旋转
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[nodiscard]] inline double extrusion(const Point3D& /*p*/, double sdf_2d) {
|
|
|
|
|
|
return sdf_2d;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 实现)
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 的特例:截面为圆)
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[nodiscard]] inline double revolution(const Point3D& /*p*/, double sdf_2d, double /*offset*/) {
|
|
|
|
|
|
return sdf_2d;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════
|
|
|
|
|
|
// Complex Primitives (implemented in src/sdf/)
|
|
|
|
|
|
// ═══════════════════════════════════════════════
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 无限圆锥
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[nodiscard]] double cone(const Point3D& p, double angle_rad, double height);
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 三角棱柱的 SDF(非内联实现)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 以 XY 平面中任意三角形为截面、沿 Z 轴 ±height/2 挤出的棱柱。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param p 采样点
|
|
|
|
|
|
* @param a 三角形顶点 A(XY 平面内)
|
|
|
|
|
|
* @param b 三角形顶点 B(XY 平面内)
|
|
|
|
|
|
* @param c 三角形顶点 C(XY 平面内)
|
|
|
|
|
|
* @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 直角三角形楔形体
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[nodiscard]] double triangular_prism(const Point3D& p, const Point3D& a, const Point3D& b,
|
|
|
|
|
|
const Point3D& c, double height);
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 单个圆环
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[nodiscard]] double link(const Point3D& p, double length, double major_r, double minor_r);
|
|
|
|
|
|
|
2026-07-24 11:04:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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 有限高圆锥台
|
|
|
|
|
|
*/
|
2026-07-24 07:04:55 +00:00
|
|
|
|
[[nodiscard]] double infinite_cone(const Point3D& p, const Point3D& apex,
|
|
|
|
|
|
const Vector3D& axis, double angle_rad);
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace vde::sdf
|