OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / tests / test_level_mesh.cpp

180 lines · cpp ref:
// ============================================================================
// 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;
}