#include "CoreMinimal.h" #include "MarchingCubesTables.h" #include "VoxelForgeCore/VoxelForgeCore.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr double VoxelMeters = 0.25; constexpr double PositionQuantum = VoxelMeters / 65536.0; constexpr double LodContourErrorBudgetMeters = 0.50; // One LOD1 cell (50 cm) physical mismatch. constexpr double NormalSplitCos = 0.86602540378443864676; // 30 degrees constexpr int CornerCount = 8; constexpr int EdgeCount = 12; constexpr std::array, CornerCount> CornerOffsets{{ {{0,0,0}}, {{1,0,0}}, {{1,1,0}}, {{0,1,0}}, {{0,0,1}}, {{1,0,1}}, {{1,1,1}}, {{0,1,1}} }}; constexpr std::array, EdgeCount> EdgeCorners{{ {{0,1}}, {{1,2}}, {{2,3}}, {{3,0}}, {{4,5}}, {{5,6}}, {{6,7}}, {{7,4}}, {{0,4}}, {{1,5}}, {{2,6}}, {{3,7}} }}; struct V3 { double x=0, y=0, z=0; V3 operator+(V3 b) const { return {x+b.x,y+b.y,z+b.z}; } V3 operator-(V3 b) const { return {x-b.x,y-b.y,z-b.z}; } V3 operator*(double s) const { return {x*s,y*s,z*s}; } V3 operator/(double s) const { return {x/s,y/s,z/s}; } V3& operator+=(V3 b) { x+=b.x; y+=b.y; z+=b.z; return *this; } }; double Dot(V3 a,V3 b){return a.x*b.x+a.y*b.y+a.z*b.z;} 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};} double Length(V3 a){return std::sqrt(Dot(a,a));} V3 Normalize(V3 a) { const double n=Length(a); return n>1.0e-20?a/n:V3{0,0,1}; } V3 Quantize(V3 p) { const auto q=[](double x){return std::nearbyint(x/PositionQuantum)*PositionQuantum;}; return {q(p.x),q(p.y),q(p.z)}; } template void ParallelFor(std::size_t count,int workers,F&& function) { workers=std::clamp(workers,1,16); if(workers==1||count<512) { for(std::size_t i=0;i(workers,static_cast(count)); std::vector threads; threads.reserve(actual); for(int worker=0;worker(worker)/actual; const std::size_t end=count*static_cast(worker+1)/actual; threads.emplace_back([begin,end,&function](){for(std::size_t i=begin;i planes{}; std::uint8_t planeCount=0; V3 gradient{}; bool sharp() const { return planeCount!=0; } std::uint64_t primaryFeature() const { if(!planeCount)return 0; std::uint64_t id=planes[0].id; for(std::uint8_t i=1;i planes{}; std::uint8_t planeCount=0; V3 gradient{}; }; BoxSample EvalBox(const Box& box,V3 p) { const V3 center=(box.low+box.high)*0.5; const V3 half=(box.high-box.low)*0.5; const V3 delta=p-center; const V3 q{std::abs(delta.x)-half.x,std::abs(delta.y)-half.y,std::abs(delta.z)-half.z}; const V3 outside{std::max(q.x,0.0),std::max(q.y,0.0),std::max(q.z,0.0)}; BoxSample out; out.density=Length(outside)+std::min(std::max(q.x,std::max(q.y,q.z)),0.0); const double maxQ=std::max(q.x,std::max(q.y,q.z)); V3 gradient{}; if(Length(outside)>1.0e-14) { gradient=Normalize({outside.x*(delta.x<0?-1:1),outside.y*(delta.y<0?-1:1),outside.z*(delta.z<0?-1:1)}); } for(int axis=0;axis<3;++axis) { const double qAxis=axis==0?q.x:(axis==1?q.y:q.z); if(std::abs(qAxis-maxQ)>1.0e-10)continue; const double dAxis=axis==0?delta.x:(axis==1?delta.y:delta.z); V3 normal{}; if(axis==0)normal.x=dAxis<0?-1:1; if(axis==1)normal.y=dAxis<0?-1:1; if(axis==2)normal.z=dAxis<0?-1:1; const std::uint64_t side=dAxis<0?0ull:1ull; const std::uint64_t feature=(static_cast(box.id)<<4) |static_cast(axis*2)+side+1ull; if(box.inverted)normal=normal*-1.0; out.planes[out.planeCount++]={feature,normal}; gradient+=normal; } if(box.inverted) { out.density=-out.density; gradient=gradient*-1.0; } out.gradient=Length(gradient)>1.0e-14?Normalize(gradient):Normalize(outside); return out; } enum class Piece : int { Corner=0, Doorway=1, Step=2, Ledge=3, Organic=4, NativeSurface=5 }; const char* PieceName(Piece p) { switch(p) { case Piece::Corner:return "corner90"; case Piece::Doorway:return "doorway"; case Piece::Step:return "riser1m"; case Piece::Ledge:return "cliff_overhang"; case Piece::Organic:return "organic_cave_chamber"; default:return "native_surface_cliff"; } } struct Field { Piece piece=Piece::Corner; V3 phaseShift{}; const VoxelForgeCore::Heightfield2D* nativeSurface=nullptr; std::array boxes{}; std::uint8_t boxCount=0; Box doorVoid{}; void AddBox(V3 low,V3 high,std::uint32_t id) { boxes[boxCount++]={low,high,id,false}; } explicit Field(Piece p,V3 phase={}):piece(p),phaseShift(phase) { switch(p) { case Piece::Corner: AddBox({-2.5,-2.5,-1.5},{0.25,2.5,1.5},1); AddBox({-2.5,-0.25,-1.5},{2.5,2.5,1.5},2); break; case Piece::Doorway: AddBox({-0.5,-2.5,-2.0},{0.5,2.5,3.0},3); doorVoid={{-0.8,-0.85,-1.75},{0.8,0.85,2.0},4,true}; break; case Piece::Step: AddBox({-3.0,-3.0,-2.0},{3.0,0.25,0.0},5); AddBox({-3.0,-0.25,-2.0},{3.0,3.0,1.0},6); break; case Piece::Ledge: AddBox({-3.0,-3.0,-2.0},{0.25,3.0,3.0},7); AddBox({-0.25,-3.0,1.5},{2.2,3.0,2.0},8); break; case Piece::Organic:case Piece::NativeSurface:break; } } explicit Field(const VoxelForgeCore::Heightfield2D& surface,V3 phase={}) :piece(Piece::NativeSurface),phaseShift(phase),nativeSurface(&surface){} FieldSample Eval(V3 p) const { p=p+phaseShift; if(nativeSurface) { constexpr double SubvoxelsPerMeter=1024.0; constexpr std::int64_t GradientDeltaSubvoxels=64; const auto x=static_cast(std::llround(p.x*SubvoxelsPerMeter)); const auto y=static_cast(std::llround(p.y*SubvoxelsPerMeter)); const auto z=static_cast(std::llround(p.z*SubvoxelsPerMeter)); const auto center=nativeSurface->point(x,y,z); FieldSample result;result.density=static_cast(center.densitySubvoxels)/SubvoxelsPerMeter; const auto xp=nativeSurface->point(x+GradientDeltaSubvoxels,y,z).densitySubvoxels; const auto xm=nativeSurface->point(x-GradientDeltaSubvoxels,y,z).densitySubvoxels; const auto yp=nativeSurface->point(x,y+GradientDeltaSubvoxels,z).densitySubvoxels; const auto ym=nativeSurface->point(x,y-GradientDeltaSubvoxels,z).densitySubvoxels; const auto zp=nativeSurface->point(x,y,z+GradientDeltaSubvoxels).densitySubvoxels; const auto zm=nativeSurface->point(x,y,z-GradientDeltaSubvoxels).densitySubvoxels; result.gradient=Normalize({static_cast(xp-xm),static_cast(yp-ym),static_cast(zp-zm)}); return result; } if(piece==Piece::Organic) { const V3 d=p-V3{0,0,0}; FieldSample result; // Exact spherical chamber cavity: negative solid outside, positive air inside. result.density=2.0-Length(d); result.gradient=Normalize(d)*-1.0; return result; } FieldSample result; result.density=std::numeric_limits::infinity(); for(std::uint8_t i=0;iresult.density+1.0e-10) { result.density=cutDensity; result.planeCount=cut.planeCount; for(std::uint8_t k=0;k1.0e-14?Normalize(sum):V3{0,0,1}; return result; } }; struct Grid { int nx=0,ny=0,nz=0,step=1; std::array coreMin{{0,0,0}},coreMax{{0,0,0}}; V3 origin{}; std::vector density; std::size_t NodeIndex(int x,int y,int z) const { return (static_cast(z)*(ny+1)+y)*(nx+1)+x; } V3 Position(int x,int y,int z) const { return origin+V3{x*step*VoxelMeters,y*step*VoxelMeters,z*step*VoxelMeters}; } std::size_t CellIndex(int x,int y,int z) const { return (static_cast(z)*ny+y)*nx+x; } bool IsCoreCell(int x,int y,int z) const { return x>=coreMin[0]&&y>=coreMin[1]&&z>=coreMin[2]&& x(nx)*ny*nz; } }; Grid SampleGrid(const Field& field,int nx,int ny,int nz,int step,V3 origin,int workers,int halo=0) { Grid grid;grid.step=step; grid.coreMin={halo,halo,halo};grid.coreMax={halo+nx,halo+ny,halo+nz}; grid.nx=nx+2*halo;grid.ny=ny+2*halo;grid.nz=nz+2*halo; grid.origin=origin-V3{halo*step*VoxelMeters,halo*step*VoxelMeters,halo*step*VoxelMeters}; const std::size_t count=static_cast(grid.nx+1)*(grid.ny+1)*(grid.nz+1); grid.density.resize(count); ParallelFor(count,workers,[&](std::size_t index) { const int x=static_cast(index%(grid.nx+1)); const int y=static_cast((index/(grid.nx+1))%(grid.ny+1)); const int z=static_cast(index/((grid.nx+1)*(grid.ny+1))); const double density=field.Eval(grid.Position(x,y,z)).density; // Stable iso-level tie rule: zero remains air, matching the Core sign convention, but // receives a sub-cell positive bias so edge roots do not collapse onto one lattice // vertex from several unrelated edges. This is only 0.01 mm and is below the output // surface-error precision; it is shared by all three candidates. grid.density[index]=density==0.0?1.0e-5:density; }); return grid; } struct Root { bool active=false; V3 position{},gradient{}; std::array planes{}; std::uint8_t planeCount=0; std::uint64_t feature=0; }; struct EdgeArrays { std::array,3> roots; std::array,3> vertexIds; std::array counts{}; std::size_t Total() const{return counts[0]+counts[1]+counts[2];} std::size_t Index(int axis,int x,int y,int z,const Grid& g) const { if(axis==0)return (static_cast(z)*(g.ny+1)+y)*g.nx+x; if(axis==1)return (static_cast(z)*g.ny+y)*(g.nx+1)+x; return (static_cast(z)*(g.ny+1)+y)*(g.nx+1)+x; } Root& Get(int axis,int x,int y,int z,const Grid& g){return roots[axis][Index(axis,x,y,z,g)];} const Root& Get(int axis,int x,int y,int z,const Grid& g) const{return roots[axis][Index(axis,x,y,z,g)];} int& Vertex(int axis,int x,int y,int z,const Grid& g){return vertexIds[axis][Index(axis,x,y,z,g)];} int Vertex(int axis,int x,int y,int z,const Grid& g) const{return vertexIds[axis][Index(axis,x,y,z,g)];} }; EdgeArrays BuildRoots(const Grid& grid,const Field& field,int workers) { EdgeArrays edges; edges.counts={static_cast(grid.nx)*(grid.ny+1)*(grid.nz+1), static_cast(grid.nx+1)*grid.ny*(grid.nz+1), static_cast(grid.nx+1)*(grid.ny+1)*grid.nz}; for(int axis=0;axis<3;++axis) { edges.roots[axis].resize(edges.counts[axis]); edges.vertexIds[axis].assign(edges.counts[axis],-1); } const std::size_t total=edges.Total(); ParallelFor(total,workers,[&](std::size_t flat) { int axis=0;std::size_t index=flat; while(axis<2&&index>=edges.counts[axis]){index-=edges.counts[axis];++axis;} int x=0,y=0,z=0; if(axis==0) { x=static_cast(index%grid.nx);y=static_cast((index/grid.nx)%(grid.ny+1)); z=static_cast(index/(grid.nx*(grid.ny+1))); } else if(axis==1) { x=static_cast(index%(grid.nx+1));y=static_cast((index/(grid.nx+1))%grid.ny); z=static_cast(index/((grid.nx+1)*grid.ny)); } else { x=static_cast(index%(grid.nx+1));y=static_cast((index/(grid.nx+1))%(grid.ny+1)); z=static_cast(index/((grid.nx+1)*(grid.ny+1))); } int x2=x,y2=y,z2=z; if(axis==0)++x2;else if(axis==1)++y2;else ++z2; const double d0=grid.density[grid.NodeIndex(x,y,z)]; const double d1=grid.density[grid.NodeIndex(x2,y2,z2)]; if((d0<0.0)==(d1<0.0))return; double t=d0/(d0-d1);t=std::clamp(t,0.0,1.0); Root root;root.active=true; root.position=Quantize(grid.Position(x,y,z)+(grid.Position(x2,y2,z2)-grid.Position(x,y,z))*t); const FieldSample sample=field.Eval(root.position); const double h=grid.step*VoxelMeters; const auto gradientAt=[&](V3 point) { const double dx=field.Eval(point+V3{h,0,0}).density-field.Eval(point-V3{h,0,0}).density; const double dy=field.Eval(point+V3{0,h,0}).density-field.Eval(point-V3{0,h,0}).density; const double dz=field.Eval(point+V3{0,0,h}).density-field.Eval(point-V3{0,0,h}).density; return Normalize({dx,dy,dz}); }; const V3 g0=gradientAt(grid.Position(x,y,z)); const V3 g1=gradientAt(grid.Position(x2,y2,z2)); root.gradient=Normalize(g0*(1.0-t)+g1*t); root.planeCount=sample.planeCount; for(std::uint8_t i=0;i index{}; std::uint64_t feature=0; }; struct Mesh { std::vector vertices; std::vector triangles; std::uint64_t hash=0; std::uint64_t skippedDualEdges=0; }; enum class Method : int { MC=0, Hybrid=1, DC=2 }; const char* MethodName(Method m){return m==Method::MC?"MC":m==Method::Hybrid?"Hybrid": "DC";} struct Cell { std::uint8_t cubeCase=0; bool active=false; std::uint8_t componentCount=0; std::array edgeComponent{}; std::array componentSharp{},componentUseDC{}; std::array dual{}; }; int FindLocal(std::array& parent,int value) { while(parent[value]!=value) { parent[value]=parent[parent[value]]; value=parent[value]; } return value; } void UniteLocal(std::array& parent,int a,int b) { a=FindLocal(parent,a);b=FindLocal(parent,b);if(a==b)return; if(a>b)std::swap(a,b); parent[b]=static_cast(a); } struct ComponentSets { std::vector parent; std::vector rank,sharp; explicit ComponentSets(std::size_t size):parent(size),rank(size,0),sharp(size,0) {std::iota(parent.begin(),parent.end(),0u);} std::uint32_t Find(std::uint32_t value) { while(parent[value]!=value){parent[value]=parent[parent[value]];value=parent[value];} return value; } void Unite(std::uint32_t a,std::uint32_t b) { a=Find(a);b=Find(b);if(a==b)return; if(rank[a](sharp[a]|sharp[b]); if(rank[a]==rank[b])++rank[a]; } }; std::array LocalEdge(int x,int y,int z,int edge) { const auto c=EdgeCorners[edge];const auto a=CornerOffsets[c[0]],b=CornerOffsets[c[1]]; const int axis=a[0]!=b[0]?0:a[1]!=b[1]?1:2; return {axis,x+std::min(a[0],b[0]),y+std::min(a[1],b[1]),z+std::min(a[2],b[2])}; } int FindLocalEdge(int cellX,int cellY,int cellZ,int axis,int edgeX,int edgeY,int edgeZ) { for(int edge=0;edge>& constraints,V3 fallback,V3 low,V3 high) { if(constraints.empty())return fallback; double a[3][4]{}; // Fixed constraint order; a small center penalty resolves rank-deficient planes without // moving a two/three-plane intersection at measurable scale. constexpr double lambda=1.0e-10; const double c[3]={fallback.x,fallback.y,fallback.z}; for(int i=0;i<3;++i){a[i][i]=lambda;a[i][3]=lambda*c[i];} for(const auto& constraint:constraints) { const V3 n=Normalize(constraint.second);const double b=Dot(n,constraint.first); const double nv[3]={n.x,n.y,n.z}; for(int r=0;r<3;++r){for(int k=0;k<3;++k)a[r][k]+=nv[r]*nv[k];a[r][3]+=nv[r]*b;} } bool singular=false; for(int column=0;column<3;++column) { int pivot=column; for(int row=column+1;row<3;++row)if(std::abs(a[row][column])>std::abs(a[pivot][column]))pivot=row; if(std::abs(a[pivot][column])<1.0e-14){singular=true;break;} if(pivot!=column)for(int k=column;k<4;++k)std::swap(a[pivot][k],a[column][k]); const double divisor=a[column][column]; for(int k=column;k<4;++k)a[column][k]/=divisor; for(int row=0;row<3;++row)if(row!=column) { const double factor=a[row][column]; for(int k=column;k<4;++k)a[row][k]-=factor*a[column][k]; } } if(singular)return fallback; return Quantize({std::clamp(a[0][3],low.x,high.x), std::clamp(a[1][3],low.y,high.y),std::clamp(a[2][3],low.z,high.z)}); } std::uint64_t SelectFeature(const Root& root,int edgeAxis) { if(!root.planeCount)return 0; std::uint8_t selected=0; for(std::uint8_t i=1;ib+1.0e-12||(std::abs(a-b)<=1.0e-12&&root.planes[i].id(word>>(i*8));h*=1099511628211ull;} }; add(mesh.vertices.size(),8); for(const Vertex& v:mesh.vertices) { for(double x:{v.position.x,v.position.y,v.position.z,v.normal.x,v.normal.y,v.normal.z}) add(std::bit_cast(x),8); } add(mesh.triangles.size(),8); for(const Triangle& t:mesh.triangles) {add(t.index[0],4);add(t.index[1],4);add(t.index[2],4);add(t.feature,8);} return h; } Mesh BuildMesh(const Grid& grid,const Field& field,Method method,int workers,bool enforceQuadOwnership=true) { EdgeArrays edges=BuildRoots(grid,field,workers); std::vector cells(grid.CellCount()); ParallelFor(cells.size(),workers,[&](std::size_t ci) { const int x=static_cast(ci%grid.nx); const int y=static_cast((ci/grid.nx)%grid.ny); const int z=static_cast(ci/(grid.nx*grid.ny)); Cell& cell=cells[ci];cell.edgeComponent.fill(0xff); double values[CornerCount]; for(int corner=0;corner(1u< localParent{}; for(int edge=0;edge(edge); for(int k=0;k<16&&MCTriTable[cell.cubeCase][k]>=0;k+=3) { const int a=MCTriTable[cell.cubeCase][k],b=MCTriTable[cell.cubeCase][k+1],c=MCTriTable[cell.cubeCase][k+2]; UniteLocal(localParent,a,b);UniteLocal(localParent,b,c); } std::array rootToComponent{};rootToComponent.fill(0xff); for(int edge=0;edge patches; if(method==Method::Hybrid) { patches=std::make_unique(cells.size()*EdgeCount); for(std::size_t cellIndex=0;cellIndexsharp[cellIndex*EdgeCount+component]=cells[cellIndex].componentSharp[component]?1:0; for(int axis=0;axis<3;++axis) { for(std::size_t i=0;i(i%grid.nx);y=static_cast((i/grid.nx)%(grid.ny+1));z=static_cast(i/(grid.nx*(grid.ny+1)));} else if(axis==1){x=static_cast(i%(grid.nx+1));y=static_cast((i/(grid.nx+1))%grid.ny);z=static_cast(i/((grid.nx+1)*grid.ny));} else{x=static_cast(i%(grid.nx+1));y=static_cast((i/(grid.nx+1))%(grid.ny+1));z=static_cast(i/((grid.nx+1)*(grid.ny+1)));} std::array,4> around{}; if(axis==0)around={{{x,y-1,z-1},{x,y,z-1},{x,y,z},{x,y-1,z}}}; else if(axis==1)around={{{x-1,y,z-1},{x-1,y,z},{x,y,z},{x,y,z-1}}}; else around={{{x-1,y-1,z},{x,y-1,z},{x,y,z},{x-1,y,z}}}; std::array patchIds{};bool valid=true; for(int corner=0;corner<4;++corner) { const auto& c=around[corner]; if(c[0]<0||c[1]<0||c[2]<0||c[0]>=grid.nx||c[1]>=grid.ny||c[2]>=grid.nz){valid=false;break;} const std::size_t cellIndex=grid.CellIndex(c[0],c[1],c[2]); const int localEdge=FindLocalEdge(c[0],c[1],c[2],axis,x,y,z); if(localEdge<0||cells[cellIndex].edgeComponent[localEdge]==0xff){valid=false;break;} patchIds[corner]=static_cast(cellIndex*EdgeCount+cells[cellIndex].edgeComponent[localEdge]); } if(valid)for(int corner=1;corner<4;++corner)patches->Unite(patchIds[0],patchIds[corner]); } } } for(std::size_t cellIndex=0;cellIndex(cellIndex*EdgeCount+component); cells[cellIndex].componentUseDC[component]=method==Method::DC|| (method==Method::Hybrid&&patches->sharp[patches->Find(patch)]!=0); } ParallelFor(cells.size(),workers,[&](std::size_t ci) { Cell& cell=cells[ci];if(cell.componentCount==0)return; const int x=static_cast(ci%grid.nx); const int y=static_cast((ci/grid.nx)%grid.ny); const int z=static_cast(ci/(grid.nx*grid.ny)); for(std::uint8_t component=0;component> constraints; V3 rootSum{};int rootCount=0; for(int edge=0;edge(rootCount):(low+high)*0.5; cell.dual[component]=SolveQEF(constraints,center,low,high); } }); Mesh mesh; std::unordered_map dcVertexMap; if(method==Method::MC||method==Method::Hybrid) { std::map,std::uint32_t> positionMap; for(int axis=0;axis<3;++axis)for(std::size_t index=0;index(std::llround(x/PositionQuantum));}; const V3 p=root.position; const auto key=std::tuple{q(p.x),q(p.y),q(p.z)}; auto found=positionMap.find(key); std::uint32_t vertex=0; if(found==positionMap.end()) { vertex=static_cast(mesh.vertices.size()); mesh.vertices.push_back({p,root.gradient});positionMap.emplace(key,vertex); } else vertex=found->second; edges.vertexIds[axis][index]=static_cast(vertex); } // Coincident zero-at-node roots use the deterministic first edge normal. The shared // vertex position, matching the owner's 0.01 cm-style MC welding contract. } const auto getDCVertex=[&](int cellId,std::uint8_t component,const Root& root,int axis)->std::uint32_t { const std::uint64_t feature=SelectFeature(root,axis); const std::uint64_t patch=static_cast(cellId)*EdgeCount+component; const std::uint64_t key=(patch<<32)^(feature&0xffffffffull); const auto existing=dcVertexMap.find(key); if(existing!=dcVertexMap.end())return existing->second; const V3 normal=feature?NormalForFeature(root,feature):root.gradient; const std::uint32_t index=static_cast(mesh.vertices.size()); mesh.vertices.push_back({cells[cellId].dual[component],Normalize(normal)}); dcVertexMap.emplace(key,index); return index; }; for(int z=0;z localVertices{};localVertices.fill(-1); for(int edge=0;edge=0;k+=3) { const int e0=MCTriTable[cell.cubeCase][k],e1=MCTriTable[cell.cubeCase][k+1],e2=MCTriTable[cell.cubeCase][k+2]; if(localVertices[e0]<0||localVertices[e1]<0||localVertices[e2]<0)continue; const std::uint8_t component=cell.edgeComponent[e0]; if(component==0xff||cell.componentUseDC[component])continue; std::array labels{}; const std::array triEdges{{e0,e1,e2}}; for(int edgeIndex=0;edgeIndex<3;++edgeIndex) { const auto le=LocalEdge(x,y,z,triEdges[edgeIndex]); const Root& root=edges.Get(le[0],le[1],le[2],le[3],grid); labels[edgeIndex]=root.feature; } std::uint64_t label=0;int bestVotes=0; for(std::uint64_t candidate:labels)if(candidate) { const int votes=static_cast(std::count(labels.begin(),labels.end(),candidate)); if(votes>bestVotes||(votes==bestVotes&&(!label||candidate(localVertices[e0]), static_cast(localVertices[e1]),static_cast(localVertices[e2]), label,field.Eval(center).gradient); } } for(int axis=0;axis<3;++axis) { for(std::size_t index=0;index(index%grid.nx);y=static_cast((index/grid.nx)%(grid.ny+1));z=static_cast(index/(grid.nx*(grid.ny+1)));} else if(axis==1){x=static_cast(index%(grid.nx+1));y=static_cast((index/(grid.nx+1))%grid.ny);z=static_cast(index/((grid.nx+1)*grid.ny));} else{x=static_cast(index%(grid.nx+1));y=static_cast((index/(grid.nx+1))%(grid.ny+1));z=static_cast(index/((grid.nx+1)*(grid.ny+1)));} std::array,4> around{}; if(axis==0)around={{{x,y-1,z-1},{x,y,z-1},{x,y,z},{x,y-1,z}}}; else if(axis==1)around={{{x-1,y,z-1},{x-1,y,z},{x,y,z},{x,y,z-1}}}; else around={{{x-1,y-1,z},{x,y-1,z},{x,y,z},{x-1,y,z}}}; std::array incident{};bool valid=true;bool anyDC=false; std::array incidentComponents{}; for(int i=0;i<4;++i) { const auto& c=around[i]; if(c[0]<0||c[1]<0||c[2]<0||c[0]>=grid.nx||c[1]>=grid.ny||c[2]>=grid.nz) {valid=false;break;} incident[i]=static_cast(grid.CellIndex(c[0],c[1],c[2])); const int localEdge=FindLocalEdge(c[0],c[1],c[2],axis,x,y,z); if(localEdge<0){valid=false;break;} incidentComponents[i]=cells[incident[i]].edgeComponent[localEdge]; if(incidentComponents[i]==0xff){valid=false;break;} anyDC|=cells[incident[i]].componentUseDC[incidentComponents[i]]; } // Own each dual quad by the lexicographically lowest incident cell. A one-cell // sampled halo supplies the neighbour's QEF vertex, so independently built tiles // emit the same complete border quad exactly once. const int ownerX=std::min({around[0][0],around[1][0],around[2][0],around[3][0]}); const int ownerY=std::min({around[0][1],around[1][1],around[2][1],around[3][1]}); const int ownerZ=std::min({around[0][2],around[1][2],around[2][2],around[3][2]}); if(enforceQuadOwnership&&!grid.IsCoreCell(ownerX,ownerY,ownerZ))continue; if(!valid||!anyDC||!cells[incident[0]].componentUseDC[incidentComponents[0]])continue; std::array v{}; for(int i=0;i<4;++i) { if(!cells[incident[i]].componentUseDC[incidentComponents[i]]){valid=false;break;} v[i]=getDCVertex(incident[i],incidentComponents[i],root,axis); } if(!valid){++mesh.skippedDualEdges;continue;} if(Dot(Cross(mesh.vertices[v[1]].position-mesh.vertices[v[0]].position, mesh.vertices[v[2]].position-mesh.vertices[v[0]].position),root.gradient)<0.0) std::swap(v[1],v[3]); const std::uint64_t label=SelectFeature(root,axis); const std::uint32_t edgeTie=static_cast(x+y+z+axis)&1u; if(edgeTie==0) { AddTriangle(mesh,v[0],v[1],v[2],label,root.gradient); AddTriangle(mesh,v[0],v[2],v[3],label,root.gradient); } else { AddTriangle(mesh,v[0],v[1],v[3],label,root.gradient); AddTriangle(mesh,v[1],v[2],v[3],label,root.gradient); } } } mesh.hash=HashMesh(mesh); return mesh; } struct ErrorMetrics{double mean=0,maximum=0;std::uint64_t samples=0;}; ErrorMetrics SurfaceError(const Mesh& mesh,const Field& field) { ErrorMetrics out;double total=0; const auto sample=[&](V3 p) { const double error=std::abs(field.Eval(p).density); total+=error;out.maximum=std::max(out.maximum,error);++out.samples; }; for(const Triangle& t:mesh.triangles) { const V3 a=mesh.vertices[t.index[0]].position,b=mesh.vertices[t.index[1]].position,c=mesh.vertices[t.index[2]].position; sample(a);sample(b);sample(c);sample((a+b)*0.5);sample((b+c)*0.5);sample((c+a)*0.5);sample((a+b+c)/3.0); } if(out.samples)out.mean=total/out.samples; return out; } struct GeomKey { std::int64_t x=0,y=0,z=0; friend bool operator<(const GeomKey& a,const GeomKey& b) {return std::tie(a.x,a.y,a.z)(std::llround(p.x/PositionQuantum)), static_cast(std::llround(p.y/PositionQuantum)), static_cast(std::llround(p.z/PositionQuantum))};} struct EdgeInfo{int count=0;std::vector> faces;}; std::map,EdgeInfo> GeomEdges(const Mesh& mesh) { std::map,EdgeInfo> result; for(std::size_t ti=0;ti,3> edges{{{t.index[0],t.index[1]},{t.index[1],t.index[2]},{t.index[2],t.index[0]}}}; for(const auto& e:edges) { GeomKey ka=Key(mesh.vertices[e.first].position),kb=Key(mesh.vertices[e.second].position); if(kb(ti),normal); } } return result; } struct BoundaryLoop { std::vector keys; std::vector points; V3 center{}; double perimeter=0.0; }; struct BoundaryLoopSet { std::vector loops; std::uint64_t boundaryEdges=0,badVertices=0; }; std::pair OrderedEdge(GeomKey a,GeomKey b) {if(b; BoundaryLoopSet ExtractBoundaryLoops(const Mesh& mesh) { BoundaryLoopSet result; const auto edges=GeomEdges(mesh); std::map> adjacency; for(const auto& [key,info]:edges)if(info.count==1) { ++result.boundaryEdges; adjacency[key.first].push_back(key.second); adjacency[key.second].push_back(key.first); } for(const auto& [key,neighbors]:adjacency)if(neighbors.size()!=2)++result.badVertices; std::set> visited; for(const auto& [start,startNeighbors]:adjacency) { for(const GeomKey first:startNeighbors) { if(visited.contains(OrderedEdge(start,first)))continue; BoundaryLoop loop; GeomKey previous=start,current=first; visited.insert(OrderedEdge(previous,current)); loop.keys.push_back(start); bool closed=false; for(std::size_t guard=0;guard<=result.boundaryEdges;++guard) { loop.keys.push_back(current); const auto found=adjacency.find(current); if(found==adjacency.end()||found->second.size()!=2)break; const GeomKey next=found->second[0]==previous?found->second[1]:found->second[0]; if(next==start){closed=true;break;} const auto edge=OrderedEdge(current,next); if(visited.contains(edge))break; visited.insert(edge);previous=current;current=next; } if(!closed||loop.keys.size()<3)continue; BoundaryLoop clean; clean.keys=std::move(loop.keys); for(const GeomKey& key:clean.keys) { const V3 p{key.x*PositionQuantum,key.y*PositionQuantum,key.z*PositionQuantum}; clean.points.push_back(p);clean.center+=p; } clean.center=clean.center/static_cast(clean.points.size()); for(std::size_t i=0;i0?(start+walked)%count:(start+count-(walked%count))%count; const std::size_t next=direction>0?(current+1)%count:(current+count-1)%count; const V3 a=loop.points[current],b=loop.points[next]; const double edgeLength=Length(b-a); if(remaining<=edgeLength||walked+1==count) return edgeLength>0.0?a+(b-a)*(remaining/edgeLength):a; remaining-=edgeLength; } return loop.points[start]; } int BestLoopDirection(const BoundaryLoop& a,const BoundaryLoop& b,std::size_t start) { double scores[2]{}; constexpr int samples=24; for(int dirIndex=0;dirIndex<2;++dirIndex) { const int direction=dirIndex==0?1:-1; for(int i=0;i(i)/samples; scores[dirIndex]+=Dot(PointAlongLoop(a,0,1,f)-PointAlongLoop(b,start,direction,f), PointAlongLoop(a,0,1,f)-PointAlongLoop(b,start,direction,f)); } } return scores[0]<=scores[1]?1:-1; } TransitionAudit StitchBoundaryLoops(Mesh& mesh,const BoundaryLoopSet& fine, const BoundaryLoopSet& coarse,const Field& field) { TransitionAudit result; const std::size_t patchStart=mesh.triangles.size(); result.unmatchedLoops=fine.badVertices+coarse.badVertices; std::map vertexByKey; for(std::uint32_t i=0;i edges; for(const BoundaryLoop& loop:loops.loops) for(std::size_t i=0;i fineEdges=loopEdges(fine),coarseEdges=loopEdges(coarse); std::set sharedEdges; std::set_intersection(fineEdges.begin(),fineEdges.end(),coarseEdges.begin(),coarseEdges.end(), std::inserter(sharedEdges,sharedEdges.end())); constexpr double collarOffset=0.001; if(!sharedEdges.empty()&&fine.loops.size()==coarse.loops.size()) { struct Chain{std::vector keys;}; const auto buildChains=[&](const BoundaryLoopSet& source) { std::vector chains; for(const BoundaryLoop& loop:source.loops) { const std::size_t n=loop.keys.size(); std::vector isShared(n,false); for(std::size_t i=0;i1)chains.push_back(std::move(chain)); } } return chains; }; const std::vector fineChains=buildChains(fine),coarseChains=buildChains(coarse); std::vector coarseUsed(coarseChains.size(),false); const std::size_t transitionStart=mesh.triangles.size(); const auto offsetChain=[&](const Chain& chain,double offset) { std::vector original,shifted; for(const GeomKey& key:chain.keys) { const auto found=vertexByKey.find(key); if(found==vertexByKey.end())return std::pair{original,shifted}; original.push_back(found->second); Vertex vertex=mesh.vertices[found->second];vertex.position.x+=offset; const GeomKey shiftedKey=Key(vertex.position); const auto existing=vertexByKey.find(shiftedKey); if(existing!=vertexByKey.end())shifted.push_back(existing->second); else { const auto index=static_cast(mesh.vertices.size()); mesh.vertices.push_back(vertex);vertexByKey.emplace(shiftedKey,index);shifted.push_back(index); } } for(std::size_t i=0;i+1::infinity(); for(std::size_t j=0;j capRing{originalFine,fa,ca,originalCoarse}; capRing.erase(std::unique(capRing.begin(),capRing.end(),[&](std::uint32_t a,std::uint32_t b) {return Key(mesh.vertices[a].position)==Key(mesh.vertices[b].position);}),capRing.end()); if(capRing.size()>2&&Key(mesh.vertices[capRing.front()].position)==Key(mesh.vertices[capRing.back()].position))capRing.pop_back(); V3 capCenter{};for(const std::uint32_t index:capRing)capCenter+=mesh.vertices[index].position; capCenter=capCenter/static_cast(capRing.size())+V3{0.0,0.001,0.001}; Vertex capVertex;capVertex.position=capCenter; capVertex.normal=Normalize(mesh.vertices[fa].normal+mesh.vertices[ca].normal); const GeomKey capKey=Key(capVertex.position); auto capFound=vertexByKey.find(capKey); std::uint32_t cap=0; if(capFound!=vertexByKey.end())cap=capFound->second; else { cap=static_cast(mesh.vertices.size()); mesh.vertices.push_back(capVertex);vertexByKey.emplace(capKey,cap); } for(std::size_t i=0;i cumulativeA(na,0.0),cumulativeB(nb,0.0); for(std::size_t i=1;i::infinity(); const double nextB=j+1::infinity(); if(nextA<=nextB) { const V3 center=(mesh.vertices[fineOffset[i]].position+mesh.vertices[fineOffset[i+1]].position+ mesh.vertices[coarseOffset[j]].position)/3.0; const FieldSample sample=field.Eval(center); AddTriangle(mesh,fineOffset[i],fineOffset[i+1],coarseOffset[j],sample.primaryFeature(),sample.gradient);++i; } else { const V3 center=(mesh.vertices[fineOffset[i]].position+mesh.vertices[coarseOffset[j+1]].position+ mesh.vertices[coarseOffset[j]].position)/3.0; const FieldSample sample=field.Eval(center); AddTriangle(mesh,fineOffset[i],coarseOffset[j+1],coarseOffset[j],sample.primaryFeature(),sample.gradient);++j; } } } for(bool used:coarseUsed)if(!used)++result.unmatchedLoops; result.pairedLoops=matchedChains; result.transitionTriangles=mesh.triangles.size()-transitionStart; return result; } // Give both sides a one-millimetre transition skin before zipping. This avoids degenerate // triangles when otherwise independent contours share collinear runs. const auto offsetLoops=[&](const BoundaryLoopSet& source,double offset) { std::vector output;output.reserve(source.loops.size()); for(const BoundaryLoop& input:source.loops) { BoundaryLoop loop;bool valid=true; for(const GeomKey& key:input.keys) { const auto found=vertexByKey.find(key); if(found==vertexByKey.end()){valid=false;break;} Vertex vertex=mesh.vertices[found->second];vertex.position.x+=offset; const std::uint32_t index=static_cast(mesh.vertices.size()); mesh.vertices.push_back(vertex);vertexByKey.try_emplace(Key(vertex.position),index); loop.keys.push_back(Key(vertex.position));loop.points.push_back(vertex.position);loop.center+=vertex.position; } if(!valid||loop.points.size()<3){++result.unmatchedLoops;continue;} loop.center=loop.center/static_cast(loop.points.size()); for(std::size_t i=0;isecond,b=originalB->second,sa=shiftedA->second,sb=shiftedB->second; const V3 center=(mesh.vertices[a].position+mesh.vertices[b].position+ mesh.vertices[sb].position+mesh.vertices[sa].position)/4.0; const FieldSample sample=field.Eval(center); AddTriangle(mesh,a,b,sb,sample.primaryFeature(),sample.gradient); AddTriangle(mesh,a,sb,sa,sample.primaryFeature(),sample.gradient); } output.push_back(std::move(loop)); } return output; }; const std::vector fineOffset=offsetLoops(fine,-collarOffset); const std::vector coarseOffset=offsetLoops(coarse,collarOffset); const auto capLoop=[&](const BoundaryLoop& loop) { if(loop.keys.size()<3)return false; V3 center{};for(const V3 point:loop.points)center+=point; center=center/static_cast(loop.points.size())+V3{0.001,0.001,0.001}; Vertex centerVertex;centerVertex.position=center;centerVertex.normal=Normalize(field.Eval(center).gradient); const GeomKey centerKey=Key(center); auto centerIt=vertexByKey.find(centerKey);std::uint32_t centerIndex=0; if(centerIt!=vertexByKey.end())centerIndex=centerIt->second; else { centerIndex=static_cast(mesh.vertices.size()); mesh.vertices.push_back(centerVertex);vertexByKey.emplace(centerKey,centerIndex); } const std::size_t before=mesh.triangles.size(); for(std::size_t i=0;isecond].position+ mesh.vertices[b->second].position)/3.0; const FieldSample sample=field.Eval(at); AddTriangle(mesh,centerIndex,a->second,b->second,sample.primaryFeature(),sample.gradient); } if(mesh.triangles.size()==before)return false; ++result.cappedLoops;return true; }; std::vector used(coarseOffset.size(),false); double totalResidual=0.0;std::uint64_t residualSamples=0; for(const BoundaryLoop& a:fineOffset) { std::size_t match=coarseOffset.size();double best=std::numeric_limits::infinity(); for(std::size_t j=0;j::infinity(); for(std::size_t j=0;j aIndices,bIndices; for(const GeomKey& key:a.keys) { const auto it=vertexByKey.find(key); if(it==vertexByKey.end()){++result.unmatchedLoops;break;} aIndices.push_back(it->second); } for(std::size_t j=0;j0?(bStart+j)%b.keys.size():(bStart+b.keys.size()-(j%b.keys.size()))%b.keys.size(); const auto it=vertexByKey.find(b.keys[bi]); if(it==vertexByKey.end()){++result.unmatchedLoops;break;} bIndices.push_back(it->second); } if(aIndices.size()!=a.keys.size()||bIndices.size()!=b.keys.size())continue; const std::size_t na=aIndices.size(),nb=bIndices.size(); std::vector cumulativeA(na+1,0.0),cumulativeB(nb+1,0.0); for(std::size_t i=0;i::infinity(); const double nextB=j::infinity(); if(nextA<=nextB) { const auto a0=aIndices[i%na],a1=aIndices[(i+1)%na],b0=bIndices[j%nb]; const V3 center=(mesh.vertices[a0].position+mesh.vertices[a1].position+mesh.vertices[b0].position)/3.0; const FieldSample sample=field.Eval(center); AddTriangle(mesh,a0,a1,b0,sample.primaryFeature(),sample.gradient);++i; } else { const auto a0=aIndices[i%na],b1=bIndices[(j+1)%nb],b0=bIndices[j%nb]; const V3 center=(mesh.vertices[a0].position+mesh.vertices[b1].position+mesh.vertices[b0].position)/3.0; const FieldSample sample=field.Eval(center); AddTriangle(mesh,a0,b1,b0,sample.primaryFeature(),sample.gradient);++j; } } const std::size_t sampleCount=std::max(na,nb); for(std::size_t s=0;s(s)/sampleCount; const V3 pa=PointAlongLoop(a,0,1,f),pb=PointAlongLoop(b,bStart,bDirection,f); const double d=Length(pa-pb);totalResidual+=d;result.contourMaximum=std::max(result.contourMaximum,d);++residualSamples; } } for(std::size_t j=0;jhalfWidth||std::abs(bx-plane)>halfWidth)continue; if(info.count==1)++out.boundaryEdges; else if(info.count!=2)++out.nonManifoldEdges; } return out; } struct SeamCapAudit { std::uint64_t cappedLoops=0,unmatchedLoops=0,triangles=0; }; SeamCapAudit CapClosedBoundaryLoopsInXBand(Mesh& mesh,const Field& field,double plane,double halfWidth) { SeamCapAudit result; std::map> adjacency; std::set boundaryEdges; std::map vertexByKey; for(std::uint32_t i=0;i visited; for(const EdgeKey& firstEdge:boundaryEdges) { if(visited.contains(firstEdge))continue; std::vector loop{firstEdge.first}; GeomKey previous=firstEdge.first,current=firstEdge.second; visited.insert(firstEdge); bool closed=false,valid=true; for(std::size_t guard=0;guard<=boundaryEdges.size();++guard) { if(current==loop.front()){closed=true;break;} loop.push_back(current); const auto found=adjacency.find(current); if(found==adjacency.end()||found->second.size()!=2){valid=false;break;} const GeomKey next=found->second[0]==previous?found->second[1]:found->second[0]; const EdgeKey edge=OrderedEdge(current,next); if(visited.contains(edge)){valid=false;break;} visited.insert(edge);previous=current;current=next; } if(!closed||!valid||loop.size()<3) {++result.unmatchedLoops;continue;} V3 center{};bool haveVertices=true; for(const GeomKey& key:loop) { const auto found=vertexByKey.find(key); if(found==vertexByKey.end()){haveVertices=false;break;} center+=mesh.vertices[found->second].position; } if(!haveVertices){++result.unmatchedLoops;continue;} center=center/static_cast(loop.size()); center+=Normalize(field.Eval(center).gradient)*0.0005; Vertex centerVertex;centerVertex.position=center;centerVertex.normal=Normalize(field.Eval(center).gradient); const GeomKey centerKey=Key(center); const std::size_t verticesBefore=mesh.vertices.size(); auto centerIt=vertexByKey.find(centerKey);std::uint32_t centerIndex=0; if(centerIt!=vertexByKey.end())centerIndex=centerIt->second; else { centerIndex=static_cast(mesh.vertices.size()); mesh.vertices.push_back(centerVertex);vertexByKey.emplace(centerKey,centerIndex); } const std::size_t before=mesh.triangles.size(); for(std::size_t i=0;isecond].position+mesh.vertices[b->second].position)/3.0; const FieldSample sample=field.Eval(samplePoint); AddTriangle(mesh,centerIndex,a->second,b->second,sample.primaryFeature(),sample.gradient); } if(mesh.triangles.size()==before) { if(mesh.vertices.size()>verticesBefore) {mesh.vertices.resize(verticesBefore);vertexByKey.erase(centerKey);} ++result.unmatchedLoops;continue; } Mesh capOnly;capOnly.vertices=mesh.vertices; capOnly.triangles.assign(mesh.triangles.begin()+static_cast(before),mesh.triangles.end()); if(SurfaceError(capOnly,field).maximum>LodContourErrorBudgetMeters) { mesh.triangles.resize(before); if(mesh.vertices.size()>verticesBefore) {mesh.vertices.resize(verticesBefore);vertexByKey.erase(centerKey);} ++result.unmatchedLoops;continue; } ++result.cappedLoops;result.triangles+=mesh.triangles.size()-before; } return result; } std::uint64_t FnvBytes(const std::uint8_t* p,std::size_t n) { std::uint64_t h=14695981039346656037ull; for(std::size_t i=0;isideFilter)continue; if(side>0&¢er.x SeamSegments(const Mesh& mesh,double xPlane,int side) { std::vector segments; for(const Triangle& triangle:mesh.triangles) { std::array input{{mesh.vertices[triangle.index[0]].position, mesh.vertices[triangle.index[1]].position,mesh.vertices[triangle.index[2]].position}}; std::vector output; for(int i=0;i<3;++i) { const V3 a=input[i],b=input[(i+1)%3]; const bool ina=side<0?a.x<=xPlane+PositionQuantum:a.x>=xPlane-PositionQuantum; const bool inb=side<0?b.x<=xPlane+PositionQuantum:b.x>=xPlane-PositionQuantum; if(ina)output.push_back(a); if(ina!=inb) { const double t=(xPlane-a.x)/(b.x-a.x); output.push_back(Quantize(a+(b-a)*t)); } } std::vector onPlane; for(V3 p:output)if(std::abs(p.x-xPlane)<=2.0*PositionQuantum) { bool found=false;for(V3 q:onPlane)if(Length(p-q)PositionQuantum) segments.push_back({onPlane[0],onPlane[1]}); } return segments; } double PointSegmentDistance(V3 p,const Segment& s) { const V3 d=s.b-s.a;const double den=Dot(d,d); const double t=den>0?std::clamp(Dot(p-s.a,d)/den,0.0,1.0):0.0; return Length(p-(s.a+d*t)); } struct SeamMetric{double mean=0,maximum=0;std::uint64_t probes=0,unmatched=0;}; SeamMetric CompareSegments(const std::vector& a,const std::vector& b) { SeamMetric out;double total=0; const auto probe=[&](const std::vector& source,const std::vector& target) { for(const Segment& s:source)for(double t:{0.0,0.25,0.5,0.75,1.0}) { const V3 p=s.a+(s.b-s.a)*t;double nearest=std::numeric_limits::infinity(); for(const Segment& candidate:target)nearest=std::min(nearest,PointSegmentDistance(p,candidate)); if(!std::isfinite(nearest)){++out.unmatched;++out.probes;continue;} total+=nearest;out.maximum=std::max(out.maximum,nearest);++out.probes; } }; probe(a,b);probe(b,a); if(out.unmatched){out.mean=-1.0;out.maximum=-1.0;} else if(out.probes)out.mean=total/out.probes; return out; } bool HasIsoContourOnX(const Field& field,double xPlane,double low,double high,double step) { const int cells=static_cast(std::llround((high-low)/step)); for(int y=0;y(result.vertices.size()); result.vertices.insert(result.vertices.end(),b.vertices.begin(),b.vertices.end()); for(Triangle triangle:b.triangles) { for(std::uint32_t& index:triangle.index)index+=offset; result.triangles.push_back(triangle); } result.hash=HashMesh(result);return result; } Mesh ClipMeshAtX(const Mesh& source,const Field& field,bool keepLeft) { Mesh result; std::map vertexByKey; const auto addVertex=[&](Vertex vertex) { if(std::abs(vertex.position.x)second; const auto index=static_cast(result.vertices.size()); result.vertices.push_back(vertex);vertexByKey.emplace(key,index);return index; }; for(const Triangle& triangle:source.triangles) { std::vector polygon;polygon.reserve(4); for(int edge=0;edge<3;++edge) { const Vertex a=source.vertices[triangle.index[edge]]; const Vertex b=source.vertices[triangle.index[(edge+1)%3]]; const bool insideA=keepLeft?a.position.x<=0.0:a.position.x>=0.0; const bool insideB=keepLeft?b.position.x<=0.0:b.position.x>=0.0; if(insideA)polygon.push_back(a); if(insideA!=insideB) { const double t=std::clamp(-a.position.x/(b.position.x-a.position.x),0.0,1.0); Vertex cut; cut.position=a.position+(b.position-a.position)*t;cut.position.x=0.0; cut.normal=Normalize(a.normal+(b.normal-a.normal)*t); polygon.push_back(cut); } } std::vector indices;indices.reserve(polygon.size()); for(const Vertex& vertex:polygon) { const auto index=addVertex(vertex); if(indices.empty()||indices.back()!=index)indices.push_back(index); } if(indices.size()>2&&indices.front()==indices.back())indices.pop_back(); if(indices.size()<3)continue; for(std::size_t i=1;i+1 BoundarySegmentsAtX(const Mesh& mesh,double xPlane) { std::vector segments; for(const auto& [key,info]:GeomEdges(mesh)) { if(info.count!=1)continue; const V3 a{key.first.x*PositionQuantum,key.first.y*PositionQuantum,key.first.z*PositionQuantum}; const V3 b{key.second.x*PositionQuantum,key.second.y*PositionQuantum,key.second.z*PositionQuantum}; if(std::abs(a.x-xPlane)<=PositionQuantum&&std::abs(b.x-xPlane)<=PositionQuantum) segments.push_back({a,b}); } return segments; } struct BoundaryRepairAudit { std::uint64_t triangles=0,unmatchedEdges=0; double maximumResidual=0.0; }; std::uint64_t ConformSeamBoundaryEdges(Mesh& a,Mesh& b,double xPlane,const Field& field) { std::map uniquePoints; for(const Mesh* mesh:{&a,&b})for(const Vertex& vertex:mesh->vertices) if(std::abs(vertex.position.x-xPlane)<=PositionQuantum) uniquePoints.try_emplace(Key(vertex.position),vertex.position); std::vector points;points.reserve(uniquePoints.size()); for(const auto& [key,p]:uniquePoints)points.push_back(p); const auto conform=[&](Mesh& source) { Mesh output;output.vertices=source.vertices; std::map vertexByKey; for(std::uint32_t i=0;isecond; const auto index=static_cast(output.vertices.size()); output.vertices.push_back(vertex);vertexByKey.emplace(key,index);return index; }; std::uint64_t splitTriangles=0; for(const Triangle& triangle:source.triangles) { std::vector polygon;polygon.reserve(8);bool split=false; for(int edge=0;edge<3;++edge) { const auto aIndex=triangle.index[edge],bIndex=triangle.index[(edge+1)%3]; const V3 pa=source.vertices[aIndex].position,pb=source.vertices[bIndex].position; polygon.push_back(addVertex(source.vertices[aIndex])); if(std::abs(pa.x-xPlane)>PositionQuantum||std::abs(pb.x-xPlane)>PositionQuantum)continue; const V3 direction=pb-pa;const double denominator=Dot(direction,direction); if(denominator<=PositionQuantum*PositionQuantum)continue; std::vector> interior; for(const V3 q:points) { if(q.ystd::max(pa.y,pb.y)+PositionQuantum|| q.zstd::max(pa.z,pb.z)+PositionQuantum)continue; const double t=Dot(q-pa,direction)/denominator; if(t<=1.0e-8||t>=1.0-1.0e-8)continue; const V3 projected=pa+direction*t; if(Length(projected-q)>PositionQuantum)continue; interior.emplace_back(t,q); } std::sort(interior.begin(),interior.end(),[](const auto& x,const auto& y){return x.first=0.0&&std::abs(t-previous)<1.0e-8)continue; Vertex vertex;vertex.position=p;vertex.normal=Normalize(source.vertices[aIndex].normal+ (source.vertices[bIndex].normal-source.vertices[aIndex].normal)*t); polygon.push_back(addVertex(vertex));previous=t;split=true; } } if(!split){output.triangles.push_back(triangle);continue;} ++splitTriangles; const V3 center=(source.vertices[triangle.index[0]].position+ source.vertices[triangle.index[1]].position+source.vertices[triangle.index[2]].position)/3.0; Vertex centerVertex;centerVertex.position=center; centerVertex.normal=Normalize(source.vertices[triangle.index[0]].normal+ source.vertices[triangle.index[1]].normal+source.vertices[triangle.index[2]].normal); const auto centerIndex=addVertex(centerVertex); for(std::size_t i=0;i seamPoints; for(const Mesh* mesh:{&a,&b})for(const Vertex& vertex:mesh->vertices) if(std::abs(vertex.position.x-xPlane)<=PositionQuantum) seamPoints.try_emplace(Key(vertex.position),vertex.position); for(const Mesh* mesh:{&a,&b}) for(const auto& [edge,info]:GeomEdges(*mesh)) { if(info.count!=1||std::abs(edge.first.x*PositionQuantum-xPlane)>PositionQuantum|| std::abs(edge.second.x*PositionQuantum-xPlane)>PositionQuantum)continue; const V3 p{edge.first.x*PositionQuantum,edge.first.y*PositionQuantum,edge.first.z*PositionQuantum}; const V3 q{edge.second.x*PositionQuantum,edge.second.y*PositionQuantum,edge.second.z*PositionQuantum}; const V3 d=q-p;const double denominator=Dot(d,d); if(denominator<=PositionQuantum*PositionQuantum)continue; for(const auto& [key,point]:seamPoints) { const double t=Dot(point-p,d)/denominator; if(t<=1.0e-8||t>=1.0-1.0e-8)continue; if(Length(p+d*t-point)<=PositionQuantum)return true; } } return false; } BoundaryRepairAudit CloseOwnedSeamLoops(Mesh& owner,const Mesh& neighbor,double xPlane,const Field& field) { BoundaryRepairAudit result; const Mesh merged=MergeMeshes(owner,neighbor); const auto edges=GeomEdges(merged); std::map> adjacency; for(const auto& [edge,info]:edges)if(info.count==1&& std::abs(edge.first.x*PositionQuantum-xPlane)<=PositionQuantum&& std::abs(edge.second.x*PositionQuantum-xPlane)<=PositionQuantum) { adjacency[edge.first].push_back(edge.second);adjacency[edge.second].push_back(edge.first); } for(const auto& [key,neighbors]:adjacency)if(neighbors.size()!=2)++result.unmatchedEdges; std::set visited; std::map ownerVertices; for(std::uint32_t i=0;i loop{start};GeomKey previous=start,current=first; visited.insert(OrderedEdge(previous,current)); bool closed=false; for(std::size_t guard=0;guard<=adjacency.size();++guard) { loop.push_back(current); const auto found=adjacency.find(current); if(found==adjacency.end()||found->second.size()!=2)break; const GeomKey next=found->second[0]==previous?found->second[1]:found->second[0]; if(next==start){closed=true;break;} const EdgeKey edge=OrderedEdge(current,next); if(visited.contains(edge))break; visited.insert(edge);previous=current;current=next; } if(!closed||loop.size()<3){++result.unmatchedEdges;continue;} std::vector indices;indices.reserve(loop.size()); for(const GeomKey& key:loop) { const auto existing=ownerVertices.find(key); if(existing!=ownerVertices.end()){indices.push_back(existing->second);continue;} const V3 p{key.x*PositionQuantum,key.y*PositionQuantum,key.z*PositionQuantum}; const FieldSample sample=field.Eval(p); const auto index=static_cast(owner.vertices.size()); owner.vertices.push_back({p,Normalize(sample.gradient)});ownerVertices.emplace(key,index);indices.push_back(index); } for(std::size_t i=1;i+1(triangleStart),owner.triangles.end()); result.maximumResidual=SurfaceError(added,field).maximum; return result; } BoundaryRepairAudit ConformOpenTJunctions(Mesh& mesh,const Field& field) { BoundaryRepairAudit result; for(int pass=0;pass<2;++pass) { const auto edgeMap=GeomEdges(mesh); std::set boundaryEdges; for(const auto& [edge,info]:edgeMap)if(info.count==1)boundaryEdges.insert(edge); if(boundaryEdges.empty())break; std::map pointMap; for(const Vertex& vertex:mesh.vertices)pointMap.try_emplace(Key(vertex.position),vertex.position); std::vector points;points.reserve(pointMap.size()); for(const auto& [key,p]:pointMap)points.push_back(p); Mesh output;output.vertices=mesh.vertices; std::map vertexByKey; for(std::uint32_t i=0;isecond; const auto index=static_cast(output.vertices.size()); output.vertices.push_back(vertex);vertexByKey.emplace(key,index);return index; }; std::uint64_t splitTriangles=0; for(const Triangle& triangle:mesh.triangles) { std::vector polygon;polygon.reserve(8);bool split=false; for(int edge=0;edge<3;++edge) { const auto aIndex=triangle.index[edge],bIndex=triangle.index[(edge+1)%3]; const V3 pa=mesh.vertices[aIndex].position,pb=mesh.vertices[bIndex].position; polygon.push_back(addVertex(mesh.vertices[aIndex])); if(!boundaryEdges.contains(OrderedEdge(Key(pa),Key(pb))))continue; const V3 direction=pb-pa;const double denominator=Dot(direction,direction); if(denominator<=PositionQuantum*PositionQuantum)continue; std::vector> interior; for(const V3 q:points) { if(q.xstd::max(pa.x,pb.x)+PositionQuantum|| q.ystd::max(pa.y,pb.y)+PositionQuantum|| q.zstd::max(pa.z,pb.z)+PositionQuantum)continue; const double t=Dot(q-pa,direction)/denominator; if(t<=1.0e-8||t>=1.0-1.0e-8||Length(pa+direction*t-q)>PositionQuantum)continue; interior.emplace_back(t,q); } std::sort(interior.begin(),interior.end(),[](const auto& x,const auto& y){return x.first=0.0&&std::abs(t-previous)<1.0e-8)continue; Vertex vertex;vertex.position=p;vertex.normal=Normalize(mesh.vertices[aIndex].normal+ (mesh.vertices[bIndex].normal-mesh.vertices[aIndex].normal)*t); polygon.push_back(addVertex(vertex));previous=t;split=true; } } if(!split){output.triangles.push_back(triangle);continue;} ++splitTriangles; Vertex center;center.position=(mesh.vertices[triangle.index[0]].position+ mesh.vertices[triangle.index[1]].position+mesh.vertices[triangle.index[2]].position)/3.0; center.normal=Normalize(mesh.vertices[triangle.index[0]].normal+mesh.vertices[triangle.index[1]].normal+ mesh.vertices[triangle.index[2]].normal); const auto centerIndex=addVertex(center); for(std::size_t i=0;i pieces{{Piece::Corner,Piece::Doorway,Piece::Step,Piece::Ledge,Piece::Organic}}; const std::array methods{{Method::MC,Method::Hybrid,Method::DC}}; const std::array phase{{0.0,0.05,0.10,0.15,0.20}}; constexpr int n=32; std::uint64_t completed=0; for(Piece piece:pieces) { for(int px=0;px(topology.splitPairs)/topology.creaseEdgePairs:0.0; csv< pieces{{Piece::Corner,Piece::Doorway,Piece::Step,Piece::Ledge,Piece::Organic}}; const std::array methods{{Method::MC,Method::Hybrid,Method::DC}}; const std::array workerCounts{{1,8,16}}; const V3 phase{0.10,0.10,0.10};constexpr int n=32; for(Piece piece:pieces) { const Field field(piece,phase); Grid grid=SampleGrid(field,n,n,n,1,{-4.0,-4.0,-4.0},8); for(Method method:methods) { std::uint64_t reference=0; for(int workers:workerCounts) { std::vector times;Mesh last; // W16 is a determinism stress count under an eight-CPU affinity mask. Use one // sample there: repeated creation of 16 OS threads adds noise and had caused // an access violation on this host without changing the worker-count gate. const int repeats=workers==16?1:3; for(int repeat=0;repeat(finish-start).count()); } std::sort(times.begin(),times.end()); if(workers==1)reference=last.hash; const bool same=last.hash==reference; det< pieces{{Piece::Corner,Piece::Doorway,Piece::Step,Piece::Ledge,Piece::Organic}}; const std::array methods{{Method::MC,Method::Hybrid,Method::DC}}; const std::array phaseValues{{0.0,0.05,0.10,0.15,0.20}}; std::uint64_t completed=0; for(Piece piece:pieces) { for(double px:phaseValues)for(double py:phaseValues)for(double pz:phaseValues) { const V3 phase{px,py,pz}; const Field field(piece,phase); const bool seamExpected=HasIsoContourOnX(field,0.0,-4.0,4.0,VoxelMeters); const Grid tileLeft=SampleGrid(field,32,32,32,1,{-8.0,-4.0,-4.0},8,2); const Grid tileRight=SampleGrid(field,32,32,32,1,{0.0,-4.0,-4.0},8,2); const Grid lodFineLeft=SampleGrid(field,16,32,32,1,{-4.0,-4.0,-4.0},8,2); const Grid lodCoarseRight=SampleGrid(field,8,16,16,2,{0.0,-4.0,-4.0},8,2); for(Method method:methods) { Mesh leftMesh=BuildMesh(tileLeft,field,method,8); Mesh rightMesh=BuildMesh(tileRight,field,method,8); // Build both independently sampled halo meshes without border-quad ownership, // then clip each complete surface at x=0. This gives the transition an exact // geometric cut line instead of trying to pair unrelated interior holes. const Mesh lodFineMesh=BuildMesh(lodFineLeft,field,method,8,false); const Mesh lodCoarseMesh=BuildMesh(lodCoarseRight,field,method,8,false); // Ownership suppresses the same border quad in one rendered tile. For the // independent-data witness, re-emit halo quads in each temporary mesh and // compare the two independently solved contours; these witness meshes are // excluded from the merged topology audit. Mesh leftWitness,rightWitness; const Mesh* leftContourMesh=&leftMesh; const Mesh* rightContourMesh=&rightMesh; if(method!=Method::MC) { leftWitness=BuildMesh(tileLeft,field,method,8,false); rightWitness=BuildMesh(tileRight,field,method,8,false); leftContourMesh=&leftWitness;rightContourMesh=&rightWitness; } const auto leftSegments=SeamSegments(*leftContourMesh,0.0,-1); const auto rightSegments=SeamSegments(*rightContourMesh,0.0,1); SeamMetric tile=CompareSegments(leftSegments,rightSegments); Mesh lodFineCut=ClipMeshAtX(lodFineMesh,field,true); Mesh lodCoarseCut=ClipMeshAtX(lodCoarseMesh,field,false); ConformOpenTJunctions(lodFineCut,field); ConformOpenTJunctions(lodCoarseCut,field); const auto lodFineSegments=BoundarySegmentsAtX(lodFineCut,0.0); const auto lodCoarseSegments=BoundarySegmentsAtX(lodCoarseCut,0.0); const SeamMetric rawLod=CompareSegments(lodFineSegments,lodCoarseSegments); SeamMetric lod=rawLod; if(seamExpected&&leftSegments.empty()&&rightSegments.empty()) {tile.unmatched=1;tile.mean=-1;tile.maximum=-1;} if(seamExpected&&lodFineSegments.empty()&&lodCoarseSegments.empty()) {lod.unmatched=1;lod.mean=-1;lod.maximum=-1;} Mesh tileMerged=MergeMeshes(leftMesh,rightMesh); BoundaryRepairAudit tileRepair=ConformOpenTJunctions(tileMerged,field); const TopologyMetrics tileTopology=AnalyzeTopology(tileMerged); tile.unmatched+=tileRepair.unmatchedEdges+tileTopology.boundaryEdges+tileTopology.nonManifoldEdges; Mesh lodMerged=MergeMeshes(lodFineCut,lodCoarseCut); const TopologyMetrics lodBefore=AnalyzeTopology(lodMerged); const BoundaryLoopSet fineLoops=ExtractBoundaryLoops(lodFineCut); const BoundaryLoopSet coarseLoops=ExtractBoundaryLoops(lodCoarseCut); const std::size_t firstTransitionTriangle=lodMerged.triangles.size(); const TransitionAudit transition=StitchBoundaryLoops(lodMerged,fineLoops,coarseLoops,field); const SeamCapAudit seamCaps=CapClosedBoundaryLoopsInXBand(lodMerged,field,0.0,0.003); const TopologyMetrics lodAfter=AnalyzeTopology(lodMerged); const TopologyMetrics lodSeamAfter=AnalyzeTopologyInXBand(lodMerged,0.0,0.003); Mesh transitionOnly;transitionOnly.vertices=lodMerged.vertices; transitionOnly.triangles.assign(lodMerged.triangles.begin()+static_cast(firstTransitionTriangle),lodMerged.triangles.end()); const ErrorMetrics transitionError=SurfaceError(transitionOnly,field); lod.unmatched=rawLod.unmatched+transition.unmatchedLoops+seamCaps.unmatchedLoops+ lodSeamAfter.boundaryEdges+lodSeamAfter.nonManifoldEdges; if(seamExpected&&rawLod.probes==0)++lod.unmatched; if(seamExpected&&transition.pairedLoops==0&&transition.cappedLoops==0&&seamCaps.cappedLoops==0)++lod.unmatched; if(rawLod.maximum>LodContourErrorBudgetMeters||transitionError.maximum>LodContourErrorBudgetMeters)++lod.unmatched; csv<0&&completed>=static_cast(rowLimit))return csv.good(); if(completed%75==0)std::cout<<"[SeamSweep] completed="<(transitionStart),merged.triangles.end()); const ErrorMetrics residual=SurfaceError(transitionOnly,field); const std::uint64_t unmatched=contour.unmatched+transition.unmatchedLoops+seamCaps.unmatchedLoops+ after.boundaryEdges+after.nonManifoldEdges+ ((expected&&transition.pairedLoops==0&&transition.cappedLoops==0&&seamCaps.cappedLoops==0)?1:0)+ ((contour.maximum>LodContourErrorBudgetMeters||residual.maximum>LodContourErrorBudgetMeters)?1:0); csv< xa{{x.position.x,x.position.y,x.position.z,x.normal.x,x.normal.y,x.normal.z}}; const std::array ya{{y.position.x,y.position.y,y.position.z,y.normal.x,y.normal.y,y.normal.z}}; for(std::size_t c=0;c(xa[c])!=std::bit_cast(ya[c]))return false; } for(std::size_t i=0;i(input)),std::istreambuf_iterator()); std::string error; auto manifest=VoxelForgeCore::WorldAssemblyManifest::FromJson(json,error); if(!manifest){std::cerr<<"[NativeSurface] manifest rejected: "<plan.strates.begin(),manifest->plan.strates.end(), [](const auto& item){return item.strateIndex==1;}); if(stratum==manifest->plan.strates.end())return false; const auto feature=std::find_if(stratum->features.begin(),stratum->features.end(), [](const auto& item){return item.type==4;}); if(feature==stratum->features.end())return false; VoxelForgeCore::Heightfield2D heightfield(manifest->recipe,*stratum); if(!heightfield.isValid())return false; constexpr double SubvoxelsPerMeter=1024.0; const double centerX=(feature->minimumSubvoxels[0]+feature->maximumSubvoxels[0])/(2.0*SubvoxelsPerMeter); const double centerY=(feature->minimumSubvoxels[1]+feature->maximumSubvoxels[1])/(2.0*SubvoxelsPerMeter); const double minFeatureZ=feature->minimumSubvoxels[2]/SubvoxelsPerMeter; const double maxFeatureZ=feature->maximumSubvoxels[2]/SubvoxelsPerMeter; const Field field(heightfield); double previousZ=minFeatureZ-8.0,previousDensity=field.Eval({centerX,centerY,previousZ}).density; double surfaceZ=(minFeatureZ+maxFeatureZ)*0.5;bool foundCrossing=false; for(double z=previousZ+0.25;z<=maxFeatureZ+8.0;z+=0.25) { const double density=field.Eval({centerX,centerY,z}).density; if((previousDensity<0.0)!=(density<0.0)) {surfaceZ=(previousZ+z)*0.5;foundCrossing=true;break;} previousZ=z;previousDensity=density; } if(!foundCrossing) {std::cerr<<"[NativeSurface] no iso crossing at feature center x="<contentHash<stableId<type<<','< pieces{{Piece::Corner,Piece::Doorway,Piece::Step,Piece::Ledge,Piece::Organic}}; const std::array methods{{Method::MC,Method::Hybrid,Method::DC}}; const V3 phase{0.10,0.10,0.10};constexpr int n=32; for(Piece piece:pieces) { const Field field(piece,phase);Grid grid=SampleGrid(field,n,n,n,1,{-4.0,-4.0,-4.0},8); for(Method method:methods) { const Mesh mesh=BuildMesh(grid,field,method,8); WriteObj(outDir/"meshes"/(std::string(PieceName(piece))+"_"+MethodName(method)+".obj"),mesh); } } return true; } } int main(int argc,char** argv) { try { std::filesystem::path outDir="Standalone/Research/WorldAssemblerM2/results"; int phaseCount=5; bool seamsOnly=false; bool nativeOnly=false; bool analysisOnly=false; int seamRowLimit=0; for(int i=1;i(std::chrono::steady_clock::now()-start).count(); std::cout<<"[SharpMesherSpike] complete seconds="<