OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / tests / test_material_approaches.cpp

154 lines · cpp ref:
// Material-correctness oracle — verify the 4 avenues (level-mesh-architecture.md §Material).
// Two failing cases, measured by material-error rate at surface sample points vs ground truth:
//   Case A: 3-way concave corner (floor mat1 + wallX mat2 + wallY mat3 meet at origin).
//   Case B: coplanar split (two floors share z=0, mat1 x<0 / mat2 x>0, boundary line x=0).
// Approaches (per surface fragment, given the cell's captured candidate faces):
//   CUR  current: <=2 candidates, coplanar merged, pick by |dot(face_n, frag_n)| ; dominant fallback
//   P1   point-location: <=N candidates (distinct planes), pick by min|n.p-d| gated by normal
//   P3   3-candidate    : like CUR but keep 3 candidates
//   P4   un-merge+extent: keep coplanar faces, pick the face whose 2D EXTENT contains p
//   P2   multi-material : boundary emitted as geometry => correct by construction (topology cost)
#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>
struct V3{float x,y,z;};
static V3 operator-(V3 a,V3 b){return{a.x-b.x,a.y-b.y,a.z-b.z};}
static float dot(V3 a,V3 b){return a.x*b.x+a.y*b.y+a.z*b.z;}
struct Plane{V3 n;float d;};                 // n.p = d on face; inside brush = n.p <= d
struct Brush{std::vector<Plane> pl;int mat;};
static float bsdf(V3 p,const Brush&b){float m=-1e30f;for(auto&q:b.pl)m=fmaxf(m,dot(q.n,p)-q.d);return m;}
static float usdf(V3 p,const std::vector<Brush>&B){float m=1e30f;for(auto&b:B)m=fminf(m,bsdf(p,b));return m;}
static Brush box(V3 lo,V3 hi,int mat){
    Brush b;b.mat=mat;
    b.pl={{{1,0,0},hi.x},{{-1,0,0},-lo.x},{{0,1,0},hi.y},{{0,-1,0},-lo.y},{{0,0,1},hi.z},{{0,0,-1},-lo.z}};
    return b;}

// GROUND TRUTH material at surface point p: the exposed brush face p lies on = the brush with
// min |bsdf| that p is on the boundary of AND not strictly inside another brush (unburied).
static int gt_material(V3 p,const std::vector<Brush>&B){
    int best=-1;float bestd=1e30f;
    for(size_t i=0;i<B.size();i++){
        float s=fabsf(bsdf(p,B[i]));               // ~0 => on this brush's surface
        if(s>0.02f)continue;
        // unburied: p not strictly inside a DIFFERENT brush
        bool buried=false;
        for(size_t j=0;j<B.size();j++){if(j==i)continue;if(bsdf(p,B[j])<-0.02f){buried=true;break;}}
        if(buried)continue;
        if(s<bestd){bestd=s;best=B[i].mat;}
    }
    return best;
}
// exact active face (brush,plane) at surface point p: argmax plane of the min-sdf brush
static void active_face(V3 p,const std::vector<Brush>&B,V3&n,int&mat,int&bi){
    float best=1e30f;bi=0;for(size_t i=0;i<B.size();i++){float s=bsdf(p,B[i]);if(s<best){best=s;bi=(int)i;}}
    float bp=-1e30f;n={0,0,1};for(auto&q:B[bi].pl){float dp=dot(q.n,p)-q.d;if(dp>bp){bp=dp;n=q.n;}}mat=B[bi].mat;}

struct Cand{V3 n;float d;int mat;int brush;};   // a captured candidate face

// approach id: 0 CUR, 1 P1, 2 P3, 3 P4  (P2 handled analytically)
static int select_mat(int ap,const std::vector<Cand>&C,V3 p,V3 fn,const std::vector<Brush>&B){
    if(C.empty())return -1;
    if(ap==0||ap==2){                         // CUR / P3: pick by normal agreement among first K
        int K=(ap==0)?2:3; int best=-1;float bd=-1;
        for(int i=0;i<(int)C.size()&&i<K;i++){float a=fabsf(dot(C[i].n,fn));if(a>bd){bd=a;best=i;}}
        return C[best].mat;
    }
    if(ap==1){                                // P1: plane-distance, normal-gated
        int best=-1;float bd=1e30f;
        for(auto&c:C){float pd=fabsf(dot(c.n,p)-c.d);float na=fabsf(dot(c.n,fn));if(na<0.5f)continue;if(pd<bd){bd=pd;best=&c-&C[0];}}
        if(best<0){for(auto&c:C){float pd=fabsf(dot(c.n,p)-c.d);if(pd<bd){bd=pd;best=&c-&C[0];}}}
        return C[best].mat;
    }
    // P4: pick the candidate whose brush EXTENT contains p (p inside that brush's other planes)
    int best=-1;float bd=1e30f;
    for(auto&c:C){
        const Brush&b=B[c.brush];bool inside=true;
        for(auto&q:b.pl){if(dot(q.n,q.n)>1.5f)continue; if(dot(q.n,p)-q.d>0.03f){inside=false;break;}}
        float pd=fabsf(dot(c.n,p)-c.d);
        if(inside&&pd<bd){bd=pd;best=&c-&C[0];}
    }
    if(best<0)return select_mat(1,C,p,fn,B);  // fall back to P1 if none contain
    return C[best].mat;
}

