OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / tests / test_csg4_gate.cpp

168 lines · cpp ref:
// ============================================================================
// CSG4 PORT GATE — does libdc/src/dc_csg4.cpp reproduce the oracle?
//
// tests/test_csg4_polycount.cpp is the reference implementation, validated on
// the real 1164-brush START.MAP. This gate runs the LIBRARY port over the same
// dump and asserts it produces the same answer. If it diverges, the port is
// wrong — the oracle is not.
//
// ACCEPTANCE (docs/level-mesh-implementation.md §6.2, §12 step 1):
//   6487 faces · 12747 triangles · 32101 m² · 2593 removed · 837 fragmented
//   mean AABB overlap 6.9 · max 53
//
// Also exercises the INCREMENTAL path (dc_csg4::clip_brush), which is what makes
// a brush edit local (docs §6.3). A brush re-clipped on its own must produce
// exactly the faces the full pass produced for it — otherwise interactive edits
// would silently drift from the baked mesh.
//
// Build: g++ -O2 -std=c++17 -I libdc/include -o /tmp/csg4_gate \
//          tests/test_csg4_gate.cpp libdc/src/dc_csg4.cpp
// ============================================================================

#include "dc_csg4.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>

// ---------------------------------------------------------------------------
// Dump loader (format written by hyper-tps/scenes/test/oracle_dump.gd)
// ---------------------------------------------------------------------------
struct LoadedBrush {
    std::vector<ConvexPlane> planes;
    std::vector<uint32_t> proj;
    uint32_t material = 0;
};

static bool load_dump(const char* path, std::vector<LoadedBrush>& out, float& ws) {
    FILE* f = fopen(path, "rb");
    if (!f) { fprintf(stderr, "cannot open %s\n", path); return false; }
    char m[4]; size_t r = fread(m, 1, 4, f); (void)r;
    if (memcmp(m, "DCOR", 4) != 0) { fprintf(stderr, "bad magic\n"); fclose(f); return false; }
    unsigned ver; r = fread(&ver, 4, 1, f);
    r = fread(&ws, 4, 1, f);
    float carve[6]; r = fread(carve, 4, 6, f);
    unsigned n; r = fread(&n, 4, 1, f);
    out.reserve(n);
    for (unsigned i = 0; i < n; i++) {
        LoadedBrush b;
        float c[3]; r = fread(c, 4, 3, f);
        float radius, strength; r = fread(&radius, 4, 1, f); r = fread(&strength, 4, 1, f);
        r = fread(&b.material, 4, 1, f);
        unsigned pc; r = fread(&pc, 4, 1, f);
        b.planes.reserve(pc); b.proj.reserve(pc);
        for (unsigned p = 0; p < pc; p++) {
            float v[4]; unsigned pr;
            r = fread(v, 4, 4, f); r = fread(&pr, 4, 1, f);
            b.planes.push_back({v[0], v[1], v[2], v[3]});
            b.proj.push_back(pr);
        }
        out.push_back(std::move(b));
    }
    fclose(f);
    return true;
}

// ---------------------------------------------------------------------------
static int g_fail = 0;

static void check_u(const char* label, uint32_t got, uint32_t want) {
    const bool ok = (got == want);
    if (!ok) g_fail++;
    printf("  %-28s %-10u expected %-10u %s\n", label, got, want, ok ? "OK" : "**FAIL**");
}
static void check_f(const char* label, double got, double want, double tol) {
    const bool ok = std::fabs(got - want) <= tol;
    if (!ok) g_fail++;
    printf("  %-28s %-10.1f expected %-10.1f %s\n", label, got, want, ok ? "OK" : "**FAIL**");
}

