// Test: Fandisk DC verification — checks that Dual Contouring preserves
// sharp features when reconstructing the fandisk benchmark model from its SDF.
//
// Build via CMake (see test/CMakeLists.txt) or manually:
// cd dc-terrain
// g++ -std=c++17 -O2 -fopenmp -I libdc/include \
// test/test_fandisk.cpp libdc/src/*.cpp libdc/src/generators/*.cpp \
// thirdparty/meshoptimizer/*.cpp -o test/test_fandisk -lm
//
// Run:
// ./test/test_fandisk [path/to/fandisk.obj]
#include "dc_generator_mesh_sdf.h"
#include "dc_mesher.h"
#include <cstdio>
#include <cassert>
#include <fstream>
#include <string>
#include <algorithm>
#include <sys/stat.h>
static void write_obj(const DCMeshData& mesh, const std::string& path) {
std::ofstream f(path);
if (!f.is_open()) {
fprintf(stderr, "Failed to open %s for writing\n", path.c_str());
return;
}
for (size_t i = 0; i + 2 < mesh.positions.size(); i += 3) {
f << "v " << mesh.positions[i] << " " << mesh.positions[i+1]
<< " " << mesh.positions[i+2] << "\n";
}
for (size_t i = 0; i + 2 < mesh.normals.size(); i += 3) {
f << "vn " << mesh.normals[i] << " " << mesh.normals[i+1]
<< " " << mesh.normals[i+2] << "\n";
}
for (size_t i = 0; i + 2 < mesh.indices.size(); i += 3) {
f << "f " << (mesh.indices[i]+1) << "//" << (mesh.indices[i]+1) << " "
<< (mesh.indices[i+1]+1) << "//" << (mesh.indices[i+1]+1) << " "
<< (mesh.indices[i+2]+1) << "//" << (mesh.indices[i+2]+1) << "\n";
}
printf(" Wrote %s\n", path.c_str());
}
static void ensure_dir(const char* path) {
mkdir(path, 0755);
}
int main(int argc, char** argv) {
const char* obj_path = "test/data/fandisk.obj";
if (argc > 1) obj_path = argv[1];
printf("=== Fandisk DC Verification Test ===\n");
DCGeneratorMeshSDF gen;
if (!gen.load_obj(obj_path)) {
fprintf(stderr, "FAIL: Could not load %s\n", obj_path);
return 1;
}
float bmin[3], bmax[3];
gen.get_bounds(bmin, bmax);
printf("Bounds: (%.4f, %.4f, %.4f) - (%.4f, %.4f, %.4f)\n",
bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2]);
// Add 10% padding around mesh bounds
float pad = 0.0f;
for (int i = 0; i < 3; i++)
pad = std::max(pad, (bmax[i] - bmin[i]) * 0.1f);
float origin[3] = { bmin[0] - pad, bmin[1] - pad, bmin[2] - pad };
float extent[3] = { bmax[0] - bmin[0] + 2*pad,
bmax[1] - bmin[1] + 2*pad,
bmax[2] - bmin[2] + 2*pad };
float max_extent = std::max({extent[0], extent[1], extent[2]});
printf("Origin: (%.4f, %.4f, %.4f)\n", origin[0], origin[1], origin[2]);
printf("Extent: %.4f (padded)\n", max_extent);
ensure_dir("test/output");
int prev_verts = 0;
for (int res : {32, 64, 128}) {
printf("\n--- Resolution %d ---\n", res);
float voxel_size = max_extent / res;
printf(" Voxel size: %.6f\n", voxel_size);
DCMesherConfig config;
config.auto_smooth = true;
config.smooth_angle_deg = 30.0f;
config.qef_bias = 0.01f;
config.laplacian_smooth = false;
config.clip_to_bounds = false; // don't clip — we want the full mesh
DCMeshData mesh = dc_mesh_from_generator(gen, origin, voxel_size, res, config);
int verts = (int)(mesh.positions.size() / 3);
int tris = (int)(mesh.indices.size() / 3);
int norms = (int)(mesh.normals.size() / 3);
printf(" Vertices: %d\n", verts);
printf(" Normals: %d\n", norms);
printf(" Triangles: %d\n", tris);
assert(verts > 0 && "DC must produce vertices from fandisk SDF");
assert(tris > 0 && "DC must produce triangles from fandisk SDF");
// Higher resolution should produce more geometry
if (prev_verts > 0) {
assert(verts > prev_verts && "Higher resolution should produce more vertices");
}
prev_verts = verts;
// Write output OBJ for visual inspection
char fname[256];
snprintf(fname, sizeof(fname), "test/output/fandisk_dc_%d.obj", res);
write_obj(mesh, fname);
}
printf("\n=== All fandisk tests PASSED ===\n");
return 0;
}