wip(assembler): round L22 -- one 8 m layout chunk for every strate; real ceiling -> content -> bedrock

StrateLayoutChunkSizeMeters (world setting, default 8 m; owner decision) is resolved once in
the director/adapter and standalone loader; every generator reads the resolved width (no
CHUNK_SIZE / Core-tile inference). Every strate gets solid ceiling and bedrock bands from its
BoundarySealThickness in the shared composition path (diggable by default); faces come from the
shared boundary path. Owner world: FloatingIslands +8..-120 m (128 m, now with a bedrock floor),
SurfaceWorld -120..-632 m (unchanged; full-detail and exact-query hashes equal L21); the 60 m gap is
gone. The strict layout validator passes on the real world, with 0 endpoint dispatch
disagreements. Spawn/benchmark camera derived from route.front. Ledges under the islands gone
(reviewer capture). Evaluator 7, recipe schema 2, layout snapshot 2, FI geometry 4.
Open: SW cap profiles exceed the ceiling band (owner call); transitions still Hard; Unreal
setting/manager/adapter edits uncompiled here.

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 12:03:14 +02:00
1 parent 8753f6dbe4
commit 55c26a05a7
28 files changed
+1432 -579

No files matched your search

+186 -25
View File
@@ -817,8 +817,9 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
struct FFarMeshColumnIndex
{
FFarMeshColumnIndex(const FVoxelMeshData& InMesh,const FIntVector& InOrigin,int32 InExtent,
int32 InCells,double InStep)
int32 InCells,double InStep,double InVoxelSizeCm)
:Mesh(InMesh),Origin(InOrigin),Extent(InExtent),Cells(InCells),Step(InStep),
VoxelSizeCm(InVoxelSizeCm),
ByColumn(static_cast<size_t>(InCells)*InCells)
{
for(int32 Triangle=0;Triangle+2<Mesh.Triangles.Num();Triangle+=3)
@@ -908,16 +909,54 @@ struct FFarMeshColumnIndex
return true;
}
bool HasUpwardHit(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 HasUpwardCellCoverage(double X0,double Y0,double X1,double Y1,
double Z,double Tolerance) const
{
constexpr double Samples[5]={0.0,0.25,0.5,0.75,1.0};
for(const double V:Samples)for(const double U:Samples)
if(!HasUpwardHit(X0+(X1-X0)*U,Y0+(Y1-Y0)*V,Z,Tolerance))return false;
return true;
}
private:
double PositionAxis(int32 Index,int32 Axis) const
{
const FVector& P=Mesh.Vertices[Index];
return (Axis==0?P.X:(Axis==1?P.Y:P.Z))*0.04;
const double WorldCentimetres=Axis==0?P.X:(Axis==1?P.Y:P.Z);
return WorldCentimetres/VoxelSizeCm;
}
const FVoxelMeshData& Mesh;
FIntVector Origin;
int32 Extent=0,Cells=0;
double Step=1.0;
double VoxelSizeCm=25.0;
std::vector<std::vector<int32>> ByColumn;
};
@@ -1040,7 +1079,8 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
const bool bHasColors=Mesh.Colors.Num()==OldVertexCount;
const FColor Color=bHasColors&&OldVertexCount>0?Mesh.Colors[0]:FColor(160,160,160,255);
const int32 Cells=std::max(1,World.CoarseTileCells);
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step);
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step,
static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0);
std::vector<FVector> NewVertices,NewNormals;
std::vector<FVector2f> NewUVs;std::vector<FColor> NewColors;
std::vector<int32> NewTriangles;
@@ -1188,40 +1228,43 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
const int32 GridSide=Extent/GridStep+1;
struct FCandidate
{
FStandaloneShapeWorld::FResolvedStratumBounds Bounds;
std::vector<uint8> SolidBelow;
double FaceZ=0.0;
double Thickness=0.0;
std::vector<uint8> SolidAbove;
};
std::vector<FCandidate> Candidates;
const double CoreMicrons=std::max<std::uint32_t>(1,World.CoreVoxelSizeMicrons);
for(const FStrateSlot& Slot:World.Manager->GetLayout())
{
const auto* Stratum=Shape.FindResolvedLayoutStratum(Slot.StrateIndex);
if(!Stratum||!Stratum->ceiling.presentInCurrentField
||Stratum->ceiling.topMicrometres<=Stratum->ceiling.bottomMicrometres)continue;
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);
Candidate.FaceZ=static_cast<double>(Stratum->ceiling.bottomMicrometres)/CoreMicrons;
Candidate.Thickness=static_cast<double>(Stratum->ceiling.topMicrometres
-Stratum->ceiling.bottomMicrometres)/CoreMicrons;
if(Candidate.FaceZ<Origin.Z-1.0e-6||Candidate.FaceZ>Origin.Z+Extent+1.0e-6)continue;
const double Epsilon=std::min(0.5,Candidate.Thickness*0.25);
if(Epsilon<=0.0)continue;
Candidate.SolidBelow.resize(static_cast<std::size_t>(GridSide)*GridSide,0);
bool AnySolidBelow=false;
Candidate.SolidAbove.resize(static_cast<std::size_t>(GridSide)*GridSide,0);
bool AnySolidAbove=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));
// The shared upper boundary closes the stratum even where its raw field stays
// solid above the configured top. A zero-crossing test leaves holes there.
// Le plafond partage ferme la strate meme si le champ reste solide au-dessus.
const bool bSolidBelow=std::isfinite(Below)&&Below<0.0f;
Candidate.SolidBelow[static_cast<std::size_t>(Y*GridSide+X)]=bSolidBelow?1:0;
AnySolidBelow=AnySolidBelow||bSolidBelow;
const float Above=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY),
static_cast<float>(Candidate.FaceZ+Epsilon));
const bool bSolidAbove=std::isfinite(Above)&&Above<0.0f;
Candidate.SolidAbove[static_cast<std::size_t>(Y*GridSide+X)]=bSolidAbove?1:0;
AnySolidAbove=AnySolidAbove||bSolidAbove;
}
if(AnySolidBelow)Candidates.push_back(std::move(Candidate));
if(AnySolidAbove)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);
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step,
static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0);
const int32 OldVertexCount=Mesh.Vertices.Num();
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
const bool bHasUVs=Mesh.UVs.Num()==OldVertexCount;
@@ -1235,7 +1278,7 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
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 double CeilingZ=Candidate.FaceZ;
const auto AddVertex=[&](int32 X,int32 Y)
{
const std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X);
@@ -1253,8 +1296,8 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
{
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.SolidBelow[I00]||!Candidate.SolidBelow[I10]
||!Candidate.SolidBelow[I01]||!Candidate.SolidBelow[I11])continue;
if(!Candidate.SolidAbove[I00]||!Candidate.SolidAbove[I10]
||!Candidate.SolidAbove[I01]||!Candidate.SolidAbove[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
@@ -1303,3 +1346,121 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
Mesh.NumCeilingTriangles+=static_cast<int32>(NewTriangles.size()/3);
}
// Add the interior-facing top of each bedrock band. The density composition already
// makes this band solid and editable; this planar closure keeps its visible floor
// continuous when a coarse marching-cubes step would otherwise skip a sub-voxel band.
inline void AppendStratumBoundaryFloors(const FStandaloneOwnerWorld& World,
const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,int32 Step,FVoxelMeshData& Mesh)
{
if(Key.Level<0||Step<=0||!World.Manager||!Shape.ResolvedLayout)return;
const FIntVector Origin=Key.OriginVoxels();
const int32 Extent=Key.ExtentVoxels();
if(Extent<=0||Extent%Step!=0)return;
const int32 GridSide=Extent/Step+1;
struct FCandidate { double FloorZ=0.0; double Thickness=0.0; };
std::vector<FCandidate> Candidates;
const double CoreMicrons=std::max<std::uint32_t>(1,World.CoreVoxelSizeMicrons);
for(const FStrateSlot& Slot:World.Manager->GetLayout())
{
const auto* Stratum=Shape.FindResolvedLayoutStratum(Slot.StrateIndex);
if(!Stratum||!Stratum->bedrock.presentInCurrentField
||Stratum->bedrock.topMicrometres<=Stratum->bedrock.bottomMicrometres)continue;
const double FloorZ=static_cast<double>(Stratum->bedrock.topMicrometres)/CoreMicrons;
if(FloorZ<Origin.Z-1.0e-6||FloorZ>Origin.Z+Extent+1.0e-6)continue;
Candidates.push_back({FloorZ,static_cast<double>(Stratum->bedrock.topMicrometres
-Stratum->bedrock.bottomMicrometres)/CoreMicrons});
}
if(Candidates.empty())return;
const int32 Cells=std::max(1,World.CoarseTileCells);
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step,
static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0);
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)
{
const double Epsilon=std::min(0.5,Candidate.Thickness*0.25);
if(Epsilon<=0.0)continue;
std::vector<uint8> SolidBelow(static_cast<std::size_t>(GridSide)*GridSide,0);
for(int32 Y=0;Y<GridSide;++Y)for(int32 X=0;X<GridSide;++X)
{
const double WorldX=Origin.X+X*Step,WorldY=Origin.Y+Y*Step;
const float Density=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY),
static_cast<float>(Candidate.FloorZ-Epsilon));
SolidBelow[static_cast<std::size_t>(Y*GridSide+X)]=
std::isfinite(Density)&&Density<0.0f?1:0;
}
std::vector<int32> VertexMap(static_cast<std::size_t>(GridSide)*GridSide,INDEX_NONE);
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*Step,WorldY=Origin.Y+Y*Step;
NewVertices.emplace_back(WorldX*VoxelSizeCm,WorldY*VoxelSizeCm,
Candidate.FloorZ*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(!SolidBelow[I00]||!SolidBelow[I10]||!SolidBelow[I01]||!SolidBelow[I11])continue;
const double X0=Origin.X+X*Step,Y0=Origin.Y+Y*Step;
const double X1=X0+Step,Y1=Y0+Step;
if(MeshIndex.HasUpwardCellCoverage(X0,Y0,X1,Y1,Candidate.FloorZ,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,B,C,A,C,D});
}
}
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;
}
+27 -4
View File
@@ -170,6 +170,8 @@ namespace floating_island_detail
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutCenterTopZSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutMinimumZSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutMaximumZSubvoxels), 8);
if(recipe.geometryVersion>=4)
HashWord(hash,static_cast<std::uint64_t>(recipe.layoutChunkWidthSubvoxels),8);
HashWord(hash, recipe.primaryIslandCount, 4);
HashWord(hash, static_cast<std::uint64_t>(recipe.islandDensityQ16), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.spacingSubvoxels), 8);
@@ -245,10 +247,10 @@ namespace floating_island_detail
inline bool ValidateRecipe(const FloatingIslandRecipeV1& recipe, std::string& error)
{
const std::array<std::int64_t, 25> values = {
const std::array<std::int64_t, 26> values = {
recipe.layoutOriginXSubvoxels, recipe.layoutOriginYSubvoxels,
recipe.layoutCenterTopZSubvoxels, recipe.layoutMinimumZSubvoxels,
recipe.layoutMaximumZSubvoxels, recipe.islandDensityQ16,
recipe.layoutMaximumZSubvoxels, recipe.layoutChunkWidthSubvoxels, recipe.islandDensityQ16,
recipe.spacingSubvoxels, recipe.minimumRadiusSubvoxels,
recipe.maximumRadiusSubvoxels, recipe.thicknessRatioQ16,
recipe.verticalJitterQ16, recipe.topFlattenQ16,
@@ -266,7 +268,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 > 3
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 4
|| (recipe.geometryVersion == 1
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|| recipe.unitsPerVoxel != Q
@@ -275,6 +277,11 @@ namespace floating_island_detail
|| recipe.ownerToCoreScaleNumerator != ExpectedNumerator
|| recipe.ownerToCoreScaleDenominator != ExpectedDenominator
|| recipe.primaryIslandCount < 3 || recipe.primaryIslandCount > 9
|| recipe.layoutChunkWidthSubvoxels <= 0
|| static_cast<long double>(recipe.layoutChunkWidthSubvoxels)*recipe.coreVoxelSizeMicrons
< 250000.0L*Q
|| static_cast<long double>(recipe.layoutChunkWidthSubvoxels)*recipe.coreVoxelSizeMicrons
> 64000000.0L*Q
|| recipe.layoutMaximumZSubvoxels <= recipe.layoutMinimumZSubvoxels
|| recipe.layoutMaximumZSubvoxels - recipe.layoutMinimumZSubvoxels > 2048 * Q
|| std::any_of(values.begin(), values.end(), [](auto value) { return !ValidScalar(value); }))
@@ -1196,7 +1203,8 @@ namespace floating_island_detail
}
std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
std::string_view json, std::string& error)
std::string_view json, std::string& error,
std::int64_t resolvedLayoutChunkWidthSubvoxels)
{
VFJson::Value root;
if (!VFJson::Parser(std::string(json)).Parse(root, error)) return std::nullopt;
@@ -1222,6 +1230,15 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
manifest.recipe.crustDepthBonusQ16 = 0;
manifest.recipe.rootDepthRatioQ16 = 0;
}
if(manifest.recipe.geometryVersion>=4)
{
if(!floating_island_detail::ReadInteger(recipeValue.Get("layout_chunk_width_q"),
manifest.recipe.layoutChunkWidthSubvoxels))
{error="floating-island recipe is missing its resolved layout chunk width";return std::nullopt;}
if(resolvedLayoutChunkWidthSubvoxels>0
&& manifest.recipe.layoutChunkWidthSubvoxels!=resolvedLayoutChunkWidthSubvoxels)
{error="floating-island recipe layout width disagrees with the resolved world layout";return std::nullopt;}
}
if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema)
|| !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash)
|| !floating_island_detail::ReadU32(recipeValue.Get("schema_version"), manifest.recipe.schemaVersion)
@@ -1257,6 +1274,10 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
|| !floating_island_detail::ReadHash(planValue.Get("content_hash"), planHash)
|| schema != 1)
{ error = "floating-island manifest is missing a required versioned recipe or plan field"; return std::nullopt; }
if(manifest.recipe.geometryVersion<4 && resolvedLayoutChunkWidthSubvoxels>0)
manifest.recipe.layoutChunkWidthSubvoxels=resolvedLayoutChunkWidthSubvoxels;
else if(manifest.recipe.geometryVersion<4)
{ error="legacy floating-island manifest requires the world's resolved layout chunk width"; return std::nullopt; }
manifest.recipe.contentHash = recipeHash;
manifest.contentHash = manifestHash;
manifest.plan.contentHash = planHash;
@@ -1368,6 +1389,8 @@ std::string FloatingIslandManifestV1::ToJson() const
out << ",\"geometry_version\":" << recipe.geometryVersion
<< ",\"crust_depth_bonus_q16\":" << recipe.crustDepthBonusQ16
<< ",\"root_depth_ratio_q16\":" << recipe.rootDepthRatioQ16;
if(recipe.geometryVersion>=4)
out << ",\"layout_chunk_width_q\":" << recipe.layoutChunkWidthSubvoxels;
out << ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels
<< ",\"contour_warp_q\":" << recipe.contourWarpSubvoxels
<< ",\"roughness_q\":" << recipe.roughnessSubvoxels
+50 -35
View File
@@ -39,37 +39,42 @@ namespace
}
std::string LayoutHex(std::uint64_t value)
{std::ostringstream out;out<<std::hex<<std::setfill('0')<<std::setw(16)<<value;return out.str();}
std::int64_t LayoutTransitionLimit(const ResolvedLayoutStratumV1& stratum)
std::int64_t LayoutTransitionLimit(const ResolvedLayoutStratumV2& stratum)
{
if(stratum.transitionMode=="Hard")return 0;
if(stratum.transitionMode=="Hard"||stratum.transitionFallsBackToHard)return 0;
return stratum.transitionWidthMicrometres
+(stratum.transitionMode=="Interleaved"?stratum.transitionWarpAmplitudeMicrometres:0);
}
std::string LayoutBandJson(const ResolvedLayoutBandV1& band)
std::string LayoutBandJson(const ResolvedLayoutBandV2& band)
{
std::ostringstream out;out<<"{\"kind\":"<<LayoutEscape(band.kind)
<<",\"bottom_um\":"<<band.bottomMicrometres<<",\"top_um\":"<<band.topMicrometres
<<",\"present\":"<<(band.presentInCurrentField?"true":"false")
<<",\"diggable\":"<<(band.diggable?"true":"false")
<<",\"protected\":"<<(band.protectedBand?"true":"false")
<<",\"solid_density_q16\":"<<band.solidDensityQ16
<<",\"behaviour\":"<<LayoutEscape(band.behaviour)
<<",\"density_side\":"<<LayoutEscape(band.densitySide)
<<",\"cap_path\":"<<LayoutEscape(band.capPath)<<'}';return out.str();
}
ResolvedLayoutBandV1 ParseLayoutBand(const VFJson::Value& value)
ResolvedLayoutBandV2 ParseLayoutBand(const VFJson::Value& value)
{
ResolvedLayoutBandV1 b;b.kind=value.Get("kind").String();
ResolvedLayoutBandV2 b;b.kind=value.Get("kind").String();
b.bottomMicrometres=value.Get("bottom_um").Integer();b.topMicrometres=value.Get("top_um").Integer();
b.presentInCurrentField=value.Get("present").Boolean();b.diggable=value.Get("diggable").Boolean(true);
b.protectedBand=value.Get("protected").Boolean();b.behaviour=value.Get("behaviour").String();
b.protectedBand=value.Get("protected").Boolean();b.solidDensityQ16=value.Get("solid_density_q16").Integer(65536);
b.behaviour=value.Get("behaviour").String();
b.densitySide=value.Get("density_side").String();b.capPath=value.Get("cap_path").String();return b;
}
std::string LayoutBodyJson(const ResolvedLayoutSnapshotV1& s)
std::string LayoutBodyJson(const ResolvedLayoutSnapshotV2& s)
{
std::ostringstream out;out<<"{\"schema_version\":"<<s.schemaVersion
<<",\"world_identity\":"<<LayoutEscape(s.worldIdentity)
<<",\"owner_voxel_size_um\":"<<s.ownerVoxelSizeMicrometres
<<",\"core_voxel_size_um\":"<<s.coreVoxelSizeMicrometres<<",\"strates\":[";
<<",\"core_voxel_size_um\":"<<s.coreVoxelSizeMicrometres
<<",\"strate_layout_chunk_size_um\":"<<s.strateLayoutChunkSizeMicrometres
<<",\"strate_layout_chunk_width_subvoxels\":"<<s.strateLayoutChunkWidthSubvoxels
<<",\"strates\":[";
for(std::size_t i=0;i<s.strates.size();++i)
{
if(i)out<<',';const auto& x=s.strates[i];
@@ -90,6 +95,7 @@ namespace
<<",\"transition_warp_amplitude_um\":"<<x.transitionWarpAmplitudeMicrometres
<<",\"transition_interpenetration_um\":"<<x.transitionInterpenetrationMicrometres
<<",\"transition_honoured_in_core\":"<<(x.transitionHonouredInCore?"true":"false")
<<",\"transition_falls_back_to_hard\":"<<(x.transitionFallsBackToHard?"true":"false")
<<",\"transition_dispatch_path\":"<<LayoutEscape(x.transitionDispatchPath)
<<",\"bound_dispatch_path\":"<<LayoutEscape(x.boundDispatchPath)
<<",\"query_dispatch_path\":"<<LayoutEscape(x.queryDispatchPath)
@@ -117,17 +123,18 @@ namespace
{return obj.Get(key).Boolean(fallback);}
}
std::string ResolvedLayoutSnapshotV1::ToJson() const
std::string ResolvedLayoutSnapshotV2::ToJson() const
{
const std::string body=LayoutBodyJson(*this);
return body.substr(0,body.size()-1)+",\"stable_identity\":"
+LayoutEscape(stableIdentity.empty()?"resolved-layout-v1-fnv64:"+LayoutHex(LayoutHash(body)):stableIdentity)+"}";
+LayoutEscape(stableIdentity.empty()?"resolved-layout-v2-fnv64:"+LayoutHex(LayoutHash(body)):stableIdentity)+"}";
}
bool ResolvedLayoutSnapshotV1::finalize(std::string& error)
bool ResolvedLayoutSnapshotV2::finalize(std::string& error)
{
error.clear();
if(schemaVersion!=ResolvedLayoutSchemaVersionV1||worldIdentity.empty()||strates.empty())
if(schemaVersion!=ResolvedLayoutSchemaVersionV2||worldIdentity.empty()||strates.empty()
||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0)
{error="resolved layout has an unsupported version, empty world identity, or no strates";return false;}
std::stable_sort(strates.begin(),strates.end(),[](const auto& a,const auto& b)
{return a.interiorTopMicrometres!=b.interiorTopMicrometres
@@ -143,20 +150,21 @@ bool ResolvedLayoutSnapshotV1::finalize(std::string& error)
{error="resolved layout contains an invalid interior interval or voxel size";return false;}
}
stableIdentity.clear();const std::string body=LayoutBodyJson(*this);
stableIdentity="resolved-layout-v1-fnv64:"+LayoutHex(LayoutHash(body));
std::vector<ResolvedLayoutIssueV1> issues;
return validate(ResolvedLayoutValidationModeV1::compatibility,issues,error);
stableIdentity="resolved-layout-v2-fnv64:"+LayoutHex(LayoutHash(body));
std::vector<ResolvedLayoutIssueV2> issues;
return validate(ResolvedLayoutValidationModeV2::compatibility,issues,error);
}
bool ResolvedLayoutSnapshotV1::validate(ResolvedLayoutValidationModeV1 mode,
std::vector<ResolvedLayoutIssueV1>& issues,std::string& error) const
bool ResolvedLayoutSnapshotV2::validate(ResolvedLayoutValidationModeV2 mode,
std::vector<ResolvedLayoutIssueV2>& issues,std::string& error) const
{
issues.clear();error.clear();
if(schemaVersion!=ResolvedLayoutSchemaVersionV1||worldIdentity.empty()||strates.empty())
if(schemaVersion!=ResolvedLayoutSchemaVersionV2||worldIdentity.empty()||strates.empty()
||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0)
{error="resolved layout schema/world/strata header is invalid";return false;}
const auto add=[&](std::string code,std::string detail)
{issues.push_back({std::move(code),std::move(detail)});};
const bool strict=mode==ResolvedLayoutValidationModeV1::strict;
const bool strict=mode==ResolvedLayoutValidationModeV2::strict;
for(std::size_t i=0;i<strates.size();++i)
{
const auto& s=strates[i];
@@ -169,7 +177,8 @@ bool ResolvedLayoutSnapshotV1::validate(ResolvedLayoutValidationModeV1 mode,
if(s.managerChunkWidthMicrometres>0&&s.dispatchChunkWidthMicrometres>0
&&s.managerChunkWidthMicrometres!=s.dispatchChunkWidthMicrometres)
add("ambiguous_dispatch_units",s.stableId+" manager chunk width differs from Core dispatch chunk width");
if(i+1<strates.size()&&s.transitionMode!="Hard"&&!s.transitionHonouredInCore)
if(i+1<strates.size()&&s.transitionMode!="Hard"&&!s.transitionHonouredInCore
&&!s.transitionFallsBackToHard)
add("transition_not_honoured",s.stableId+" declares a blend/interleave that Core does not apply on this interface");
if(!s.ceiling.presentInCurrentField)add("missing_ceiling",s.stableId+" has no explicit ceiling band in today's field");
if(!s.bedrock.presentInCurrentField)add("missing_bedrock",s.stableId+" has no explicit bedrock floor in today's field");
@@ -180,12 +189,14 @@ bool ResolvedLayoutSnapshotV1::validate(ResolvedLayoutValidationModeV1 mode,
add(!s.ceiling.presentInCurrentField?"missing_ceiling":"missing_bedrock",s.stableId+" lacks a required boundary band");
if(!s.content.presentInCurrentField)
add("missing_content",s.stableId+" has no content generator band");
const auto bandShape=[&](const ResolvedLayoutBandV1& b,std::string_view expected)
const auto bandShape=[&](const ResolvedLayoutBandV2& b,std::string_view expected)
{
if(b.kind!=expected||b.topMicrometres<=b.bottomMicrometres)
add("wrong_order",s.stableId+" has an invalid "+std::string(expected)+" band");
if(b.protectedBand&&b.diggable)
add("invalid_protection",s.stableId+" band is both protected and diggable");
if(b.solidDensityQ16<=0)
add("invalid_solid_density",s.stableId+" has a non-positive solid band strength");
};
bandShape(s.ceiling,"ceiling");bandShape(s.content,"content");bandShape(s.bedrock,"bedrock");
if(s.ceiling.bottomMicrometres!=s.content.topMicrometres
@@ -255,18 +266,20 @@ bool ResolvedLayoutSnapshotV1::validate(ResolvedLayoutValidationModeV1 mode,
return true;
}
std::optional<ResolvedLayoutSnapshotV1> ResolvedLayoutSnapshotV1::FromJson(
std::optional<ResolvedLayoutSnapshotV2> ResolvedLayoutSnapshotV2::FromJson(
std::string_view json,std::string& error)
{
VFJson::Value root;if(!VFJson::Parser(std::string(json)).Parse(root,error))return std::nullopt;
if(!root.IsObject()){error="resolved layout JSON root must be an object";return std::nullopt;}
ResolvedLayoutSnapshotV1 s;s.schemaVersion=static_cast<std::uint32_t>(LayoutInt(root,"schema_version"));
ResolvedLayoutSnapshotV2 s;s.schemaVersion=static_cast<std::uint32_t>(LayoutInt(root,"schema_version"));
s.worldIdentity=LayoutString(root,"world_identity");
s.ownerVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"owner_voxel_size_um"));
s.coreVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"core_voxel_size_um"));
s.strateLayoutChunkSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"strate_layout_chunk_size_um"));
s.strateLayoutChunkWidthSubvoxels=LayoutInt(root,"strate_layout_chunk_width_subvoxels");
for(const auto& v:root.Get("strates").AsArray())
{
ResolvedLayoutStratumV1 x;x.stableId=LayoutString(v,"stable_id");
ResolvedLayoutStratumV2 x;x.stableId=LayoutString(v,"stable_id");
x.order=static_cast<std::int32_t>(LayoutInt(v,"order"));x.strateIndex=static_cast<std::int32_t>(LayoutInt(v,"strate_index"));
x.generatorKind=LayoutString(v,"generator_kind");x.recipeId=LayoutString(v,"recipe_id");
x.recipeVersion=static_cast<std::uint32_t>(LayoutInt(v,"recipe_version"));
@@ -278,12 +291,14 @@ std::optional<ResolvedLayoutSnapshotV1> ResolvedLayoutSnapshotV1::FromJson(
x.transitionWidthMicrometres=LayoutInt(v,"transition_width_um");
x.transitionWarpAmplitudeMicrometres=LayoutInt(v,"transition_warp_amplitude_um");
x.transitionInterpenetrationMicrometres=LayoutInt(v,"transition_interpenetration_um");
x.transitionHonouredInCore=LayoutBool(v,"transition_honoured_in_core");x.transitionDispatchPath=LayoutString(v,"transition_dispatch_path");
x.transitionHonouredInCore=LayoutBool(v,"transition_honoured_in_core");
x.transitionFallsBackToHard=LayoutBool(v,"transition_falls_back_to_hard");
x.transitionDispatchPath=LayoutString(v,"transition_dispatch_path");
x.boundDispatchPath=LayoutString(v,"bound_dispatch_path");x.queryDispatchPath=LayoutString(v,"query_dispatch_path");
x.managerLookup=LayoutString(v,"manager_lookup");x.managerChunkWidthMicrometres=LayoutInt(v,"manager_chunk_width_um");
x.dispatchChunkWidthMicrometres=LayoutInt(v,"dispatch_chunk_width_um");
for(const auto& a:v.Get("anchors").AsArray())
{ResolvedLayoutAnchorV1 p;p.kind=LayoutString(a,"kind");p.stableId=LayoutString(a,"stable_id");p.units=LayoutString(a,"units");
{ResolvedLayoutAnchorV2 p;p.kind=LayoutString(a,"kind");p.stableId=LayoutString(a,"stable_id");p.units=LayoutString(a,"units");
for(const auto& value:a.Get("values").AsArray())p.values.push_back(value.Integer());x.anchors.push_back(std::move(p));}
s.strates.push_back(std::move(x));
}
@@ -293,12 +308,12 @@ std::optional<ResolvedLayoutSnapshotV1> ResolvedLayoutSnapshotV1::FromJson(
return s;
}
ResolvedLayoutValidatorAuditV1 AuditResolvedLayoutValidatorV1()
ResolvedLayoutValidatorAuditV2 AuditResolvedLayoutValidatorV2()
{
ResolvedLayoutValidatorAuditV1 result;
ResolvedLayoutValidatorAuditV2 result;
auto make=[](std::string id,std::int32_t order,std::int64_t top,std::int64_t bottom)
{
ResolvedLayoutStratumV1 s;s.stableId=std::move(id);s.order=order;s.strateIndex=order;s.generatorKind="synthetic";
ResolvedLayoutStratumV2 s;s.stableId=std::move(id);s.order=order;s.strateIndex=order;s.generatorKind="synthetic";
s.interiorTopMicrometres=top;s.interiorBottomMicrometres=bottom;s.voxelSizeMicrometres=250000;
s.transitionMode="Gradient";s.transitionWidthMicrometres=4000000;
s.transitionHonouredInCore=true;
@@ -307,15 +322,15 @@ ResolvedLayoutValidatorAuditV1 AuditResolvedLayoutValidatorV1()
s.bedrock={"bedrock",bottom-1000000,bottom,true,true,false,"breakable bedrock","lower face","none"};
return s;
};
const auto expectReject=[&](std::string name,ResolvedLayoutSnapshotV1 candidate)
const auto expectReject=[&](std::string name,ResolvedLayoutSnapshotV2 candidate)
{
++result.controlsRun;std::vector<ResolvedLayoutIssueV1> issues;std::string error;
if(!candidate.validate(ResolvedLayoutValidationModeV1::strict,issues,error))++result.controlsRejectedAsExpected;
++result.controlsRun;std::vector<ResolvedLayoutIssueV2> issues;std::string error;
if(!candidate.validate(ResolvedLayoutValidationModeV2::strict,issues,error))++result.controlsRejectedAsExpected;
else result.failures.push_back({"negative_control_accepted",std::move(name)+" was accepted by strict validator"});
};
auto base=ResolvedLayoutSnapshotV1{};base.worldIdentity="synthetic";base.strates={make("a",0,10000000,0),make("b",1,-2000000,-12000000)};
{std::vector<ResolvedLayoutIssueV1> issues;std::string error;
if(!base.validate(ResolvedLayoutValidationModeV1::strict,issues,error))
auto base=ResolvedLayoutSnapshotV2{};base.worldIdentity="synthetic";base.strates={make("a",0,10000000,0),make("b",1,-2000000,-12000000)};
{std::vector<ResolvedLayoutIssueV2> issues;std::string error;
if(!base.validate(ResolvedLayoutValidationModeV2::strict,issues,error))
result.failures.push_back({"positive_control_rejected","valid ceiling/content/bedrock layout was rejected"});}
auto boundaryGap=base;boundaryGap.strates[1].ceiling.topMicrometres-=1;
expectReject("bedrock/next-ceiling gap",boundaryGap);
+176 -89
View File
@@ -33,6 +33,7 @@
class FStandaloneShapeWorld
{
public:
const VoxelForgeCore::ResolvedLayoutSnapshotV2* ResolvedLayout=nullptr;
struct FResolvedStratumBounds
{
double BottomZ=0.0;
@@ -47,12 +48,53 @@ public:
: static_cast<double>(Owner->CoreVoxelSizeMicrons) / 10000.0;
}
double GetCoreVoxelsPerOwnerChunk() const
double GetResolvedLayoutChunkWidthVoxels() const
{
const double OwnerVoxelSizeCm = Owner == nullptr || Owner->OwnerVoxelSizeMicrons == 0
? 0.5 * static_cast<double>(VOXEL_SIZE)
: static_cast<double>(Owner->OwnerVoxelSizeMicrons) / 10000.0;
return static_cast<double>(CHUNK_SIZE) * OwnerVoxelSizeCm / GetCoreVoxelSizeCm();
return ResolvedLayout&&ResolvedLayout->strateLayoutChunkWidthSubvoxels>0
?static_cast<double>(ResolvedLayout->strateLayoutChunkWidthSubvoxels)
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
:Owner?static_cast<double>(Owner->StrateLayoutChunkWidthSubvoxels)
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel:0.0;
}
const VoxelForgeCore::ResolvedLayoutStratumV2* FindResolvedLayoutStratum(int32 StrateIndex) const
{
if(!ResolvedLayout)return nullptr;
const auto it=std::find_if(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(),
[StrateIndex](const auto& item){return item.strateIndex==StrateIndex;});
return it==ResolvedLayout->strates.end()?nullptr:&*it;
}
bool IsInsideResolvedStrateBoundaryBand(float Z) const
{
if(!ResolvedLayout||!Owner||!Owner->Manager||!std::isfinite(Z))return false;
const double worldZCentimetres=static_cast<double>(Z)*GetCoreVoxelSizeCm();
const int32 strateIndex=Owner->Manager->GetStrateIndex(static_cast<float>(worldZCentimetres));
const auto* stratum=FindResolvedLayoutStratum(strateIndex);
if(!stratum)return false;
const double zMicrometres=static_cast<double>(Z)*Owner->CoreVoxelSizeMicrons;
const auto solidAt=[&](const VoxelForgeCore::ResolvedLayoutBandV2& band)
{return band.presentInCurrentField&&zMicrometres>=band.bottomMicrometres
&&zMicrometres<band.topMicrometres;};
return solidAt(stratum->ceiling)||solidAt(stratum->bedrock);
}
float ApplyResolvedStrateBoundaryBand(float density,float X,float Y,float Z) const
{
(void)X;(void)Y;
if(!ResolvedLayout||!Owner||!Owner->Manager||!std::isfinite(Z))return density;
const double worldZCentimetres=static_cast<double>(Z)*GetCoreVoxelSizeCm();
const int32 strateIndex=Owner->Manager->GetStrateIndex(static_cast<float>(worldZCentimetres));
const auto* stratum=FindResolvedLayoutStratum(strateIndex);
if(!stratum)return density;
const double zMicrometres=static_cast<double>(Z)*Owner->CoreVoxelSizeMicrons;
const auto solidAt=[&](const VoxelForgeCore::ResolvedLayoutBandV2& band)
{return band.presentInCurrentField&&zMicrometres>=band.bottomMicrometres
&&zMicrometres<band.topMicrometres;};
const auto* band=solidAt(stratum->ceiling)?&stratum->ceiling
:solidAt(stratum->bedrock)?&stratum->bedrock:nullptr;
if(!band)return density;
return -std::max(0.05f,static_cast<float>(band->solidDensityQ16)/65536.0f);
}
static bool MakeResolvedStratumBounds(double BottomZ,double TopZ,double SealThickness,
@@ -67,6 +109,17 @@ public:
bool GetResolvedStratumBounds(const FStrateSlot& Slot,FResolvedStratumBounds& Out) const
{
if(!Owner||!Owner->Manager)return false;
if(const auto* resolved=FindResolvedLayoutStratum(Slot.StrateIndex))
{
const double coreMicrons=std::max<std::uint32_t>(1,Owner->CoreVoxelSizeMicrons);
const double bottom=static_cast<double>(resolved->interiorBottomMicrometres)/coreMicrons;
const double top=static_cast<double>(resolved->interiorTopMicrometres)/coreMicrons;
const double ceiling=static_cast<double>(resolved->ceiling.topMicrometres
-resolved->ceiling.bottomMicrometres)/coreMicrons;
const double bedrock=static_cast<double>(resolved->bedrock.topMicrometres
-resolved->bedrock.bottomMicrometres)/coreMicrons;
return MakeResolvedStratumBounds(bottom,top,std::max(ceiling,bedrock),Out);
}
const int32 MidChunkZ=(Slot.TopChunkZ+Slot.BottomChunkZ)/2;
if(bUseNativeManifestFields&&WorldAssembly
&&Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,MidChunkZ))
@@ -85,8 +138,9 @@ public:
}
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,
const double chunkWidth=GetResolvedLayoutChunkWidthVoxels();
return MakeResolvedStratumBounds(static_cast<double>(Slot.BottomChunkZ)*chunkWidth,
static_cast<double>(Slot.TopChunkZ+1)*chunkWidth,
static_cast<double>(Params.BoundarySealThickness),Out);
}
@@ -355,11 +409,12 @@ public:
bool bValidatePassageRoutes=true,
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InGpuVisualDetailTiles={},
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InCpuVisualDetailTiles={},
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly=nullptr)
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly=nullptr,
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout=nullptr)
{
using Clock=std::chrono::steady_clock;
const auto TotalStart=Clock::now();
Owner=&World;WorldAssembly=InWorldAssembly;
Owner=&World;WorldAssembly=InWorldAssembly;ResolvedLayout=InResolvedLayout;
WorkerCount=std::clamp(Workers,1,16);DetailVariant=Variant;
Scheduler=InScheduler;RoundTunnelsEnabled=bInRoundTunnels;
bUseNativeManifestFields=true;
@@ -516,10 +571,11 @@ public:
const FIntVector& ClipMaxVoxels,EDetailVariant Variant,
bool bInRoundTunnels,
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly,
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout,
std::string& Error)
{
using Clock=std::chrono::steady_clock;
Owner=&World;WorldAssembly=InWorldAssembly;
Owner=&World;WorldAssembly=InWorldAssembly;ResolvedLayout=InResolvedLayout;
WorkerCount=1;DetailVariant=Variant;Scheduler=nullptr;
// Analytic pages retain their bounded owner bins for route bookkeeping. Once published,
// the Core world binds each page to its base shape as the single density evaluator.
@@ -555,18 +611,20 @@ public:
void InitializeNativeSurfaceOnly(FStandaloneOwnerWorld& World,
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly,
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout,
const FIntVector& InMinimum,const FIntVector& InMaximum)
{
Owner=&World;WorldAssembly=InWorldAssembly;
Owner=&World;WorldAssembly=InWorldAssembly;ResolvedLayout=InResolvedLayout;
NativeOnlyMinimum=InMinimum;NativeOnlyMaximum=InMaximum;
bUseNativeManifestFields=true;bNativeOnly=true;bAnalyticOnly=true;Built=true;
NativeOnlyViolations.store(0,std::memory_order_relaxed);
}
bool BuildShapeDependencyProof(FStandaloneOwnerWorld& World,
const FIntVector& MinVoxels,const FIntVector& MaxVoxels,bool bInRoundTunnels)
const FIntVector& MinVoxels,const FIntVector& MaxVoxels,bool bInRoundTunnels,
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout=nullptr)
{
Owner=&World;WorkerCount=1;RoundTunnelsEnabled=bInRoundTunnels;
Owner=&World;ResolvedLayout=InResolvedLayout;WorkerCount=1;RoundTunnelsEnabled=bInRoundTunnels;
bAnalyticOnly=true;bNativeOnly=false;Stats={};
for(int32 Axis=0;Axis<3;++Axis)
{
@@ -1071,19 +1129,19 @@ public:
float NativeDensity=0.0f;
if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{RecordNativeOnlyViolation();return 0.0f;}
return NativeDensity;
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
}
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
{
++ThreadClassificationStats().NativeFloatFieldCalls;
return ApplyEditBrushes(IslandDensity,X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(IslandDensity,X,Y,Z),X,Y,Z);
}
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{
++ThreadClassificationStats().NativeFloatFieldCalls;
return ApplyEditBrushes(NativeDensity,X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
}
if(UsesLegacySurfaceField(X,Y,Z))
return LegacySurfaceDensityAt(X,Y,Z);
@@ -1104,7 +1162,7 @@ public:
if(UsesLegacySurfaceField(X,Y,Z))return "SurfaceWorld-legacy";
if(Owner&&Owner->Manager)
{
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CHUNK_SIZE));
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(GetResolvedLayoutChunkWidthVoxels()));
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ));
switch(Type)
{
@@ -1133,21 +1191,21 @@ public:
if(!NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),NativeDensity))
{RecordNativeOnlyViolation();return 0.0f;}
return NativeDensity;
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
}
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),IslandDensity))
{
++ThreadClassificationStats().NativeLatticeFieldCalls;
return ApplyEditBrushes(IslandDensity,X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(IslandDensity,X,Y,Z),X,Y,Z);
}
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),NativeDensity))
{
++ThreadClassificationStats().NativeLatticeFieldCalls;
return ApplyEditBrushes(NativeDensity,X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
}
if(UsesLegacySurfaceField(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z)))
@@ -1171,19 +1229,19 @@ public:
float NativeDensity=0.0f;
if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{RecordNativeOnlyViolation();return 0.0f;}
return NativeDensity;
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
}
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
{
++ThreadClassificationStats().NativeUnclampedFieldCalls;
return ApplyEditBrushes(IslandDensity,X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(IslandDensity,X,Y,Z),X,Y,Z);
}
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{
++ThreadClassificationStats().NativeUnclampedFieldCalls;
return ApplyEditBrushes(NativeDensity,X,Y,Z);
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
}
if(UsesLegacySurfaceField(X,Y,Z))
return LegacySurfaceDensityAt(X,Y,Z);
@@ -1224,6 +1282,12 @@ public:
OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z);
return;
}
if(IsInsideResolvedStrateBoundaryBand(static_cast<float>(Z)))
{
for(int32 Lane=0;Lane<Count;++Lane)
OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z);
return;
}
bool bHasLegacySurfaceSample=false;
bool bHasNativeIslandSample=false;
for(int32 Lane=0;Lane<Count;++Lane)
@@ -1307,13 +1371,14 @@ private:
float Density=static_cast<float>(Proxy)*(1.0f/256.0f);
if((Density<0.0f)!=bRock)
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/256.0f);
return ApplyEditBrushes(Density,FX,FY,FZ);
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(Density,FX,FY,FZ),FX,FY,FZ);
}
// Match the fixed fallback to the edge of the stored proxy band. The
// displacement is tapered to zero there, so this avoids a magnitude
// jump along the candidate bit-mask boundary.
constexpr float ProxyBandEdge=DetailSupremum+0.125f;
return ApplyEditBrushes(bRock?-ProxyBandEdge:ProxyBandEdge,FX,FY,FZ);
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(
bRock?-ProxyBandEdge:ProxyBandEdge,FX,FY,FZ),FX,FY,FZ);
}
float AnalyticDensityAt(float X,float Y,float Z,bool bClampNearZero=true,
@@ -1329,14 +1394,21 @@ private:
if(OutFloorInfluence)*OutFloorInfluence=0.0f;
float IslandDensity=0.0f;
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
return bApplyEdits?ApplyEditBrushes(IslandDensity,X,Y,Z):IslandDensity;
{
const float composed=ApplyResolvedStrateBoundaryBand(IslandDensity,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
}
if(UsesLegacySurfaceField(X,Y,Z))
{
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{
NativeDensity=ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(NativeDensity,X,Y,Z):NativeDensity;
}
const float Density=Owner->Generator->GetDensityAt(X,Y,Z);
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
const float composed=ApplyResolvedStrateBoundaryBand(Density,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
}
const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]
&&Z>=Min[2]&&Z<=Max[2];
@@ -1350,7 +1422,8 @@ private:
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
bClampNearZero?0.05f:1.0e-7f);
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
const float composed=ApplyResolvedStrateBoundaryBand(Density,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
}
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
bool bFloorSurface=false,bCriticalAir=false;float CaveInfluence=0.0f,FloorInfluence=0.0f;
@@ -1379,6 +1452,7 @@ private:
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
bClampNearZero?0.05f:1.0e-7f);
Density=ApplyResolvedStrateBoundaryBand(Density,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
}
@@ -1388,10 +1462,11 @@ public:
{
if(SharedDensitySource)
return SharedDensitySource->GetNativeSurfaceHeightsAt(X,Y,ChunkZ,OutGroundZ,OutCeilingZ);
if(!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
if(!WorldAssembly||!ResolvedLayout||!Owner||!Owner->Manager||!Owner->Generator
||!std::isfinite(X)||!std::isfinite(Y))return false;
const std::int64_t chunkMinimum=static_cast<std::int64_t>(ChunkZ)*CHUNK_SIZE;
const std::int64_t chunkMaximum=chunkMinimum+CHUNK_SIZE;
const double ChunkWidth=GetResolvedLayoutChunkWidthVoxels();
const std::int64_t chunkMinimum=static_cast<std::int64_t>(std::llround(ChunkZ*ChunkWidth));
const std::int64_t chunkMaximum=static_cast<std::int64_t>(std::llround((ChunkZ+1)*ChunkWidth));
if(bNativeOnly&&(!NativeOnlyContainsColumn(X,Y)
||chunkMaximum<NativeOnlyMinimum.Z||chunkMinimum>NativeOnlyMaximum.Z
||Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
@@ -1400,7 +1475,8 @@ public:
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
!=ECaveGeneratorType::SurfaceWorld)return false;
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
static_cast<float>((static_cast<double>(ChunkZ)+0.5)
*ResolvedLayout->strateLayoutChunkSizeMicrometres/10000.0));
const auto* Plan=FindWorldAssemblyStratum(StrateIndex,ChunkZ);
if(!Plan)return false;
constexpr double Scale=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
@@ -1413,7 +1489,7 @@ public:
const VoxelForgeCore::Heightfield2D Block(WorldAssembly->recipe,*Plan);
if(!Block.isValid())return false;
const auto Column=Block.point(ToFixed(X),ToFixed(Y),
static_cast<int64>(ChunkZ)*CHUNK_SIZE*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
static_cast<int64>(ChunkZ)*ResolvedLayout->strateLayoutChunkWidthSubvoxels);
OutGroundZ=static_cast<float>(Column.groundHeightSubvoxels/Scale);
OutCeilingZ=static_cast<float>(Column.capHeightSubvoxels/Scale);
return std::isfinite(OutGroundZ)&&std::isfinite(OutCeilingZ);
@@ -1429,10 +1505,9 @@ public:
const int32 Pad=std::max(Step,1);
const int32 BMin=Origin.Z-Pad;
const int32 BMax=Origin.Z+(Cells+1)*Step;
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin)
/static_cast<float>(CHUNK_SIZE));
const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax)
/static_cast<float>(CHUNK_SIZE));
const float LayoutWidth=static_cast<float>(GetResolvedLayoutChunkWidthVoxels());
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin)/LayoutWidth);
const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax)/LayoutWidth);
const VoxelForgeCore::WorldAssemblyStratumPlanV1* Plan=nullptr;
for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ)
{
@@ -1474,53 +1549,32 @@ public:
const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* IslandPlan=nullptr;
bool bSurfaceOverlap=false,bIslandOverlap=false,bOtherStrateOverlap=false;
bool bIslandOtherStrateOverlap=false,bBadPlan=false;
if(Owner&&Owner->Manager)
if(Owner&&Owner->Manager&&ResolvedLayout)
{
// Keep the delivered SurfaceWorld chunk mapping unchanged.
// Conserver le mapping de chunks SurfaceWorld deja livre.
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin[2])
/static_cast<float>(CHUNK_SIZE));
const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax[2])
/static_cast<float>(CHUNK_SIZE));
const float LayoutWidth=static_cast<float>(GetResolvedLayoutChunkWidthVoxels());
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin[2])/LayoutWidth);
const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax[2])/LayoutWidth);
for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ)
{
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(
FIntVector(0,0,ChunkZ));
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ));
const int32 StrateIndex=Owner->Manager->GetStrateIndex(static_cast<float>(
(static_cast<double>(ChunkZ)+0.5)*ResolvedLayout->strateLayoutChunkSizeMicrometres/10000.0));
if(Type==ECaveGeneratorType::SurfaceWorld)
{
bSurfaceOverlap=true;
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
bSurfaceOverlap=true;bIslandOtherStrateOverlap=true;
const auto* Candidate=FindWorldAssemblyStratum(StrateIndex,ChunkZ);
if(!Candidate||(SurfacePlan&&SurfacePlan!=Candidate))
bBadPlan=true;
if(!Candidate||(SurfacePlan&&SurfacePlan!=Candidate))bBadPlan=true;
SurfacePlan=Candidate;
}
else if(Type!=ECaveGeneratorType::FloatingIslands)
bOtherStrateOverlap=true;
}
// Island strata preserve owner-authored physical bounds through the resolved scale ratio.
// Les strates d'iles conservent les bornes physiques owner via le rapport de resolution.
const double CoreVoxelsPerIslandChunk=GetCoreVoxelsPerOwnerChunk();
const int32 FirstIslandChunk=FMath::FloorToInt(static_cast<float>(BMin[2])
/static_cast<float>(CoreVoxelsPerIslandChunk));
const int32 LastIslandChunk=FMath::FloorToInt(static_cast<float>(BMax[2])
/static_cast<float>(CoreVoxelsPerIslandChunk));
for(int32 ChunkZ=FirstIslandChunk;ChunkZ<=LastIslandChunk;++ChunkZ)
{
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(
FIntVector(0,0,ChunkZ));
if(Type==ECaveGeneratorType::FloatingIslands)
else if(Type==ECaveGeneratorType::FloatingIslands)
{
bIslandOverlap=true;
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
bIslandOverlap=true;bOtherStrateOverlap=true;
const auto* Candidate=FindWorldAssemblyFloatingIslandStratum(StrateIndex,ChunkZ);
if(!Candidate||(IslandPlan&&IslandPlan!=Candidate))bBadPlan=true;
IslandPlan=Candidate;
}
else bIslandOtherStrateOverlap=true;
else
{bOtherStrateOverlap=true;bIslandOtherStrateOverlap=true;}
}
}
const auto Finish=[&](EVoxelTileClass Verdict,bool bUnknown=false)
@@ -1536,6 +1590,25 @@ public:
else ++Counts.MixedBoxes;
return Result;
};
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
&&EditBoundsMin.Z<=BMax[2]&&EditBoundsMax.Z>=BMin[2])
return Finish(EVoxelTileClass::Mixed,true);
if(ResolvedLayout&&Owner)
{
const double ZMinMicrometres=static_cast<double>(BMin[2])*Owner->CoreVoxelSizeMicrons;
const double ZMaxMicrometres=static_cast<double>(BMax[2])*Owner->CoreVoxelSizeMicrons;
for(const auto& Stratum:ResolvedLayout->strates)
for(const auto* Band:{&Stratum.ceiling,&Stratum.bedrock})
if(Band->presentInCurrentField&&ZMaxMicrometres>=Band->bottomMicrometres
&&ZMinMicrometres<Band->topMicrometres)
{
if(ZMinMicrometres>=Band->bottomMicrometres
&&ZMaxMicrometres<Band->topMicrometres)
return Finish(EVoxelTileClass::AllSolid);
return Finish(EVoxelTileClass::Mixed);
}
}
if(bNativeOnly)
{
const bool bInsideProofBox=BMin[0]>=NativeOnlyMinimum.X
@@ -1555,10 +1628,6 @@ public:
||(bIslandOverlap&&(bIslandOtherStrateOverlap
||(!bSurfaceOverlap&&bOtherStrateOverlap))))
return Finish(EVoxelTileClass::Mixed,true);
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
&&EditBoundsMin.Z<=BMax[2]&&EditBoundsMax.Z>=BMin[2])
return Finish(EVoxelTileClass::Mixed,true);
// The native interval path below is the exact field selected first by DensityAtLattice
// for SurfaceWorld and FloatingIslands. Legacy analytic primitives/layer bounds cannot
// modify that field; only the explicit edit transaction above can. Keep their guards on
@@ -2151,13 +2220,16 @@ private:
bool NativeSurfaceDensityAt(float X,float Y,float Z,float& OutDensity) const
{
if(!bUseNativeManifestFields||!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
if(!bUseNativeManifestFields||!WorldAssembly||!ResolvedLayout||!Owner||!Owner->Manager||!Owner->Generator
||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))return false;
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CHUNK_SIZE));
const float LayoutWidth=static_cast<float>(GetResolvedLayoutChunkWidthVoxels());
if(!(LayoutWidth>0.0f))return false;
const int32 ChunkZ=FMath::FloorToInt(Z/LayoutWidth);
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
!=ECaveGeneratorType::SurfaceWorld)return false;
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
static_cast<float>((static_cast<double>(ChunkZ)+0.5)
*ResolvedLayout->strateLayoutChunkSizeMicrometres/10000.0));
const auto* Plan=FindWorldAssemblyStratum(StrateIndex,ChunkZ);
if(!Plan)return false;
constexpr double Scale=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
@@ -2177,16 +2249,18 @@ private:
bool NativeFloatingIslandDensityAt(float X,float Y,float Z,float& OutDensity) const
{
if(!bUseNativeManifestFields||!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))
||!ResolvedLayout||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))
return false;
// Convert Core query Z back to the owner's legacy chunk coordinate.
// Convertir Z du Core vers la coordonnee de chunk legacy du proprietaire.
const double CoreVoxelsPerLegacyChunk=GetCoreVoxelsPerOwnerChunk();
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CoreVoxelsPerLegacyChunk));
const double LayoutChunkWidth=GetResolvedLayoutChunkWidthVoxels();
if(!(LayoutChunkWidth>0.0))return false;
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(LayoutChunkWidth));
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
!=ECaveGeneratorType::FloatingIslands)return false;
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
static_cast<float>((static_cast<double>(ChunkZ)+0.5)
*ResolvedLayout->strateLayoutChunkSizeMicrometres/10000.0));
const auto* Plan=FindWorldAssemblyFloatingIslandStratum(StrateIndex,ChunkZ);
if(!Plan)return false;
constexpr double Scale=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
@@ -2212,7 +2286,7 @@ private:
Density=std::min(Density,-std::max<std::int64_t>(1,Top-Zq));
OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
}
else
else if(Plan->manifest.recipe.geometryVersion<4)
{
OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
FResolvedStratumBounds Bounds;
@@ -2226,15 +2300,16 @@ private:
static_cast<float>(Bounds.SealThickness),Params.BaseDensity);
OutDensity=-InternalDensity;
}
else OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
return std::isfinite(OutDensity);
}
bool UsesLegacySurfaceField(float X,float Y,float Z) const
{
if(!Owner||!Owner->Manager||!Owner->Generator)return false;
const FIntVector ChunkCoord(FMath::FloorToInt(X/static_cast<float>(CHUNK_SIZE)),
FMath::FloorToInt(Y/static_cast<float>(CHUNK_SIZE)),
FMath::FloorToInt(Z/static_cast<float>(CHUNK_SIZE)));
const double LayoutChunkWidth=GetResolvedLayoutChunkWidthVoxels();
if(!(LayoutChunkWidth>0.0))return false;
const FIntVector ChunkCoord(0,0,FMath::FloorToInt(Z/static_cast<float>(LayoutChunkWidth)));
return Owner->Manager->GetGeneratorTypeForChunk(ChunkCoord)
==ECaveGeneratorType::SurfaceWorld;
}
@@ -2244,7 +2319,8 @@ private:
// SurfaceWorld consumes biome overrides, terrain ops, water level and its sky cap in
// the legacy stack. Reuse that immutable evaluator as an exact temporary bridge.
// SurfaceWorld lit biomes, ops de terrain, niveau d'eau et plafond; reutiliser ce champ.
return ApplyEditBrushes(Owner->Generator->GetDensityAt(X,Y,Z),X,Y,Z);
const float density=ApplyResolvedStrateBoundaryBand(Owner->Generator->GetDensityAt(X,Y,Z),X,Y,Z);
return ApplyEditBrushes(density,X,Y,Z);
}
float ApplyEditBrushes(float Density,double X,double Y,double Z) const
@@ -3571,8 +3647,9 @@ private:
auto Unit=[&](uint32 H){return static_cast<double>(Hash(H)>>8)*(1.0/16777216.0);};
for(const FStrateSlot& Slot:Layout)
{
const double ZBottom=static_cast<double>(Slot.BottomChunkZ)*CHUNK_SIZE;
const double ZTop=static_cast<double>(Slot.TopChunkZ+1)*CHUNK_SIZE;
const double LayoutChunkWidth=GetResolvedLayoutChunkWidthVoxels();
const double ZBottom=static_cast<double>(Slot.BottomChunkZ)*LayoutChunkWidth;
const double ZTop=static_cast<double>(Slot.TopChunkZ+1)*LayoutChunkWidth;
for(int32 GY=GY0;GY<=GY1;++GY)for(int32 GX=GX0;GX<=GX1;++GX)
{
uint32 H=Hash(Seed^static_cast<uint32>(GX)*0x9E3779B9u
@@ -4953,6 +5030,16 @@ public:
OutDensity[Lane]=DensityAtLattice(X,Y,ZStart+Lane*ZStep);
return;
}
bool bHasResolvedBoundaryBand=false;
for(int32 Lane=0;Lane<Count&&!bHasResolvedBoundaryBand;++Lane)
bHasResolvedBoundaryBand=IsInsideResolvedStrateBoundaryBand(
static_cast<float>(ZStart+Lane*ZStep));
if(bHasResolvedBoundaryBand)
{
for(int32 Lane=0;Lane<Count;++Lane)
OutDensity[Lane]=DensityAtLattice(X,Y,ZStart+Lane*ZStep);
return;
}
bool bHasLegacySurfaceSample=false;
for(int32 Lane=0;Lane<Count;++Lane)
bHasLegacySurfaceSample|=UsesLegacySurfaceField(static_cast<float>(X),
+67 -23
View File
@@ -34,6 +34,7 @@ namespace VoxelForgeCore
{
std::shared_ptr<FStandaloneOwnerWorld> owner;
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
WorldConfiguration configuration;
};
@@ -390,8 +391,8 @@ namespace VoxelForgeCore
{
const VoxelForgeCore::Heightfield2D Block(Manifest.recipe,Plan);
if(!Block.isValid())continue;
const int64 StratumMinZ=static_cast<int64>(Plan.bottomChunkZ)*CHUNK_SIZE*Scale;
const int64 StratumMaxZ=(static_cast<int64>(Plan.topChunkZ)+1)*CHUNK_SIZE*Scale;
const int64 StratumMinZ=static_cast<int64>(Plan.bottomChunkZ)*Manifest.recipe.layoutChunkWidthSubvoxels*Scale;
const int64 StratumMaxZ=(static_cast<int64>(Plan.topChunkZ)+1)*Manifest.recipe.layoutChunkWidthSubvoxels*Scale;
if(TileMaxZ<StratumMinZ||TileMinZ>StratumMaxZ)continue;
struct FGridSample
{
@@ -439,8 +440,10 @@ namespace VoxelForgeCore
FGroundProfile Profile;
Profile.GridStep=GridStepVoxels;Profile.Coordinates=GridCoordinates;
Profile.MinimumZ=static_cast<double>(std::min(Plan.bottomChunkZ,Plan.topChunkZ))*CHUNK_SIZE;
Profile.MaximumZ=static_cast<double>(std::max(Plan.bottomChunkZ,Plan.topChunkZ)+1)*CHUNK_SIZE;
Profile.MinimumZ=static_cast<double>(std::min(Plan.bottomChunkZ,Plan.topChunkZ))
*Manifest.recipe.layoutChunkWidthSubvoxels/Scale;
Profile.MaximumZ=static_cast<double>(std::max(Plan.bottomChunkZ,Plan.topChunkZ)+1)
*Manifest.recipe.layoutChunkWidthSubvoxels/Scale;
Profile.Ground.reserve(Grid.size());Profile.Cap.reserve(Grid.size());
for(const auto& Sample:Grid)
{Profile.Ground.push_back(Sample.Ground/Scale);Profile.Cap.push_back(Sample.Cap/Scale);}
@@ -865,13 +868,15 @@ namespace VoxelForgeCore
struct detail_StandaloneSnapshotAdapter
{
static ResolvedWorldSnapshot FromOwner(std::shared_ptr<FStandaloneOwnerWorld> owner,
std::shared_ptr<const WorldAssemblyManifest> worldAssembly = {})
std::shared_ptr<const WorldAssemblyManifest> worldAssembly = {},
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout = {})
{
if(!owner||!owner->Settings||!owner->Manager||!owner->Generator)return {};
auto data=std::make_shared<ResolvedWorldSnapshot::Data>();
data->configuration=MakeConfiguration(*owner);
data->owner=std::move(owner);
data->worldAssembly=std::move(worldAssembly);
data->resolvedLayout=std::move(resolvedLayout);
return ResolvedWorldSnapshot(std::move(data));
}
};
@@ -889,6 +894,16 @@ namespace VoxelForgeCore
if(!parsed)return {};
worldAssembly=std::make_shared<WorldAssemblyManifest>(std::move(*parsed));
}
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
if(jsonRoot.Get("resolved_layout_json").IsString())
{
auto parsed=ResolvedLayoutSnapshotV2::FromJson(jsonRoot.Get("resolved_layout_json").String(),error);
if(!parsed)return {};
std::vector<ResolvedLayoutIssueV2> issues;std::string strictError;
if(!parsed->validate(ResolvedLayoutValidationModeV2::strict,issues,strictError))
{error="strict resolved-layout validation failed: "+strictError;return {};}
resolvedLayout=std::make_shared<ResolvedLayoutSnapshotV2>(std::move(*parsed));
}
const bool seedChanged=owner->Seed!=seed;
owner->Settings->Seed=seed;
@@ -911,7 +926,7 @@ namespace VoxelForgeCore
owner->MaxClipLevel=maxLodOverride;
owner->Settings->MaxClipLevel=maxLodOverride;
}
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner),std::move(worldAssembly));
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner),std::move(worldAssembly),std::move(resolvedLayout));
}
std::optional<ResolvedWorldSnapshot> ResolvedWorldSnapshot::FromJson(
@@ -928,11 +943,23 @@ namespace VoxelForgeCore
if(!parsed)return std::nullopt;
worldAssembly=std::make_shared<WorldAssemblyManifest>(std::move(*parsed));
}
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner),std::move(worldAssembly));
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
if(root.Get("resolved_layout_json").IsString())
{
auto parsed=ResolvedLayoutSnapshotV2::FromJson(root.Get("resolved_layout_json").String(),error);
if(!parsed)return std::nullopt;
std::vector<ResolvedLayoutIssueV2> issues;std::string strictError;
if(!parsed->validate(ResolvedLayoutValidationModeV2::strict,issues,strictError))
{error="strict resolved-layout validation failed: "+strictError;return std::nullopt;}
resolvedLayout=std::make_shared<ResolvedLayoutSnapshotV2>(std::move(*parsed));
}
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner),std::move(worldAssembly),std::move(resolvedLayout));
}
const WorldAssemblyManifest* ResolvedWorldSnapshot::worldAssembly() const noexcept
{ return data_?data_->worldAssembly.get():nullptr; }
const ResolvedLayoutSnapshotV2* ResolvedWorldSnapshot::resolvedLayout() const noexcept
{ return data_?data_->resolvedLayout.get():nullptr; }
const WorldConfiguration& ResolvedWorldSnapshot::configuration() const
{
@@ -944,6 +971,7 @@ namespace VoxelForgeCore
{
std::shared_ptr<FStandaloneOwnerWorld> owner;
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
WorldConfiguration configuration;
std::unique_ptr<FStandaloneShapeWorld> shape;
std::atomic<bool> shapeReady{false};
@@ -984,6 +1012,7 @@ namespace VoxelForgeCore
{
impl_->owner=snapshot.data_->owner;
impl_->worldAssembly=snapshot.data_->worldAssembly;
impl_->resolvedLayout=snapshot.data_->resolvedLayout;
impl_->configuration=snapshot.data_->configuration;
if(impl_->worldAssembly)
{
@@ -1141,7 +1170,7 @@ namespace VoxelForgeCore
static_cast<int32>(std::clamp<std::uint32_t>(request.workerLimit,1,16)),ToNative(request.detail),
request.includeFarWorld,error,&scheduler,request.roundTunnels,request.profileDetailBand,
request.validatePassageRoutes,request.gpuVisualDetailTiles,
request.cpuVisualDetailTiles,impl_->worldAssembly.get());
request.cpuVisualDetailTiles,impl_->worldAssembly.get(),impl_->resolvedLayout.get());
if(ok)
{
impl_->shape=std::move(shape);
@@ -1156,23 +1185,25 @@ namespace VoxelForgeCore
bool World::isTileShapeFree(const TileKey& key) const
{
if(!impl_||!impl_->owner||!impl_->worldAssembly||!impl_->owner->Manager
if(!impl_||!impl_->owner||!impl_->worldAssembly||!impl_->resolvedLayout||!impl_->owner->Manager
||!impl_->owner->Generator||!impl_->editLog.empty())return false;
FIntVector boundsMin,boundsMax;
if(!NativeOnlyBoundsForTile(*impl_,key,boundsMin,boundsMax))return false;
const std::int64_t tileMinimum[3]={boundsMin.X,boundsMin.Y,boundsMin.Z};
const std::int64_t tileMaximum[3]={boundsMax.X,boundsMax.Y,boundsMax.Z};
const int32 firstSurfaceChunk=FloorDiv(boundsMin.Z,CHUNK_SIZE);
const int32 lastSurfaceChunk=FloorDiv(boundsMax.Z,CHUNK_SIZE);
const int32 LayoutChunkWidth=static_cast<int32>(std::max<std::int64_t>(1,
impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel));
const int32 firstSurfaceChunk=FloorDiv(boundsMin.Z,LayoutChunkWidth);
const int32 lastSurfaceChunk=FloorDiv(boundsMax.Z,LayoutChunkWidth);
const WorldAssemblyStratumPlanV1* selectedPlan=nullptr;
for(int32 chunkZ=firstSurfaceChunk;chunkZ<=lastSurfaceChunk;++chunkZ)
{
if(impl_->owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,chunkZ))
!=ECaveGeneratorType::SurfaceWorld)return false;
const int32 strateIndex=impl_->owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(chunkZ)*CHUNK_SIZE
+CHUNK_SIZE*0.5)*VOXEL_SIZE));
static_cast<float>((static_cast<double>(chunkZ)+0.5)
*impl_->resolvedLayout->strateLayoutChunkSizeMicrometres/10000.0));
const auto found=std::find_if(impl_->worldAssembly->plan.strates.begin(),
impl_->worldAssembly->plan.strates.end(),[strateIndex,chunkZ](const auto& plan)
{return plan.strateIndex==strateIndex&&chunkZ<=plan.topChunkZ
@@ -1184,9 +1215,8 @@ namespace VoxelForgeCore
if(!selectedPlan||!Heightfield2D(impl_->worldAssembly->recipe,*selectedPlan).isValid())
return false;
const double coreVoxelsPerOwnerChunk=static_cast<double>(CHUNK_SIZE)
*static_cast<double>(impl_->owner->OwnerVoxelSizeMicrons)
/static_cast<double>(std::max(1u,impl_->owner->CoreVoxelSizeMicrons));
const double coreVoxelsPerOwnerChunk=static_cast<double>(
impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels)/WorldAssemblySubvoxelsPerVoxel;
const int32 firstIslandChunk=FMath::FloorToInt(
static_cast<double>(boundsMin.Z)/coreVoxelsPerOwnerChunk);
const int32 lastIslandChunk=FMath::FloorToInt(
@@ -1207,7 +1237,7 @@ namespace VoxelForgeCore
if(OwnerPassageTouchesBox(*impl_->owner,boundsMin,boundsMax))return false;
FStandaloneShapeWorld proofGeometry;
if(!proofGeometry.BuildShapeDependencyProof(*impl_->owner,boundsMin,boundsMax,
impl_->owner->Generator->bStandaloneResearchRoundTunnels))return false;
impl_->owner->Generator->bStandaloneResearchRoundTunnels,impl_->resolvedLayout.get()))return false;
const FIntVector tileOrigin=ToNative(key).OriginVoxels();
if(proofGeometry.HasShapeDependencyInBox(tileOrigin,1,CHUNK_SIZE))return false;
return true;
@@ -1321,6 +1351,7 @@ namespace VoxelForgeCore
auto region=std::make_shared<FStandaloneShapeWorld>();
if(!region->BuildAnalyticRegion(*impl_->owner,fineMin,fineMax,regionMin,regionMax,
ToNative(base.detail),base.roundTunnels,impl_->worldAssembly.get(),
impl_->resolvedLayout.get(),
regionErrors[index]))return;
compiledRegions[index]=std::move(region);
};
@@ -1371,7 +1402,8 @@ namespace VoxelForgeCore
auto region=std::make_shared<FStandaloneShapeWorld>();
if(!region->Build(*impl_->owner,fineMin,fineMax,regionMin,regionMax,
static_cast<int32>(clampedWorkers),ToNative(base.detail),false,error,&scheduler,
base.roundTunnels,base.profileDetailBand,false,{}, {},impl_->worldAssembly.get()))return false;
base.roundTunnels,base.profileDetailBand,false,{}, {},impl_->worldAssembly.get(),
impl_->resolvedLayout.get()))return false;
if(!impl_->editLog.empty())region->SetEditTransaction(impl_->editLog);
const auto& build=region->GetStats();
++stats.regionsBuilt;
@@ -1631,6 +1663,7 @@ namespace VoxelForgeCore
if(!options.diagnosticForceNativeOnly&&!isTileShapeFree(key))
{error="shape-free guard: tile failed the native-only readiness proof";return std::nullopt;}
nativeOnlyShape.InitializeNativeSurfaceOnly(*impl_->owner,impl_->worldAssembly.get(),
impl_->resolvedLayout.get(),
nativeOnlyMinimum,nativeOnlyMaximum);
}
else if(impl_->tileRegionCount.load(std::memory_order_acquire)!=0)
@@ -1787,15 +1820,19 @@ namespace VoxelForgeCore
const int64 TileMaximumZ=static_cast<int64>(origin.Z)+extent;
for(const auto& Plan:impl_->worldAssembly->plan.strates)
{
const int64 PlanMinimumZ=static_cast<int64>(std::min(Plan.bottomChunkZ,Plan.topChunkZ))*CHUNK_SIZE;
const int64 PlanMaximumZ=(static_cast<int64>(std::max(Plan.bottomChunkZ,Plan.topChunkZ))+1)*CHUNK_SIZE;
const int64 PlanMinimumZ=static_cast<int64>(std::min(Plan.bottomChunkZ,Plan.topChunkZ))
*impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel;
const int64 PlanMaximumZ=(static_cast<int64>(std::max(Plan.bottomChunkZ,Plan.topChunkZ)+1))
*impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel;
bHasNativeSurfaceHorizonPlan=bHasNativeSurfaceHorizonPlan
||(TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ);
}
for(const auto& Entry:impl_->worldAssembly->floatingIslands)
{
const int64 PlanMinimumZ=static_cast<int64>(std::min(Entry.bottomChunkZ,Entry.topChunkZ))*CHUNK_SIZE;
const int64 PlanMaximumZ=(static_cast<int64>(std::max(Entry.bottomChunkZ,Entry.topChunkZ))+1)*CHUNK_SIZE;
const int64 PlanMinimumZ=static_cast<int64>(std::min(Entry.bottomChunkZ,Entry.topChunkZ))
*impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel;
const int64 PlanMaximumZ=(static_cast<int64>(std::max(Entry.bottomChunkZ,Entry.topChunkZ)+1))
*impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel;
bHasIslandPlanInTile=bHasIslandPlanInTile
||(TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ);
}
@@ -1896,7 +1933,11 @@ namespace VoxelForgeCore
const double CoreVoxelSizeCm=static_cast<double>(impl_->configuration.voxelSizeMicrons)/10000.0;
const double OwnerVoxelSizeCm=impl_->owner!=nullptr&&impl_->owner->OwnerVoxelSizeMicrons>0
?static_cast<double>(impl_->owner->OwnerVoxelSizeMicrons)/10000.0:0.5*static_cast<double>(VOXEL_SIZE);
const double CoreVoxelsPerOwnerChunk=static_cast<double>(CHUNK_SIZE)*OwnerVoxelSizeCm/CoreVoxelSizeCm;
const double CoreVoxelsPerOwnerChunk=impl_->resolvedLayout
?static_cast<double>(impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels)
/WorldAssemblySubvoxelsPerVoxel
:static_cast<double>(impl_->owner->StrateLayoutChunkWidthSubvoxels)
/WorldAssemblySubvoxelsPerVoxel;
const int32 CenterChunkZ=FMath::FloorToInt(TileCenterZ/CoreVoxelsPerOwnerChunk);
const int32 StrateIndex=world.Manager->GetStrateIndex(
static_cast<float>(TileCenterZ*CoreVoxelSizeCm));
@@ -2210,7 +2251,10 @@ namespace VoxelForgeCore
out.diagnostics.horizonCapTriangles=CapStats.Triangles;
}
if(impl_->worldAssembly&&!bOverlapsPlayerEdit)
{
AppendStratumBoundaryFloors(world,farShape,nativeKey,step,mesh);
AppendStratumBoundaryCeilings(world,farShape,nativeKey,step,mesh);
}
out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
if(bHasWorldAssembly)
+25 -19
View File
@@ -235,6 +235,9 @@ namespace world_assembly_detail
|| recipe.evaluatorVersion > WorldAssemblyEvaluatorVersion
|| recipe.unitsPerVoxel != WorldAssemblySubvoxelsPerVoxel)
{ error = "world assembly recipe version, evaluator or fixed scale is unsupported"; return false; }
if (recipe.layoutChunkWidthSubvoxels <= 0
|| recipe.layoutChunkWidthSubvoxels > 256ll * 512ll)
{ error = "resolved strate-layout chunk width is outside the supported physical range"; return false; }
if (recipe.biomes.empty() || recipe.biomes.size() > MaxBiomeCount)
{ error = "world assembly recipe must contain one to eight biome profiles"; return false; }
if (recipe.biomeCellSizeSubvoxels <= 0 || recipe.biomeCellSizeSubvoxels > 100000000ll
@@ -296,7 +299,8 @@ namespace world_assembly_detail
return true;
}
inline bool ValidateStratum(const WorldAssemblyStratumPlanV1& stratum, std::string& error)
inline bool ValidateStratum(const WorldAssemblyStratumPlanV1& stratum,
std::int64_t layoutChunkWidthSubvoxels, std::string& error)
{
if (stratum.strateIndex < 0 || stratum.topChunkZ < stratum.bottomChunkZ
|| stratum.capWavelengthSubvoxels <= 0 || stratum.capUndulationSubvoxels < 0
@@ -330,9 +334,9 @@ namespace world_assembly_detail
if (feature.type == 2 || feature.type == 4)
{
const std::int64_t stratumMinZ = static_cast<std::int64_t>(stratum.bottomChunkZ)
* 32 * WorldAssemblySubvoxelsPerVoxel;
* layoutChunkWidthSubvoxels;
const std::int64_t stratumMaxZ = (static_cast<std::int64_t>(stratum.topChunkZ) + 1)
* 32 * WorldAssemblySubvoxelsPerVoxel;
* layoutChunkWidthSubvoxels;
constexpr std::int64_t maximumExceptionExtent = 1024
* WorldAssemblySubvoxelsPerVoxel;
for (std::size_t axis = 0; axis < 3; ++axis)
@@ -420,6 +424,7 @@ namespace world_assembly_detail
std::uint64_t hash = StartHash();
HashWord(hash, recipe.schemaVersion, 4); HashWord(hash, recipe.evaluatorVersion, 4);
HashWord(hash, recipe.unitsPerVoxel, 4); HashWord(hash, recipe.seed, 4);
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutChunkWidthSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeCellSizeSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeWarpSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeWarpWavelengthSubvoxels), 8);
@@ -628,6 +633,7 @@ namespace world_assembly_detail
if (!value.IsObject() || !ReadU32(value.Get("schema_version"), recipe.schemaVersion)
|| !ReadU32(value.Get("evaluator_version"), recipe.evaluatorVersion)
|| !ReadU32(value.Get("units_per_voxel"), recipe.unitsPerVoxel)
|| !ReadInteger(value.Get("layout_chunk_width_q"), recipe.layoutChunkWidthSubvoxels)
|| !ReadU32(value.Get("seed"), recipe.seed)
|| !ReadInteger(value.Get("biome_cell_size_q"), recipe.biomeCellSizeSubvoxels)
|| !ReadInteger(value.Get("biome_warp_q"), recipe.biomeWarpSubvoxels)
@@ -693,6 +699,7 @@ namespace world_assembly_detail
out << "{\"schema_version\":" << recipe.schemaVersion
<< ",\"evaluator_version\":" << recipe.evaluatorVersion
<< ",\"units_per_voxel\":" << recipe.unitsPerVoxel
<< ",\"layout_chunk_width_q\":" << recipe.layoutChunkWidthSubvoxels
<< ",\"seed\":" << recipe.seed
<< ",\"biome_cell_size_q\":" << recipe.biomeCellSizeSubvoxels
<< ",\"biome_warp_q\":" << recipe.biomeWarpSubvoxels
@@ -861,7 +868,8 @@ namespace world_assembly_detail
|| !ReadI32(islandValue.Get("bottom_chunk_z"), entry.bottomChunkZ))
{ error = "world assembly floating-island slot is missing its resolved strate bounds"; return std::nullopt; }
const std::string nestedJson = JsonText(islandValue.Get("manifest"));
auto parsedIsland = FloatingIslandManifestV1::FromJson(nestedJson, error);
auto parsedIsland = FloatingIslandManifestV1::FromJson(
nestedJson, error, manifest.recipe.layoutChunkWidthSubvoxels);
if (!parsedIsland) return std::nullopt;
entry.manifest = std::move(*parsedIsland);
manifest.floatingIslands.push_back(std::move(entry));
@@ -1751,15 +1759,15 @@ bool WorldAssemblyManifest::finalize(std::string& error)
{ error = "SurfaceWorld stratum recipe seed does not match the world assembly season seed"; return false; }
stratum.recipeOverride.contentHash = world_assembly_detail::HashRecipe(stratum.recipeOverride);
}
if (!world_assembly_detail::ValidateStratum(stratum, error)) return false;
if (!world_assembly_detail::ValidateStratum(stratum, recipe.layoutChunkWidthSubvoxels, error)) return false;
if (recipe.evaluatorVersion >= 4)
{
if (stratum.routeConstraints.version != 1 || stratum.route.size() < 2)
{ error = "world assembly evaluator 4 requires a versioned entrance-to-exit route"; return false; }
const std::int64_t bottomZ = static_cast<std::int64_t>(stratum.bottomChunkZ)
* 32 * WorldAssemblySubvoxelsPerVoxel;
* recipe.layoutChunkWidthSubvoxels;
const std::int64_t topZ = (static_cast<std::int64_t>(stratum.topChunkZ) + 1)
* 32 * WorldAssemblySubvoxelsPerVoxel;
* recipe.layoutChunkWidthSubvoxels;
const std::int64_t halfHeight = stratum.routeConstraints.capsuleHeightSubvoxels / 2;
for (const auto& point : stratum.route)
{
@@ -1789,14 +1797,12 @@ bool WorldAssemblyManifest::finalize(std::string& error)
const auto& Scale = islands.manifest.recipe;
if (Scale.ownerToCoreScaleNumerator == 0 || Scale.ownerToCoreScaleDenominator == 0)
{ error = "floating-island owner-to-Core scale is invalid"; return false; }
const double CoreVoxelsPerOwnerChunk = static_cast<double>(CoreTileVoxelCount)
* Scale.ownerToCoreScaleNumerator / Scale.ownerToCoreScaleDenominator;
const std::int64_t bottomQ = static_cast<std::int64_t>(std::llround(
static_cast<double>(islands.bottomChunkZ) * CoreVoxelsPerOwnerChunk
* WorldAssemblySubvoxelsPerVoxel));
const std::int64_t topQ = static_cast<std::int64_t>(std::llround(
static_cast<double>(static_cast<std::int64_t>(islands.topChunkZ) + 1)
* CoreVoxelsPerOwnerChunk * WorldAssemblySubvoxelsPerVoxel));
if(Scale.layoutChunkWidthSubvoxels<=0)
{error="floating-island strate has no resolved layout chunk width";return false;}
const std::int64_t bottomQ = static_cast<std::int64_t>(islands.bottomChunkZ)
*Scale.layoutChunkWidthSubvoxels;
const std::int64_t topQ = (static_cast<std::int64_t>(islands.topChunkZ)+1)
*Scale.layoutChunkWidthSubvoxels;
if (islands.manifest.recipe.layoutMinimumZSubvoxels < bottomQ
|| islands.manifest.recipe.layoutMaximumZSubvoxels > topQ)
{ error = "floating-island recipe layout escapes the resolved owner strate bounds"; return false; }
@@ -2000,7 +2006,7 @@ std::array<std::int64_t,2> Heightfield2D::capHeightBounds() const noexcept
if(recipe_->evaluatorVersion>=5)
{
const long double TopSubvoxels=(static_cast<long double>(plan_->topChunkZ)+1.0L)
*CoreTileVoxelCount*WorldAssemblySubvoxelsPerVoxel;
*recipe_->layoutChunkWidthSubvoxels;
Minimum=std::min(Minimum,TopSubvoxels);
Maximum=std::min(Maximum,TopSubvoxels);
}
@@ -2019,7 +2025,7 @@ std::int64_t Heightfield2D::farSurfaceCapHeightSubvoxels(std::int64_t x,
// Clip only the far render surface. Keep the near density evaluator bit-identical.
// Borner uniquement le plafond lointain. L'evaluateur de densite proche reste identique.
const std::int64_t ownedUpperBoundary=(static_cast<std::int64_t>(plan_->topChunkZ)+1)
*CoreTileVoxelCount*WorldAssemblySubvoxelsPerVoxel;
*recipe_->layoutChunkWidthSubvoxels;
return std::min(column.capHeightSubvoxels,ownedUpperBoundary);
}
@@ -2298,8 +2304,8 @@ FieldInfluenceBounds Heightfield2D::influenceBounds() const noexcept
{
FieldInfluenceBounds out;
if (!isValid()) return out;
out.minimumZSubvoxels = static_cast<std::int64_t>(plan_->bottomChunkZ) * 32 * WorldAssemblySubvoxelsPerVoxel;
out.maximumZSubvoxels = (static_cast<std::int64_t>(plan_->topChunkZ) + 1) * 32 * WorldAssemblySubvoxelsPerVoxel;
out.minimumZSubvoxels = static_cast<std::int64_t>(plan_->bottomChunkZ) * recipe_->layoutChunkWidthSubvoxels;
out.maximumZSubvoxels = (static_cast<std::int64_t>(plan_->topChunkZ) + 1) * recipe_->layoutChunkWidthSubvoxels;
return out;
}