Files
ViewDesignEngine/tests/sdf/test_sdf_tree.cpp
T
茂之钳 6418097f98
CI / Build & Test (push) Failing after 33s
CI / Release Build (push) Failing after 37s
fix: SDF test files — core namespace qualification
2026-07-24 07:11:03 +00:00

286 lines
9.1 KiB
C++

#include "vde/sdf/sdf_tree.h"
#include <gtest/gtest.h>
#include <cmath>
using namespace vde::sdf;
using vde::core::Point3D;
using vde::core::Vector3D;
// ── Null/empty tree ──────────────────────────────────────────────
TEST(SdfTree, NullNodeReturnsLargeValue) {
SdfNodePtr null_node = nullptr;
double d = evaluate(null_node, Point3D(0, 0, 0));
EXPECT_GT(d, 1e10);
}
// ── Primitive evaluation ─────────────────────────────────────────
TEST(SdfTree, SphereInsideOutsideBoundary) {
auto s = SdfNode::sphere(1.0);
// Origin = inside
double d_in = evaluate(s, Point3D(0, 0, 0));
EXPECT_LT(d_in, 0.0);
EXPECT_NEAR(d_in, -1.0, 1e-6);
// Surface (r=1 on X axis)
double d_surf = evaluate(s, Point3D(1, 0, 0));
EXPECT_NEAR(d_surf, 0.0, 1e-6);
// Outside
double d_out = evaluate(s, Point3D(2, 0, 0));
EXPECT_GT(d_out, 0.0);
EXPECT_NEAR(d_out, 1.0, 1e-6);
}
TEST(SdfTree, BoxEvaluation) {
auto b = SdfNode::box(Point3D(1, 1, 1));
// Center = inside
EXPECT_LT(evaluate(b, Point3D(0, 0, 0)), 0.0);
// Surface
EXPECT_NEAR(evaluate(b, Point3D(1, 0, 0)), 0.0, 1e-6);
// Outside
EXPECT_GT(evaluate(b, Point3D(2, 0, 0)), 0.0);
}
TEST(SdfTree, TorusEvaluation) {
auto t = SdfNode::torus(1.0, 0.3);
// Point on the ring centerline (should be -minor_radius = inside)
double d = evaluate(t, Point3D(1, 0, 0));
EXPECT_LT(d, 0.0);
EXPECT_NEAR(d, -0.3, 1e-6);
}
TEST(SdfTree, CylinderEvaluation) {
auto c = SdfNode::cylinder(0.5, 1.0);
EXPECT_LT(evaluate(c, Point3D(0, 0, 0)), 0.0); // center
EXPECT_NEAR(evaluate(c, Point3D(0.5, 0, 0)), 0.0, 1e-6); // surface
EXPECT_GT(evaluate(c, Point3D(1, 0, 0)), 0.0); // outside radius
EXPECT_GT(evaluate(c, Point3D(0, 2, 0)), 0.0); // above cap
}
TEST(SdfTree, PlaneEvaluation) {
auto p = SdfNode::plane(Vector3D(0, 1, 0), 0.0); // horizontal at y=0
EXPECT_GT(evaluate(p, Point3D(0, 1, 0)), 0.0); // above
EXPECT_LT(evaluate(p, Point3D(0, -1, 0)), 0.0); // below
EXPECT_NEAR(evaluate(p, Point3D(0, 0, 0)), 0.0, 1e-6);
}
// ── CSG boolean operations ───────────────────────────────────────
TEST(SdfTree, UnionSphereBox) {
auto tree = SdfNode::op_union(
SdfNode::sphere(1.0),
SdfNode::box(Point3D(1, 1, 1))
);
// Point inside sphere
EXPECT_LT(evaluate(tree, Point3D(0, 0, 0)), 0.0);
// Point inside box (but outside sphere)
EXPECT_LT(evaluate(tree, Point3D(1.2, 0, 0)), 0.0);
// Point outside both
EXPECT_GT(evaluate(tree, Point3D(3, 0, 0)), 0.0);
}
TEST(SdfTree, IntersectionSphereBox) {
auto tree = SdfNode::op_intersection(
SdfNode::sphere(1.0),
SdfNode::box(Point3D(0.5, 0.5, 0.5))
);
// Inside intersection region
EXPECT_LT(evaluate(tree, Point3D(0, 0, 0)), 0.0);
// Inside sphere but outside box
EXPECT_GT(evaluate(tree, Point3D(0.8, 0, 0)), 0.0);
}
TEST(SdfTree, DifferenceSphereBox) {
auto tree = SdfNode::op_difference(
SdfNode::sphere(2.0),
SdfNode::box(Point3D(1, 1, 1))
);
// Inside sphere, outside box → inside
EXPECT_LT(evaluate(tree, Point3D(1.5, 0, 0)), 0.0);
// Inside box (removed region) → outside
EXPECT_GT(evaluate(tree, Point3D(0, 0, 0)), 0.0);
}
// ── Smooth boolean operations ────────────────────────────────────
TEST(SdfTree, SmoothUnion) {
auto tree = SdfNode::smooth_union(
SdfNode::sphere(1.0),
SdfNode::sphere(1.0),
0.3
);
// Both spheres at origin → should be equivalent
EXPECT_LT(evaluate(tree, Point3D(0, 0, 0)), 0.0);
}
// ── Domain transforms ────────────────────────────────────────────
TEST(SdfTree, TranslatedSphere) {
auto tree = SdfNode::translate(
SdfNode::sphere(1.0),
Point3D(2, 0, 0)
);
// Original origin should be far away
EXPECT_GT(evaluate(tree, Point3D(0, 0, 0)), 0.0);
// New center should be inside
EXPECT_LT(evaluate(tree, Point3D(2, 0, 0)), 0.0);
EXPECT_NEAR(evaluate(tree, Point3D(2, 0, 0)), -1.0, 1e-6);
// Surface at new center + radius
EXPECT_NEAR(evaluate(tree, Point3D(3, 0, 0)), 0.0, 1e-6);
}
TEST(SdfTree, ScaledSphere) {
auto tree = SdfNode::scale(
SdfNode::sphere(1.0),
Point3D(2, 1, 1)
);
// At (2,0,0) in world = (1,0,0) in local → on surface
EXPECT_NEAR(evaluate(tree, Point3D(2, 0, 0)), 0.0, 1e-6);
// At (0,0,0) in world = inside
EXPECT_LT(evaluate(tree, Point3D(0, 0, 0)), 0.0);
}
TEST(SdfTree, RepeatedSphere) {
auto tree = SdfNode::repeat(
SdfNode::sphere(0.3),
Point3D(2, 2, 2)
);
// Origin → mapped to (0,0,0) inside
EXPECT_LT(evaluate(tree, Point3D(0, 0, 0)), 0.0);
// (2,0,0) → mapped to (0,0,0) inside
EXPECT_LT(evaluate(tree, Point3D(2, 0, 0)), 0.0);
// Between copies → outside
EXPECT_GT(evaluate(tree, Point3D(1, 1, 0)), 0.0);
}
TEST(SdfTree, MirroredX) {
auto tree = SdfNode::mirror_x(
SdfNode::sphere(1.0)
);
// Both sides should have the sphere
EXPECT_LT(evaluate(tree, Point3D(0, 0, 0)), 0.0);
EXPECT_LT(evaluate(tree, Point3D(-0.5, 0, 0)), 0.0);
// Surface at ±1
EXPECT_NEAR(evaluate(tree, Point3D(1, 0, 0)), 0.0, 1e-6);
EXPECT_NEAR(evaluate(tree, Point3D(-1, 0, 0)), 0.0, 1e-6);
}
// ── Nested operations ────────────────────────────────────────────
TEST(SdfTree, NestedUnionTwist) {
auto inner = SdfNode::op_union(
SdfNode::sphere(1.0),
SdfNode::box(Point3D(0.6, 0.6, 0.6))
);
auto tree = SdfNode::twist(std::move(inner), 0.5);
// Should still evaluate successfully
double d = evaluate(tree, Point3D(0, 0.5, 0));
EXPECT_LT(d, 0.0); // interior of union
}
TEST(SdfTree, DeepNesting) {
auto a = SdfNode::sphere(1.0);
auto b = SdfNode::box(Point3D(0.5, 0.5, 0.5));
auto uni = SdfNode::op_union(std::move(a), std::move(b));
auto scaled = SdfNode::scale(std::move(uni), Point3D(2, 2, 2));
auto translated = SdfNode::translate(std::move(scaled), Point3D(3, 0, 0));
// Original origin → should be inside after inverse transform + union
double d = evaluate(translated, Point3D(3, 0, 0));
EXPECT_LT(d, 0.0);
}
// ── Modifiers ────────────────────────────────────────────────────
TEST(SdfTree, RoundModifier) {
auto tree = SdfNode::round(
SdfNode::box(Point3D(1, 1, 1)),
0.2
);
// Center still inside
EXPECT_LT(evaluate(tree, Point3D(0, 0, 0)), 0.0);
// Original corner point (1,1,0) should now be outside (rounded away)
double d_corner = evaluate(tree, Point3D(1, 0, 0));
// Rounded box should have slightly larger radius at corners
// The exact value depends on implementation, just verify it's farther
double d_box = evaluate(SdfNode::box(Point3D(1, 1, 1)), Point3D(1, 0, 0));
EXPECT_NEAR(d_corner, d_box - 0.2, 1e-6);
}
TEST(SdfTree, OnionModifier) {
auto tree = SdfNode::onion(
SdfNode::sphere(2.0),
0.3
);
// Center should be outside (hollowed out)
EXPECT_GT(evaluate(tree, Point3D(0, 0, 0)), 0.0);
// Shell surface should exist
EXPECT_NEAR(evaluate(tree, Point3D(1.7, 0, 0)), 0.0, 1e-6);
}
// ── Bounds estimation ────────────────────────────────────────────
TEST(SdfTree, EstimateBoundsSphere) {
auto s = SdfNode::sphere(2.0);
Point3D b = estimate_bounds(s, 0.0);
EXPECT_NEAR(b.x(), 2.0, 1e-6);
EXPECT_NEAR(b.y(), 2.0, 1e-6);
EXPECT_NEAR(b.z(), 2.0, 1e-6);
}
TEST(SdfTree, EstimateBoundsWithMargin) {
auto s = SdfNode::sphere(2.0);
Point3D b = estimate_bounds(s, 1.0);
EXPECT_NEAR(b.x(), 3.0, 1e-6);
}
TEST(SdfTree, EstimateBoundsUnion) {
auto tree = SdfNode::op_union(
SdfNode::sphere(2.0),
SdfNode::box(Point3D(3, 3, 3))
);
Point3D b = estimate_bounds(tree, 0.0);
// Box half-extent norm = sqrt(27) ≈ 5.196
EXPECT_GT(b.norm(), 4.0);
}
TEST(SdfTree, EstimateBoundsTranslated) {
auto tree = SdfNode::translate(
SdfNode::sphere(1.0),
Point3D(5, 0, 0)
);
Point3D b = estimate_bounds(tree, 0.0);
EXPECT_GT(b.norm(), 5.0);
}
// ── Tree traversal ───────────────────────────────────────────────
TEST(SdfTree, VisitCountsNodes) {
auto tree = SdfNode::op_union(
SdfNode::sphere(1.0),
SdfNode::box(Point3D(1, 1, 1))
);
int count = 0;
tree->visit([&count](const SdfNode&) { ++count; });
EXPECT_EQ(count, 3); // union + sphere + box
}