diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 4102725..155191f 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -1299,8 +1299,30 @@ private: 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; + const float Floor=std::min(Cave,std::clamp(FloorInfluence,0.0f,1.0f)); + const float StrataWeight=1.0f-Cave; + const float CaveWeight=Cave-Floor; + const float FloorWeight=Floor; + // The three influences are a partition of unity. Cave floors blend at the + // cave family's amplitude instead of multiplying both family contributions. + return 1.35f*StrataWeight+1.00f*CaveWeight+0.55f*FloorWeight; + } + static float BlendDetailFamilies(float Relief,float OwnerRelief, + float CaveInfluence,float FloorInfluence,float PassageInfluence) + { + const float Cave=std::clamp(CaveInfluence,0.0f,1.0f); + const float Floor=std::min(Cave,std::clamp(FloorInfluence,0.0f,1.0f)); + const float Passage=std::clamp(PassageInfluence,0.0f,1.0f); + const float NonPassageWeight=1.0f-Passage; + const float StrataWeight=NonPassageWeight*(1.0f-Cave); + const float CaveWeight=NonPassageWeight*(Cave-Floor); + const float FloorWeight=NonPassageWeight*Floor; + const float StrataRelief=1.35f*(Relief+0.5f*OwnerRelief); + const float CaveRelief=1.00f*Relief; + const float FloorRelief=0.55f*Relief; + const float PassageRelief=0.75f*Relief; + return StrataWeight*StrataRelief+CaveWeight*CaveRelief + +FloorWeight*FloorRelief+Passage*PassageRelief; } static constexpr float DetailBandInnerRadius=1.5f; static float DetailBandFade(float SignedDistance,float Edge) @@ -3883,8 +3905,8 @@ private: if(Field) OwnerRelief=OwnerCaveRoughnessForDetail(X,Y,Z,SurfaceSDF,*Field, CaveInfluence,PassageWeight,OwnerNoise); - const float Contrast=DetailContrast(CaveInfluence,FloorInfluence); - const float Detail=std::clamp((Relief+OwnerRelief*0.5f)*Contrast,-3.9f,3.9f); + const float Detail=std::clamp(BlendDetailFamilies(Relief,OwnerRelief, + CaveInfluence,FloorInfluence,PassageWeight),-3.9f,3.9f); return Detail; } @@ -3920,8 +3942,8 @@ private: if(Profile) Profile->OwnerRoughnessSampleNanoseconds+=static_cast( std::chrono::duration_cast(Clock::now()-OwnerStart).count()); - const float Contrast=DetailContrast(CaveInfluence,FloorInfluence); - const float Detail=std::clamp((Relief+OwnerRelief*0.5f)*Contrast,-3.9f,3.9f); + const float Detail=std::clamp(BlendDetailFamilies(Relief,OwnerRelief, + CaveInfluence,FloorInfluence,PassageWeight),-3.9f,3.9f); return Detail; } diff --git a/Standalone/Core/verification/owner23_candidate31_owner_ray_after.png b/Standalone/Core/verification/owner23_candidate31_owner_ray_after.png new file mode 100644 index 0000000..fbd55a7 Binary files /dev/null and b/Standalone/Core/verification/owner23_candidate31_owner_ray_after.png differ diff --git a/Standalone/Core/verification/owner23_candidate51_owner_ray_after.png b/Standalone/Core/verification/owner23_candidate51_owner_ray_after.png new file mode 100644 index 0000000..8bf11a7 Binary files /dev/null and b/Standalone/Core/verification/owner23_candidate51_owner_ray_after.png differ diff --git a/Standalone/Source/StandaloneViewer.cpp b/Standalone/Source/StandaloneViewer.cpp index d8a7706..ce4f74d 100644 --- a/Standalone/Source/StandaloneViewer.cpp +++ b/Standalone/Source/StandaloneViewer.cpp @@ -70,10 +70,12 @@ namespace ReverseSideSolidSkirtTriangles=0,RepeatedOffsetPatches=0,FieldMismatchPatches=0, NormalMismatchPatches=0,GradientFallbackPatches=0,GradientFallbackSamples=0, DetailDisplacementSamples=0,HighDetailDisplacementSamples=0,DetailDisplacementPatches=0, - CurvedEdgeChecks=0,CurvedEdgeRootPatches=0,CurvedEdgeRootBadVertices=0; + CurvedEdgeChecks=0,CurvedEdgeRootPatches=0,CurvedEdgeRootBadVertices=0, + DetailGradientSamples=0,DetailGradientOverLimit=0; double MaximumAirSkirtDensity=0.0,MaximumPatchDominance=0.0; double MaximumDetailDisplacement=0.0; double MaximumCurvedEdgeRootError=0.0; + double MaximumDetailGradient=0.0; uint64 FieldEdgeChecks=0,AnalyticCrossingMisses=0,OldInt8RangeEdges=0,NormalChecks=0; double SumAbsSourceResidual=0.0,MaximumAbsSourceResidual=0.0, SumAnalyticEdgeShiftVoxels=0.0,MaximumAnalyticEdgeShiftVoxels=0.0, @@ -173,6 +175,26 @@ namespace {P[0],P[1],P[2]}); if(DetailSample.caveInfluence>=0.60f&&DetailSample.floorInfluence>=0.60f) { + constexpr double GradientProbeOffset=0.5; + double DetailGradientSquared=0.0; + for(int32 Axis=0;Axis<3;++Axis) + { + double Minus[3]={P[0],P[1],P[2]}; + double Plus[3]={P[0],P[1],P[2]}; + Minus[Axis]-=GradientProbeOffset; + Plus[Axis]+=GradientProbeOffset; + const double DMinus=Shape.detailSampleAt( + {Minus[0],Minus[1],Minus[2]}).displacement; + const double DPlus=Shape.detailSampleAt( + {Plus[0],Plus[1],Plus[2]}).displacement; + const double Gradient=(DPlus-DMinus)/(2.0*GradientProbeOffset); + DetailGradientSquared+=Gradient*Gradient; + } + const double DetailGradient=std::sqrt(DetailGradientSquared); + ++Result.DetailGradientSamples; + Result.MaximumDetailGradient=std::max( + Result.MaximumDetailGradient,DetailGradient); + if(DetailGradient>0.75)++Result.DetailGradientOverLimit; const double DetailDisplacement=std::abs(DetailSample.displacement); ++Result.DetailDisplacementSamples; Result.MaximumDetailDisplacement=std::max( @@ -818,15 +840,32 @@ namespace if(NormalLength>1.0e-8)for(double& Value:Normal)Value/=NormalLength; const double Inward1=Shape.densityAt({Center.x-Normal[0],Center.y-Normal[1],Center.z-Normal[2]}); const double Inward2=Shape.densityAt({Center.x-Normal[0]*2.0,Center.y-Normal[1]*2.0,Center.z-Normal[2]*2.0}); - if(Density>0.25) + double MaximumProbeDensity=Density; + const double ProbeWeights[10][3]={ + {1.0,0.0,0.0},{0.0,1.0,0.0},{0.0,0.0,1.0}, + {1.0/3.0,1.0/3.0,1.0/3.0}, + {0.5,0.5,0.0},{0.0,0.5,0.5},{0.5,0.0,0.5}, + {0.75,0.125,0.125},{0.125,0.75,0.125},{0.125,0.125,0.75}}; + for(const auto& Weights:ProbeWeights) + { + const VoxelForgeCore::Vec3d Probe{ + PA[0]*Weights[0]+PB[0]*Weights[1]+PC[0]*Weights[2], + PA[1]*Weights[0]+PB[1]*Weights[1]+PC[1]*Weights[2], + PA[2]*Weights[0]+PB[2]*Weights[1]+PC[2]*Weights[2]}; + MaximumProbeDensity=std::max(MaximumProbeDensity,static_cast( + Shape.densityAt({Probe.x*4.0,Probe.y*4.0,Probe.z*4.0}))); + } + if(MaximumProbeDensity>0.25) { ++Result.ExposedSkirtTriangles; - Result.MaximumAirSkirtDensity=std::max(Result.MaximumAirSkirtDensity,Density); + Result.MaximumAirSkirtDensity=std::max( + Result.MaximumAirSkirtDensity,MaximumProbeDensity); if(AlternativeDensity<-0.25)++Result.ReverseSideSolidSkirtTriangles; if(bPrintSkirts&&Result.ExposedSkirtTriangles<=8) - std::printf("[ExposedTileSkirt] level=%d tile=(%d,%d,%d) center=(%.2f,%.2f,%.2f) density=%.3f reverse_density=%.3f inward=(%.3f,%.3f) normal=(%.3f,%.3f,%.3f)\n", + std::printf("[ExposedTileSkirt] level=%d tile=(%d,%d,%d) center=(%.2f,%.2f,%.2f) center_density=%.3f max_probe_density=%.3f reverse_density=%.3f inward=(%.3f,%.3f) normal=(%.3f,%.3f,%.3f)\n", Tile.Key.Level,Tile.Key.Coord.X,Tile.Key.Coord.Y,Tile.Key.Coord.Z, - Center.x,Center.y,Center.z,Density,AlternativeDensity,Inward1,Inward2, + Center.x,Center.y,Center.z,Density,MaximumProbeDensity, + AlternativeDensity,Inward1,Inward2, Normal[0],Normal[1],Normal[2]); } } @@ -2138,16 +2177,17 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; { for(const FVoxelTileKey& Key:Leavers)RemoveResidentTile(Key); } - DesiredTiles=std::move(NewDesired.Tiles); + DesiredTiles=NewDesired.Tiles; DesiredTileSet=std::move(NewSet); RefreshResidentSkirtMasks(); for(int32 I=0;I<=MaxLevels;++I)DesiredByLevel[I]=0; for(const FVoxelTileKey& Tile:DesiredTiles)++DesiredByLevel[Tile.Level]; std::vector ResidentKeys;ResidentKeys.reserve(ResidentTiles.size()); for(const auto& Pair:ResidentTiles) - if(NewSet.contains(Pair.first))ResidentKeys.push_back(Pair.first); + if(DesiredTileSet.contains(Pair.first))ResidentKeys.push_back(Pair.first); ++CrossingIndex; - const FStandaloneStreamingUpdate Update=Streamer->UpdateDesired(CameraVoxels,ResidentKeys,CrossingIndex); + const FStandaloneStreamingUpdate Update=Streamer->UpdateDesiredWithSet( + std::move(NewDesired),ResidentKeys,CrossingIndex); for(const auto& Pair:Update.CancelledByCrossing) { FCrossingWork& Work=CrossingWork[Pair.first]; @@ -3525,6 +3565,10 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; MeshArtifactStats.DetailDisplacementPatches+=TileAudit.DetailDisplacementPatches; MeshArtifactStats.MaximumDetailDisplacement=std::max( MeshArtifactStats.MaximumDetailDisplacement,TileAudit.MaximumDetailDisplacement); + MeshArtifactStats.DetailGradientSamples+=TileAudit.DetailGradientSamples; + MeshArtifactStats.DetailGradientOverLimit+=TileAudit.DetailGradientOverLimit; + MeshArtifactStats.MaximumDetailGradient=std::max( + MeshArtifactStats.MaximumDetailGradient,TileAudit.MaximumDetailGradient); MeshArtifactStats.CurvedEdgeChecks+=TileAudit.CurvedEdgeChecks; MeshArtifactStats.CurvedEdgeRootPatches+=TileAudit.CurvedEdgeRootPatches; MeshArtifactStats.CurvedEdgeRootBadVertices+=TileAudit.CurvedEdgeRootBadVertices; @@ -3690,12 +3734,25 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; MeshArtifactStats.WorstShiftExactD1,MeshArtifactStats.MaximumAbsSourceResidual, MeshArtifactStats.WorstResidualPosition[0],MeshArtifactStats.WorstResidualPosition[1], MeshArtifactStats.WorstResidualPosition[2]); - std::printf("[MeshCurvedEdgeRootAudit] midpoint_edges=%llu bad_vertices=%llu patches=%llu max_midpoint_root_error=%.4f vox error_threshold=0.080 status=%s\n", + const uint64 RootBadLimit=std::max(64, + MeshArtifactStats.CurvedEdgeChecks*35u/1000u); + const double RootBadShare=MeshArtifactStats.CurvedEdgeChecks + ?static_cast(MeshArtifactStats.CurvedEdgeRootBadVertices) + /MeshArtifactStats.CurvedEdgeChecks:0.0; + const bool bRootAuditPass=MeshArtifactStats.CurvedEdgeRootPatches==0 + &&MeshArtifactStats.CurvedEdgeRootBadVertices<=RootBadLimit; + std::printf("[MeshCurvedEdgeRootAudit] midpoint_edges=%llu bad_vertices=%llu bad_share=%.3f%% allowed=%llu patches=%llu max_midpoint_root_error=%.4f vox error_threshold=0.080 status=%s\n", static_cast(MeshArtifactStats.CurvedEdgeChecks), static_cast(MeshArtifactStats.CurvedEdgeRootBadVertices), + RootBadShare*100.0,static_cast(RootBadLimit), static_cast(MeshArtifactStats.CurvedEdgeRootPatches), MeshArtifactStats.MaximumCurvedEdgeRootError, - MeshArtifactStats.CurvedEdgeRootPatches==0?"PASS":"FAIL"); + bRootAuditPass?"PASS":"FAIL"); + std::printf("[MeshDetailGradientAudit] overlap_samples=%llu over_limit=%llu max_gradient=%.4f vox_per_vox limit=0.750 status=%s\n", + static_cast(MeshArtifactStats.DetailGradientSamples), + static_cast(MeshArtifactStats.DetailGradientOverLimit), + MeshArtifactStats.MaximumDetailGradient, + MeshArtifactStats.DetailGradientOverLimit==0?"PASS":"FAIL"); std::printf("[MeshSurfaceArtifactAudit] levels=0,1 tiles=%llu surface_vertices=%llu edge_vertices=%llu midpoint_band_vertices=%llu midpoint_share=%.2f%% visible_skirt_triangles=%llu hidden_same_lod_skirt_triangles=%llu exposed_skirt_triangles=%llu reverse_side_solid=%llu max_air_density=%.3f vox repeated_offset_patches=%llu field_residual_patches=%llu normal_mismatch_patches=%llu gradient_fallback_patches=%llu fallback_gradient_samples=%llu high_detail_vertices=%llu/%llu high_detail_patches=%llu max_detail_displacement=%.3f vox max_patch_dominance=%.3f status=%s\n", static_cast(MeshArtifactStats.Tiles), static_cast(MeshArtifactStats.SurfaceVertices), @@ -3721,7 +3778,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; &&MeshArtifactStats.NormalMismatchPatches==0 &&MeshArtifactStats.GradientFallbackPatches==0 &&MeshArtifactStats.DetailDisplacementPatches==0 - &&MeshArtifactStats.CurvedEdgeRootPatches==0?"PASS":"FAIL"); + &&bRootAuditPass + &&MeshArtifactStats.DetailGradientOverLimit==0?"PASS":"FAIL"); std::printf("[CoarsePanelAudit] tiles=%llu planar_open_components=%llu off_surface_panels=%llu panel_triangles=%llu max_source_error=%.3f vox status=%s\n", static_cast(CoarsePanelStats.Tiles), static_cast(CoarsePanelStats.PlanarOpenComponents), @@ -3775,7 +3833,10 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; if(bShardAudit&&CoreWorld&&(MeshArtifactStats.ExposedSkirtTriangles!=0 ||MeshArtifactStats.RepeatedOffsetPatches!=0||MeshArtifactStats.FieldMismatchPatches!=0 ||MeshArtifactStats.NormalMismatchPatches!=0||MeshArtifactStats.GradientFallbackPatches!=0 - ||MeshArtifactStats.CurvedEdgeRootPatches!=0))return 13; + ||MeshArtifactStats.CurvedEdgeRootPatches!=0 + ||MeshArtifactStats.CurvedEdgeRootBadVertices>std::max( + 64,MeshArtifactStats.CurvedEdgeChecks*35u/1000u) + ||MeshArtifactStats.DetailGradientOverLimit!=0))return 13; if(bShardAudit&&CoreWorld&&CoarsePanelStats.OffSurfacePanels!=0)return 13; if(FarAudit) { diff --git a/Standalone/Source/StreamingWorld.h b/Standalone/Source/StreamingWorld.h index 1590e8f..c309ea1 100644 --- a/Standalone/Source/StreamingWorld.h +++ b/Standalone/Source/StreamingWorld.h @@ -483,6 +483,13 @@ public: return UpdateDesiredSet(std::move(NewDesired),ResidentKeys,CrossingIndex); } + FStandaloneStreamingUpdate UpdateDesiredWithSet( + FStandaloneDesiredTileSet NewDesired,const std::vector& ResidentKeys, + uint64 CrossingIndex=0) + { + return UpdateDesiredSet(std::move(NewDesired),ResidentKeys,CrossingIndex); + } + std::vector TakeReady() { std::vector Out; diff --git a/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp b/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp index a9d3de6..3c7bed5 100644 --- a/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp +++ b/Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp @@ -25,6 +25,18 @@ namespace GVoxelForgeUseOperatorBlock, TEXT("Use the operator-major density block path for non-shared mesher grids.")); bool GVoxelForgeBlockSwitchParsed = false; + int32 GVoxelForgeRawSkirtsForAudit = 0; + bool GVoxelForgeRawSkirtsSwitchParsed = false; + + void VF_ParseRawSkirtsAuditSwitch() + { + if(GVoxelForgeRawSkirtsSwitchParsed)return; + GVoxelForgeRawSkirtsSwitchParsed=true; + GVoxelForgeRawSkirtsForAudit=FString(FCommandLine::Get()).Contains( + TEXT("--audit-raw-skirts"))?1:0; + if(GVoxelForgeRawSkirtsForAudit) + std::printf("[SkirtAuditControl] raw skirt curtains enabled for negative-control scan\n"); + } int32 GVoxelForgeTilePostReachDebug = 0; FAutoConsoleVariableRef CVarVoxelForgeTilePostReachDebug( @@ -93,6 +105,7 @@ void UVoxelMarchingCubesMesher::InitializeConsoleSwitches() { VF_ParseOperatorBlockSwitch(); VF_ParseTilePostReachSwitches(); + VF_ParseRawSkirtsAuditSwitch(); } //============================================================================= @@ -562,7 +575,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, VoxelDensityProfile::RecordClassifierCall(bSampleClassifier); } EVoxelTileClass Verdict; - if(ShapeWorldContext) + if(ShapeWorldContext&&Step<=2) { const auto ShapeVerdict=ShapeWorldContext->ClassifyBox( BlockOrigin,Step,BlockCells); @@ -1631,6 +1644,23 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, return Idx; }; + std::vector SurfaceVertexDensitySamples; + if(ShapeWorldContext) + SurfaceVertexDensitySamples.assign(static_cast(MeshData.Vertices.Num()), + std::numeric_limits::quiet_NaN()); + const auto SampleSurfaceVertexDensity=[&](int32 OriginalIndex,const FVector& Position) + { + if(!ShapeWorldContext)return 0.0f; + if(OriginalIndex<0||static_cast(OriginalIndex)>=SurfaceVertexDensitySamples.size()) + return ShapeWorldContext->DensityAt(Position.X/VOXEL_SIZE, + Position.Y/VOXEL_SIZE,Position.Z/VOXEL_SIZE); + float& Density=SurfaceVertexDensitySamples[static_cast(OriginalIndex)]; + if(std::isnan(Density)) + Density=ShapeWorldContext->DensityAt(Position.X/VOXEL_SIZE, + Position.Y/VOXEL_SIZE,Position.Z/VOXEL_SIZE); + return Density; + }; + // 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 // d'indices contigus par groupe qu'exige RMC. @@ -1655,7 +1685,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, Position.Y/VOXEL_SIZE,Position.Z/VOXEL_SIZE); return Vertex; }; - const auto InterpolateVertex=[](const FSkirtVertex& A,const FSkirtVertex& B,float T) + 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; @@ -1667,13 +1697,31 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, static_cast(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast(A.Color.G),static_cast(B.Color.G),T)),0,255)), static_cast(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast(A.Color.B),static_cast(B.Color.B),T)),0,255)), static_cast(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast(A.Color.A),static_cast(B.Color.A),T)),0,255))); - Out.Density=FMath::Lerp(A.Density,B.Density,T); + Out.Density=ShapeWorldContext + ?ShapeWorldContext->DensityAt(Out.Position.X/VOXEL_SIZE, + Out.Position.Y/VOXEL_SIZE,Out.Position.Z/VOXEL_SIZE) + :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) + const auto EmitClippedTriangle=[&](auto&& Self,const FSkirtVertex& A, + const FSkirtVertex& B,const FSkirtVertex& C,uint8 FaceMask, + int32 RefinementDepth)->void { + if(GVoxelForgeRawSkirtsForAudit) + { + const FSkirtVertex Input[3]={A,B,C}; + int32 Indices[3]; + for(int32 Vertex=0;Vertex<3;++Vertex) + Indices[Vertex]=Input[Vertex].OriginalIndex>=0 + ?Input[Vertex].OriginalIndex + :AddSkirtVert(Input[Vertex].Position,Input[Vertex].Normal, + Input[Vertex].Color); + Tris.Add(Indices[0]);Tris.Add(Indices[1]);Tris.Add(Indices[2]); + Tris.Add(Indices[0]);Tris.Add(Indices[2]);Tris.Add(Indices[1]); + FaceMasks.Add(FaceMask);FaceMasks.Add(FaceMask); + return; + } FSkirtVertex Input[3]={A,B,C}; FSkirtVertex Polygon[5]; int32 PolygonCount=0; @@ -1704,7 +1752,56 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, const FSkirtVertex& P0=Polygon[0]; const FSkirtVertex& P1=Polygon[Fan]; const FSkirtVertex& P2=Polygon[Fan+1]; - if(ShapeWorldContext) + if(ShapeWorldContext&&Step<=2) + { + const auto ProbeDensity=[&](double WA,double WB,double WC) + { + const FVector Position=P0.Position*static_cast(WA) + +P1.Position*static_cast(WB) + +P2.Position*static_cast(WC); + return ShapeWorldContext->DensityAt(Position.X/VOXEL_SIZE, + Position.Y/VOXEL_SIZE,Position.Z/VOXEL_SIZE); + }; + const auto VertexProbeDensity=[&](const FSkirtVertex& Vertex) + { + return Vertex.OriginalIndex>=0 + ?SampleSurfaceVertexDensity(Vertex.OriginalIndex,Vertex.Position) + :Vertex.Density; + }; + float MaximumProbeDensity=FMath::Max(VertexProbeDensity(P0), + FMath::Max(VertexProbeDensity(P1),VertexProbeDensity(P2))); + const double ProbeWeights[4][3]={ + {1.0/3.0,1.0/3.0,1.0/3.0}, + {0.5,0.5,0.0},{0.0,0.5,0.5},{0.5,0.0,0.5}}; + const int32 ProbeCount=RefinementDepth==0?4:1; + for(int32 Probe=0;Probe0.25f)break; + } + if(MaximumProbeDensity>0.25f) + { + if(RefinementDepth>=1)continue; + const auto MakeMidpoint=[&](const FSkirtVertex& X, + const FSkirtVertex& Y) + { + FSkirtVertex Mid=InterpolateVertex(X,Y,0.5f); + Mid.OriginalIndex=INDEX_NONE; + return Mid; + }; + const FSkirtVertex M01=MakeMidpoint(P0,P1); + const FSkirtVertex M12=MakeMidpoint(P1,P2); + const FSkirtVertex M20=MakeMidpoint(P2,P0); + Self(Self,P0,M01,M20,FaceMask,RefinementDepth+1); + Self(Self,M01,P1,M12,FaceMask,RefinementDepth+1); + Self(Self,M20,M12,P2,FaceMask,RefinementDepth+1); + Self(Self,M01,M12,M20,FaceMask,RefinementDepth+1); + continue; + } + } + else if(ShapeWorldContext) { const FVector Center=(P0.Position+P1.Position+P2.Position) /(3.0f*VOXEL_SIZE); @@ -1739,8 +1836,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, // Clip both source triangles against the solid half-space before emitting // each orientation. A curtain that points into tunnel air must not become // visible geometry beyond the sampled isosurface. - EmitClippedTriangle(A,B,B2,FaceMask); - EmitClippedTriangle(A,B2,A2,FaceMask); + EmitClippedTriangle(EmitClippedTriangle,A,B,B2,FaceMask,0); + EmitClippedTriangle(EmitClippedTriangle,A,B2,A2,FaceMask,0); } } MeshData.NumSkirtTriangles += (Tris.Num() - BaseTriNum) / 3;