wip(assembler): M2 round 3 -- opt-in hybrid sharp mesher (WorldAssemblerHybridV1) in the Unreal Core route

Default MarchingCubesV1 unchanged (W1/W8/W16 = round-6 hashes). Hybrid DC only in LOD0 interior cells
with tagged features; tile-border cells and LOD1+ stay MC. BuildHost: fixtures repeat exactly, cooked
collision bounds = render bounds, same-LOD borders pass, sampled SurfaceWorld cliff bit-identical to MC.
Open: tagged tiles cost 5.7-6.7x MC; corner-top field contact error 20 cm; 5 MC doorway LOD cases.

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-10 09:53:04 +02:00
1 parent a3b7d2015e
commit 22a6b30494
21 files changed
+3009 -23

No files matched your search

@@ -62,6 +62,23 @@ namespace VoxelForgeCore
std::uint32_t skirtTriangleCount = 0;
};
// Versioned render-mesher choices. marchingCubesV1 preserves the production Core path;
// worldAssemblerHybridV1 applies feature-aware DC only to tagged type-2 plan boxes at LOD0.
// Choix versionnes du mailleur. marchingCubesV1 conserve la voie Core actuelle;
// worldAssemblerHybridV1 applique le DC aux boites type 2 balisees, uniquement en LOD0.
enum class MesherRecipe : std::uint8_t
{
marchingCubesV1 = 0,
worldAssemblerHybridV1 = 1
};
struct SharpFeatureBox
{
std::uint64_t stableId = 0;
std::array<std::int64_t, 3> minimumSubvoxels{};
std::array<std::int64_t, 3> maximumSubvoxels{};
};
enum class TileClass : std::uint8_t { allSolid, allAir, mixed, unknown };
struct TileGenerationStats
@@ -657,6 +674,7 @@ namespace VoxelForgeCore
struct TileGenerationOptions
{
MesherRecipe mesherRecipe = MesherRecipe::marchingCubesV1;
std::int32_t bandMinZVoxel = -2147483647 - 1;
std::int32_t bandMaxZVoxel = 2147483647;
bool hashDensity = false;
+49 -2
View File
@@ -890,11 +890,10 @@ namespace VoxelForgeCore
if(const auto found=impl_->tileRegions.find(key);found!=impl_->tileRegions.end())
page=found->second;
}
const FStandaloneShapeWorld& tileShape=page?*page:*impl_->shape;
const FStandaloneShapeWorld* tileShapeContext=page?page.get():impl_->shape.get();
UVoxelMarchingCubesMesher mesher;
mesher.SetGenerator(world.Generator.get());
mesher.SetGeometryVoxelSizeCm(25.0f);
mesher.SetStandaloneShapeWorld(&tileShape);
mesher.bStandaloneSkipUvAndColors=options.skipUvAndColors;
mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors;
mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups;
@@ -904,6 +903,54 @@ namespace VoxelForgeCore
const int32 level=key.lod,extent=CHUNK_SIZE<<level;
const FVoxelTileKey nativeKey=ToNative(key);
const FIntVector origin=nativeKey.OriginVoxels();
if(options.mesherRecipe!=MesherRecipe::marchingCubesV1
&&options.mesherRecipe!=MesherRecipe::worldAssemblerHybridV1)
{error="tile mesher recipe version is unsupported";return std::nullopt;}
TArray<FVoxelHybridSharpFeatureBox> sharpFeatures;
if(options.mesherRecipe==MesherRecipe::worldAssemblerHybridV1&&level==0&&impl_->worldAssembly)
{
constexpr std::int64_t SubvoxelsPerVoxel=WorldAssemblySubvoxelsPerVoxel;
const std::array<std::int64_t,3> tileMin={
static_cast<std::int64_t>(origin.X)*SubvoxelsPerVoxel,
static_cast<std::int64_t>(origin.Y)*SubvoxelsPerVoxel,
static_cast<std::int64_t>(origin.Z)*SubvoxelsPerVoxel};
const std::array<std::int64_t,3> tileMax={
static_cast<std::int64_t>(origin.X+extent)*SubvoxelsPerVoxel,
static_cast<std::int64_t>(origin.Y+extent)*SubvoxelsPerVoxel,
static_cast<std::int64_t>(origin.Z+extent)*SubvoxelsPerVoxel};
for(const auto& stratum:impl_->worldAssembly->plan.strates)
for(const auto& feature:stratum.features)
{
// Type 2 is the plan's finite, hard box-solid exception; the type-4 shelf is
// smooth-min blended and therefore remains on MC until it carries sharp planes.
// Le type 2 est une boite solide nette; l'etagere type 4 reste MC car elle est lisse.
if(feature.type!=2)continue;
bool overlaps=true;
for(std::size_t axis=0;axis<3;++axis)
overlaps=overlaps&&feature.minimumSubvoxels[axis]<=tileMax[axis]
&&feature.maximumSubvoxels[axis]>=tileMin[axis];
if(!overlaps)continue;
FVoxelHybridSharpFeatureBox box;
box.StableId=feature.stableId;
for(std::size_t axis=0;axis<3;++axis)
{
box.MinimumSubvoxels[axis]=feature.minimumSubvoxels[axis];
box.MaximumSubvoxels[axis]=feature.maximumSubvoxels[axis];
}
sharpFeatures.Add(box);
}
if(!sharpFeatures.IsEmpty())
{
// Streamed analytic pages omit the native assembly plan. Use the immutable base
// shape only for tagged tiles so their mesher samples the same field as the tags.
// Les pages analytiques omettent le plan natif ; utiliser la base uniquement pour
// les tuiles balisees afin d'echantillonner le champ correspondant aux tags.
tileShapeContext=impl_->shape.get();
mesher.SetHybridSharpFeatures(MoveTemp(sharpFeatures));
}
}
const FStandaloneShapeWorld& tileShape=*tileShapeContext;
mesher.SetStandaloneShapeWorld(&tileShape);
int32 cells=level<world.FullResClipLevels?CHUNK_SIZE:FMath::Max(1,world.CoarseTileCells);
int32 step=FMath::Max(1,extent/cells);
bool bFeatureRefinedResolution=false;