OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / libdc / test / test_snap.cpp

125 lines · cpp ref:
// Build+run:  g++ -std=c++17 test_snap.cpp -o test_snap && ./test_snap
// CPU oracle for the plane-snap math (before any GPU/shader change).
// Verifies: cluster a cell's edge normals into distinct planes, then snap the
// vertex onto the exact plane (face) / crease-line (edge) / corner-point (corner).
// The snapped vertex MUST satisfy the exact plane equations — that's what makes it
// grid-independent and razor-sharp.
#include <cstdio>
#include <cmath>
#include <vector>

struct V3 { float x, y, z; };
static V3 sub(V3 a, V3 b){ return {a.x-b.x, a.y-b.y, a.z-b.z}; }
static V3 add(V3 a, V3 b){ return {a.x+b.x, a.y+b.y, a.z+b.z}; }
static V3 mul(V3 a, float s){ return {a.x*s, a.y*s, a.z*s}; }
static float dot(V3 a, V3 b){ return a.x*b.x + a.y*b.y + a.z*b.z; }
static V3 cross(V3 a, V3 b){ return {a.y*b.z-a.z*b.y, a.z*b.x-a.x*b.z, a.x*b.y-a.y*b.x}; }

struct Plane { V3 n; float d; };   // n·x = d  (n unit)
struct Edge  { V3 pos; V3 norm; }; // Hermite: crossing position + surface normal

// Cluster the cell's edge normals into <=3 distinct planes (tight, sign-agnostic).
// clean=false if a 4th distinct direction appears (=> organic, don't snap).
static int cluster(const std::vector<Edge>& edges, Plane out[3], bool& clean) {
    int nc = 0; clean = true;
    for (const auto& e : edges) {
        float pd = dot(e.norm, e.pos);           // plane offset for this edge
        int matched = -1;
        for (int c = 0; c < nc; c++)
            if (std::fabs(dot(e.norm, out[c].n)) > 0.985f) { matched = c; break; }
        if (matched < 0) {
            if (nc < 3) { out[nc].n = e.norm; out[nc].d = pd; nc++; }
            else clean = false;
        }
    }
    return nc;
}

static V3 snap_plane(V3 x, Plane p) {           // project x onto n·x=d
    return sub(x, mul(p.n, dot(p.n, x) - p.d));
}
static V3 snap_line(V3 x, Plane a, Plane b) {   // project x onto the two-plane crease
    float c = dot(a.n, b.n);
    float det = 1.0f - c*c;
    if (std::fabs(det) < 1e-5f) return snap_plane(x, a);   // near-parallel => 1 plane
    float r0 = a.d - dot(a.n, x), r1 = b.d - dot(b.n, x);
    float t0 = (r0 - c*r1) / det, t1 = (r1 - c*r0) / det;
    return add(x, add(mul(a.n, t0), mul(b.n, t1)));
}
static V3 snap_corner(Plane a, Plane b, Plane c) {   // 3-plane intersection point
    float denom = dot(a.n, cross(b.n, c.n));
    if (std::fabs(denom) < 1e-6f) return {1e30f,0,0};    // degenerate
    V3 t = add(add(mul(cross(b.n, c.n), a.d), mul(cross(c.n, a.n), b.d)),
               mul(cross(a.n, b.n), c.d));
    return mul(t, 1.0f/denom);
}

static V3 do_snap(V3 x, const std::vector<Edge>& edges) {
    Plane p[3]; bool clean;
    int nc = cluster(edges, p, clean);
    if (!clean) return x;                    // organic: leave the QEF result
    if (nc == 1) return snap_plane(x, p[0]);
    if (nc == 2) return snap_line(x, p[0], p[1]);
    if (nc >= 3) return snap_corner(p[0], p[1], p[2]);
    return x;
}

static float plane_err(V3 x, Plane p){ return std::fabs(dot(p.n, x) - p.d); }

int main() {
    int fails = 0;
    // Planes of a cube face at z=5 (up), side at x=3 (right), side at y=2 (fwd).
    Plane pz{{0,0,1}, 5}, px{{1,0,0}, 3}, py{{0,1,0}, 2};

    // --- FACE cell: all crossings on plane z=5, scattered in x,y ---------------
    {
        std::vector<Edge> es = {
            {{1.2f, 0.4f, 5.0f}, {0,0,1}}, {{1.8f, 0.9f, 5.0f}, {0,0,-1}}, // sign flip ok
            {{1.5f, 0.1f, 5.0f}, {0,0,1}},
        };
        V3 x = {1.5f, 0.5f, 4.3f};            // QEF vertex off the plane
        V3 s = do_snap(x, es);
        float e = plane_err(s, pz);
        printf("FACE  snap=(%.3f,%.3f,%.3f)  plane_err=%.5f %s\n", s.x,s.y,s.z, e, e<1e-4f?"OK":"FAIL");
        if (e >= 1e-4f) fails++;
    }
    // --- EDGE cell: crossings on z=5 and x=3 -> snap onto the crease line ------
    {
        std::vector<Edge> es = {
            {{3.0f, 0.4f, 5.0f}, {0,0,1}}, {{3.0f, 0.9f, 5.0f}, {0,0,1}},   // top face
            {{3.0f, 0.2f, 4.6f}, {1,0,0}}, {{3.0f, 0.7f, 4.2f}, {-1,0,0}},  // side face
        };
        V3 x = {2.6f, 0.5f, 4.4f};
        V3 s = do_snap(x, es);
        float e = plane_err(s, pz) + plane_err(s, px);
        printf("EDGE  snap=(%.3f,%.3f,%.3f)  on-both-planes_err=%.5f %s\n", s.x,s.y,s.z, e, e<1e-4f?"OK":"FAIL");
        if (e >= 1e-4f) fails++;
    }
    // --- CORNER cell: crossings on z=5, x=3, y=2 -> snap to the corner point ---
    {
        std::vector<Edge> es = {
            {{3.0f, 2.0f, 5.0f}, {0,0,1}}, {{2.9f, 1.9f, 5.0f}, {0,0,1}},
            {{3.0f, 1.8f, 4.7f}, {1,0,0}}, {{2.7f, 2.0f, 4.6f}, {0,1,0}},
        };
        V3 x = {2.5f, 1.5f, 4.5f};
        V3 s = do_snap(x, es);
        float e = plane_err(s, pz) + plane_err(s, px) + plane_err(s, py);
        printf("CORNER snap=(%.3f,%.3f,%.3f) expect(3,2,5) err=%.5f %s\n", s.x,s.y,s.z, e, e<1e-4f?"OK":"FAIL");
        if (e >= 1e-4f) fails++;
    }
    // --- ORGANIC cell: many varied normals (>3 clusters) -> must NOT snap ------
    {
        std::vector<Edge> es = {
            {{0,0,0},{1,0,0}}, {{0,0,0},{0,1,0}}, {{0,0,0},{0,0,1}},
            {{0,0,0},{0.7f,0.7f,0}}, {{0,0,0},{0,0.7f,0.7f}},   // 4th/5th direction
        };
        V3 x = {9,9,9};
        V3 s = do_snap(x, es);
        bool unchanged = (s.x==9 && s.y==9 && s.z==9);
        printf("ORGANIC unchanged=%s %s\n", unchanged?"yes":"no", unchanged?"OK":"FAIL");
        if (!unchanged) fails++;
    }
    printf("\n%s (%d failures)\n", fails==0?"ALL PASS":"FAILURES", fails);
    return fails ? 1 : 0;
}