// Capture candidate faces for the cell at center cc (mimics bake_planeset). merge_coplanar
// drops same-normal dupes (CUR/P1/P3); cap limits count (CUR=2,P3=3); P1/P4 use cap=8, P4 no-merge.
static std::vector<Cand> capture(V3 cc,float vs,const std::vector<Brush>&B,bool merge,int cap){
    std::vector<Cand> C;float T=vs*1.2f;
    for(size_t bi=0;bi<B.size();bi++){const Brush&b=B[bi];
        for(auto&pl:b.pl){
            float fd=dot(pl.n,cc)-pl.d;if(fabsf(fd)>=T)continue;
            bool onb=true;for(auto&q:b.pl){if(&q==&pl)continue;if(dot(q.n,cc)-q.d>T){onb=false;break;}}
            if(!onb)continue;
            V3 fpt=cc; V3 qp={fpt.x+pl.n.x*vs*0.5f,fpt.y+pl.n.y*vs*0.5f,fpt.z+pl.n.z*vs*0.5f};
            bool bur=false;for(size_t bk=0;bk<B.size();bk++){if(bk==bi)continue;if(bsdf(qp,B[bk])<0){bur=true;break;}}
            if(bur)continue;
            if(merge){bool dup=false;for(auto&c:C)if(fabsf(dot(c.n,pl.n))>0.999f){dup=true;break;}if(dup)continue;}
            if((int)C.size()<cap)C.push_back({pl.n,pl.d,b.mat,(int)bi});
        }
    }
    return C;
}

static void run_case(const char*name,std::vector<Brush>&B,float vs){
    V3 org{-3,-3,-3};int G=(int)ceilf(6.0f/vs)+1;
    auto D=[&](int i,int j,int k){return usdf({org.x+i*vs,org.y+j*vs,org.z+k*vs},B);};
    // per approach error counters over boundary surface-samples
    long tot=0,err[5]={0,0,0,0,0};
    for(int k=0;k<G-1;k++)for(int j=0;j<G-1;j++)for(int i=0;i<G-1;i++){
        bool neg=false,pos=false;for(int a=0;a<2;a++)for(int b=0;b<2;b++)for(int c=0;c<2;c++){float d=D(i+a,j+b,k+c);if(d<0)neg=true;else pos=true;}
        if(!(neg&&pos))continue;
        V3 cc{org.x+(i+.5f)*vs,org.y+(j+.5f)*vs,org.z+(k+.5f)*vs};
        // GATE: only cells where quantization bites — full (unmerged) capture has MORE distinct
        // materials than CUR's 2-candidate merged capture can represent (crease-oracle lesson).
        auto distinct=[](std::vector<Cand>&C){std::vector<int>m;for(auto&c:C)m.push_back(c.mat);
            std::sort(m.begin(),m.end());m.erase(std::unique(m.begin(),m.end()),m.end());return (int)m.size();};
        {auto full=capture(cc,vs,B,false,8);auto cur=capture(cc,vs,B,true,2);
         if(distinct(full)<=distinct(cur))continue;}
        // CLEAN sampling: one fragment exactly ON each distinct exposed face present in the cell.
        auto face_set=capture(cc,vs,B,false,8);   // unmerged: includes coplanar dupes
        for(size_t fi=0;fi<face_set.size();fi++){
            Cand&f=face_set[fi];
            bool dup=false;                       // skip a face already sampled (same mat+normal)
            for(size_t q=0;q<fi;q++) if(face_set[q].mat==f.mat&&fabsf(dot(face_set[q].n,f.n))>0.999f){dup=true;break;}
            if(dup)continue;
            // project cell centre onto face f's plane
            float off=dot(f.n,cc)-f.d;
            V3 p{cc.x-f.n.x*off,cc.y-f.n.y*off,cc.z-f.n.z*off};
            if(fabsf(p.x-cc.x)>0.75f*vs||fabsf(p.y-cc.y)>0.75f*vs||fabsf(p.z-cc.z)>0.75f*vs)continue; // in cell
            int g=gt_material(p,B);
            if(g!=f.mat)continue;                 // p must genuinely be on face f (unburied, in extent)
            V3 fn=f.n;
            auto cCUR=capture(cc,vs,B,true,2);
            auto cP1 =capture(cc,vs,B,true,8);
            auto cP3 =capture(cc,vs,B,true,3);
            auto cP4 =capture(cc,vs,B,false,8);
            tot++;
            if(select_mat(0,cCUR,p,fn,B)!=g)err[0]++;
            if(select_mat(1,cP1 ,p,fn,B)!=g)err[1]++;
            if(select_mat(2,cP3 ,p,fn,B)!=g)err[2]++;
            if(select_mat(3,cP4 ,p,fn,B)!=g)err[3]++;
            // P2 multi-material DC: boundary emitted as geometry => material==g by construction (err 0)
        }
    }
    if(!tot){printf("%-14s (no boundary samples)\n",name);return;}
    printf("%-16s  CUR=%.1f%%  P1=%.1f%%  P3=%.1f%%  P4=%.1f%%  P2=%.1f%%   (material-error over %ld boundary frags)\n",
        name,100.0*err[0]/tot,100.0*err[1]/tot,100.0*err[2]/tot,100.0*err[3]/tot,100.0*err[4]/tot,tot);
}

int main(){
    printf("Material-error rate at boundary fragments (lower = correcter). vs=0.25\n");
    printf("  CUR=current(2cand,merged) P1=point-loc P3=3cand P4=unmerge+extent P2=multi-material-DC\n");
    float vs=0.25f;
    { // Case A: 3-way concave corner
      std::vector<Brush> B={ box({-2,-2,-2},{2,2,0},1), box({0,-2,0},{2,2,3},2), box({-2,0,0},{2,2,3},3) };
      run_case("A 3-way corner",B,vs);
    }
    { // Case B: coplanar split (two floors share z=0, boundary x=0)
      std::vector<Brush> B={ box({-2,-2,-2},{0,2,0},1), box({0,-2,-2},{2,2,0},2) };
      run_case("B coplanar",B,vs);
    }
    return 0;
}