wip(assembler): round L18 -- FloatingIslands false floors removed; shared strate ceiling

FI geometry v3 drops the all-XY lower boundary seal that created false floors (3.5/8 m); the
ceiling comes from the shared VF_ApplyUpperBoundarySeal and AppendStratumBoundaryCeilings path
for every generator, at each strate's own configured upper boundary (owner: every strate has a
ceiling). Far-LOD ceiling continuity: 0 gaps at LOD4-8. SurfaceWorld bit-identical to L14
(14 configs + 24 m crossing); FI deterministic (28-row matrix); route parity and proofs pass.
Unreal: FI geometryVersion 2->3 and the shared upper-seal helper extracted (UBT compile needed on
the owner PC). Open: small ceiling holes; lower-stratum LOD ring steps seen through the FI void.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-20 07:55:53 +02:00
1 parent 87041290dd
commit dcf0763ec5
36 files changed
+1690 -54

No files matched your search

+171 -5
View File
@@ -864,6 +864,46 @@ struct FFarMeshColumnIndex
return Hits;
}
bool HasDownwardHit(double X,double Y,double Z,double Tolerance) const
{
const int32 CX=std::clamp(static_cast<int32>(std::floor((X-Origin.X)/Step)),0,Cells-1);
const int32 CY=std::clamp(static_cast<int32>(std::floor((Y-Origin.Y)/Step)),0,Cells-1);
const auto& Candidates=ByColumn[static_cast<size_t>(CY)*Cells+CX];
for(const int32 Triangle:Candidates)
{
const int32 IA=Mesh.Triangles[Triangle*3],IB=Mesh.Triangles[Triangle*3+1],
IC=Mesh.Triangles[Triangle*3+2];
if(IA<0||IB<0||IC<0||IA>=Mesh.Normals.Num()||IB>=Mesh.Normals.Num()
||IC>=Mesh.Normals.Num())continue;
if(Mesh.Normals[IA].Z+Mesh.Normals[IB].Z+Mesh.Normals[IC].Z>=-1.5)continue;
const double AX=PositionAxis(IA,0),AY=PositionAxis(IA,1),AZ=PositionAxis(IA,2);
const double BX=PositionAxis(IB,0),BY=PositionAxis(IB,1),BZ=PositionAxis(IB,2);
const double CXv=PositionAxis(IC,0),CYv=PositionAxis(IC,1),CZ=PositionAxis(IC,2);
const double Den=(BX-AX)*(CYv-AY)-(BY-AY)*(CXv-AX);
if(std::abs(Den)<1.0e-10)continue;
const double U=((X-AX)*(CYv-AY)-(Y-AY)*(CXv-AX))/Den;
const double V=((BX-AX)*(Y-AY)-(BY-AY)*(X-AX))/Den;
const double W=1.0-U-V;
if(U<-1.0e-7||V<-1.0e-7||W<-1.0e-7)continue;
const double Hit=AZ+U*(BZ-AZ)+V*(CZ-AZ);
if(std::abs(Hit-Z)<=Tolerance)return true;
}
return false;
}
bool HasDownwardCellCoverage(double X0,double Y0,double X1,double Y1,
double Z,double Tolerance) const
{
constexpr double Samples[3]={0.125,0.5,0.875};
for(const double V:Samples)for(const double U:Samples)
{
const double X=X0+(X1-X0)*U;
const double Y=Y0+(Y1-Y0)*V;
if(!HasDownwardHit(X,Y,Z,Tolerance))return false;
}
return true;
}
private:
double PositionAxis(int32 Index,int32 Axis) const
{
@@ -974,11 +1014,11 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
std::vector<FBoundary> Boundaries;
for(const FStrateSlot& Slot:Layout)
{
const FStrateGenerationParams Params=World.Manager->GetGenerationParams(
FIntVector(0,0,(Slot.TopChunkZ+Slot.BottomChunkZ)/2));
const double HalfWidth=std::max(8.0,static_cast<double>(Params.BoundarySealThickness)+8.0);
Boundaries.push_back({static_cast<double>(Slot.BottomChunkZ)*CHUNK_SIZE,HalfWidth});
Boundaries.push_back({static_cast<double>(Slot.TopChunkZ+1)*CHUNK_SIZE,HalfWidth});
FStandaloneShapeWorld::FResolvedStratumBounds Resolved;
if(!Shape.GetResolvedStratumBounds(Slot,Resolved))continue;
const double HalfWidth=std::max(8.0,Resolved.SealThickness+8.0);
Boundaries.push_back({Resolved.BottomZ,HalfWidth});
Boundaries.push_back({Resolved.TopZ,HalfWidth});
}
std::sort(Boundaries.begin(),Boundaries.end(),[](const FBoundary& A,const FBoundary& B){return A.Z<B.Z;});
Boundaries.erase(std::unique(Boundaries.begin(),Boundaries.end(),[](const FBoundary& A,const FBoundary& B)
@@ -1132,3 +1172,129 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
Mesh.TriangleSkirtFaceMasks=std::move(CombinedFaceMasks);
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
}
inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,int32 Step,FVoxelMeshData& Mesh)
{
if(Key.Level<0||Step<=0||!World.Manager)return;
const FIntVector Origin=Key.OriginVoxels();
const int32 Extent=Key.ExtentVoxels();
const int32 GridStep=Step;
if(Extent<=0||GridStep<=0||Extent%GridStep!=0)return;
const int32 GridSide=Extent/GridStep+1;
struct FCandidate
{
FStandaloneShapeWorld::FResolvedStratumBounds Bounds;
std::vector<uint8> Crossing;
};
std::vector<FCandidate> Candidates;
for(const FStrateSlot& Slot:World.Manager->GetLayout())
{
FCandidate Candidate;
if(!Shape.GetResolvedStratumBounds(Slot,Candidate.Bounds)
||Candidate.Bounds.SealThickness<=0.0
||Candidate.Bounds.TopZ<Origin.Z-1.0e-6
||Candidate.Bounds.TopZ>Origin.Z+Extent+1.0e-6)continue;
const double Epsilon=std::min(0.5,Candidate.Bounds.SealThickness*0.25);
if(Epsilon<=0.0)continue;
Candidate.Crossing.resize(static_cast<std::size_t>(GridSide)*GridSide,0);
bool AnyCrossing=false;
for(int32 Y=0;Y<GridSide;++Y)for(int32 X=0;X<GridSide;++X)
{
const double WorldX=Origin.X+X*GridStep;
const double WorldY=Origin.Y+Y*GridStep;
const float Below=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY),
static_cast<float>(Candidate.Bounds.TopZ-Epsilon));
const float Above=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY),
static_cast<float>(Candidate.Bounds.TopZ+Epsilon));
const bool bCrosses=std::isfinite(Below)&&std::isfinite(Above)&&Below<0.0f&&Above>0.0f;
Candidate.Crossing[static_cast<std::size_t>(Y*GridSide+X)]=bCrosses?1:0;
AnyCrossing=AnyCrossing||bCrosses;
}
if(AnyCrossing)Candidates.push_back(std::move(Candidate));
}
if(Candidates.empty())return;
const int32 Cells=std::max(1,World.CoarseTileCells);
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step);
const int32 OldVertexCount=Mesh.Vertices.Num();
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
const bool bHasUVs=Mesh.UVs.Num()==OldVertexCount;
const bool bHasColors=Mesh.Colors.Num()==OldVertexCount;
const FColor Color=bHasColors&&OldVertexCount>0?Mesh.Colors[0]:FColor(160,160,160,255);
const double VoxelSizeCm=static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0;
std::vector<FVector> NewVertices,NewNormals;
std::vector<FVector2f> NewUVs;
std::vector<FColor> NewColors;
std::vector<int32> NewTriangles;
for(const FCandidate& Candidate:Candidates)
{
std::vector<int32> VertexMap(static_cast<std::size_t>(GridSide)*GridSide,INDEX_NONE);
const double CeilingZ=Candidate.Bounds.TopZ;
const auto AddVertex=[&](int32 X,int32 Y)
{
const std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X);
int32& Mapped=VertexMap[GridIndex];
if(Mapped!=INDEX_NONE)return Mapped;
Mapped=static_cast<int32>(NewVertices.size());
const double WorldX=Origin.X+X*GridStep,WorldY=Origin.Y+Y*GridStep;
NewVertices.emplace_back(WorldX*VoxelSizeCm,WorldY*VoxelSizeCm,CeilingZ*VoxelSizeCm);
NewNormals.emplace_back(0.0,0.0,-1.0);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>(WorldX),static_cast<float>(WorldY));
if(bHasColors)NewColors.push_back(Color);
return Mapped;
};
for(int32 Y=0;Y+1<GridSide;++Y)for(int32 X=0;X+1<GridSide;++X)
{
const std::size_t I00=static_cast<std::size_t>(Y*GridSide+X);
const std::size_t I10=I00+1,I01=I00+GridSide,I11=I01+1;
if(!Candidate.Crossing[I00]||!Candidate.Crossing[I10]
||!Candidate.Crossing[I01]||!Candidate.Crossing[I11])continue;
const double CellX0=Origin.X+X*GridStep,CellY0=Origin.Y+Y*GridStep;
const double CellX1=CellX0+GridStep,CellY1=CellY0+GridStep;
// Suppress a coincident plane only when it covers the full cell. A hit at just the
// center of a curved shell triangle is not enough to leave the rest of this cap cell open.
if(MeshIndex.HasDownwardCellCoverage(CellX0,CellY0,CellX1,CellY1,CeilingZ,0.001))continue;
const int32 A=AddVertex(X,Y),B=AddVertex(X+1,Y);
const int32 C=AddVertex(X+1,Y+1),D=AddVertex(X,Y+1);
NewTriangles.insert(NewTriangles.end(),{A,C,B,A,D,C});
}
}
if(NewTriangles.empty())return;
const int32 AddedVertices=static_cast<int32>(NewVertices.size());
auto InsertVertices=[&](auto& Existing,const auto& Added)
{
using TArrayType=std::remove_reference_t<decltype(Existing)>;
TArrayType Combined;Combined.Reserve(Existing.Num()+static_cast<int32>(Added.size()));
for(int32 I=0;I<OldSurfaceVertices;++I)Combined.Add(Existing[I]);
for(const auto& Value:Added)Combined.Add(Value);
for(int32 I=OldSurfaceVertices;I<Existing.Num();++I)Combined.Add(Existing[I]);
Existing=std::move(Combined);
};
InsertVertices(Mesh.Vertices,NewVertices);InsertVertices(Mesh.Normals,NewNormals);
if(bHasUVs)InsertVertices(Mesh.UVs,NewUVs);
if(bHasColors)InsertVertices(Mesh.Colors,NewColors);
for(int32& Index:Mesh.Triangles)if(Index>=OldSurfaceVertices)Index+=AddedVertices;
const int32 CeilingStart=std::max(0,Mesh.Triangles.Num()-Mesh.NumCeilingTriangles*3);
TArray<int32> CombinedTriangles;CombinedTriangles.Reserve(
Mesh.Triangles.Num()+static_cast<int32>(NewTriangles.size()));
for(int32 I=0;I<CeilingStart;++I)CombinedTriangles.Add(Mesh.Triangles[I]);
for(const int32 Index:NewTriangles)CombinedTriangles.Add(OldSurfaceVertices+Index);
for(int32 I=CeilingStart;I<Mesh.Triangles.Num();++I)CombinedTriangles.Add(Mesh.Triangles[I]);
const int32 OldTriangleCount=Mesh.Triangles.Num()/3;
TArray<uint8> ExistingFaceMasks=Mesh.TriangleSkirtFaceMasks;
ExistingFaceMasks.SetNum(OldTriangleCount);
TArray<uint8> CombinedFaceMasks;CombinedFaceMasks.Reserve(
OldTriangleCount+static_cast<int32>(NewTriangles.size()/3));
for(int32 Triangle=0;Triangle<CeilingStart/3;++Triangle)
CombinedFaceMasks.Add(ExistingFaceMasks[Triangle]);
for(std::size_t Triangle=0;Triangle<NewTriangles.size()/3;++Triangle)
CombinedFaceMasks.Add(0);
for(int32 Triangle=CeilingStart/3;Triangle<OldTriangleCount;++Triangle)
CombinedFaceMasks.Add(ExistingFaceMasks[Triangle]);
Mesh.Triangles=std::move(CombinedTriangles);
Mesh.TriangleSkirtFaceMasks=std::move(CombinedFaceMasks);
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
Mesh.NumCeilingTriangles+=static_cast<int32>(NewTriangles.size()/3);
}
@@ -266,7 +266,7 @@ namespace floating_island_detail
? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0;
const std::uint32_t ExpectedDenominator = ScaleGcd > 0
? recipe.coreVoxelSizeMicrons / ScaleGcd : 0;
if (recipe.schemaVersion != 1 || (recipe.geometryVersion != 1 && recipe.geometryVersion != 2)
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 3
|| (recipe.geometryVersion == 1
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|| recipe.unitsPerVoxel != Q
+86 -15
View File
@@ -5,6 +5,7 @@
#include "VoxelCaveMorphology.h"
#include "VoxelNoise.h"
#include "VoxelStrateManager.h"
#include "VoxelDensityPrimitives.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include "VoxelForgeFieldShared.h"
@@ -32,6 +33,13 @@
class FStandaloneShapeWorld
{
public:
struct FResolvedStratumBounds
{
double BottomZ=0.0;
double TopZ=0.0;
double SealThickness=0.0;
};
double GetCoreVoxelSizeCm() const
{
return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0
@@ -47,6 +55,41 @@ public:
return static_cast<double>(CHUNK_SIZE) * OwnerVoxelSizeCm / GetCoreVoxelSizeCm();
}
static bool MakeResolvedStratumBounds(double BottomZ,double TopZ,double SealThickness,
FResolvedStratumBounds& Out)
{
if(!std::isfinite(BottomZ)||!std::isfinite(TopZ)||!std::isfinite(SealThickness)
||TopZ<=BottomZ||SealThickness<0.0)return false;
Out={BottomZ,TopZ,SealThickness};
return true;
}
bool GetResolvedStratumBounds(const FStrateSlot& Slot,FResolvedStratumBounds& Out) const
{
if(!Owner||!Owner->Manager)return false;
const int32 MidChunkZ=(Slot.TopChunkZ+Slot.BottomChunkZ)/2;
if(bUseNativeManifestFields&&WorldAssembly
&&Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,MidChunkZ))
==ECaveGeneratorType::FloatingIslands)
{
const auto* Plan=FindWorldAssemblyFloatingIslandStratum(Slot.StrateIndex,MidChunkZ);
if(Plan&&Plan->manifest.recipe.geometryVersion>=3)
{
constexpr double Scale=static_cast<double>(
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
return MakeResolvedStratumBounds(
static_cast<double>(Plan->manifest.recipe.layoutMinimumZSubvoxels)/Scale,
static_cast<double>(Plan->manifest.recipe.layoutMaximumZSubvoxels)/Scale,
static_cast<double>(Plan->manifest.recipe.boundarySealSubvoxels)/Scale,Out);
}
}
const FStrateGenerationParams Params=Owner->Manager->GetGenerationParams(
FIntVector(0,0,MidChunkZ));
return MakeResolvedStratumBounds(static_cast<double>(Slot.BottomChunkZ)*CHUNK_SIZE,
static_cast<double>(Slot.TopChunkZ+1)*CHUNK_SIZE,
static_cast<double>(Params.BoundarySealThickness),Out);
}
enum class EDetailVariant : uint8 { Coherent, Smooth, PerVoxel };
struct FBoxClassification
{
@@ -714,12 +757,12 @@ public:
const double TileZ0=Origin.Z,TileZ1=Origin.Z+static_cast<double>(Cells)*Step;
for(const FStrateSlot& Slot:Owner->Manager->GetLayout())
{
const FStrateGenerationParams Params=Owner->Manager->GetGenerationParams(
FIntVector(0,0,(Slot.TopChunkZ+Slot.BottomChunkZ)/2));
FResolvedStratumBounds Bounds;
if(!GetResolvedStratumBounds(Slot,Bounds))continue;
const float HalfWidth=static_cast<float>(std::max(8.0,
static_cast<double>(Params.BoundarySealThickness)+8.0));
const float Boundaries[2]={static_cast<float>(Slot.BottomChunkZ)*CHUNK_SIZE,
static_cast<float>(Slot.TopChunkZ+1)*CHUNK_SIZE};
Bounds.SealThickness+8.0));
const float Boundaries[2]={static_cast<float>(Bounds.BottomZ),
static_cast<float>(Bounds.TopZ)};
for(const float Z:Boundaries)
if(Z+HalfWidth>=TileZ0&&Z-HalfWidth<=TileZ1)
Out.stratumBoundaries.push_back({Z,HalfWidth});
@@ -1549,13 +1592,18 @@ public:
const std::int64_t Seal=IslandPlan->manifest.recipe.boundarySealSubvoxels;
const std::int64_t BoxBottom=static_cast<std::int64_t>(BMin[2])*Scale;
const std::int64_t BoxTop=static_cast<std::int64_t>(BMax[2])*Scale;
const bool bBottomSealOverlap=BoxBottom<Bottom+Seal&&BoxTop>=Bottom;
const bool bTopSealOverlap=BoxBottom<=Top&&BoxTop>Top-Seal;
const bool bSharedTopSealEnabled=IslandPlan->manifest.recipe.geometryVersion<3
||!VoxelDensityAblation::IsBoundarySealOff();
const bool bTopSealOverlap=bSharedTopSealEnabled
&&BoxBottom<=Top&&BoxTop>Top-Seal;
const bool bBottomSealOverlap=IslandPlan->manifest.recipe.geometryVersion<3
&&BoxBottom<Bottom+Seal&&BoxTop>=Bottom;
if(bBottomSealOverlap)
return Finish(BoxBottom>=Bottom&&BoxTop<=Bottom+Seal
?EVoxelTileClass::AllSolid:EVoxelTileClass::Mixed);
if(bTopSealOverlap)
return Finish(BoxBottom>=Top-Seal&&BoxTop<=Top
return Finish((IslandPlan->manifest.recipe.geometryVersion<3
?BoxBottom>=Top-Seal:BoxBottom>Top-Seal)&&BoxTop<=Top
?EVoxelTileClass::AllSolid:EVoxelTileClass::Mixed);
const VoxelForgeCore::FloatingIslandFieldV1 Block(
IslandPlan->manifest.recipe,IslandPlan->manifest.plan);
@@ -2130,9 +2178,28 @@ private:
const std::int64_t Top=Plan->manifest.recipe.layoutMaximumZSubvoxels;
const std::int64_t Seal=Plan->manifest.recipe.boundarySealSubvoxels;
const std::int64_t Zq=ToFixed(Z);
if(Zq>=Bottom&&Zq<Bottom+Seal)Density=std::min(Density,-std::max<std::int64_t>(1,Zq-Bottom));
if(Zq<=Top&&Zq>Top-Seal)Density=std::min(Density,-std::max<std::int64_t>(1,Top-Zq));
OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
if(Plan->manifest.recipe.geometryVersion<3)
{
if(Zq>=Bottom&&Zq<Bottom+Seal)
Density=std::min(Density,-std::max<std::int64_t>(1,Zq-Bottom));
if(Zq<=Top&&Zq>Top-Seal)
Density=std::min(Density,-std::max<std::int64_t>(1,Top-Zq));
OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
}
else
{
OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
FResolvedStratumBounds Bounds;
if(!MakeResolvedStratumBounds(static_cast<double>(Bottom)/Scale,
static_cast<double>(Top)/Scale,static_cast<double>(Seal)/Scale,Bounds))
return false;
const FFloatingIslandParams Params=Owner->Manager->GetFloatingIslandParamsForChunk(
FIntVector(0,0,ChunkZ));
float InternalDensity=-OutDensity;
VF_ApplyUpperBoundarySeal(InternalDensity,Z,static_cast<float>(Bounds.TopZ),
static_cast<float>(Bounds.SealThickness),Params.BaseDensity);
OutDensity=-InternalDensity;
}
return std::isfinite(OutDensity);
}
@@ -2351,19 +2418,23 @@ private:
int32 Top=MIN_int32,Bottom=MAX_int32;
for(const FStrateSlot& Slot:Layout)
{
Top=std::max(Top,Slot.TopChunkZ);Bottom=std::min(Bottom,Slot.BottomChunkZ);
FResolvedStratumBounds Bounds;
if(!GetResolvedStratumBounds(Slot,Bounds))continue;
const int32 ResolvedBottom=FMath::FloorToInt(static_cast<float>(Bounds.BottomZ));
const int32 ResolvedTop=FMath::CeilToInt(static_cast<float>(Bounds.TopZ));
Top=std::max(Top,ResolvedTop);Bottom=std::min(Bottom,ResolvedBottom);
if(!Slot.Definition)continue;
const int32 MidChunkZ=(Slot.TopChunkZ+Slot.BottomChunkZ)/2;
FLayerField Field;
Field.Lo=Slot.BottomChunkZ*CHUNK_SIZE;
Field.Hi=(Slot.TopChunkZ+1)*CHUNK_SIZE;
Field.Lo=ResolvedBottom;
Field.Hi=ResolvedTop;
Field.StrateIndex=Slot.StrateIndex;
Field.Params=Owner->Manager->GetGenerationParams(FIntVector(0,0,MidChunkZ));
LayerFields.push_back(std::move(Field));
}
if(Top>=Bottom)
{
LayerRanges.push_back({Bottom*CHUNK_SIZE,(Top+1)*CHUNK_SIZE});
LayerRanges.push_back({Bottom,Top});
}
}
+2
View File
@@ -2204,6 +2204,8 @@ namespace VoxelForgeCore
out.diagnostics.horizonGroundTrianglesRemoved=GroundStats.RemovedTriangles;
out.diagnostics.horizonCapTriangles=CapStats.Triangles;
}
if(impl_->worldAssembly&&!bOverlapsPlayerEdit)
AppendStratumBoundaryCeilings(world,farShape,nativeKey,step,mesh);
out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
if(bHasWorldAssembly)