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:
1 parent
8753f6dbe4
commit
55c26a05a7
28 files changed
+1432
-579
No files matched your search
@@ -248,9 +248,9 @@ namespace VoxelForgeCore
|
||||
// This is independent of tile size and is the fixed input scale for Core field blocks.
|
||||
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
|
||||
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
||||
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 1;
|
||||
// Version 6 binds every compiled tile page to the world's base density evaluator.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 6;
|
||||
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2;
|
||||
// Version 7 binds plans to the physical strate-layout chunk width and shared boundary bands.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 7;
|
||||
|
||||
enum class FieldChannel : std::uint32_t
|
||||
{
|
||||
@@ -302,6 +302,8 @@ namespace VoxelForgeCore
|
||||
std::uint32_t schemaVersion = WorldAssemblySchemaVersion;
|
||||
std::uint32_t evaluatorVersion = WorldAssemblyEvaluatorVersion;
|
||||
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
|
||||
// Resolved physical strate chunk width in 1/256 Core voxel units; tileVoxelCount remains 32.
|
||||
std::int64_t layoutChunkWidthSubvoxels = 8192;
|
||||
std::uint32_t seed = 0;
|
||||
std::int64_t biomeCellSizeSubvoxels = 256 * 800;
|
||||
std::int64_t biomeWarpSubvoxels = 256 * 250;
|
||||
@@ -393,8 +395,9 @@ namespace VoxelForgeCore
|
||||
// Profile 1 preserves the original shallow pads. Profile 2 adds a thick crust,
|
||||
// bounded lobed roots and arched spans without changing the feature-family schema.
|
||||
// Version 3 keeps the version-2 island shapes, removes the all-XY lower boundary seal,
|
||||
// and routes its upper seal through the shared strate-boundary primitive.
|
||||
std::uint32_t geometryVersion = 3;
|
||||
// and routes its upper seal through the shared strate-boundary primitive. Version 4
|
||||
// records the director-resolved strate layout width in the recipe.
|
||||
std::uint32_t geometryVersion = 4;
|
||||
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
|
||||
// Import scale is explicit in microns; the reduced ratio maps owner-authored lengths to Core voxels.
|
||||
// L'echelle d'import est explicite en microns; le rapport reduit convertit les longueurs owner.
|
||||
@@ -408,6 +411,9 @@ namespace VoxelForgeCore
|
||||
std::int64_t layoutCenterTopZSubvoxels = -64 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t layoutMinimumZSubvoxels = -128 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t layoutMaximumZSubvoxels = 0;
|
||||
// Director-resolved physical strate layout chunk in 1/256 Core voxel units.
|
||||
// This is independent of the internal 32-voxel Core tile.
|
||||
std::int64_t layoutChunkWidthSubvoxels = 8192;
|
||||
std::uint32_t primaryIslandCount = 3;
|
||||
std::int64_t islandDensityQ16 = 1ll << 15;
|
||||
// Legacy owner lengths use ownerVoxelSizeMicrons; the scale ratio converts them to Core voxels.
|
||||
@@ -531,7 +537,8 @@ namespace VoxelForgeCore
|
||||
std::uint64_t contentHash = 0;
|
||||
|
||||
static std::optional<FloatingIslandManifestV1> FromJson(
|
||||
std::string_view json, std::string& error);
|
||||
std::string_view json, std::string& error,
|
||||
std::int64_t resolvedLayoutChunkWidthSubvoxels = 0);
|
||||
bool finalize(std::string& error);
|
||||
bool validate(std::string& error) const;
|
||||
std::string ToJson() const;
|
||||
@@ -566,10 +573,10 @@ namespace VoxelForgeCore
|
||||
// Read-only, versioned account of the physical layout resolved by the director and the
|
||||
// Core adapter. Coordinates are half-open micrometre intervals; recipe anchors retain their
|
||||
// source fixed-point units so this record never rounds the recipe itself.
|
||||
inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV1 = 1;
|
||||
enum class ResolvedLayoutValidationModeV1 : std::uint8_t { compatibility, strict };
|
||||
inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV2 = 2;
|
||||
enum class ResolvedLayoutValidationModeV2 : std::uint8_t { compatibility, strict };
|
||||
|
||||
struct ResolvedLayoutAnchorV1
|
||||
struct ResolvedLayoutAnchorV2
|
||||
{
|
||||
std::string kind;
|
||||
std::string stableId;
|
||||
@@ -577,7 +584,7 @@ namespace VoxelForgeCore
|
||||
std::vector<std::int64_t> values;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutBandV1
|
||||
struct ResolvedLayoutBandV2
|
||||
{
|
||||
std::string kind; // ceiling, content, bedrock
|
||||
std::int64_t bottomMicrometres = 0;
|
||||
@@ -588,9 +595,10 @@ namespace VoxelForgeCore
|
||||
std::string behaviour;
|
||||
std::string densitySide;
|
||||
std::string capPath;
|
||||
std::int64_t solidDensityQ16 = 1ll << 16;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutStratumV1
|
||||
struct ResolvedLayoutStratumV2
|
||||
{
|
||||
std::string stableId;
|
||||
std::int32_t order = 0;
|
||||
@@ -602,56 +610,59 @@ namespace VoxelForgeCore
|
||||
std::int64_t interiorTopMicrometres = 0;
|
||||
std::uint32_t voxelSizeMicrometres = 0;
|
||||
std::array<std::int64_t, 3> latticeOriginMicrometres{};
|
||||
ResolvedLayoutBandV1 ceiling;
|
||||
ResolvedLayoutBandV1 content;
|
||||
ResolvedLayoutBandV1 bedrock;
|
||||
ResolvedLayoutBandV2 ceiling;
|
||||
ResolvedLayoutBandV2 content;
|
||||
ResolvedLayoutBandV2 bedrock;
|
||||
std::string transitionMode; // mode at this strate's lower interface
|
||||
std::int32_t transitionBlendChunks = 0;
|
||||
std::int64_t transitionWidthMicrometres = 0;
|
||||
std::int64_t transitionWarpAmplitudeMicrometres = 0;
|
||||
std::int64_t transitionInterpenetrationMicrometres = 0;
|
||||
bool transitionHonouredInCore = false;
|
||||
bool transitionFallsBackToHard = false;
|
||||
std::string transitionDispatchPath;
|
||||
std::string boundDispatchPath;
|
||||
std::string queryDispatchPath;
|
||||
std::string managerLookup;
|
||||
std::int64_t managerChunkWidthMicrometres = 0;
|
||||
std::int64_t dispatchChunkWidthMicrometres = 0;
|
||||
std::vector<ResolvedLayoutAnchorV1> anchors;
|
||||
std::vector<ResolvedLayoutAnchorV2> anchors;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutIssueV1
|
||||
struct ResolvedLayoutIssueV2
|
||||
{
|
||||
std::string code;
|
||||
std::string detail;
|
||||
};
|
||||
|
||||
struct VFCORE_API ResolvedLayoutSnapshotV1
|
||||
struct VFCORE_API ResolvedLayoutSnapshotV2
|
||||
{
|
||||
std::uint32_t schemaVersion = ResolvedLayoutSchemaVersionV1;
|
||||
std::uint32_t schemaVersion = ResolvedLayoutSchemaVersionV2;
|
||||
std::string worldIdentity;
|
||||
std::uint32_t ownerVoxelSizeMicrometres = 0;
|
||||
std::uint32_t coreVoxelSizeMicrometres = 0;
|
||||
std::vector<ResolvedLayoutStratumV1> strates;
|
||||
std::uint32_t strateLayoutChunkSizeMicrometres = 8000000;
|
||||
std::int64_t strateLayoutChunkWidthSubvoxels = 8192;
|
||||
std::vector<ResolvedLayoutStratumV2> strates;
|
||||
std::string stableIdentity;
|
||||
|
||||
bool finalize(std::string& error);
|
||||
bool validate(ResolvedLayoutValidationModeV1 mode,
|
||||
std::vector<ResolvedLayoutIssueV1>& issues,
|
||||
bool validate(ResolvedLayoutValidationModeV2 mode,
|
||||
std::vector<ResolvedLayoutIssueV2>& issues,
|
||||
std::string& error) const;
|
||||
std::string ToJson() const;
|
||||
static std::optional<ResolvedLayoutSnapshotV1> FromJson(
|
||||
static std::optional<ResolvedLayoutSnapshotV2> FromJson(
|
||||
std::string_view json, std::string& error);
|
||||
};
|
||||
|
||||
struct ResolvedLayoutValidatorAuditV1
|
||||
struct ResolvedLayoutValidatorAuditV2
|
||||
{
|
||||
std::uint32_t controlsRun = 0;
|
||||
std::uint32_t controlsRejectedAsExpected = 0;
|
||||
bool passed = false;
|
||||
std::vector<ResolvedLayoutIssueV1> failures;
|
||||
std::vector<ResolvedLayoutIssueV2> failures;
|
||||
};
|
||||
VFCORE_API ResolvedLayoutValidatorAuditV1 AuditResolvedLayoutValidatorV1();
|
||||
VFCORE_API ResolvedLayoutValidatorAuditV2 AuditResolvedLayoutValidatorV2();
|
||||
|
||||
struct FloatingIslandBoundsAuditResult
|
||||
{
|
||||
@@ -930,6 +941,7 @@ namespace VoxelForgeCore
|
||||
explicit operator bool() const noexcept { return static_cast<bool>(data_); }
|
||||
const WorldConfiguration& configuration() const;
|
||||
const WorldAssemblyManifest* worldAssembly() const noexcept;
|
||||
const ResolvedLayoutSnapshotV2* resolvedLayout() const noexcept;
|
||||
|
||||
private:
|
||||
struct Data;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user