int main(int argc, char** argv) {
    const char* dump = (argc > 1) ? argv[1] : "/tmp/startmap_brushes.bin";

    std::vector<LoadedBrush> loaded;
    float ws = 0.0f;
    if (!load_dump(dump, loaded, ws)) return 2;

    std::vector<CSG4Brush> brushes;
    brushes.reserve(loaded.size());
    for (uint32_t i = 0; i < (uint32_t)loaded.size(); i++) {
        CSG4Brush b;
        b.planes = loaded[i].planes.data();
        b.plane_count = (uint32_t)loaded[i].planes.size();
        b.plane_proj = loaded[i].proj.data();
        b.material_id = loaded[i].material;
        b.id = i;
        brushes.push_back(b);
    }
    dc_csg4::compute_bounds(brushes);

    printf("CSG4 PORT GATE — libdc/src/dc_csg4.cpp vs tests/test_csg4_polycount.cpp\n");
    printf("brushes=%zu  world_scale=%.4f\n\n", brushes.size(), ws);

    std::vector<CSG4Face> faces;
    CSG4Stats st;
    dc_csg4::clip_all(brushes, faces, &st);

    printf("FULL PASS\n");
    check_u("raw faces",        st.raw_faces,   6930);
    check_u("raw triangles",    st.raw_tris,    13718);
    check_u("clipped faces",    st.out_faces,   6487);
    check_u("clipped triangles",st.out_tris,    12747);
    check_u("faces removed",    st.removed,     2593);
    check_u("faces fragmented", st.fragmented,  837);
    check_u("max overlap",      st.max_overlap, 53);
    check_f("raw area (m2)",    st.raw_area,    48338.7, 1.0);
    check_f("clipped area (m2)",st.out_area,    32100.8, 1.0);
    check_f("mean overlap",     st.mean_overlap, 6.9,    0.05);
    printf("  %-28s %zu\n", "faces emitted (vector)", faces.size());
    if (faces.size() != st.out_faces) {
        printf("  **FAIL** emitted face count disagrees with stats\n");
        g_fail++;
    }

    // ---- provenance: every face must carry a usable brush/face/material ----
    {
        uint32_t bad = 0;
        for (const CSG4Face& f : faces) {
            if (f.brush_id >= brushes.size()) { bad++; continue; }
            if (f.face_index >= brushes[f.brush_id].plane_count) { bad++; continue; }
            if (f.pts.size() < 3) bad++;
        }
        printf("\nPROVENANCE\n");
        check_u("faces with bad provenance", bad, 0);
    }

    // ---- incremental path must agree with the full pass ----
    // A brush re-clipped alone must yield exactly the faces the full pass gave
    // it; otherwise an interactive edit would drift from the baked mesh.
    {
        printf("\nINCREMENTAL (dc_csg4::clip_brush) vs FULL PASS\n");
        const uint32_t probe[] = {0, 1, 7, 42, 100, 523, 900, 1163};
        uint32_t mismatches = 0, checked = 0;
        for (uint32_t bi : probe) {
            if (bi >= brushes.size()) continue;
            std::vector<CSG4Face> inc;
            dc_csg4::clip_brush(brushes, bi, inc);

            uint32_t full_faces = 0, full_tris = 0, inc_tris = 0;
            for (const CSG4Face& f : faces)
                if (f.brush_id == bi) { full_faces++; full_tris += dc_csg4::face_tri_count(f); }
            for (const CSG4Face& f : inc) inc_tris += dc_csg4::face_tri_count(f);

            const bool ok = (inc.size() == full_faces) && (inc_tris == full_tris);
            if (!ok) { mismatches++; g_fail++; }
            printf("  brush %-5u incr=%-4zu/%-5u full=%-4u/%-5u %s\n",
                   bi, inc.size(), inc_tris, full_faces, full_tris, ok ? "OK" : "**FAIL**");
            checked++;
        }
        printf("  %u brushes checked, %u mismatches\n", checked, mismatches);
    }

    printf("\n%s\n", g_fail == 0 ? "GATE PASSED" : "GATE FAILED");
    return g_fail == 0 ? 0 : 1;
}