wip(standalone): owner LOD0 defects -- skirt curtains clipped, exact edge roots; interrupted (OpenAI capacity)

Not verified as a whole. In the tree when Luna's session ended ("model at capacity"):
- fins: LOD seam skirts could hang into tunnel air; skirt triangles are now clipped against the solid
  half-space and carry a per-face mask so a host can hide skirts between same-LOD neighbours.
- choppy LOD0 stairs: marching cubes' linear edge interpolation misplaced vertices where the relief
  bends within one cell; edge crossings corrected with the exact field (82 patches, midpoint-edge
  errors 18,818 -> 3,288); normal halo now ignores the +-4.125 fallback samples.
- an explicit unclamped Core distance query for audits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-06 00:17:14 +02:00
1 parent 08ceed200d
commit b3af0e589e
7 files changed
+1304 -62

No files matched your search

@@ -56,6 +56,9 @@ namespace VoxelForgeCore
TileKey key; TileKey key;
std::vector<MeshVertex> vertices; std::vector<MeshVertex> vertices;
std::vector<std::uint32_t> indices; std::vector<std::uint32_t> indices;
// One entry per triangle. Zero is a surface triangle; skirt entries contain the
// boundary-face bit mask (bit order: -X,+X,-Y,+Y,-Z,+Z).
std::vector<std::uint8_t> skirtFaceMasks;
std::vector<float> debugDensitySamples; std::vector<float> debugDensitySamples;
MeshGroups groups; MeshGroups groups;
std::uint64_t densitySampleCount = 0; std::uint64_t densitySampleCount = 0;
@@ -148,6 +151,13 @@ namespace VoxelForgeCore
std::uint64_t walkFailures = 0; std::uint64_t walkFailures = 0;
}; };
struct DetailFieldSample
{
float displacement = 0.0f;
float caveInfluence = 0.0f;
float floorInfluence = 0.0f;
};
struct TileRegionCompileStats struct TileRegionCompileStats
{ {
std::size_t regionsBuilt = 0; std::size_t regionsBuilt = 0;
@@ -229,6 +239,9 @@ namespace VoxelForgeCore
ShapeBuildStats buildStats() const; ShapeBuildStats buildStats() const;
float densityAt(Vec3d voxelPosition) const; float densityAt(Vec3d voxelPosition) const;
// Analytic source distance without the occupancy-preserving near-zero clamp or packed proxy.
float unclampedDensityAt(Vec3d voxelPosition) const;
DetailFieldSample detailSampleAt(Vec3d voxelPosition) const;
float densityAtLattice(Vec3i voxelPosition) const; float densityAtLattice(Vec3i voxelPosition) const;
bool isSolidAt(Vec3d voxelPosition) const; bool isSolidAt(Vec3d voxelPosition) const;
bool isSolidAlongSegment(Vec3d start, Vec3d end, double sampleSpacingVoxels = 0.5) const; bool isSolidAlongSegment(Vec3d start, Vec3d end, double sampleSpacingVoxels = 0.5) const;
+91 -35
View File
@@ -313,7 +313,7 @@ public:
const size_t NZ=static_cast<size_t>(Size[2]); const size_t NZ=static_cast<size_t>(Size[2]);
WordsPerZ=(NZ+63u)/64u; WordsPerZ=(NZ+63u)/64u;
RockWords.assign(static_cast<size_t>(Size[0])*Size[1]*WordsPerZ,0); RockWords.assign(static_cast<size_t>(Size[0])*Size[1]*WordsPerZ,0);
SurfaceDistanceQ=std::make_unique_for_overwrite<int8[]>(static_cast<size_t>(NodeCount)); SurfaceDistanceQ=std::make_unique_for_overwrite<int16[]>(static_cast<size_t>(NodeCount));
BuildPackedRockIntervals(Stats.RockIntervalLines); BuildPackedRockIntervals(Stats.RockIntervalLines);
BuildCandidateBands(Stats.CandidateAxisLines,Stats.CandidateMergeLines, BuildCandidateBands(Stats.CandidateAxisLines,Stats.CandidateMergeLines,
Stats.CandidateMergeSeconds); Stats.CandidateMergeSeconds);
@@ -348,7 +348,7 @@ public:
Stats.DetailBandLines.WorkerSeconds*1000.0); Stats.DetailBandLines.WorkerSeconds*1000.0);
std::printf("[ShapeAllocation] words=%.2fMiB candidate_axis_words=%.2fMiB proxy=%.2fMiB primitives=%.2fMiB spatial_bins=%llu spatial_bin_refs=%llu spatial_bin_capacity=%llu inline_bins=%llu overflow_bins=%llu far_primitive_build=%.3fms far_bin_rebuild=%.3fms\n", std::printf("[ShapeAllocation] words=%.2fMiB candidate_axis_words=%.2fMiB proxy=%.2fMiB primitives=%.2fMiB spatial_bins=%llu spatial_bin_refs=%llu spatial_bin_capacity=%llu inline_bins=%llu overflow_bins=%llu far_primitive_build=%.3fms far_bin_rebuild=%.3fms\n",
static_cast<double>(WordBytes)/1048576.0,static_cast<double>(CandidateAxisBytes)/1048576.0, static_cast<double>(WordBytes)/1048576.0,static_cast<double>(CandidateAxisBytes)/1048576.0,
static_cast<double>(Stats.SignNodes*sizeof(int8))/1048576.0, static_cast<double>(Stats.SignNodes*sizeof(int16))/1048576.0,
static_cast<double>(Primitives.capacity()*sizeof(FPrimitive))/1048576.0, static_cast<double>(Primitives.capacity()*sizeof(FPrimitive))/1048576.0,
static_cast<unsigned long long>(SpatialBins.size()), static_cast<unsigned long long>(SpatialBins.size()),
static_cast<unsigned long long>(BinReferences), static_cast<unsigned long long>(BinReferences),
@@ -364,7 +364,7 @@ public:
static_cast<unsigned long long>(Stats.SignNodes), static_cast<unsigned long long>(Stats.SignNodes),
static_cast<double>(WordBytes)/1048576.0, static_cast<double>(WordBytes)/1048576.0,
static_cast<unsigned long long>(Stats.SurfaceProxyNodes), static_cast<unsigned long long>(Stats.SurfaceProxyNodes),
static_cast<double>(Stats.SignNodes*sizeof(int8))/1048576.0,WorkerCount); static_cast<double>(Stats.SignNodes*sizeof(int16))/1048576.0,WorkerCount);
std::printf("[ShapeStage] local-shapes=%.3f ms intervals+signs=%.3f ms surface-band=%.3f ms far-parents=%.3f ms total=%.3f ms candidates=%llu flips=%llu walk=%llu/%llu\n", std::printf("[ShapeStage] local-shapes=%.3f ms intervals+signs=%.3f ms surface-band=%.3f ms far-parents=%.3f ms total=%.3f ms candidates=%llu flips=%llu walk=%llu/%llu\n",
Stats.ShapeSeconds*1000.0,Stats.IntervalSeconds*1000.0,Stats.SurfaceSeconds*1000.0,Stats.FarShapeSeconds*1000.0, Stats.ShapeSeconds*1000.0,Stats.IntervalSeconds*1000.0,Stats.SurfaceSeconds*1000.0,Stats.FarShapeSeconds*1000.0,
std::chrono::duration<double>(Clock::now()-TotalStart).count()*1000.0, std::chrono::duration<double>(Clock::now()-TotalStart).count()*1000.0,
@@ -539,6 +539,24 @@ public:
static_cast<float>(Z)); static_cast<float>(Z));
} }
float DensityAtUnclamped(float X,float Y,float Z) const
{
return AnalyticDensityAt(X,Y,Z,false);
}
VoxelForgeCore::DetailFieldSample DetailSampleAt(float X,float Y,float Z) const
{
VoxelForgeCore::DetailFieldSample Sample;
AnalyticDensityAt(X,Y,Z,false,&Sample.displacement,
&Sample.caveInfluence,&Sample.floorInfluence);
return Sample;
}
bool IsProxyBandFallback(float Density) const
{
return std::abs(std::abs(Density)-(DetailSupremum+0.125f))<1.0e-4f;
}
bool IsAnalyticOnly() const { return bAnalyticOnly; } bool IsAnalyticOnly() const { return bAnalyticOnly; }
void DensityAtLatticeX8(int32 XStart,int32 XStep,int32 Y,int32 Z, void DensityAtLatticeX8(int32 XStart,int32 XStep,int32 Y,int32 Z,
@@ -578,11 +596,11 @@ public:
float Density; float Density;
if(CandidateWords[Word]&Mask) if(CandidateWords[Word]&Mask)
{ {
const int8 Proxy=SurfaceDistanceQ[NodeBase const int16 Proxy=SurfaceDistanceQ[NodeBase
+static_cast<size_t>(Lane*XStep)]; +static_cast<size_t>(Lane*XStep)];
Density=static_cast<float>(Proxy)*(1.0f/16.0f); Density=static_cast<float>(Proxy)*(1.0f/256.0f);
if((Density<0.0f)!=bRock) if((Density<0.0f)!=bRock)
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/16.0f); Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/256.0f);
} }
else else
Density=bRock?-ProxyBandEdge:ProxyBandEdge; Density=bRock?-ProxyBandEdge:ProxyBandEdge;
@@ -607,10 +625,10 @@ private:
if(CandidateWords[Word]&Mask) if(CandidateWords[Word]&Mask)
{ {
const size_t Node=Zi*static_cast<size_t>(Size[1])*Size[0]+Column; const size_t Node=Zi*static_cast<size_t>(Size[1])*Size[0]+Column;
const int8 Proxy=SurfaceDistanceQ[Node]; const int16 Proxy=SurfaceDistanceQ[Node];
float Density=static_cast<float>(Proxy)*(1.0f/16.0f); float Density=static_cast<float>(Proxy)*(1.0f/256.0f);
if((Density<0.0f)!=bRock) if((Density<0.0f)!=bRock)
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/16.0f); Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/256.0f);
return ApplyEditBrushes(Density,FX,FY,FZ); return ApplyEditBrushes(Density,FX,FY,FZ);
} }
// Match the fixed fallback to the edge of the stored proxy band. The // Match the fixed fallback to the edge of the stored proxy band. The
@@ -620,8 +638,14 @@ private:
return ApplyEditBrushes(bRock?-ProxyBandEdge:ProxyBandEdge,FX,FY,FZ); return ApplyEditBrushes(bRock?-ProxyBandEdge:ProxyBandEdge,FX,FY,FZ);
} }
float AnalyticDensityAt(float X,float Y,float Z) const float AnalyticDensityAt(float X,float Y,float Z,bool bClampNearZero=true,
float* OutDetailDisplacement=nullptr,
float* OutCaveInfluence=nullptr,
float* OutFloorInfluence=nullptr) const
{ {
if(OutDetailDisplacement)*OutDetailDisplacement=0.0f;
if(OutCaveInfluence)*OutCaveInfluence=0.0f;
if(OutFloorInfluence)*OutFloorInfluence=0.0f;
const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1] const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]
&&Z>=Min[2]&&Z<=Max[2]; &&Z>=Min[2]&&Z<=Max[2];
if(!bInsideFine) if(!bInsideFine)
@@ -631,8 +655,9 @@ private:
if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f); if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f);
const bool bRock=RockDistance>=0.0f; const bool bRock=RockDistance>=0.0f;
float Density=-RockDistance; float Density=-RockDistance;
if((Density<0.0f)!=bRock||std::abs(Density)<0.05f) if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),0.05f); Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
bClampNearZero?0.05f:1.0e-7f);
return ApplyEditBrushes(Density,X,Y,Z); return ApplyEditBrushes(Density,X,Y,Z);
} }
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
@@ -641,19 +666,27 @@ private:
float RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF, float RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF,
&bFloorSurface,&CaveInfluence,&FloorInfluence,&bCriticalAir,nullptr,false, &bFloorSurface,&CaveInfluence,&FloorInfluence,&bCriticalAir,nullptr,false,
&PassageInfluence); &PassageInfluence);
if(OutCaveInfluence)*OutCaveInfluence=CaveInfluence;
if(OutFloorInfluence)*OutFloorInfluence=FloorInfluence;
if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth
&&std::abs(RockDistance)<=DetailSupremum+0.125f) &&std::abs(RockDistance)<=DetailSupremum+0.125f)
{ {
const float Fade=DetailBandFadeAt(X,Y,Z,RockDistance,SurfaceSDF, const float Fade=DetailBandFadeAt(X,Y,Z,RockDistance,SurfaceSDF,
CaveInfluence,FloorInfluence); CaveInfluence,FloorInfluence);
if(Fade>0.0f)RockDistance+=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence, if(Fade>0.0f)
FloorInfluence,PassageInfluence)*Fade; {
const float Detail=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,
FloorInfluence,PassageInfluence)*Fade;
RockDistance+=Detail;
if(OutDetailDisplacement)*OutDetailDisplacement=Detail;
}
} }
if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f); if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f);
const bool bRock=RockDistance>=0.0f; const bool bRock=RockDistance>=0.0f;
float Density=-RockDistance; float Density=-RockDistance;
if((Density<0.0f)!=bRock||std::abs(Density)<0.05f) if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),0.05f); Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
bClampNearZero?0.05f:1.0e-7f);
return ApplyEditBrushes(Density,X,Y,Z); return ApplyEditBrushes(Density,X,Y,Z);
} }
@@ -923,7 +956,9 @@ public:
const double OX=CameraX*4.0,OY=CameraY*4.0,OZ=CameraZ*4.0; const double OX=CameraX*4.0,OY=CameraY*4.0,OZ=CameraZ*4.0;
auto Distance=[&](double X,double Y,double Z,ESurfaceFamily* OutFamily=nullptr, auto Distance=[&](double X,double Y,double Z,ESurfaceFamily* OutFamily=nullptr,
float* OutRaw=nullptr,float* OutDetail=nullptr, float* OutRaw=nullptr,float* OutDetail=nullptr,
bool* OutFloorSurface=nullptr,float* OutPassageInfluence=nullptr) bool* OutFloorSurface=nullptr,float* OutPassageInfluence=nullptr,
float* OutCaveInfluence=nullptr,float* OutFloorInfluence=nullptr,
float* OutSurfaceSDF=nullptr)
{ {
ESurfaceFamily Family=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; ESurfaceFamily Family=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
bool bFloorSurface=false;float CaveInfluence=0.0f,FloorInfluence=0.0f; bool bFloorSurface=false;float CaveInfluence=0.0f,FloorInfluence=0.0f;
@@ -948,6 +983,9 @@ public:
if(OutDetail)*OutDetail=Detail; if(OutDetail)*OutDetail=Detail;
if(OutFloorSurface)*OutFloorSurface=bFloorSurface; if(OutFloorSurface)*OutFloorSurface=bFloorSurface;
if(OutPassageInfluence)*OutPassageInfluence=PassageInfluence; if(OutPassageInfluence)*OutPassageInfluence=PassageInfluence;
if(OutCaveInfluence)*OutCaveInfluence=CaveInfluence;
if(OutFloorInfluence)*OutFloorInfluence=FloorInfluence;
if(OutSurfaceSDF)*OutSurfaceSDF=SurfaceSDF;
return D; return D;
}; };
uint64 TotalHits=0,TotalAxisLocked=0,TotalNearZero=0,FloorTaggedHits=0,FloorAxisLocked=0; uint64 TotalHits=0,TotalAxisLocked=0,TotalNearZero=0,FloorTaggedHits=0,FloorAxisLocked=0;
@@ -982,7 +1020,9 @@ public:
const double XH=OX+DX*HitT,YH=OY+DY*HitT,ZH=OZ+DZ*HitT; const double XH=OX+DX*HitT,YH=OY+DY*HitT,ZH=OZ+DZ*HitT;
ESurfaceFamily Family=ESurfaceFamily::Strata;float Raw=0.0f,Detail=0.0f; ESurfaceFamily Family=ESurfaceFamily::Strata;float Raw=0.0f,Detail=0.0f;
bool bFloorSurface=false;float PassageInfluence=0.0f; bool bFloorSurface=false;float PassageInfluence=0.0f;
Distance(XH,YH,ZH,&Family,&Raw,&Detail,&bFloorSurface,&PassageInfluence); float CaveInfluence=0.0f,FloorInfluence=0.0f,SurfaceSDF=100000.0f;
Distance(XH,YH,ZH,&Family,&Raw,&Detail,&bFloorSurface,&PassageInfluence,
&CaveInfluence,&FloorInfluence,&SurfaceSDF);
const double GX=Distance(XH+0.5,YH,ZH)-Distance(XH-0.5,YH,ZH); const double GX=Distance(XH+0.5,YH,ZH)-Distance(XH-0.5,YH,ZH);
const double GY=Distance(XH,YH+0.5,ZH)-Distance(XH,YH-0.5,ZH); const double GY=Distance(XH,YH+0.5,ZH)-Distance(XH,YH-0.5,ZH);
const double GZ=Distance(XH,YH,ZH+0.5)-Distance(XH,YH,ZH-0.5); const double GZ=Distance(XH,YH,ZH+0.5)-Distance(XH,YH,ZH-0.5);
@@ -1036,8 +1076,19 @@ public:
{ {
const char* Name=Family==ESurfaceFamily::Strata?"strata": const char* Name=Family==ESurfaceFamily::Strata?"strata":
Family==ESurfaceFamily::Cave?"cave":"solid_overlay"; Family==ESurfaceFamily::Cave?"cave":"solid_overlay";
std::printf("[SurfaceRay] t=%.2f vox hit=(%.2f,%.2f,%.2f) family=%s floor_surface=%s passage_influence=%.3f raw_sdf=%.4f detail=%.4f axis_dot=%.4f\n", const FLayerField* Field=FindLayerField(ZH);
HitT,XH,YH,ZH,Name,bFloorSurface?"yes":"no",PassageInfluence,Raw,Detail,AxisDot); FOwnerNoiseLineContext OwnerNoise8,OwnerNoise2;
const float Owner8=Field?OwnerCaveRoughness(XH,YH,ZH,SurfaceSDF,
*Field,CaveInfluence,OwnerNoise8,nullptr,8):0.0f;
const float Owner2=Field?OwnerCaveRoughness(XH,YH,ZH,SurfaceSDF,
*Field,CaveInfluence,OwnerNoise2,nullptr,2):0.0f;
const float Relief=SurfaceRelief(static_cast<float>(XH),
static_cast<float>(YH),static_cast<float>(ZH),
static_cast<uint32>(Owner->Seed));
std::printf("[SurfaceRay] t=%.2f vox hit=(%.2f,%.2f,%.2f) family=%s floor_surface=%s cave_influence=%.3f floor_influence=%.3f passage_influence=%.3f surface_sdf=%.4f raw_sdf=%.4f detail=%.4f relief=%.4f owner8=%.4f owner2=%.4f axis_dot=%.4f\n",
HitT,XH,YH,ZH,Name,bFloorSurface?"yes":"no",CaveInfluence,
FloorInfluence,PassageInfluence,SurfaceSDF,Raw,Detail,Relief,
Owner8,Owner2,AxisDot);
} }
bFound=true;break; bFound=true;break;
} }
@@ -1245,6 +1296,12 @@ private:
constexpr float Width=1.5f; constexpr float Width=1.5f;
return SmoothUnit((Edge-std::abs(SignedDistance))/Width); return SmoothUnit((Edge-std::abs(SignedDistance))/Width);
} }
static float DetailContrast(float CaveInfluence,float FloorInfluence)
{
const float Cave=std::clamp(CaveInfluence,0.0f,1.0f);
const float Floor=std::clamp(FloorInfluence,0.0f,1.0f);
return 1.35f-0.80f*Cave+1.85f*Floor;
}
static constexpr float DetailBandInnerRadius=1.5f; static constexpr float DetailBandInnerRadius=1.5f;
static float DetailBandFade(float SignedDistance,float Edge) static float DetailBandFade(float SignedDistance,float Edge)
{ {
@@ -1271,8 +1328,7 @@ private:
} }
const float ReliefBound=std::abs(MacroValue) const float ReliefBound=std::abs(MacroValue)
+(0.80f+0.25f*MacroValue)*(0.85f+0.18f); +(0.80f+0.25f*MacroValue)*(0.85f+0.18f);
const float Contrast=1.35f-0.80f*std::clamp(CaveInfluence,0.0f,1.0f) const float Contrast=DetailContrast(CaveInfluence,FloorInfluence);
+1.85f*std::clamp(FloorInfluence,0.0f,1.0f);
const float LocalSupremum=std::min(3.9f,(ReliefBound+0.5f*OwnerBound)*Contrast); const float LocalSupremum=std::min(3.9f,(ReliefBound+0.5f*OwnerBound)*Contrast);
return std::clamp(LocalSupremum+0.125f,DetailBandInnerRadius,DetailSupremum+0.125f); return std::clamp(LocalSupremum+0.125f,DetailBandInnerRadius,DetailSupremum+0.125f);
} }
@@ -3040,10 +3096,10 @@ private:
void StoreSurfaceDistance(size_t Node,float Density) void StoreSurfaceDistance(size_t Node,float Density)
{ {
int32 Q=static_cast<int32>(std::lround(Density*16.0f)); int32 Q=static_cast<int32>(std::lround(Density*256.0f));
Q=std::clamp(Q,-127,127); Q=std::clamp(Q,-32767,32767);
if(Q==0)Q=Density<0.0f?-1:1; if(Q==0)Q=Density<0.0f?-1:1;
SurfaceDistanceQ[Node]=static_cast<int8>(Q); SurfaceDistanceQ[Node]=static_cast<int16>(Q);
} }
bool IsInsideSolidOverlay(int32 X,int32 Y,int32 Z) const bool IsInsideSolidOverlay(int32 X,int32 Y,int32 Z) const
@@ -3727,8 +3783,8 @@ private:
static float SurfaceRelief(float X,float Y,float Z,uint32 Seed) static float SurfaceRelief(float X,float Y,float Z,uint32 Seed)
{ {
// The low band carries the broad rock shapes and also gently varies mid-band // The low band carries the broad rock shapes and also gently varies mid-band
// amplitude. The quieter 6-voxel octave keeps fine relief without restoring // amplitude. Keep the finest octave wider than a few marching-cubes cells so
// the same tight bubble pattern on every exposed surface. // it does not alias into voxel-sized ledges on the full-detail surface.
const float Macro=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,32,Seed^0xB5297A4Du); const float Macro=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,32,Seed^0xB5297A4Du);
const float Mid=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,16,Seed^0x63D83595u); const float Mid=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,16,Seed^0x63D83595u);
const float Fine=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,6,Seed^0xA511E9B3u); const float Fine=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,6,Seed^0xA511E9B3u);
@@ -3794,7 +3850,7 @@ private:
FOwnerNoiseLineContext OwnerNoise; FOwnerNoiseLineContext OwnerNoise;
FDetailNoiseLineContext(float X,float Y,uint32 Seed) FDetailNoiseLineContext(float X,float Y,uint32 Seed)
:Macro(X,Y,32,Seed^0xB5297A4Du),Mid(X,Y,16,Seed^0x63D83595u), :Macro(X,Y,32,Seed^0xB5297A4Du),Mid(X,Y,16,Seed^0x63D83595u),
Fine(X,Y,6,Seed^0xA511E9B3u) Fine(X,Y,6,Seed^0xA511E9B3u)
{} {}
float SurfaceReliefAt(float Z,bool bMacroKnown=false,float KnownMacro=0.0f) float SurfaceReliefAt(float Z,bool bMacroKnown=false,float KnownMacro=0.0f)
@@ -3827,9 +3883,9 @@ private:
if(Field) if(Field)
OwnerRelief=OwnerCaveRoughnessForDetail(X,Y,Z,SurfaceSDF,*Field, OwnerRelief=OwnerCaveRoughnessForDetail(X,Y,Z,SurfaceSDF,*Field,
CaveInfluence,PassageWeight,OwnerNoise); CaveInfluence,PassageWeight,OwnerNoise);
const float Contrast=1.35f-0.80f*std::clamp(CaveInfluence,0.0f,1.0f) const float Contrast=DetailContrast(CaveInfluence,FloorInfluence);
+1.85f*std::clamp(FloorInfluence,0.0f,1.0f); const float Detail=std::clamp((Relief+OwnerRelief*0.5f)*Contrast,-3.9f,3.9f);
return std::clamp((Relief+OwnerRelief*0.5f)*Contrast,-3.9f,3.9f); return Detail;
} }
float DetailNoiseOnLine(double X,double Y,double Z,float SurfaceSDF, float DetailNoiseOnLine(double X,double Y,double Z,float SurfaceSDF,
@@ -3864,9 +3920,9 @@ private:
if(Profile) if(Profile)
Profile->OwnerRoughnessSampleNanoseconds+=static_cast<uint64>( Profile->OwnerRoughnessSampleNanoseconds+=static_cast<uint64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now()-OwnerStart).count()); std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now()-OwnerStart).count());
const float Contrast=1.35f-0.80f*std::clamp(CaveInfluence,0.0f,1.0f) const float Contrast=DetailContrast(CaveInfluence,FloorInfluence);
+1.85f*std::clamp(FloorInfluence,0.0f,1.0f); const float Detail=std::clamp((Relief+OwnerRelief*0.5f)*Contrast,-3.9f,3.9f);
return std::clamp((Relief+OwnerRelief*0.5f)*Contrast,-3.9f,3.9f); return Detail;
} }
bool EvaluateFast(double X,double Y,double Z) const bool EvaluateFast(double X,double Y,double Z) const
@@ -4012,7 +4068,7 @@ private:
std::unordered_map<FBinKey,FBinItems,FBinHash> SpatialBins; std::unordered_map<FBinKey,FBinItems,FBinHash> SpatialBins;
std::unordered_map<FBinKey,FBinItems,FBinHash> CriticalAirBins; std::unordered_map<FBinKey,FBinItems,FBinHash> CriticalAirBins;
std::vector<uint64> RockWords; std::vector<uint64> RockWords;
std::unique_ptr<int8[]> SurfaceDistanceQ; std::unique_ptr<int16[]> SurfaceDistanceQ;
std::array<std::vector<uint64>,3> CandidateAxisWords; std::array<std::vector<uint64>,3> CandidateAxisWords;
std::vector<uint64> CandidateWords; std::vector<uint64> CandidateWords;
FBuildStats Stats; FBuildStats Stats;
+10
View File
@@ -128,6 +128,8 @@ namespace VoxelForgeCore
{ AddWord(hash,color.R,1); AddWord(hash,color.G,1); AddWord(hash,color.B,1); AddWord(hash,color.A,1); } { AddWord(hash,color.R,1); AddWord(hash,color.G,1); AddWord(hash,color.B,1); AddWord(hash,color.A,1); }
AddWord(hash,static_cast<std::uint64_t>(mesh.Triangles.Num()),8); AddWord(hash,static_cast<std::uint64_t>(mesh.Triangles.Num()),8);
for(const int32 index:mesh.Triangles)AddWord(hash,static_cast<std::uint32_t>(index),4); for(const int32 index:mesh.Triangles)AddWord(hash,static_cast<std::uint32_t>(index),4);
AddWord(hash,static_cast<std::uint64_t>(mesh.TriangleSkirtFaceMasks.Num()),8);
for(const uint8 faces:mesh.TriangleSkirtFaceMasks)AddWord(hash,faces,1);
AddWord(hash,static_cast<std::uint32_t>(mesh.NumSurfaceVertices),4); AddWord(hash,static_cast<std::uint32_t>(mesh.NumSurfaceVertices),4);
AddWord(hash,static_cast<std::uint32_t>(mesh.NumSkirtTriangles),4); AddWord(hash,static_cast<std::uint32_t>(mesh.NumSkirtTriangles),4);
AddWord(hash,static_cast<std::uint32_t>(mesh.NumCeilingTriangles),4); AddWord(hash,static_cast<std::uint32_t>(mesh.NumCeilingTriangles),4);
@@ -502,6 +504,10 @@ namespace VoxelForgeCore
float World::densityAt(Vec3d position) const float World::densityAt(Vec3d position) const
{ return impl_&&impl_->shape?impl_->shape->DensityAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; } { return impl_&&impl_->shape?impl_->shape->DensityAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
float World::unclampedDensityAt(Vec3d position) const
{ return impl_&&impl_->shape?impl_->shape->DensityAtUnclamped(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
DetailFieldSample World::detailSampleAt(Vec3d position) const
{ return impl_&&impl_->shape?impl_->shape->DetailSampleAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):DetailFieldSample{}; }
float World::densityAtLattice(Vec3i position) const float World::densityAtLattice(Vec3i position) const
{ return impl_&&impl_->shape?impl_->shape->DensityAtLattice(position.x,position.y,position.z):0.0f; } { return impl_&&impl_->shape?impl_->shape->DensityAtLattice(position.x,position.y,position.z):0.0f; }
bool World::isSolidAt(Vec3d position) const { return densityAt(position)<0.0f; } bool World::isSolidAt(Vec3d position) const { return densityAt(position)<0.0f; }
@@ -614,12 +620,16 @@ namespace VoxelForgeCore
+static_cast<std::uint64_t>(mesh.Normals.GetAllocatedSize()) +static_cast<std::uint64_t>(mesh.Normals.GetAllocatedSize())
+static_cast<std::uint64_t>(mesh.UVs.GetAllocatedSize()) +static_cast<std::uint64_t>(mesh.UVs.GetAllocatedSize())
+static_cast<std::uint64_t>(mesh.Colors.GetAllocatedSize()) +static_cast<std::uint64_t>(mesh.Colors.GetAllocatedSize())
+static_cast<std::uint64_t>(mesh.TriangleSkirtFaceMasks.GetAllocatedSize())
+static_cast<std::uint64_t>(mesh.Triangles.GetAllocatedSize()); +static_cast<std::uint64_t>(mesh.Triangles.GetAllocatedSize());
out.groups.surfaceVertexCount=static_cast<std::uint32_t>(mesh.NumSurfaceVertices); out.groups.surfaceVertexCount=static_cast<std::uint32_t>(mesh.NumSurfaceVertices);
out.groups.ceilingTriangleCount=static_cast<std::uint32_t>(mesh.NumCeilingTriangles); out.groups.ceilingTriangleCount=static_cast<std::uint32_t>(mesh.NumCeilingTriangles);
out.groups.groundTriangleCount=static_cast<std::uint32_t>( out.groups.groundTriangleCount=static_cast<std::uint32_t>(
std::max(0,mesh.Triangles.Num()/3-mesh.NumCeilingTriangles)); std::max(0,mesh.Triangles.Num()/3-mesh.NumCeilingTriangles));
out.groups.skirtTriangleCount=static_cast<std::uint32_t>(mesh.NumSkirtTriangles); out.groups.skirtTriangleCount=static_cast<std::uint32_t>(mesh.NumSkirtTriangles);
out.skirtFaceMasks.assign(mesh.TriangleSkirtFaceMasks.begin(),
mesh.TriangleSkirtFaceMasks.end());
out.skirtFaceMasks.resize(mesh.Triangles.Num()/3,0);
out.vertices.reserve(static_cast<std::size_t>(mesh.Vertices.Num())); out.vertices.reserve(static_cast<std::size_t>(mesh.Vertices.Num()));
for(int32 i=0;i<mesh.Vertices.Num();++i) for(int32 i=0;i<mesh.Vertices.Num();++i)
{ {
File diff suppressed because it is too large. Load diff
+14
View File
@@ -36,6 +36,9 @@ struct FStandaloneCpuTile
uint64 RequestedCrossing=0; uint64 RequestedCrossing=0;
std::vector<FStandaloneCpuVertex> Vertices; std::vector<FStandaloneCpuVertex> Vertices;
std::vector<uint32> Indices; std::vector<uint32> Indices;
std::vector<uint8> TriangleSkirtFaceMasks;
uint32 SurfaceVertexCount=0;
uint32 SkirtTriangleCount=0;
uint64 DensitySamples=0; uint64 DensitySamples=0;
double WorkerSeconds=0.0; double WorkerSeconds=0.0;
std::chrono::steady_clock::time_point CompletedAt{}; std::chrono::steady_clock::time_point CompletedAt{};
@@ -79,6 +82,8 @@ inline uint64 HashStandaloneMeshPayload(const FVoxelMeshData& Mesh)
{ AddWord(Color.R,1);AddWord(Color.G,1);AddWord(Color.B,1);AddWord(Color.A,1); } { AddWord(Color.R,1);AddWord(Color.G,1);AddWord(Color.B,1);AddWord(Color.A,1); }
AddWord(static_cast<uint64>(Mesh.Triangles.Num()),8); AddWord(static_cast<uint64>(Mesh.Triangles.Num()),8);
for(const int32 Index:Mesh.Triangles)AddWord(static_cast<uint32>(Index),4); for(const int32 Index:Mesh.Triangles)AddWord(static_cast<uint32>(Index),4);
AddWord(static_cast<uint64>(Mesh.TriangleSkirtFaceMasks.Num()),8);
for(const uint8 Faces:Mesh.TriangleSkirtFaceMasks)AddWord(Faces,1);
AddWord(static_cast<uint32>(Mesh.NumSurfaceVertices),4); AddWord(static_cast<uint32>(Mesh.NumSurfaceVertices),4);
AddWord(static_cast<uint32>(Mesh.NumSkirtTriangles),4); AddWord(static_cast<uint32>(Mesh.NumSkirtTriangles),4);
AddWord(static_cast<uint32>(Mesh.NumCeilingTriangles),4); AddWord(static_cast<uint32>(Mesh.NumCeilingTriangles),4);
@@ -614,6 +619,8 @@ private:
Out.CompletedAt=FClock::now();return Out; Out.CompletedAt=FClock::now();return Out;
} }
Out.DensitySamples=Result->densitySampleCount; Out.DensitySamples=Result->densitySampleCount;
Out.SurfaceVertexCount=Result->groups.surfaceVertexCount;
Out.SkirtTriangleCount=Result->groups.skirtTriangleCount;
Out.MeshPayloadAllocatedBytes=Result->meshPayloadAllocatedBytes; Out.MeshPayloadAllocatedBytes=Result->meshPayloadAllocatedBytes;
Out.DensityHash=Result->densityHash;Out.MeshHash=Result->meshHash; Out.DensityHash=Result->densityHash;Out.MeshHash=Result->meshHash;
Out.PayloadHash=Result->payloadHash;Out.TopologyHash=Result->topologyHash; Out.PayloadHash=Result->payloadHash;Out.TopologyHash=Result->topologyHash;
@@ -627,6 +634,7 @@ private:
{static_cast<float>(Vertex.normal.x),static_cast<float>(Vertex.normal.y), {static_cast<float>(Vertex.normal.x),static_cast<float>(Vertex.normal.y),
static_cast<float>(Vertex.normal.z)}}); static_cast<float>(Vertex.normal.z)}});
Out.Indices=std::move(Result->indices); Out.Indices=std::move(Result->indices);
Out.TriangleSkirtFaceMasks=std::move(Result->skirtFaceMasks);
if(bProfile) if(bProfile)
{ {
const VoxelDensityProfile::FSnapshot After=VoxelDensityProfile::SnapshotCurrentThread(); const VoxelDensityProfile::FSnapshot After=VoxelDensityProfile::SnapshotCurrentThread();
@@ -660,10 +668,13 @@ private:
} }
} }
Out.DensitySamples=static_cast<uint64>(FMath::Max<int64>(0,SampleCount)); Out.DensitySamples=static_cast<uint64>(FMath::Max<int64>(0,SampleCount));
Out.SurfaceVertexCount=static_cast<uint32>(std::clamp(Mesh.NumSurfaceVertices,0,Mesh.Vertices.Num()));
Out.SkirtTriangleCount=static_cast<uint32>(std::max(0,Mesh.NumSkirtTriangles));
Out.MeshPayloadAllocatedBytes=static_cast<uint64>(Mesh.Vertices.GetAllocatedSize()) Out.MeshPayloadAllocatedBytes=static_cast<uint64>(Mesh.Vertices.GetAllocatedSize())
+static_cast<uint64>(Mesh.Normals.GetAllocatedSize()) +static_cast<uint64>(Mesh.Normals.GetAllocatedSize())
+static_cast<uint64>(Mesh.UVs.GetAllocatedSize()) +static_cast<uint64>(Mesh.UVs.GetAllocatedSize())
+static_cast<uint64>(Mesh.Colors.GetAllocatedSize()) +static_cast<uint64>(Mesh.Colors.GetAllocatedSize())
+static_cast<uint64>(Mesh.TriangleSkirtFaceMasks.GetAllocatedSize())
+static_cast<uint64>(Mesh.Triangles.GetAllocatedSize()); +static_cast<uint64>(Mesh.Triangles.GetAllocatedSize());
if(bHashDensity) if(bHashDensity)
{ {
@@ -680,6 +691,9 @@ private:
} }
Out.Indices.reserve(static_cast<size_t>(Mesh.Triangles.Num())); Out.Indices.reserve(static_cast<size_t>(Mesh.Triangles.Num()));
for(const int32 Index:Mesh.Triangles)Out.Indices.push_back(static_cast<uint32>(Index)); for(const int32 Index:Mesh.Triangles)Out.Indices.push_back(static_cast<uint32>(Index));
Out.TriangleSkirtFaceMasks.assign(Mesh.TriangleSkirtFaceMasks.begin(),
Mesh.TriangleSkirtFaceMasks.end());
Out.TriangleSkirtFaceMasks.resize(Out.Indices.size()/3,0);
if(bHashDensity) if(bHashDensity)
{Out.MeshHash=HashStandaloneCpuTileMesh(Out);Out.bHasMeshHash=true;} {Out.MeshHash=HashStandaloneCpuTileMesh(Out);Out.bHasMeshHash=true;}
Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count(); Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count();
@@ -214,9 +214,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
return BiomeTileGridMemo.Add(ChunkZ, Samples); return BiomeTileGridMemo.Add(ChunkZ, Samples);
}; };
// Normale fournie par l'appelant (gradient lu dans la grille de densité, T1.b) — // Normale fournie par l'appelant (gradient lu dans la grille de densité, T1.b).
// plus d'échantillonnage de densité par vertex. RawNormal pointe solide→air ; on la
// normalise ici (fallback up si dégénérée).
auto GetOrCreateVertex = [&](const FVector& WorldPos, const FVector& RawNormal) -> int32 auto GetOrCreateVertex = [&](const FVector& WorldPos, const FVector& RawNormal) -> int32
{ {
const FIntVector Key( const FIntVector Key(
@@ -1299,13 +1297,26 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
{ {
return DensityGrid[((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1)]; return DensityGrid[((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1)];
}; };
auto SampleGradientG = [&](int32 gx, int32 gy, int32 gz) -> float
{
const float Cached = SampleG(gx, gy, gz);
if (!ShapeWorldContext || Step != 1
|| !ShapeWorldContext->IsProxyBandFallback(Cached))
{
return Cached;
}
return ShapeWorldContext->DensityAtUnclamped(
static_cast<float>(OriginVoxels.X + gx * Step),
static_cast<float>(OriginVoxels.Y + gy * Step),
static_cast<float>(OriginVoxels.Z + gz * Step));
};
auto GradAt = [&](int32 gx, int32 gy, int32 gz) -> FVector auto GradAt = [&](int32 gx, int32 gy, int32 gz) -> FVector
{ {
// Densité négative=solide, positive=air → le gradient pointe vers l'air (sortant). // Densité négative=solide, positive=air → le gradient pointe vers l'air (sortant).
return FVector( return FVector(
SampleG(gx + 1, gy, gz) - SampleG(gx - 1, gy, gz), SampleGradientG(gx + 1, gy, gz) - SampleGradientG(gx - 1, gy, gz),
SampleG(gx, gy + 1, gz) - SampleG(gx, gy - 1, gz), SampleGradientG(gx, gy + 1, gz) - SampleGradientG(gx, gy - 1, gz),
SampleG(gx, gy, gz + 1) - SampleG(gx, gy, gz - 1)); SampleGradientG(gx, gy, gz + 1) - SampleGradientG(gx, gy, gz - 1));
}; };
//========================================================================= //=========================================================================
@@ -1477,9 +1488,32 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const int32 A = EdgeCorners[i][0]; const int32 A = EdgeCorners[i][0];
const int32 B = EdgeCorners[i][1]; const int32 B = EdgeCorners[i][1];
const float D1 = Densities[A], D2 = Densities[B]; const float D1 = Densities[A], D2 = Densities[B];
const float T = (FMath::Abs(D2 - D1) < KINDA_SMALL_NUMBER) float T = (FMath::Abs(D2 - D1) < KINDA_SMALL_NUMBER)
? 0.5f ? 0.5f
: FMath::Clamp((IsoLevel - D1) / (D2 - D1), 0.0f, 1.0f); : FMath::Clamp((IsoLevel - D1) / (D2 - D1), 0.0f, 1.0f);
if(ShapeWorldContext&&Step==1&&T>=0.375f&&T<=0.625f)
{
const FVector Midpoint=(Positions[A]+Positions[B])*0.5f;
const float MidDensity=ShapeWorldContext->DensityAtUnclamped(
Midpoint.X/VOXEL_SIZE,Midpoint.Y/VOXEL_SIZE,
Midpoint.Z/VOXEL_SIZE);
if(FMath::Abs(MidDensity-IsoLevel)<1.0e-6f)
T=0.5f;
else if((MidDensity<IsoLevel)==(D1<IsoLevel))
{
const float Denominator=D2-MidDensity;
if(FMath::Abs(Denominator)>KINDA_SMALL_NUMBER)
T=0.5f+0.5f*FMath::Clamp(
(IsoLevel-MidDensity)/Denominator,0.0f,1.0f);
}
else
{
const float Denominator=MidDensity-D1;
if(FMath::Abs(Denominator)>KINDA_SMALL_NUMBER)
T=0.5f*FMath::Clamp(
(IsoLevel-D1)/Denominator,0.0f,1.0f);
}
}
EdgeVertices[i] = Positions[A] + T * (Positions[B] - Positions[A]); EdgeVertices[i] = Positions[A] + T * (Positions[B] - Positions[A]);
EdgeNormals[i] = Gradients[A] + T * (Gradients[B] - Gradients[A]); EdgeNormals[i] = Gradients[A] + T * (Gradients[B] - Gradients[A]);
} }
@@ -1559,6 +1593,9 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
// matériau. Une arête de surface posée sur une face de frontière a sa coordonnée d'axe EXACTE // matériau. Une arête de surface posée sur une face de frontière a sa coordonnée d'axe EXACTE
// (l'interpolation MC garde fixe l'axe de la face) → comparaison flottante exacte fiable. // (l'interpolation MC garde fixe l'axe de la face) → comparaison flottante exacte fiable.
MeshData.NumSurfaceVertices = MeshData.Vertices.Num(); MeshData.NumSurfaceVertices = MeshData.Vertices.Num();
const int32 GroundSurfaceTriangleCount=GroundTris.Num()/3;
const int32 CapSurfaceTriangleCount=CapTris.Num()/3;
TArray<uint8> GroundSkirtFaceMasks,CapSkirtFaceMasks;
if (bGenerateSkirts && (GroundTris.Num() + CapTris.Num()) > 0) if (bGenerateSkirts && (GroundTris.Num() + CapTris.Num()) > 0)
{ {
const float ExtentCm = (float)(CellsPerAxis * Step) * VOXEL_SIZE; const float ExtentCm = (float)(CellsPerAxis * Step) * VOXEL_SIZE;
@@ -1566,11 +1603,16 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const float MaxX = MinX + ExtentCm, MaxY = MinY + ExtentCm, MaxZ = MinZ + ExtentCm; const float MaxX = MinX + ExtentCm, MaxY = MinY + ExtentCm, MaxZ = MinZ + ExtentCm;
const float SkirtDepth = FMath::Max(1.0f, SkirtCells) * (float)Step * VOXEL_SIZE; const float SkirtDepth = FMath::Max(1.0f, SkirtCells) * (float)Step * VOXEL_SIZE;
auto OnBoundaryPlane = [&](const FVector& A, const FVector& B) -> bool auto BoundaryFaceMask = [&](const FVector& A, const FVector& B) -> uint8
{ {
return (A.X == MinX && B.X == MinX) || (A.X == MaxX && B.X == MaxX) uint8 Mask=0;
|| (A.Y == MinY && B.Y == MinY) || (A.Y == MaxY && B.Y == MaxY) if(A.X==MinX&&B.X==MinX)Mask|=1u<<0;
|| (A.Z == MinZ && B.Z == MinZ) || (A.Z == MaxZ && B.Z == MaxZ); if(A.X==MaxX&&B.X==MaxX)Mask|=1u<<1;
if(A.Y==MinY&&B.Y==MinY)Mask|=1u<<2;
if(A.Y==MaxY&&B.Y==MaxY)Mask|=1u<<3;
if(A.Z==MinZ&&B.Z==MinZ)Mask|=1u<<4;
if(A.Z==MaxZ&&B.Z==MaxZ)Mask|=1u<<5;
return Mask;
}; };
auto AddSkirtVert = [&](const FVector& Pos, const FVector& Nrm, const FColor& Col) -> int32 auto AddSkirtVert = [&](const FVector& Pos, const FVector& Nrm, const FColor& Col) -> int32
@@ -1592,10 +1634,87 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
// F17 — jupes émises PAR SEAU : chaque jupe hérite la classe (donc le polygroup / // F17 — jupes émises PAR SEAU : chaque jupe hérite la classe (donc le polygroup /
// matériau) de son triangle source ; ajouter les jupes après coup casserait les runs // matériau) de son triangle source ; ajouter les jupes après coup casserait les runs
// d'indices contigus par groupe qu'exige RMC. // d'indices contigus par groupe qu'exige RMC.
auto EmitSkirts = [&](TArray<int32>& Tris) auto EmitSkirts = [&](TArray<int32>& Tris,TArray<uint8>& FaceMasks)
{ {
// On ajoute en itérant : on fige le nombre de triangles de surface et on n'ajoute qu'au-delà. // On ajoute en itérant : on fige le nombre de triangles de surface et on n'ajoute qu'au-delà.
const int32 BaseTriNum = Tris.Num(); const int32 BaseTriNum = Tris.Num();
struct FSkirtVertex
{
FVector Position;
FVector Normal;
FColor Color;
float Density=0.0f;
int32 OriginalIndex=INDEX_NONE;
};
const auto MakeSkirtVertex=[&](const FVector& Position,const FVector& Normal,
const FColor& Color,bool bOnSurface,int32 OriginalIndex)
{
FSkirtVertex Vertex{Position,Normal,Color,0.0f,OriginalIndex};
if(!bOnSurface&&ShapeWorldContext)
Vertex.Density=ShapeWorldContext->DensityAt(Position.X/VOXEL_SIZE,
Position.Y/VOXEL_SIZE,Position.Z/VOXEL_SIZE);
return Vertex;
};
const auto InterpolateVertex=[](const FSkirtVertex& A,const FSkirtVertex& B,float T)
{
if(T<=1.0e-6f)return A;
if(T>=1.0f-1.0e-6f)return B;
FSkirtVertex Out;
Out.Position=FMath::Lerp(A.Position,B.Position,T);
Out.Normal=FMath::Lerp(A.Normal,B.Normal,T).GetSafeNormal();
Out.Color=FColor(
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.R),static_cast<float>(B.Color.R),T)),0,255)),
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.G),static_cast<float>(B.Color.G),T)),0,255)),
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.B),static_cast<float>(B.Color.B),T)),0,255)),
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.A),static_cast<float>(B.Color.A),T)),0,255)));
Out.Density=FMath::Lerp(A.Density,B.Density,T);
Out.OriginalIndex=INDEX_NONE;
return Out;
};
const auto EmitClippedTriangle=[&](const FSkirtVertex& A,const FSkirtVertex& B,
const FSkirtVertex& C,uint8 FaceMask)
{
FSkirtVertex Input[3]={A,B,C};
FSkirtVertex Polygon[5];
int32 PolygonCount=0;
for(int32 Edge=0;Edge<3;++Edge)
{
const FSkirtVertex& Start=Input[Edge];
const FSkirtVertex& End=Input[(Edge+1)%3];
const bool bStartSolid=Start.Density<=0.0f;
const bool bEndSolid=End.Density<=0.0f;
if(bStartSolid!=bEndSolid)
{
const float Denominator=Start.Density-End.Density;
const float T=FMath::Abs(Denominator)>1.0e-8f
?FMath::Clamp(Start.Density/Denominator,0.0f,1.0f):0.5f;
Polygon[PolygonCount++]=InterpolateVertex(Start,End,T);
}
if(bEndSolid)Polygon[PolygonCount++]=End;
}
if(PolygonCount<3)return;
int32 PolygonIndices[5];
for(int32 Vertex=0;Vertex<PolygonCount;++Vertex)
PolygonIndices[Vertex]=Polygon[Vertex].OriginalIndex>=0
?Polygon[Vertex].OriginalIndex
:AddSkirtVert(Polygon[Vertex].Position,Polygon[Vertex].Normal,
Polygon[Vertex].Color);
for(int32 Fan=1;Fan+1<PolygonCount;++Fan)
{
const FSkirtVertex& P0=Polygon[0];
const FSkirtVertex& P1=Polygon[Fan];
const FSkirtVertex& P2=Polygon[Fan+1];
if(ShapeWorldContext)
{
const FVector Center=(P0.Position+P1.Position+P2.Position)
/(3.0f*VOXEL_SIZE);
if(ShapeWorldContext->DensityAt(Center.X,Center.Y,Center.Z)>0.0f)continue;
}
Tris.Add(PolygonIndices[0]);Tris.Add(PolygonIndices[Fan]);Tris.Add(PolygonIndices[Fan+1]);
Tris.Add(PolygonIndices[0]);Tris.Add(PolygonIndices[Fan+1]);Tris.Add(PolygonIndices[Fan]);
FaceMasks.Add(FaceMask);FaceMasks.Add(FaceMask);
}
};
for (int32 t = 0; t + 2 < BaseTriNum; t += 3) for (int32 t = 0; t + 2 < BaseTriNum; t += 3)
{ {
const int32 Tri[3] = { Tris[t], Tris[t + 1], Tris[t + 2] }; const int32 Tri[3] = { Tris[t], Tris[t + 1], Tris[t + 2] };
@@ -1605,26 +1724,29 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
// COPIES par valeur — AddSkirtVert réalloue Vertices/Normals (invaliderait des refs). // COPIES par valeur — AddSkirtVert réalloue Vertices/Normals (invaliderait des refs).
const FVector PA = MeshData.Vertices[iA]; const FVector PA = MeshData.Vertices[iA];
const FVector PB = MeshData.Vertices[iB]; const FVector PB = MeshData.Vertices[iB];
if (!OnBoundaryPlane(PA, PB)) continue; const uint8 FaceMask=BoundaryFaceMask(PA,PB);
if (!FaceMask) continue;
const FVector NA = MeshData.Normals[iA]; const FVector NA = MeshData.Normals[iA];
const FVector NB = MeshData.Normals[iB]; const FVector NB = MeshData.Normals[iB];
const FColor CA = bEmitColors ? MeshData.Colors[iA] : FColor::White; const FColor CA = bEmitColors ? MeshData.Colors[iA] : FColor::White;
const FColor CB = bEmitColors ? MeshData.Colors[iB] : FColor::White; const FColor CB = bEmitColors ? MeshData.Colors[iB] : FColor::White;
const int32 iA2 = AddSkirtVert(PA - NA * SkirtDepth, NA, CA); const FSkirtVertex A=MakeSkirtVertex(PA,NA,CA,true,iA);
const int32 iB2 = AddSkirtVert(PB - NB * SkirtDepth, NB, CB); const FSkirtVertex B=MakeSkirtVertex(PB,NB,CB,true,iB);
const FSkirtVertex A2=MakeSkirtVertex(PA-NA*SkirtDepth,NA,CA,false,INDEX_NONE);
const FSkirtVertex B2=MakeSkirtVertex(PB-NB*SkirtDepth,NB,CB,false,INDEX_NONE);
// Quad (iA, iB, iB2, iA2) → 2 triangles, émis dans LES DEUX orientations. // Clip both source triangles against the solid half-space before emitting
Tris.Add(iA); Tris.Add(iB); Tris.Add(iB2); // each orientation. A curtain that points into tunnel air must not become
Tris.Add(iA); Tris.Add(iB2); Tris.Add(iA2); // visible geometry beyond the sampled isosurface.
Tris.Add(iA); Tris.Add(iB2); Tris.Add(iB); EmitClippedTriangle(A,B,B2,FaceMask);
Tris.Add(iA); Tris.Add(iA2); Tris.Add(iB2); EmitClippedTriangle(A,B2,A2,FaceMask);
} }
} }
MeshData.NumSkirtTriangles += (Tris.Num() - BaseTriNum) / 3; MeshData.NumSkirtTriangles += (Tris.Num() - BaseTriNum) / 3;
}; };
EmitSkirts(GroundTris); EmitSkirts(GroundTris,GroundSkirtFaceMasks);
EmitSkirts(CapTris); EmitSkirts(CapTris,CapSkirtFaceMasks);
} }
// F17 — concatène sol PUIS sky-cap : un run d'indices contigu par polygroup (voir // F17 — concatène sol PUIS sky-cap : un run d'indices contigu par polygroup (voir
@@ -1633,6 +1755,14 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
MeshData.Triangles.Append(GroundTris); MeshData.Triangles.Append(GroundTris);
MeshData.Triangles.Append(CapTris); MeshData.Triangles.Append(CapTris);
MeshData.NumCeilingTriangles = CapTris.Num() / 3; MeshData.NumCeilingTriangles = CapTris.Num() / 3;
MeshData.TriangleSkirtFaceMasks.Reset();
MeshData.TriangleSkirtFaceMasks.Reserve(MeshData.Triangles.Num()/3);
for(int32 Triangle=0;Triangle<GroundSurfaceTriangleCount;++Triangle)
MeshData.TriangleSkirtFaceMasks.Add(0);
MeshData.TriangleSkirtFaceMasks.Append(GroundSkirtFaceMasks);
for(int32 Triangle=0;Triangle<CapSurfaceTriangleCount;++Triangle)
MeshData.TriangleSkirtFaceMasks.Add(0);
MeshData.TriangleSkirtFaceMasks.Append(CapSkirtFaceMasks);
if (VoxelDensityProfile::AreCountersEnabled()) if (VoxelDensityProfile::AreCountersEnabled())
{ {
@@ -370,6 +370,9 @@ struct FVoxelMeshData
TArray<FVector> Normals; // Normale lissée (gradient de densité) TArray<FVector> Normals; // Normale lissée (gradient de densité)
TArray<FColor> Colors; // Masques matériau F6 (R=palette biome dominant, G=pente, TArray<FColor> Colors; // Masques matériau F6 (R=palette biome dominant, G=pente,
// B=poids de fondu de bordure, A=palette biome voisin) // B=poids de fondu de bordure, A=palette biome voisin)
// Parallel to Triangles/3. Zero marks isosurface geometry; skirt triangles carry a
// six-face mask so a streaming host can hide skirts where adjacent tiles use the same LOD.
TArray<uint8> TriangleSkirtFaceMasks;
// Number of vertices on the actual density isosurface. LOD seam skirts are appended after // Number of vertices on the actual density isosurface. LOD seam skirts are appended after
// this prefix and must not be used as witnesses for terrain visibility audits. // this prefix and must not be used as witnesses for terrain visibility audits.
int32 NumSurfaceVertices = 0; int32 NumSurfaceVertices = 0;
@@ -392,6 +395,7 @@ struct FVoxelMeshData
UVs.Empty(); UVs.Empty();
Normals.Empty(); Normals.Empty();
Colors.Empty(); Colors.Empty();
TriangleSkirtFaceMasks.Empty();
NumSurfaceVertices = 0; NumSurfaceVertices = 0;
NumSkirtTriangles = 0; NumSkirtTriangles = 0;
NumCeilingTriangles = 0; NumCeilingTriangles = 0;