173 lines
7.8 KiB
C++
173 lines
7.8 KiB
C++
#pragma once
|
|
#include "vde/core/point.h"
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
|
|
namespace vde::sdf {
|
|
using core::Point3D;
|
|
using core::Vector3D;
|
|
|
|
// ═══════════════════════════════════════════════
|
|
// Basic Primitives
|
|
// ═══════════════════════════════════════════════
|
|
|
|
/// Signed distance to sphere centered at origin
|
|
[[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
|
|
[[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
|
|
[[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;
|
|
}
|
|
|
|
/// Signed distance to torus in XZ plane, Y-axis symmetry
|
|
/// major_radius = distance from origin to tube center
|
|
/// minor_radius = tube thickness radius
|
|
[[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
|
|
[[nodiscard]] inline double capsule(const Point3D& p, const Point3D& a, const Point3D& b, double radius) {
|
|
Point3D pa = p - a;
|
|
Point3D ba = b - a;
|
|
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);
|
|
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)
|
|
[[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));
|
|
}
|
|
|
|
/// Signed distance to plane through origin with given normal
|
|
/// Normal must be unit length; offset shifts plane along normal
|
|
[[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)
|
|
[[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();
|
|
if (k1 < 1e-30) {
|
|
// Degenerate: point at origin → distance = -min(radii)
|
|
return -std::min({radii.x(), radii.y(), radii.z()});
|
|
}
|
|
return k0 * (k0 - 1.0) / k1;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════
|
|
// 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)
|
|
[[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);
|
|
}
|
|
|
|
/// Signed distance to infinite cylinder through origin along given axis
|
|
/// Axis must be unit length
|
|
[[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
|
|
[[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
|
|
// ═══════════════════════════════════════════════
|
|
|
|
/// Extrude a 2D SDF infinitely along Z axis
|
|
/// sdf_2d = pre-computed 2D SDF value at (p.x, p.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)
|
|
[[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
|
|
[[nodiscard]] inline double revolution(const Point3D& /*p*/, double sdf_2d, double /*offset*/) {
|
|
return sdf_2d;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════
|
|
// 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
|
|
[[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
|
|
[[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
|
|
[[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
|
|
[[nodiscard]] double infinite_cone(const Point3D& p, const Point3D& apex,
|
|
const Vector3D& axis, double angle_rad);
|
|
|
|
} // namespace vde::sdf
|