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:
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user