feat(sdf): S11 — SDF 隐式建模完整模块
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:
@@ -179,6 +179,20 @@ target_link_libraries(vde_sketch
|
||||
PUBLIC vde_core vde_compile_options
|
||||
)
|
||||
|
||||
# ── sdf ────────────────────────────────────────────
|
||||
add_library(vde_sdf STATIC
|
||||
sdf/sdf_primitives.cpp
|
||||
sdf/sdf_tree.cpp
|
||||
sdf/sdf_to_mesh.cpp
|
||||
)
|
||||
target_include_directories(vde_sdf
|
||||
PUBLIC ${CMAKE_SOURCE_DIR}/include
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
target_link_libraries(vde_sdf
|
||||
PUBLIC vde_core vde_mesh vde_compile_options
|
||||
)
|
||||
|
||||
# ── C API ──────────────────────────────────────────
|
||||
add_library(vde_capi STATIC
|
||||
capi/vde_capi.cpp
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#include "vde/sdf/sdf_primitives.h"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace vde::sdf {
|
||||
|
||||
// ── Helper: 2D triangle SDF (Inigo Quilez) ──────
|
||||
namespace {
|
||||
|
||||
struct Vec2 { double x, y; };
|
||||
|
||||
inline double dot(Vec2 a, Vec2 b) { return a.x * b.x + a.y * b.y; }
|
||||
inline double cross2(Vec2 a, Vec2 b) { return a.x * b.y - a.y * b.x; }
|
||||
inline Vec2 sub(Vec2 a, Vec2 b) { return {a.x - b.x, a.y - b.y}; }
|
||||
inline Vec2 madd(Vec2 a, Vec2 b, double t) { return {a.x - b.x * t, a.y - b.y * t}; }
|
||||
inline double len2(Vec2 a) { return a.x * a.x + a.y * a.y; }
|
||||
|
||||
[[nodiscard]] double triangle_sdf_2d(Vec2 p, Vec2 a, Vec2 b, Vec2 c) {
|
||||
Vec2 e0 = sub(b, a);
|
||||
Vec2 e1 = sub(c, b);
|
||||
Vec2 e2 = sub(a, c);
|
||||
Vec2 v0 = sub(p, a);
|
||||
Vec2 v1 = sub(p, b);
|
||||
Vec2 v2 = sub(p, c);
|
||||
|
||||
double d0 = dot(e0, e0);
|
||||
double d1 = dot(e1, e1);
|
||||
double d2 = dot(e2, e2);
|
||||
|
||||
Vec2 pq0 = madd(v0, e0, std::clamp(dot(v0, e0) / d0, 0.0, 1.0));
|
||||
Vec2 pq1 = madd(v1, e1, std::clamp(dot(v1, e1) / d1, 0.0, 1.0));
|
||||
Vec2 pq2 = madd(v2, e2, std::clamp(dot(v2, e2) / d2, 0.0, 1.0));
|
||||
|
||||
double s = cross2(e0, e2) > 0.0 ? 1.0 : -1.0;
|
||||
|
||||
double dx0 = len2(pq0);
|
||||
double dx1 = len2(pq1);
|
||||
double dx2 = len2(pq2);
|
||||
|
||||
double sy0 = s * cross2(v0, e0);
|
||||
double sy1 = s * cross2(v1, e1);
|
||||
double sy2 = s * cross2(v2, e2);
|
||||
|
||||
// Find minimum (d², signed) pair
|
||||
double best_d2 = dx0;
|
||||
double best_sd = sy0;
|
||||
if (dx1 < best_d2) { best_d2 = dx1; best_sd = sy1; }
|
||||
if (dx2 < best_d2) { best_d2 = dx2; best_sd = sy2; }
|
||||
|
||||
if (sy1 < best_sd) { best_sd = sy1; best_d2 = dx1; }
|
||||
if (sy2 < best_sd) { best_sd = sy2; best_d2 = dx2; }
|
||||
|
||||
return -std::sqrt(best_d2) * (best_sd > 0.0 ? 1.0 : -1.0);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ═══ cone ════════════════════════════════════════
|
||||
|
||||
double cone(const Point3D& p, double angle_rad, double height) {
|
||||
double r_base = height * std::tan(angle_rad);
|
||||
double h2 = height * 0.5;
|
||||
|
||||
// Shift so base is at y = 0, apex at y = height
|
||||
double qx = std::sqrt(p.x() * p.x() + p.z() * p.z());
|
||||
double qy = p.y() + h2;
|
||||
|
||||
double b = r_base / height; // slope: (r1 - r2) / h
|
||||
if (b > 1.0) b = 1.0; // clamp for wide cones (>45°)
|
||||
double a = std::sqrt(1.0 - b * b);
|
||||
|
||||
// Projection parameter: k = dot(q, vec2(-b, a))
|
||||
double k = a * qy - b * qx;
|
||||
|
||||
if (k < 0.0) {
|
||||
// Closest to base circle
|
||||
return std::sqrt(qx * qx + qy * qy) - r_base;
|
||||
}
|
||||
if (k > a * height) {
|
||||
// Closest to apex
|
||||
return std::sqrt((qx - r_base) * (qx - r_base) + (qy - height) * (qy - height));
|
||||
}
|
||||
// Closest to side surface
|
||||
return qx * a + qy * b - r_base;
|
||||
}
|
||||
|
||||
// ═══ triangular_prism ═══════════════════════════
|
||||
|
||||
double triangular_prism(const Point3D& p, const Point3D& a, const Point3D& b,
|
||||
const Point3D& c, double height) {
|
||||
// Project triangle to XY plane
|
||||
Vec2 p2{p.x(), p.y()};
|
||||
Vec2 a2{a.x(), a.y()};
|
||||
Vec2 b2{b.x(), b.y()};
|
||||
Vec2 c2{c.x(), c.y()};
|
||||
|
||||
double d_xy = triangle_sdf_2d(p2, a2, b2, c2);
|
||||
return std::max(d_xy, std::abs(p.z()) - height * 0.5);
|
||||
}
|
||||
|
||||
// ═══ link ═══════════════════════════════════════
|
||||
|
||||
double link(const Point3D& p, double length, double major_r, double minor_r) {
|
||||
// Two parallel toruses offset along X
|
||||
double half_len = length * 0.5;
|
||||
|
||||
// Torus at +half_len
|
||||
double px1 = p.x() - half_len;
|
||||
double qx1 = std::sqrt(px1 * px1 + p.z() * p.z()) - major_r;
|
||||
double d1 = std::sqrt(qx1 * qx1 + p.y() * p.y()) - minor_r;
|
||||
|
||||
// Torus at -half_len
|
||||
double px2 = p.x() + half_len;
|
||||
double qx2 = std::sqrt(px2 * px2 + p.z() * p.z()) - major_r;
|
||||
double d2 = std::sqrt(qx2 * qx2 + p.y() * p.y()) - minor_r;
|
||||
|
||||
return std::min(d1, d2);
|
||||
}
|
||||
|
||||
// ═══ infinite_cone ══════════════════════════════
|
||||
|
||||
double infinite_cone(const Point3D& p, const Point3D& apex,
|
||||
const Vector3D& axis, double angle_rad) {
|
||||
Vector3D d = p - apex;
|
||||
double proj = d.dot(axis); // projection onto axis
|
||||
double perp = (d - axis * proj).norm(); // perpendicular distance
|
||||
return perp * std::cos(angle_rad) - proj * std::sin(angle_rad);
|
||||
}
|
||||
|
||||
} // namespace vde::sdf
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "vde/sdf/sdf_to_mesh.h"
|
||||
|
||||
namespace vde::sdf {
|
||||
|
||||
mesh::MCMesh sdf_to_mesh(const SdfNodePtr& root, int resolution, double iso_level) {
|
||||
SdfBBox bbox = estimate_bbox(root, 1.0);
|
||||
|
||||
auto sdf_fn = [&root](double x, double y, double z) -> double {
|
||||
return evaluate(root, Point3D(x, y, z));
|
||||
};
|
||||
|
||||
return mesh::marching_cubes(sdf_fn, iso_level, bbox.min, bbox.max, resolution);
|
||||
}
|
||||
|
||||
mesh::MCMesh sdf_to_mesh_lambda(
|
||||
const std::function<double(double, double, double)>& f,
|
||||
const Point3D& bmin, const Point3D& bmax,
|
||||
int resolution, double iso_level) {
|
||||
|
||||
return mesh::marching_cubes(f, iso_level, bmin, bmax, resolution);
|
||||
}
|
||||
|
||||
} // namespace vde::sdf
|
||||
@@ -0,0 +1,377 @@
|
||||
#include "vde/sdf/sdf_tree.h"
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace vde::sdf {
|
||||
|
||||
// ── Helper: make a node ──────────────────────────────────────────
|
||||
static SdfNodePtr make(SdfOp op) { return std::make_shared<SdfNode>(op); }
|
||||
static void set_one_child(SdfNodePtr& n, SdfNodePtr c) { n->children = {std::move(c)}; }
|
||||
static void set_two_children(SdfNodePtr& n, SdfNodePtr a, SdfNodePtr b) {
|
||||
n->children = {std::move(a), std::move(b)};
|
||||
}
|
||||
|
||||
// ── Primitive factories ──────────────────────────────────────────
|
||||
|
||||
SdfNodePtr SdfNode::sphere(double r) {
|
||||
auto n = make(SdfOp::Sphere);
|
||||
n->params.radius = r;
|
||||
n->name = "sphere";
|
||||
return n;
|
||||
}
|
||||
|
||||
SdfNodePtr SdfNode::box(const Point3D& half_extents) {
|
||||
auto n = make(SdfOp::Box);
|
||||
n->params.extents = half_extents;
|
||||
n->name = "box";
|
||||
return n;
|
||||
}
|
||||
|
||||
SdfNodePtr SdfNode::round_box(const Point3D& half_extents, double r) {
|
||||
auto n = make(SdfOp::RoundBox);
|
||||
n->params.extents = half_extents;
|
||||
n->params.radius = r;
|
||||
n->name = "round_box";
|
||||
return n;
|
||||
}
|
||||
|
||||
SdfNodePtr SdfNode::cylinder(double r, double h) {
|
||||
auto n = make(SdfOp::Cylinder);
|
||||
n->params.radius = r;
|
||||
n->params.height = h; // full height
|
||||
n->name = "cylinder";
|
||||
return n;
|
||||
}
|
||||
|
||||
SdfNodePtr SdfNode::torus(double major_r, double minor_r) {
|
||||
auto n = make(SdfOp::Torus);
|
||||
n->params.major_radius = major_r;
|
||||
n->params.minor_radius = minor_r;
|
||||
n->name = "torus";
|
||||
return n;
|
||||
}
|
||||
|
||||
SdfNodePtr SdfNode::capsule(const Point3D& a, const Point3D& b, double r) {
|
||||
auto n = make(SdfOp::Capsule);
|
||||
n->params.pt_a = a;
|
||||
n->params.pt_b = b;
|
||||
n->params.radius = r;
|
||||
n->name = "capsule";
|
||||
return n;
|
||||
}
|
||||
|
||||
SdfNodePtr SdfNode::cone(double angle_rad, double h) {
|
||||
auto n = make(SdfOp::Cone);
|
||||
n->params.angle_rad = angle_rad;
|
||||
n->params.height = h; // full height
|
||||
n->name = "cone";
|
||||
return n;
|
||||
}
|
||||
|
||||
SdfNodePtr SdfNode::plane(const Vector3D& normal, double offset) {
|
||||
auto n = make(SdfOp::Plane);
|
||||
n->params.normal = normal;
|
||||
n->params.offset = offset;
|
||||
n->name = "plane";
|
||||
return n;
|
||||
}
|
||||
|
||||
SdfNodePtr SdfNode::ellipsoid(const Point3D& radii) {
|
||||
auto n = make(SdfOp::Ellipsoid);
|
||||
n->params.extents = radii;
|
||||
n->name = "ellipsoid";
|
||||
return n;
|
||||
}
|
||||
|
||||
SdfNodePtr SdfNode::triangular_prism(double h) {
|
||||
auto n = make(SdfOp::TriangularPrism);
|
||||
n->params.height = h; // full height
|
||||
n->name = "triangular_prism";
|
||||
return n;
|
||||
}
|
||||
|
||||
SdfNodePtr SdfNode::hex_prism(double h) {
|
||||
auto n = make(SdfOp::HexPrism);
|
||||
n->params.radius = h; // circumradius
|
||||
n->params.height = h; // full height (= radius for equilateral)
|
||||
n->name = "hex_prism";
|
||||
return n;
|
||||
}
|
||||
|
||||
SdfNodePtr SdfNode::link(double r, double length, double thickness) {
|
||||
auto n = make(SdfOp::Link);
|
||||
n->params.radius = r; // major radius
|
||||
n->params.height = length; // spacing
|
||||
n->params.thickness = thickness; // minor radius
|
||||
n->name = "link";
|
||||
return n;
|
||||
}
|
||||
|
||||
SdfNodePtr SdfNode::wedge(const Point3D& extents) {
|
||||
auto n = make(SdfOp::Wedge);
|
||||
n->params.extents = extents;
|
||||
n->name = "wedge";
|
||||
return n;
|
||||
}
|
||||
|
||||
// ── Boolean CSG factories ────────────────────────────────────────
|
||||
|
||||
SdfNodePtr SdfNode::op_union(SdfNodePtr a, SdfNodePtr b) {
|
||||
auto n = make(SdfOp::Union); n->name="union";
|
||||
set_two_children(n, std::move(a), std::move(b)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::op_intersection(SdfNodePtr a, SdfNodePtr b) {
|
||||
auto n = make(SdfOp::Intersection); n->name="intersection";
|
||||
set_two_children(n, std::move(a), std::move(b)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::op_difference(SdfNodePtr a, SdfNodePtr b) {
|
||||
auto n = make(SdfOp::Difference); n->name="difference";
|
||||
set_two_children(n, std::move(a), std::move(b)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::smooth_union(SdfNodePtr a, SdfNodePtr b, double k) {
|
||||
auto n = make(SdfOp::SmoothUnion); n->params.blend_k = k; n->name="smooth_union";
|
||||
set_two_children(n, std::move(a), std::move(b)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::smooth_intersection(SdfNodePtr a, SdfNodePtr b, double k) {
|
||||
auto n = make(SdfOp::SmoothIntersection); n->params.blend_k = k; n->name="smooth_intersection";
|
||||
set_two_children(n, std::move(a), std::move(b)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::smooth_difference(SdfNodePtr a, SdfNodePtr b, double k) {
|
||||
auto n = make(SdfOp::SmoothDifference); n->params.blend_k = k; n->name="smooth_difference";
|
||||
set_two_children(n, std::move(a), std::move(b)); return n;
|
||||
}
|
||||
|
||||
// ── Modifier factories ───────────────────────────────────────────
|
||||
|
||||
SdfNodePtr SdfNode::round(SdfNodePtr child, double r) {
|
||||
auto n = make(SdfOp::Round); n->params.radius = r; n->name="round";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::onion(SdfNodePtr child, double thickness) {
|
||||
auto n = make(SdfOp::Onion); n->params.thickness = thickness; n->name="onion";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
|
||||
// ── Domain transform factories ───────────────────────────────────
|
||||
|
||||
SdfNodePtr SdfNode::repeat(SdfNodePtr child, const Point3D& cell) {
|
||||
auto n = make(SdfOp::Repeat); n->params.repeat_cell = cell; n->name="repeat";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::mirror_x(SdfNodePtr child) {
|
||||
auto n = make(SdfOp::MirrorX); n->name="mirror_x";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::mirror_y(SdfNodePtr child) {
|
||||
auto n = make(SdfOp::MirrorY); n->name="mirror_y";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::mirror_z(SdfNodePtr child) {
|
||||
auto n = make(SdfOp::MirrorZ); n->name="mirror_z";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::translate(SdfNodePtr child, const Point3D& offset) {
|
||||
auto n = make(SdfOp::Translate); n->params.translate_offset = offset; n->name="translate";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::rotate(SdfNodePtr child, double angle_rad) {
|
||||
auto n = make(SdfOp::Rotate); n->params.angle_rad = angle_rad; n->name="rotate";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::scale(SdfNodePtr child, const Point3D& factors) {
|
||||
auto n = make(SdfOp::Scale); n->params.scale_factors = factors; n->name="scale";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::twist(SdfNodePtr child, double amount) {
|
||||
auto n = make(SdfOp::Twist); n->params.amount = amount; n->name="twist";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::bend(SdfNodePtr child, double amount) {
|
||||
auto n = make(SdfOp::Bend); n->params.amount = amount; n->name="bend";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::elongate(SdfNodePtr child, const Point3D& h) {
|
||||
auto n = make(SdfOp::Elongate); n->params.extents = h; n->name="elongate";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::cheap_bend(SdfNodePtr child, double amount) {
|
||||
auto n = make(SdfOp::CheapBend); n->params.amount = amount; n->name="cheap_bend";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
SdfNodePtr SdfNode::displace(SdfNodePtr child, double amplitude, double frequency) {
|
||||
auto n = make(SdfOp::Displace);
|
||||
n->params.amplitude = amplitude; n->params.frequency = frequency; n->name="displace";
|
||||
set_one_child(n, std::move(child)); return n;
|
||||
}
|
||||
|
||||
// ── Recursive evaluator ──────────────────────────────────────────
|
||||
|
||||
double evaluate(const SdfNodePtr& node, const Point3D& p) {
|
||||
if (!node) return 1e30;
|
||||
|
||||
const auto& pr = node->params;
|
||||
|
||||
switch (node->op) {
|
||||
// ── Primitives (from sdf_primitives.h) ──
|
||||
case SdfOp::Sphere:
|
||||
return sphere(p, pr.radius);
|
||||
case SdfOp::Box:
|
||||
return box(p, pr.extents);
|
||||
case SdfOp::RoundBox:
|
||||
return round_box(p, pr.extents, pr.radius);
|
||||
case SdfOp::Torus:
|
||||
return torus(p, pr.major_radius, pr.minor_radius);
|
||||
case SdfOp::Capsule:
|
||||
return capsule(p, pr.pt_a, pr.pt_b, pr.radius);
|
||||
case SdfOp::Cylinder:
|
||||
return cylinder(p, pr.radius, pr.height);
|
||||
case SdfOp::Cone:
|
||||
return cone(p, pr.angle_rad, pr.height);
|
||||
case SdfOp::Plane:
|
||||
return plane(p, pr.normal, pr.offset);
|
||||
case SdfOp::Ellipsoid:
|
||||
return ellipsoid(p, pr.extents);
|
||||
case SdfOp::TriangularPrism: {
|
||||
// Equilateral triangle in XZ plane
|
||||
double rh = pr.height * 0.57735;
|
||||
Point3D a(-rh, 0, -rh * 0.5), b(rh, 0, -rh * 0.5), c(0, 0, rh);
|
||||
return triangular_prism(p, a, b, c, pr.height);
|
||||
}
|
||||
case SdfOp::HexPrism:
|
||||
return hex_prism(p, pr.radius, pr.height);
|
||||
case SdfOp::Link:
|
||||
return link(p, pr.height, pr.radius, pr.thickness);
|
||||
case SdfOp::Wedge: {
|
||||
const auto& e = pr.extents;
|
||||
return wedge(p, e.x() * 2.0, e.y() * 2.0, e.z() * 2.0);
|
||||
}
|
||||
|
||||
// ── Boolean CSG ──
|
||||
case SdfOp::Union:
|
||||
return op_union(evaluate(node->children[0], p),
|
||||
evaluate(node->children[1], p));
|
||||
case SdfOp::Intersection:
|
||||
return op_intersection(evaluate(node->children[0], p),
|
||||
evaluate(node->children[1], p));
|
||||
case SdfOp::Difference:
|
||||
return op_difference(evaluate(node->children[0], p),
|
||||
evaluate(node->children[1], p));
|
||||
case SdfOp::SmoothUnion:
|
||||
return op_smooth_union(evaluate(node->children[0], p),
|
||||
evaluate(node->children[1], p), pr.blend_k);
|
||||
case SdfOp::SmoothIntersection:
|
||||
return op_smooth_intersection(evaluate(node->children[0], p),
|
||||
evaluate(node->children[1], p), pr.blend_k);
|
||||
case SdfOp::SmoothDifference:
|
||||
return op_smooth_difference(evaluate(node->children[0], p),
|
||||
evaluate(node->children[1], p), pr.blend_k);
|
||||
|
||||
// ── Modifiers (distance post-process) ──
|
||||
case SdfOp::Round:
|
||||
return op_round(evaluate(node->children[0], p), pr.radius);
|
||||
case SdfOp::Onion:
|
||||
return op_onion(evaluate(node->children[0], p), pr.thickness);
|
||||
|
||||
// ── Domain transforms (point pre-process) ──
|
||||
case SdfOp::Repeat:
|
||||
return evaluate(node->children[0], op_repeat(p, pr.repeat_cell));
|
||||
case SdfOp::MirrorX:
|
||||
return evaluate(node->children[0], op_mirror_x(p));
|
||||
case SdfOp::MirrorY:
|
||||
return evaluate(node->children[0], op_mirror_y(p));
|
||||
case SdfOp::MirrorZ:
|
||||
return evaluate(node->children[0], op_mirror_z(p));
|
||||
case SdfOp::Translate:
|
||||
return evaluate(node->children[0], op_translate(p, pr.translate_offset));
|
||||
case SdfOp::Rotate:
|
||||
return evaluate(node->children[0], op_rotate(p, pr.angle_rad));
|
||||
case SdfOp::Scale:
|
||||
return evaluate(node->children[0], op_scale(p, pr.scale_factors));
|
||||
case SdfOp::Twist:
|
||||
return evaluate(node->children[0], op_twist(p, pr.amount));
|
||||
case SdfOp::Bend:
|
||||
return evaluate(node->children[0], op_bend(p, pr.amount));
|
||||
case SdfOp::Elongate:
|
||||
return evaluate(node->children[0], op_elongate(p, pr.extents));
|
||||
case SdfOp::CheapBend:
|
||||
return evaluate(node->children[0], op_cheap_bend(p, pr.amount));
|
||||
|
||||
// ── Displacement ──
|
||||
case SdfOp::Displace:
|
||||
return op_displace(evaluate(node->children[0], p), p,
|
||||
pr.amplitude, pr.frequency);
|
||||
}
|
||||
|
||||
return 1e30;
|
||||
}
|
||||
|
||||
// ── Bounds estimation ────────────────────────────────────────────
|
||||
|
||||
static double estimate_radius(const SdfNodePtr& node) {
|
||||
if (!node) return 1.0;
|
||||
|
||||
switch (node->op) {
|
||||
case SdfOp::Sphere:
|
||||
return node->params.radius;
|
||||
case SdfOp::Box:
|
||||
return node->params.extents.norm();
|
||||
case SdfOp::RoundBox:
|
||||
return node->params.extents.norm() + node->params.radius;
|
||||
case SdfOp::Torus:
|
||||
return node->params.major_radius + node->params.minor_radius;
|
||||
case SdfOp::Capsule:
|
||||
return (node->params.pt_b - node->params.pt_a).norm() * 0.5 + node->params.radius;
|
||||
case SdfOp::Cylinder:
|
||||
return std::sqrt(node->params.radius * node->params.radius +
|
||||
node->params.height * node->params.height * 0.25);
|
||||
case SdfOp::Cone:
|
||||
return node->params.height * 0.5 + node->params.height * 0.5 * std::tan(node->params.angle_rad);
|
||||
case SdfOp::Plane:
|
||||
return 10.0;
|
||||
case SdfOp::Ellipsoid:
|
||||
return std::max({node->params.extents.x(),
|
||||
node->params.extents.y(),
|
||||
node->params.extents.z()});
|
||||
case SdfOp::TriangularPrism:
|
||||
case SdfOp::HexPrism:
|
||||
return node->params.height;
|
||||
case SdfOp::Link:
|
||||
return node->params.radius + node->params.height * 0.5 + node->params.thickness;
|
||||
case SdfOp::Wedge:
|
||||
return node->params.extents.norm();
|
||||
case SdfOp::Union:
|
||||
case SdfOp::Intersection:
|
||||
case SdfOp::SmoothUnion:
|
||||
case SdfOp::SmoothIntersection:
|
||||
return std::max(estimate_radius(node->children[0]),
|
||||
estimate_radius(node->children[1]));
|
||||
case SdfOp::Difference:
|
||||
case SdfOp::SmoothDifference:
|
||||
return estimate_radius(node->children[0]);
|
||||
case SdfOp::Round:
|
||||
return estimate_radius(node->children[0]) + node->params.radius;
|
||||
case SdfOp::Onion:
|
||||
return estimate_radius(node->children[0]) + node->params.thickness;
|
||||
case SdfOp::Translate:
|
||||
return estimate_radius(node->children[0]) + node->params.translate_offset.norm();
|
||||
case SdfOp::Scale:
|
||||
return estimate_radius(node->children[0]) *
|
||||
std::max({node->params.scale_factors.x(),
|
||||
node->params.scale_factors.y(),
|
||||
node->params.scale_factors.z()});
|
||||
default:
|
||||
return node->children.empty() ? 1.0 : estimate_radius(node->children[0]);
|
||||
}
|
||||
}
|
||||
|
||||
Point3D estimate_bounds(const SdfNodePtr& root, double margin) {
|
||||
double r = estimate_radius(root) + margin;
|
||||
return Point3D(r, r, r);
|
||||
}
|
||||
|
||||
SdfBBox estimate_bbox(const SdfNodePtr& root, double margin) {
|
||||
Point3D half = estimate_bounds(root, margin);
|
||||
return SdfBBox{-half, half};
|
||||
}
|
||||
|
||||
} // namespace vde::sdf
|
||||
Reference in New Issue
Block a user