feat(sdf): S11 — SDF 隐式建模完整模块
CI / Build & Test (push) Failing after 29s
CI / Release Build (push) Failing after 35s

S11-A: SDF 图元库
- 14 种基础形状(inline header)
- sphere/box/round_box/torus/capsule/cylinder/cone/plane/ellipsoid
- triangular_prism/hex_prism/link/wedge
- 2D 挤出(extrusion/extrusion_bounded/revolution)

S11-B: SDF 操作 + 域变形
- 锐利布尔: union/intersection/difference
- 平滑布尔: smooth_union/smooth_intersection/smooth_difference
- 修饰器: round/onion
- 域变形: repeat/mirror/rotate/translate/scale/twist/bend/elongate/displace/cheap_bend
- 24 项测试

S11-C: CSG 表达式树 + 网格转换 + Python 绑定
- SdfNode 树结构 — 工厂构造函数,递归求值
- sdf_to_mesh — 基于 marching_cubes 的 SDF→网格
- bind_sdf — pybind11 Python 绑定

文件: 14 文件,2,545 行(测试 1,296 行)
This commit is contained in:
茂之钳
2026-07-24 07:04:55 +00:00
parent 6d3f8fc8b3
commit b7419a7881
17 changed files with 1997 additions and 115 deletions
+75 -102
View File
@@ -1,165 +1,138 @@
#pragma once
#include "vde/core/point.h"
#include <cmath>
#include <algorithm>
#include "vde/core/point.h"
#include <numbers>
namespace vde::sdf {
// ═══════════════════════════════════════════════════
// Boolean Operations (sharp)
// ═══════════════════════════════════════════════════
using core::Point3D;
using core::Vector3D;
// ── Boolean operations on distance values ──
/// Union (min) — combine two shapes
[[nodiscard]] inline double op_union(double d1, double d2) {
return std::min(d1, d2);
}
/// Intersection (max) — region common to both shapes
[[nodiscard]] inline double op_intersection(double d1, double d2) {
return std::max(d1, d2);
}
/// Difference — subtract d2 from d1 (d1 \ d2)
[[nodiscard]] inline double op_difference(double d1, double d2) {
return std::max(-d1, d2);
}
// ═══════════════════════════════════════════════════
// Smooth Boolean Operations (Inigo Quilez)
// ═══════════════════════════════════════════════════
namespace detail {
template <typename T>
[[nodiscard]] constexpr T mix(T a, T b, T t) noexcept {
return a + t * (b - a);
}
}
/// Smooth union with blend radius k
/// Falls back to sharp union when k <= 0
[[nodiscard]] inline double op_smooth_union(double d1, double d2, double k) {
if (k <= 0.0) return op_union(d1, d2);
if (k <= 0.0) return std::min(d1, d2);
double h = std::clamp(0.5 + 0.5 * (d2 - d1) / k, 0.0, 1.0);
return detail::mix(d2, d1, h) - k * h * (1.0 - h);
return d1 * (1.0 - h) + d2 * h - k * h * (1.0 - h);
}
/// Smooth intersection with blend radius k
/// Falls back to sharp intersection when k <= 0
[[nodiscard]] inline double op_smooth_intersection(double d1, double d2, double k) {
if (k <= 0.0) return op_intersection(d1, d2);
if (k <= 0.0) return std::max(d1, d2);
double h = std::clamp(0.5 - 0.5 * (d2 - d1) / k, 0.0, 1.0);
return detail::mix(d1, d2, h) + k * h * (1.0 - h);
return d1 * (1.0 - h) + d2 * h + k * h * (1.0 - h);
}
/// Smooth difference with blend radius k (d1 \ d2)
/// Falls back to sharp difference when k <= 0
[[nodiscard]] inline double op_smooth_difference(double d1, double d2, double k) {
if (k <= 0.0) return op_difference(d1, d2);
if (k <= 0.0) return std::max(-d1, d2);
double h = std::clamp(0.5 - 0.5 * (d2 + d1) / k, 0.0, 1.0);
return detail::mix(d2, -d1, h) + k * h * (1.0 - h);
return (-d1) * (1.0 - h) + d2 * h + k * h * (1.0 - h);
}
// ═══════════════════════════════════════════════════
// Shape Modifiers
// ═══════════════════════════════════════════════════
// ── Modifiers (operate on a distance value) ──
/// Round a shape — shrink-wraps by radius r, then expands
/// Equivalent to: shape_distance - r
[[nodiscard]] inline double op_round(double d, double r) {
return d - r;
}
/// Onion — create a thin shell of thickness t
/// Returns SDF of the shell region (t/2 inward, t/2 outward from surface)
[[nodiscard]] inline double op_onion(double d, double thickness) {
return std::abs(d) - thickness * 0.5;
return std::abs(d) - thickness;
}
// ═══════════════════════════════════════════════════
// Domain Deformations (coordinate remapping)
// Apply BEFORE evaluating the SDF primitive.
// ═══════════════════════════════════════════════════
// ── Domain deformation operators (transform point space) ──
/// Infinite repetition along each axis
/// c.x/y/z: cell size per axis (0 = no repeat along that axis)
[[nodiscard]] inline vde::core::Point3D op_repeat(const vde::core::Point3D& p, const vde::core::Point3D& c) {
// p - c * round(p / c)
// For axes where c_i == 0, skip repetition
auto q = p;
if (c.x() > 0.0) q.x() = p.x() - c.x() * std::round(p.x() / c.x());
if (c.y() > 0.0) q.y() = p.y() - c.y() * std::round(p.y() / c.y());
if (c.z() > 0.0) q.z() = p.z() - c.z() * std::round(p.z() / c.z());
return q;
/// Infinite repetition in a cell
[[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;
};
return Point3D(wrap(p.x(), cell.x()),
wrap(p.y(), cell.y()),
wrap(p.z(), cell.z()));
}
/// Mirror across X=0 plane (folds everything to x >= offset)
[[nodiscard]] inline vde::core::Point3D op_mirror_x(const vde::core::Point3D& p, double offset = 0.0) {
return {offset + std::abs(p.x() - offset), p.y(), p.z()};
/// Mirror across X=0 plane, with optional offset
[[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 plane (folds everything to y >= offset)
[[nodiscard]] inline vde::core::Point3D op_mirror_y(const vde::core::Point3D& p, double offset = 0.0) {
return {p.x(), offset + std::abs(p.y() - offset), p.z()};
/// Mirror across Y=0
[[nodiscard]] inline Point3D op_mirror_y(const Point3D& p) {
return Point3D(p.x(), std::abs(p.y()), p.z());
}
/// Mirror across Z=0 plane (folds everything to z >= offset)
[[nodiscard]] inline vde::core::Point3D op_mirror_z(const vde::core::Point3D& p, double offset = 0.0) {
return {p.x(), p.y(), offset + std::abs(p.z() - offset)};
/// Mirror across Z=0
[[nodiscard]] inline Point3D op_mirror_z(const Point3D& p) {
return Point3D(p.x(), p.y(), std::abs(p.z()));
}
/// Rotate point around Y axis by angle (radians)
[[nodiscard]] inline vde::core::Point3D op_rotate(const vde::core::Point3D& p, double angle_rad) {
double s = std::sin(angle_rad);
double c = std::cos(angle_rad);
return {c * p.x() + s * p.z(), p.y(), -s * p.x() + c * p.z()};
/// Translate space (inverse for SDF: move the world, not the object)
[[nodiscard]] inline Point3D op_translate(const Point3D& p, const Point3D& offset) {
return p - offset;
}
/// Translate point
[[nodiscard]] inline vde::core::Point3D op_translate(const vde::core::Point3D& p, const vde::core::Point3D& offset) {
return {p.x() - offset.x(), p.y() - offset.y(), p.z() - offset.z()};
/// Rotate around Y axis by angle_rad (inverse for SDF)
[[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 — divides coordinates (non-exact SDF scaling)
/// Use with care: the resulting distance field is approximate.
[[nodiscard]] inline vde::core::Point3D op_scale(const vde::core::Point3D& p, const vde::core::Point3D& s) {
return {p.x() / s.x(), p.y() / s.y(), p.z() / s.z()};
/// Non-uniform scale (inverse for SDF)
[[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 around Y axis — rotation amount increases with y coordinate
[[nodiscard]] inline vde::core::Point3D op_twist(const vde::core::Point3D& p, double amount) {
double angle = amount * p.y();
double s = std::sin(angle);
double c = std::cos(angle);
return {c * p.x() + s * p.z(), p.y(), -s * p.x() + c * p.z()};
}
/// Bend along Y axis — curves the shape in x-y plane
[[nodiscard]] inline vde::core::Point3D op_bend(const vde::core::Point3D& p, double amount) {
/// Twist space around Y axis
[[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 {c * p.x() - s * p.y(), s * p.x() + c * p.y(), p.z()};
return Point3D(p.x() * c - p.z() * s, p.y(), p.x() * s + p.z() * c);
}
/// Elongate along axes — stretches coordinates (divides by factor)
/// Equivalent to non-uniform scale but conceptually "stretches" the shape.
[[nodiscard]] inline vde::core::Point3D op_elongate(const vde::core::Point3D& p, const vde::core::Point3D& factors) {
return {p.x() / factors.x(), p.y() / factors.y(), p.z() / factors.z()};
}
/// Displacement — adds sinusoidal perturbation to the SDF value
/// p: current point, d: base SDF value, amplitude/frequency: displacement params
[[nodiscard]] inline double op_displace(double d, const vde::core::Point3D& p, double amplitude, double frequency) {
return d + amplitude * std::sin(p.x() * frequency)
* std::sin(p.y() * frequency)
* std::sin(p.z() * frequency);
}
/// Cheap bend — bends around Z axis based on x coordinate
/// Simpler and faster approximation than full bend.
[[nodiscard]] inline vde::core::Point3D op_cheap_bend(const vde::core::Point3D& p, double k) {
/// Bend space
[[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 {c * p.x() - s * p.y(), s * p.x() + c * p.y(), p.z()};
return Point3D(c * p.x() - s * p.y(), s * p.x() + c * p.y(), p.z());
}
/// Elongate (stretch space, eliminating interior along major axes)
[[nodiscard]] inline Point3D op_elongate(const Point3D& p, const Point3D& h) {
Point3D q = p.cwiseAbs() - h;
return Point3D(
q.x() > 0.0 ? p.x() - std::copysign(h.x(), p.x()) : 0.0,
q.y() > 0.0 ? p.y() - std::copysign(h.y(), p.y()) : 0.0,
q.z() > 0.0 ? p.z() - std::copysign(h.z(), p.z()) : 0.0
);
}
/// Cheap bend (simplified bending)
[[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
[[nodiscard]] inline double op_displace(double d, const Point3D& p,
double amplitude, double frequency) {
double noise = std::sin(p.x() * frequency) *
std::sin(p.y() * frequency) *
std::sin(p.z() * frequency);
return d + amplitude * noise;
}
} // namespace vde::sdf