// ============================================================================
// dc_level_mesh GATE — docs/level-mesh-implementation.md §12 step 3 (libdc half).
//
// The doc's acceptance criteria for step 3 are "~12-30K triangles, sharp edges,
// no LOD pop, per-brush convex collision". Two of those are checkable here; two
// are not:
//
// triangle count -> checked (must equal the csg4 gate exactly)
// SHARP EDGES -> checked NUMERICALLY, see below
// per-brush convex collision-> checked
// no LOD pop -> NOT checkable offline. It is true by
// construction (this mesh has no LOD at all) but
// only the in-engine scene can demonstrate it.
//
// The sharpness check is the interesting one. "Sharp edges" is usually asserted
// and eyeballed; here it is a number. Every vertex carries its FACE's plane
// normal, nothing is welded, so each triangle's geometric normal (from its
// winding) must equal the normal stored on its vertices. max_normal_deviation()
// returns the worst disagreement in radians — on exact brush geometry it must be
// ~0. It also catches winding/orientation bugs that a triangle count cannot.
//
// Build: g++ -O2 -std=c++17 -I libdc/include -o /tmp/level_mesh \
// tests/test_level_mesh.cpp libdc/src/dc_csg4.cpp libdc/src/dc_level_mesh.cpp
// ============================================================================
#include "dc_brush_store.h"
#include "dc_csg4.h"
#include "dc_level_mesh.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
static int g_fail = 0;
static void ok(const char* what, bool cond) {
if (!cond) g_fail++;
printf(" %-56s %s\n", what, cond ? "OK" : "**FAIL**");
}
static void eq_u(const char* what, uint64_t got, uint64_t want) {
const bool c = (got == want);
if (!c) g_fail++;
printf(" %-56s %-10llu want %-10llu %s\n", what,
(unsigned long long)got, (unsigned long long)want, c ? "OK" : "**FAIL**");
}
static void le_f(const char* what, double got, double limit) {
const bool c = (got <= limit);
if (!c) g_fail++;
printf(" %-56s %-12.9f <= %-10.6f %s\n", what, got, limit, c ? "OK" : "**FAIL**");
}
static bool load_dump(const char* path, DCBrushStore& store) {
FILE* f = fopen(path, "rb");
if (!f) return false;
char m[4]; size_t r = fread(m, 1, 4, f); (void)r;
if (memcmp(m, "DCOR", 4) != 0) { fclose(f); return false; }
unsigned ver; float ws; float carve[6]; unsigned n;
r = fread(&ver,4,1,f); r = fread(&ws,4,1,f); r = fread(carve,4,6,f); r = fread(&n,4,1,f);
for (unsigned i = 0; i < n; i++) {
float c[3], radius, strength; unsigned material, pc;
r = fread(c,4,3,f); r = fread(&radius,4,1,f); r = fread(&strength,4,1,f);
r = fread(&material,4,1,f); r = fread(&pc,4,1,f);
std::vector<ConvexPlane> planes; std::vector<uint32_t> proj;
for (unsigned p = 0; p < pc; p++) {
float v[4]; unsigned pr;
r = fread(v,4,4,f); r = fread(&pr,4,1,f);
planes.push_back({v[0],v[1],v[2],v[3]}); proj.push_back(pr);
}
store.add_brush(planes, proj, material, 1);
}
fclose(f);
return true;
}
int main(int argc, char** argv) {
const char* dump = (argc > 1) ? argv[1] : "/tmp/startmap_brushes.bin";
DCBrushStore store;
if (!load_dump(dump, store)) {
fprintf(stderr, "dump not found at %s — this gate needs the real map\n", dump);
return 2;
}
printf("dc_level_mesh GATE — START.MAP (%zu brushes)\n\n", store.brushes().size());
std::vector<CSG4Brush> view;
store.csg4_view(view);
std::vector<CSG4Face> faces;
CSG4Stats st;
dc_csg4::clip_all(view, faces, &st);
DCLevelMeshData mesh;
dc_level_mesh::build(faces, mesh);
printf("RENDER MESH\n");
eq_u("triangles == csg4 clipped count", mesh.triangle_count(), st.out_tris);
eq_u("triangles", mesh.triangle_count(), 12747);
ok("triangle count inside the doc's 12-30K band",
mesh.triangle_count() >= 12000 && mesh.triangle_count() <= 30000);
printf(" %-56s %u\n", "vertices (unwelded, one set per face)", mesh.vertex_count());
eq_u("normals parallel to positions", mesh.normals.size(), mesh.positions.size());
eq_u("material entries == vertices", mesh.material.size(), mesh.vertex_count());
eq_u("proj entries == vertices", mesh.proj.size(), mesh.vertex_count());
eq_u("brush_id entries == vertices", mesh.brush_id.size(), mesh.vertex_count());
ok("bounds valid", mesh.bounds_valid);
printf(" extent = %.1f x %.1f x %.1f m\n",
mesh.hi.x - mesh.lo.x, mesh.hi.y - mesh.lo.y, mesh.hi.z - mesh.lo.z);
// ---- finite + normalized ----
{
uint32_t bad_pos = 0, bad_nrm = 0;
for (float v : mesh.positions) if (!std::isfinite(v)) bad_pos++;
for (size_t i = 0; i + 2 < mesh.normals.size(); i += 3) {
const float l = std::sqrt(mesh.normals[i]*mesh.normals[i]
+ mesh.normals[i+1]*mesh.normals[i+1]
+ mesh.normals[i+2]*mesh.normals[i+2]);
if (!std::isfinite(l) || std::fabs(l - 1.0f) > 1e-3f) bad_nrm++;
}
eq_u("non-finite positions", bad_pos, 0);
eq_u("non-unit normals", bad_nrm, 0);
}
// ---- indices in range ----
{
uint32_t oob = 0;
const int32_t vc = (int32_t)mesh.vertex_count();
for (int32_t i : mesh.indices) if (i < 0 || i >= vc) oob++;
eq_u("out-of-range indices", oob, 0);
}
// ---- THE SHARPNESS GATE ----
printf("\nSHARP EDGES — measured, not asserted\n");
const float dev = dc_level_mesh::max_normal_deviation(mesh);
printf(" worst triangle-vs-face normal disagreement: %.9f rad (%.6f deg)\n",
dev, dev * 57.29577951f);
le_f("max normal deviation (rad)", dev, 1e-3);
printf(" -> every vertex carries its OWN face's plane normal and nothing is\n");
printf(" welded, so creases are sharp by construction. No QEF, no\n");
printf(" auto_smooth, no angle threshold, and no resolution dependence.\n");
// ---- collision ----
printf("\nPER-BRUSH CONVEX COLLISION (unclipped hulls — volume, not surface)\n");
std::vector<DCConvexCollider> cols;
dc_level_mesh::build_colliders(view, cols);
eq_u("colliders == clean brushes", cols.size(), view.size());
{
uint32_t thin = 0, bad = 0;
uint64_t total_pts = 0;
for (const DCConvexCollider& c : cols) {
if (c.point_count() < 4) thin++;
total_pts += c.point_count();
for (float v : c.points) if (!std::isfinite(v)) bad++;
}
eq_u("colliders with <4 points", thin, 0);
eq_u("non-finite collider points", bad, 0);
printf(" %-56s %.1f\n", "mean hull points per brush",
(double)total_pts / (double)std::max<size_t>(1, cols.size()));
}
// ---- render/collision divergence is DELIBERATE (docs §10) ----
printf("\nRENDER vs COLLISION\n");
{
// Collision uses unclipped hulls, so it must cover brushes whose visible
// surface was entirely removed. 2593 faces were buried; a brush can lose
// faces to clipping yet still need a solid collider.
uint32_t rendered_brushes = 0;
std::vector<bool> seen(store.brushes().size() + 2, false);
for (uint32_t b : mesh.brush_id) if (b < seen.size() && !seen[b]) { seen[b] = true; rendered_brushes++; }
printf(" %-56s %u\n", "brushes contributing visible faces", rendered_brushes);
printf(" %-56s %zu\n", "brushes with a collider", cols.size());
ok("collision covers at least as many brushes as render",
cols.size() >= rendered_brushes);
}
printf("\nNOT CHECKED HERE: 'no LOD pop'. True by construction (this mesh has\n");
printf("no LOD at all) but only the in-engine scene can demonstrate it.\n");
printf("\n%s\n", g_fail == 0 ? "GATE PASSED" : "GATE FAILED");
return g_fail == 0 ? 0 : 1;
}