Files

360 lines
12 KiB
C++
Raw Permalink Normal View History

#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"
#include <memory>
#include <string>
#include <vector>
namespace vde::sdf {
using core::Point3D;
using core::Vector3D;
// Forward declaration
class SdfNode;
using SdfNodePtr = std::shared_ptr<SdfNode>;
/**
* @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,
Ellipsoid, TriangularPrism, HexPrism, Link, Wedge,
// Boolean CSG
Union, Intersection, Difference,
SmoothUnion, SmoothIntersection, SmoothDifference,
// Modifiers
Round, Onion,
// Domain transforms
Repeat, MirrorX, MirrorY, MirrorZ, Translate, Rotate, Scale,
Twist, Bend, Elongate, CheapBend,
// Displacement
Displace
};
/**
* @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 {
// ── 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); ///< 缩放因子
};
/**
* @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; ///< 节点名(调试用)
// ── 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);
/**
* @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));
}
/**
* @brief 前序遍历(const 版本)
*
* @tparam Fn 可调用对象,签名为 void(const SdfNode&)
* @see visit(Fn&&)
*/
template <typename Fn>
void visit(Fn&& fn) const {
fn(*this);
for (auto& c : children) c->visit(std::forward<Fn>(fn));
}
public:
/**
* @brief 构造 SDF 节点
* @param o 操作类型
*/
explicit SdfNode(SdfOp o) : op(o) {}
};
/**
* @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);
/**
* @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);
/**
* @brief SDF 树的包围盒(AABB
*
* min/max 分别为最小和最大角点坐标,包围盒以原点对称时 min = -max。
*/
struct SdfBBox {
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