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

+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;