wip(assembler): M1 repair round -- raised block and cliff fixed; native 7x faster than legacy

Luna, M1 repair round: the rectangular raised block and the broken cliff shared one cause,
a type-2 solid AABB in the generated plan; it is now a tapered, terrain-following shelf, and
a negative-control audit catches the old box behaviour; seam classification check passes.
Bound audit 298 boxes / 8,046 samples, 0 misses. Evaluator and 120-tile density/mesh/payload/
topology hashes identical at W1/W8/W16. Cave regression: walkability 5,642/5,642, 64/64 tiles.
Legacy mismatch unchanged (0 biome mismatches, 18.14% signs).
Speed (matched workloads): 120-tile full detail 0.089 s native vs 0.625 s legacy; 529-tile
clipmap 3.858 s vs 11.687 s -- 2.29 s above the ~1.57 s aim; LOD8 dominates (165 unknown
tiles, 13.27 M field calls, 10.88 M column-cache misses).
My capture review: coast matches legacy closely; surface clean but a dark flat slab on the far
horizon; cliff shelf renders as a stack of thin plates with back faces showing (sub-voxel
shelf thickness suspected).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-09 01:22:30 +02:00
1 parent fcb16fd265
commit 2c20f9eaea
61 files changed
+4308 -165

No files matched your search

@@ -62,6 +62,28 @@ namespace VoxelForgeCore
std::uint32_t skirtTriangleCount = 0;
};
enum class TileClass : std::uint8_t { allSolid, allAir, mixed, unknown };
struct TileGenerationStats
{
TileClass tileClass = TileClass::unknown;
std::uint64_t solidBoxes = 0;
std::uint64_t airBoxes = 0;
std::uint64_t mixedBoxes = 0;
std::uint64_t unknownBoxes = 0;
std::uint64_t fieldCalls = 0;
std::uint64_t floatFieldCalls = 0;
std::uint64_t latticeFieldCalls = 0;
std::uint64_t unclampedFieldCalls = 0;
std::uint64_t columnCacheHits = 0;
std::uint64_t columnCacheMisses = 0;
double classificationMilliseconds = 0.0;
double columnComputeMilliseconds = 0.0;
double meshingMilliseconds = 0.0;
bool tileWasClassified = false;
bool meshingSkipped = false;
};
struct TileMesh
{
TileKey key;
@@ -72,6 +94,7 @@ namespace VoxelForgeCore
std::vector<std::uint8_t> skirtFaceMasks;
std::vector<float> debugDensitySamples;
MeshGroups groups;
TileGenerationStats diagnostics;
std::uint64_t densitySampleCount = 0;
std::uint64_t meshPayloadAllocatedBytes = 0;
std::uint64_t densityHash = 0, meshHash = 0, payloadHash = 0, topologyHash = 0;
@@ -103,7 +126,7 @@ namespace VoxelForgeCore
// 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;
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 2;
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 3;
enum class FieldChannel : std::uint32_t
{
@@ -177,9 +200,10 @@ namespace VoxelForgeCore
struct WorldAssemblyFeatureV1
{
// Stable numeric type IDs are part of the plan contract; v1 reserves 1 for cliff planes,
// 2 for bounded overhang shelves and 3 for authored cap features.
// Les types numeriques stables font partie du plan; v1 reserve 1 aux falaises, 2 aux surplombs bornes et 3 aux caps.
// Stable numeric type IDs are part of the plan contract: 1 is a heightfield cliff plane,
// 2 is the legacy bounded box exception, 3 is a cap feature, and 4 is a tapered terrain-
// following cliff shelf.
// Types stables: 1 plan de falaise, 2 ancienne boite bornee, 3 cap, 4 corniche adaptee au terrain.
std::uint64_t stableId = 0;
std::uint32_t type = 0;
std::array<std::int64_t, 3> minimumSubvoxels{};
@@ -251,6 +275,17 @@ namespace VoxelForgeCore
std::int64_t maximumDensitySubvoxels = 0;
};
enum class HeightfieldBoxClass : std::uint8_t
{ unknown, allSolid, allAir, mixed };
struct HeightfieldColumnCacheStats
{
std::uint64_t pointCalls = 0;
std::uint64_t columnHits = 0;
std::uint64_t columnMisses = 0;
double columnComputeMilliseconds = 0.0;
};
struct FieldInfluenceBounds
{
bool unboundedXY = true;
@@ -283,6 +318,12 @@ namespace VoxelForgeCore
bool isValid() const noexcept;
HeightfieldPointSample point(std::int64_t xSubvoxels, std::int64_t ySubvoxels,
std::int64_t zSubvoxels) const noexcept;
HeightfieldBoxClass classifyLatticeBox(std::int64_t minimumXSubvoxels,
std::int64_t minimumYSubvoxels, std::int64_t minimumZSubvoxels,
std::int64_t maximumXSubvoxels, std::int64_t maximumYSubvoxels,
std::int64_t maximumZSubvoxels, std::int32_t stepVoxels) const noexcept;
static void resetThreadColumnCacheStats() noexcept;
static HeightfieldColumnCacheStats threadColumnCacheStats() noexcept;
FieldInterval interval(std::int64_t minimumXSubvoxels,
std::int64_t minimumYSubvoxels,
std::int64_t minimumZSubvoxels,
@@ -356,8 +397,6 @@ namespace VoxelForgeCore
};
enum class DetailVariant : std::uint8_t { coherent, smooth, perVoxel };
enum class TileClass : std::uint8_t { allSolid, allAir, mixed };
struct DetailBandTileRegion
{
Vec3i originVoxel{};
+68 -14
View File
@@ -33,6 +33,21 @@ class FStandaloneShapeWorld
{
public:
enum class EDetailVariant : uint8 { Coherent, Smooth, PerVoxel };
struct FBoxClassification
{
EVoxelTileClass Verdict=EVoxelTileClass::Mixed;
bool bNativeSurface=false;
bool bUnknown=false;
};
struct FClassificationThreadStats
{
uint64 SolidBoxes=0,AirBoxes=0,MixedBoxes=0,UnknownBoxes=0;
uint64 NativeFloatFieldCalls=0,NativeLatticeFieldCalls=0,NativeUnclampedFieldCalls=0;
double Milliseconds=0.0;
};
static void ResetThreadClassificationStats() { ThreadClassificationStats()={}; }
static FClassificationThreadStats GetThreadClassificationStats()
{ return ThreadClassificationStats(); }
struct FFarConnectorSegment
{
FVector Start=FVector::ZeroVector;
@@ -923,7 +938,10 @@ public:
{
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{
++ThreadClassificationStats().NativeFloatFieldCalls;
return ApplyEditBrushes(NativeDensity,X,Y,Z);
}
if(UsesLegacySurfaceField(X,Y,Z))
return LegacySurfaceDensityAt(X,Y,Z);
int32 IX=0,IY=0,IZ=0;
@@ -939,7 +957,10 @@ public:
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);
}
if(UsesLegacySurfaceField(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z)))
return LegacySurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
@@ -956,7 +977,10 @@ public:
{
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
{
++ThreadClassificationStats().NativeUnclampedFieldCalls;
return ApplyEditBrushes(NativeDensity,X,Y,Z);
}
if(UsesLegacySurfaceField(X,Y,Z))
return LegacySurfaceDensityAt(X,Y,Z);
return AnalyticDensityAt(X,Y,Z,false);
@@ -1130,12 +1154,17 @@ private:
public:
EVoxelTileClass ClassifyBox(const FIntVector& Origin,int32 Step,int32 Cells) const
{ return ClassifyBoxDetailed(Origin,Step,Cells).Verdict; }
FBoxClassification ClassifyBoxDetailed(const FIntVector& Origin,int32 Step,int32 Cells) const
{
using Clock=std::chrono::steady_clock;
const auto Start=Clock::now();
const int32 Pad=std::max(Step,1);
int32 BMin[3]={Origin.X-Pad,Origin.Y-Pad,Origin.Z-Pad};
int32 BMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step,Origin.Z+(Cells+1)*Step};
const VoxelForgeCore::WorldAssemblyStratumPlanV1* SurfacePlan=nullptr;
bool bSurfaceOverlap=false,bOtherStrateOverlap=false;
bool bSurfaceOverlap=false,bOtherStrateOverlap=false,bBadPlan=false;
if(Owner&&Owner->Manager)
{
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin[2])
@@ -1152,19 +1181,31 @@ public:
static_cast<float>((static_cast<double>(ChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
const auto* Candidate=FindWorldAssemblyStratum(StrateIndex,ChunkZ);
if(!Candidate||(SurfacePlan&&SurfacePlan!=Candidate))
return EVoxelTileClass::Mixed;
bBadPlan=true;
SurfacePlan=Candidate;
}
else bOtherStrateOverlap=true;
}
}
const auto Finish=[&](EVoxelTileClass Verdict,bool bUnknown=false)
{
FBoxClassification Result{Verdict,bSurfaceOverlap&&!bOtherStrateOverlap,bUnknown};
auto& Counts=ThreadClassificationStats();
Counts.Milliseconds+=std::chrono::duration<double,std::milli>(Clock::now()-Start).count();
if(!Result.bNativeSurface||bUnknown)++Counts.UnknownBoxes;
else if(Verdict==EVoxelTileClass::AllSolid)++Counts.SolidBoxes;
else if(Verdict==EVoxelTileClass::AllAir)++Counts.AirBoxes;
else ++Counts.MixedBoxes;
return Result;
};
// A box crossing recipes cannot use one field interval; unknown composition stays Mixed.
// Une boite qui traverse plusieurs recettes reste Mixed, car l'intervalle d'un seul champ ne suffit pas.
if(bSurfaceOverlap&&bOtherStrateOverlap)return EVoxelTileClass::Mixed;
if(bBadPlan|| (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 EVoxelTileClass::Mixed;
return Finish(EVoxelTileClass::Mixed,true);
for(int32 BZ=FloorDiv(BMin[2],ActiveSpatialBinSize);BZ<=FloorDiv(BMax[2],ActiveSpatialBinSize);++BZ)
for(int32 BY=FloorDiv(BMin[1],ActiveSpatialBinSize);BY<=FloorDiv(BMax[1],ActiveSpatialBinSize);++BY)
for(int32 BX=FloorDiv(BMin[0],ActiveSpatialBinSize);BX<=FloorDiv(BMax[0],ActiveSpatialBinSize);++BX)
@@ -1175,32 +1216,39 @@ public:
const FPrimitive& P=Primitives[Id];bool Overlap=true;
for(int32 A=0;A<3;++A)
Overlap &= P.Max[A]+DetailBand>=BMin[A]&&P.Min[A]-DetailBand<=BMax[A];
if(Overlap)return EVoxelTileClass::Mixed;
if(Overlap)return Finish(EVoxelTileClass::Mixed);
}
}
for(const FInterval& R:LayerRanges)
if(R.Lo<=BMax[2]&&R.Hi>=BMin[2]
&&(R.Lo>=BMin[2]||R.Hi<=BMax[2]))
return EVoxelTileClass::Mixed;
return Finish(EVoxelTileClass::Mixed);
if(bSurfaceOverlap)
{
if(!WorldAssembly||!SurfacePlan)return EVoxelTileClass::Mixed;
if(!WorldAssembly||!SurfacePlan)return Finish(EVoxelTileClass::Mixed,true);
const VoxelForgeCore::Heightfield2D Block(WorldAssembly->recipe,*SurfacePlan);
const auto Bounds=Block.interval(
const auto Verdict=Block.classifyLatticeBox(
static_cast<int64>(BMin[0])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
static_cast<int64>(BMin[1])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
static_cast<int64>(BMin[2])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
static_cast<int64>(BMax[0])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
static_cast<int64>(BMax[1])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
static_cast<int64>(BMax[2])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
if(!Bounds.known)return EVoxelTileClass::Mixed;
if(Bounds.minimumDensitySubvoxels>0)return EVoxelTileClass::AllAir;
if(Bounds.maximumDensitySubvoxels<0)return EVoxelTileClass::AllSolid;
return EVoxelTileClass::Mixed;
static_cast<int64>(BMax[2])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
std::max(Step,1));
switch(Verdict)
{
case VoxelForgeCore::HeightfieldBoxClass::allAir:
return Finish(EVoxelTileClass::AllAir);
case VoxelForgeCore::HeightfieldBoxClass::allSolid:
return Finish(EVoxelTileClass::AllSolid);
case VoxelForgeCore::HeightfieldBoxClass::mixed:
return Finish(EVoxelTileClass::Mixed);
default:return Finish(EVoxelTileClass::Mixed,true);
}
}
const bool bRock=EvaluateFast((BMin[0]+BMax[0])*0.5,
(BMin[1]+BMax[1])*0.5,(BMin[2]+BMax[2])*0.5);
return bRock?EVoxelTileClass::AllSolid:EVoxelTileClass::AllAir;
return Finish(bRock?EVoxelTileClass::AllSolid:EVoxelTileClass::AllAir);
}
bool IsRock(int32 X,int32 Y,int32 Z) const
@@ -1662,6 +1710,12 @@ public:
}
private:
static FClassificationThreadStats& ThreadClassificationStats()
{
static thread_local FClassificationThreadStats Stats;
return Stats;
}
const VoxelForgeCore::WorldAssemblyStratumPlanV1* FindWorldAssemblyStratum(
int32 StrateIndex,int32 ChunkZ) const
{
+77 -1
View File
@@ -632,9 +632,59 @@ namespace VoxelForgeCore
int64 sampleCount=0;std::vector<float> densityValues;
const FVoxelTileKey nativeKey=ToNative(key);
const FIntVector origin=nativeKey.OriginVoxels();
TileMesh out;out.key=key;
const bool bHasWorldAssembly=impl_->worldAssembly!=nullptr;
const bool bNeedsFarShell=bHasWorldAssembly&&key.lod>=7
&&(tileShape.HasFarWorld()||impl_->shape->HasFarWorld());
FStandaloneShapeWorld::ResetThreadClassificationStats();
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats();
bool bNativeSurfaceTile=false;
if(bHasWorldAssembly)
{
const auto Classification=tileShape.ClassifyBoxDetailed(origin,step,cells);
bNativeSurfaceTile=Classification.bNativeSurface;
if(bNativeSurfaceTile)
{
out.diagnostics.tileWasClassified=true;
switch(Classification.bUnknown?EVoxelTileClass::Mixed:Classification.Verdict)
{
case EVoxelTileClass::AllSolid:out.diagnostics.tileClass=TileClass::allSolid;break;
case EVoxelTileClass::AllAir:out.diagnostics.tileClass=TileClass::allAir;break;
case EVoxelTileClass::Mixed:out.diagnostics.tileClass=TileClass::mixed;break;
}
if((out.diagnostics.tileClass==TileClass::allSolid
||out.diagnostics.tileClass==TileClass::allAir)
&&!options.hashDensity&&!options.captureDensitySamples&&!bNeedsFarShell)
{
out.diagnostics.meshingSkipped=true;
const auto Counts=FStandaloneShapeWorld::GetThreadClassificationStats();
const auto Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
out.diagnostics.solidBoxes=Counts.SolidBoxes;
out.diagnostics.airBoxes=Counts.AirBoxes;
out.diagnostics.mixedBoxes=Counts.MixedBoxes;
out.diagnostics.unknownBoxes=Counts.UnknownBoxes;
out.diagnostics.fieldCalls=Columns.pointCalls;
out.diagnostics.floatFieldCalls=Counts.NativeFloatFieldCalls;
out.diagnostics.latticeFieldCalls=Counts.NativeLatticeFieldCalls;
out.diagnostics.unclampedFieldCalls=Counts.NativeUnclampedFieldCalls;
out.diagnostics.columnCacheHits=Columns.columnHits;
out.diagnostics.columnCacheMisses=Columns.columnMisses;
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
out.densitySampleCount=0;
return out;
}
// Native SurfaceWorld classifies each 8-cell block against its exact sample
// lattice. This lets the mesher avoid filling density grids for proven air/rock.
mesher.bUseBlockEarlyOut=true;
}
}
const auto MeshingStart=std::chrono::steady_clock::now();
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
options.bandMinZVoxel,options.bandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MeshingStart).count();
const FStandaloneShapeWorld& farShape=tileShape.HasFarWorld()?tileShape:*impl_->shape;
if(farShape.HasFarWorld())
{
@@ -642,7 +692,33 @@ namespace VoxelForgeCore
AppendFarConnectorShells(farShape,nativeKey,step,world.CoarseTileCells,mesh);
}
TileMesh out;out.key=key;out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
if(bHasWorldAssembly)
{
const auto Counts=FStandaloneShapeWorld::GetThreadClassificationStats();
const auto Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
out.diagnostics.fieldCalls=Columns.pointCalls;
out.diagnostics.floatFieldCalls=Counts.NativeFloatFieldCalls;
out.diagnostics.latticeFieldCalls=Counts.NativeLatticeFieldCalls;
out.diagnostics.unclampedFieldCalls=Counts.NativeUnclampedFieldCalls;
out.diagnostics.columnCacheHits=Columns.columnHits;
out.diagnostics.columnCacheMisses=Columns.columnMisses;
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
if(bNativeSurfaceTile)
{
out.diagnostics.solidBoxes=Counts.SolidBoxes;
out.diagnostics.airBoxes=Counts.AirBoxes;
out.diagnostics.mixedBoxes=Counts.MixedBoxes;
out.diagnostics.unknownBoxes=Counts.UnknownBoxes;
}
else
{
out.diagnostics.unknownBoxes=Counts.UnknownBoxes;
out.diagnostics.tileClass=TileClass::unknown;
}
out.diagnostics.meshingMilliseconds=MeshingMilliseconds;
}
out.meshPayloadAllocatedBytes=static_cast<std::uint64_t>(mesh.Vertices.GetAllocatedSize())
+static_cast<std::uint64_t>(mesh.Normals.GetAllocatedSize())
+static_cast<std::uint64_t>(mesh.UVs.GetAllocatedSize())
+293 -117
View File
@@ -4,7 +4,9 @@
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <algorithm>
#include <bit>
#include <charconv>
#include <chrono>
#include <cmath>
#include <limits>
#include <sstream>
@@ -99,7 +101,8 @@ namespace world_assembly_detail
inline bool ValidateRecipe(const WorldAssemblyRecipeV1& recipe, std::string& error)
{
if (recipe.schemaVersion != WorldAssemblySchemaVersion
|| (recipe.evaluatorVersion != 1 && recipe.evaluatorVersion != WorldAssemblyEvaluatorVersion)
|| recipe.evaluatorVersion == 0
|| recipe.evaluatorVersion > WorldAssemblyEvaluatorVersion
|| recipe.unitsPerVoxel != WorldAssemblySubvoxelsPerVoxel)
{ error = "world assembly recipe version, evaluator or fixed scale is unsupported"; return false; }
if (recipe.biomes.empty() || recipe.biomes.size() > MaxBiomeCount)
@@ -183,7 +186,7 @@ namespace world_assembly_detail
ids.reserve(stratum.features.size());
for (const auto& feature : stratum.features)
{
if (feature.stableId == 0 || feature.type < 1 || feature.type > 3
if (feature.stableId == 0 || feature.type < 1 || feature.type > 4
|| std::find(ids.begin(), ids.end(), feature.stableId) != ids.end())
{ error = "world assembly feature IDs and types must be stable, unique and supported"; return false; }
ids.push_back(feature.stableId);
@@ -194,7 +197,7 @@ namespace world_assembly_detail
{ error = "world assembly feature has an invalid influence bound"; return false; }
if (!std::all_of(feature.parametersSubvoxels.begin(), feature.parametersSubvoxels.end(), ValidScalar))
{ error = "world assembly feature parameter is outside the fixed-scale range"; return false; }
if (feature.type == 2)
if (feature.type == 2 || feature.type == 4)
{
const std::int64_t stratumMinZ = static_cast<std::int64_t>(stratum.bottomChunkZ)
* 32 * WorldAssemblySubvoxelsPerVoxel;
@@ -210,6 +213,12 @@ namespace world_assembly_detail
if (feature.minimumSubvoxels[2] < stratumMinZ
|| feature.maximumSubvoxels[2] > stratumMaxZ)
{ error = "world assembly overhang exception escapes its finite stratum Z bounds"; return false; }
if (feature.type == 4
&& (std::abs(feature.parametersSubvoxels[0])
+ std::abs(feature.parametersSubvoxels[1]) != 256
|| feature.parametersSubvoxels[2] <= 0
|| feature.parametersSubvoxels[3] <= 0))
{ error = "world assembly cliff shelf requires a unit horizontal axis and positive height/reach"; return false; }
}
}
for (const auto& point : stratum.route)
@@ -954,6 +963,200 @@ namespace world_assembly_detail
return SmoothQ16(static_cast<std::uint32_t>(std::clamp<std::int64_t>(edge*Q16/feather,0,Q16)));
}
struct HeightfieldColumnCacheEntry
{
const WorldAssemblyRecipeV1* recipe = nullptr;
const WorldAssemblyStratumPlanV1* plan = nullptr;
std::uint64_t recipeHash = 0, planHash = 0;
std::int64_t x = 0, y = 0;
HeightfieldPointSample column{};
};
// Coarse LOD tiles revisit a wider set of gradient-sample columns than the full-resolution
// band. Keep those columns resident for a complete worker tile so the 3D mesher does not
// repeat the procedural biome evaluation for every Z sample.
inline thread_local std::array<HeightfieldColumnCacheEntry, 65536> HeightfieldColumnCache{};
inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{};
inline HeightfieldPointSample EvaluateColumn(const WorldAssemblyRecipeV1& recipe,
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y)
{
HeightfieldPointSample out;
if(recipe.evaluatorVersion>=2)
{
const BiomeSelectionV2 selection=SelectBiomeV2(recipe,x,y);
const auto& primary=recipe.biomes[selection.primary];
const auto& neighbor=recipe.biomes[selection.neighbor];
const std::int64_t primaryHeight=SurfaceHeightV2(recipe,plan,primary,x,y);
const std::int64_t neighborHeight=selection.primary==selection.neighbor?primaryHeight
:SurfaceHeightV2(recipe,plan,neighbor,x,y);
std::int64_t ground=primaryHeight+((neighborHeight-primaryHeight)
*(static_cast<std::int64_t>(Q16)-selection.primaryWeightQ16))/Q16;
const std::int64_t primaryCap=CapHeightV2(recipe,plan,primary,x,y);
const std::int64_t neighborCap=selection.primary==selection.neighbor?primaryCap
:CapHeightV2(recipe,plan,neighbor,x,y);
std::int64_t cap=primaryCap+((neighborCap-primaryCap)
*(static_cast<std::int64_t>(Q16)-selection.primaryWeightQ16))/Q16;
for(const auto& feature:plan.features)
{
if(feature.type==1)
{
const std::uint32_t fade=FeatureFadeQ16(feature,x,y,feature.parametersSubvoxels[1]);
if(fade)ground+=MulQ16(feature.parametersSubvoxels[0],fade);
}
else if(feature.type==3)
{
const std::uint32_t fade=FeatureFadeQ16(feature,x,y,feature.parametersSubvoxels[2]);
const std::int64_t wave=std::max<std::int64_t>(1,feature.parametersSubvoxels[1]);
if(fade)
{
const std::uint32_t featureSeed=static_cast<std::uint32_t>(feature.stableId);
const std::int64_t nx=NoiseCoordinateQ16(x,wave)+SeedOffsetQ16(featureSeed,1700);
const std::int64_t ny=NoiseCoordinateQ16(y,wave)+SeedOffsetQ16(featureSeed,2900);
const std::int32_t noise=FractalNoiseQ16(nx,ny,
SeedOffsetQ16(featureSeed,4100),3);
cap+=MulQ16(MulQ16(noise,feature.parametersSubvoxels[0]),fade);
}
}
}
out.groundHeightSubvoxels=ground;
out.capHeightSubvoxels=cap;
out.biomeId=primary.stableId;
out.neighborBiomeId=neighbor.stableId;
out.biomeWeightQ16=selection.primaryWeightQ16;
out.waterSurfaceSubvoxels=plan.waterSurfaceSubvoxels;
return out;
}
const std::int64_t warpX=MulQ16(recipe.biomeWarpSubvoxels,
Noise2D(x,y,recipe.biomeWarpWavelengthSubvoxels,recipe.seed,0x574152505f585f31ull));
const std::int64_t warpY=MulQ16(recipe.biomeWarpSubvoxels,
Noise2D(x,y,recipe.biomeWarpWavelengthSubvoxels,recipe.seed,0x574152505f595f31ull));
const std::int64_t warpedX=x+warpX,warpedY=y+warpY;
const std::int64_t cellX=FloorDiv(warpedX,recipe.biomeCellSizeSubvoxels);
const std::int64_t cellY=FloorDiv(warpedY,recipe.biomeCellSizeSubvoxels);
std::uint64_t nearestDistance=std::numeric_limits<std::uint64_t>::max();
std::uint64_t secondDistance=std::numeric_limits<std::uint64_t>::max();
std::uint32_t nearestId=0,secondId=0;
const std::uint32_t jitterSeed=recipe.seed^0xa341316cu;
const std::int64_t cellSize=recipe.biomeCellSizeSubvoxels;
for(std::int64_t cy=cellY-2;cy<=cellY+2;++cy)
for(std::int64_t cx=cellX-2;cx<=cellX+2;++cx)
{
const std::uint64_t h=CoordinateHash(jitterSeed,cx,cy,0x42494f4d455f5349ull);
const std::int64_t jitterX=static_cast<std::int64_t>(h&0xffffu)*(cellSize/2)/65535-cellSize/4;
const std::int64_t jitterY=static_cast<std::int64_t>((h>>16)&0xffffu)*(cellSize/2)/65535-cellSize/4;
const std::int64_t siteX=cx*cellSize+cellSize/2+jitterX;
const std::int64_t siteY=cy*cellSize+cellSize/2+jitterY;
const std::int64_t dx=warpedX-siteX,dy=warpedY-siteY;
const std::uint64_t distance=static_cast<std::uint64_t>(dx*dx+dy*dy);
const std::uint32_t biomeId=recipe.biomes[CoordinateHash(jitterSeed,cx,cy,
0x42494f4d455f4944ull)%recipe.biomes.size()].stableId;
if(distance<nearestDistance)
{secondDistance=nearestDistance;secondId=nearestId;nearestDistance=distance;nearestId=biomeId;}
else if(distance<secondDistance&&biomeId!=nearestId)
{secondDistance=distance;secondId=biomeId;}
}
const auto findBiome=[&](std::uint32_t id)->const WorldAssemblyBiomeProfileV1*
{
const auto found=std::find_if(recipe.biomes.begin(),recipe.biomes.end(),
[id](const auto& biome){return biome.stableId==id;});
return found==recipe.biomes.end()?nullptr:&*found;
};
const auto* primary=findBiome(nearestId);
const auto* secondary=findBiome(secondId?secondId:nearestId);
if(!primary||!secondary)return out;
std::uint32_t primaryWeight=65536;
if(recipe.biomeBorderBlendSubvoxels>0&&nearestDistance<secondDistance)
{
const std::uint64_t difference=secondDistance-nearestDistance;
const std::uint64_t denominator=static_cast<std::uint64_t>(2*cellSize)
*static_cast<std::uint64_t>(recipe.biomeBorderBlendSubvoxels);
primaryWeight=SmoothQ16(RatioQ16(difference,denominator));
}
const std::int64_t primaryHeight=ProfileHeight(*primary,warpedX,warpedY,recipe.seed);
const std::int64_t secondaryHeight=ProfileHeight(*secondary,warpedX,warpedY,recipe.seed);
out.groundHeightSubvoxels=(primaryHeight*primaryWeight
+secondaryHeight*(65536-primaryWeight))/65536;
out.biomeId=primary->stableId;out.neighborBiomeId=secondary->stableId;
out.biomeWeightQ16=primaryWeight;
out.capHeightSubvoxels=plan.capHeightSubvoxels+MulQ16(plan.capUndulationSubvoxels,
Noise2D(x,y,plan.capWavelengthSubvoxels,recipe.seed,0x4341505f554e4431ull));
out.waterSurfaceSubvoxels=plan.waterSurfaceSubvoxels;
return out;
}
inline HeightfieldPointSample CachedColumn(const WorldAssemblyRecipeV1& recipe,
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y)
{
const std::uint64_t mixed=(static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull)
^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,23)
^static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&recipe))
^std::rotl(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),41);
auto& entry=HeightfieldColumnCache[static_cast<std::size_t>(mixed)&
(HeightfieldColumnCache.size()-1)];
if(entry.recipe==&recipe&&entry.plan==&plan&&entry.recipeHash==recipe.contentHash
&&entry.planHash==plan.contentHash&&entry.x==x&&entry.y==y)
{++HeightfieldColumnStats.columnHits;return entry.column;}
const auto start=std::chrono::steady_clock::now();
HeightfieldPointSample column=EvaluateColumn(recipe,plan,x,y);
HeightfieldColumnStats.columnComputeMilliseconds+=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-start).count();
++HeightfieldColumnStats.columnMisses;
entry={&recipe,&plan,recipe.contentHash,plan.contentHash,x,y,column};
return column;
}
inline std::int64_t CliffShelfDensity(const WorldAssemblyFeatureV1& feature,
const WorldAssemblyRecipeV1& recipe,const WorldAssemblyStratumPlanV1& plan,
std::int64_t x,std::int64_t y,std::int64_t z,
const HeightfieldPointSample& localColumn)
{
const auto& lo=feature.minimumSubvoxels;const auto& hi=feature.maximumSubvoxels;
if(x<lo[0]||x>hi[0]||y<lo[1]||y>hi[1]||z<lo[2]||z>hi[2])
return std::numeric_limits<std::int64_t>::max()/4;
constexpr std::int64_t Feather=8*WorldAssemblySubvoxelsPerVoxel;
const std::int64_t dirX=feature.parametersSubvoxels[0]/WorldAssemblySubvoxelsPerVoxel;
const std::int64_t dirY=feature.parametersSubvoxels[1]/WorldAssemblySubvoxelsPerVoxel;
const std::int64_t height=feature.parametersSubvoxels[2];
const std::int64_t reach=feature.parametersSubvoxels[3];
if((dirX==0&&dirY==0)||height<=0||reach<=0)return std::numeric_limits<std::int64_t>::max()/4;
const std::int64_t centerX=(lo[0]+hi[0])/2,centerY=(lo[1]+hi[1])/2;
const std::int64_t edgeX=dirX>0?lo[0]+Feather:dirX<0?hi[0]-Feather:centerX;
const std::int64_t edgeY=dirY>0?lo[1]+Feather:dirY<0?hi[1]-Feather:centerY;
const std::int64_t dx=x-edgeX,dy=y-edgeY;
const std::int64_t along=dx*dirX+dy*dirY;
const std::int64_t lateral=-dx*dirY+dy*dirX;
const std::int64_t halfWidth=std::max<std::int64_t>(0,
(dirX!=0?(hi[1]-lo[1]):(hi[0]-lo[0]))/2-Feather);
const std::int64_t aboveGround=z-localColumn.groundHeightSubvoxels;
if(aboveGround<=0||aboveGround>=height||along<0||along>reach
||std::abs(lateral)>halfWidth)
return std::numeric_limits<std::int64_t>::max()/4;
const std::uint32_t vertical=SmoothQ16(static_cast<std::uint32_t>(
std::clamp<std::int64_t>(aboveGround*Q16/height,0,Q16)));
const std::int64_t sourceOffset=MulQ16(reach,vertical);
const std::int64_t sourceX=x-dirX*sourceOffset;
const std::int64_t sourceY=y-dirY*sourceOffset;
const HeightfieldPointSample source=CachedColumn(recipe,plan,sourceX,sourceY);
if(source.groundHeightSubvoxels<=localColumn.groundHeightSubvoxels)
return std::numeric_limits<std::int64_t>::max()/4;
std::int64_t candidate=std::max({localColumn.groundHeightSubvoxels-z,
z-source.groundHeightSubvoxels,-along,along-reach,
std::abs(lateral)-halfWidth,-aboveGround,aboveGround-height});
const std::uint32_t edgeFade=FeatureFadeQ16(feature,x,y,Feather);
candidate+=static_cast<std::int64_t>(Q16-edgeFade)*Feather/Q16;
return candidate;
}
inline std::int64_t SmoothMinSubvoxels(std::int64_t a,std::int64_t b,
std::int64_t width)
{
if(width<=0)return std::min(a,b);
const std::int64_t h=std::max<std::int64_t>(width-std::abs(a-b),0);
const std::int64_t smoothQ16=h*Q16/width;
return std::min(a,b)-MulQ16(MulQ16(smoothQ16,smoothQ16),width/4);
}
inline bool InInterval(const FieldInterval& interval, std::int64_t density)
{ return interval.known && density >= interval.minimumDensitySubvoxels
&& density <= interval.maximumDensitySubvoxels; }
@@ -1143,7 +1346,8 @@ Heightfield2D::Heightfield2D(const WorldAssemblyRecipeV1& recipe,
bool Heightfield2D::isValid() const noexcept
{
if (!recipe_ || !plan_ || recipe_->schemaVersion != WorldAssemblySchemaVersion
|| (recipe_->evaluatorVersion != 1 && recipe_->evaluatorVersion != WorldAssemblyEvaluatorVersion)
|| recipe_->evaluatorVersion == 0
|| recipe_->evaluatorVersion > WorldAssemblyEvaluatorVersion
|| recipe_->unitsPerVoxel != WorldAssemblySubvoxelsPerVoxel
|| recipe_->biomes.empty() || recipe_->biomes.size() > world_assembly_detail::MaxBiomeCount
|| recipe_->biomeCellSizeSubvoxels <= 0 || recipe_->biomeWarpWavelengthSubvoxels <= 0
@@ -1169,125 +1373,88 @@ HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
|| world_assembly_detail::OutsideRange(y)
|| world_assembly_detail::OutsideRange(z)) return out;
using namespace world_assembly_detail;
++HeightfieldColumnStats.pointCalls;
out=CachedColumn(*recipe_,*plan_,x,y);
out.densitySubvoxels=std::min(z-out.groundHeightSubvoxels,
out.capHeightSubvoxels-z);
if(recipe_->evaluatorVersion>=2)
{
const BiomeSelectionV2 selection=SelectBiomeV2(*recipe_,x,y);
const auto& primary=recipe_->biomes[selection.primary];
const auto& neighbor=recipe_->biomes[selection.neighbor];
const std::int64_t primaryHeight=SurfaceHeightV2(*recipe_,*plan_,primary,x,y);
const std::int64_t neighborHeight=selection.primary==selection.neighbor?primaryHeight
:SurfaceHeightV2(*recipe_,*plan_,neighbor,x,y);
std::int64_t ground=primaryHeight+((neighborHeight-primaryHeight)
*(static_cast<std::int64_t>(Q16)-selection.primaryWeightQ16))/Q16;
const std::int64_t primaryCap=CapHeightV2(*recipe_,*plan_,primary,x,y);
const std::int64_t neighborCap=selection.primary==selection.neighbor?primaryCap
:CapHeightV2(*recipe_,*plan_,neighbor,x,y);
std::int64_t cap=primaryCap+((neighborCap-primaryCap)
*(static_cast<std::int64_t>(Q16)-selection.primaryWeightQ16))/Q16;
for(const auto& feature:plan_->features)
{
if(feature.type==1)
if(feature.type==2)
{
const std::uint32_t fade=FeatureFadeQ16(feature,x,y,feature.parametersSubvoxels[1]);
if(fade)ground+=MulQ16(feature.parametersSubvoxels[0],fade);
const auto& lo=feature.minimumSubvoxels;const auto& hi=feature.maximumSubvoxels;
if(x<lo[0]||x>hi[0]||y<lo[1]||y>hi[1]||z<lo[2]||z>hi[2])continue;
const std::int64_t depth=std::min({x-lo[0],hi[0]-x,y-lo[1],hi[1]-y,
z-lo[2],hi[2]-z});
out.densitySubvoxels=std::min(out.densitySubvoxels,-depth);
}
else if(feature.type==3)
else if(feature.type==4&&recipe_->evaluatorVersion>=3)
{
const std::uint32_t fade=FeatureFadeQ16(feature,x,y,feature.parametersSubvoxels[2]);
const std::int64_t wave=std::max<std::int64_t>(1,feature.parametersSubvoxels[1]);
if(fade)
{
const std::uint32_t featureSeed=static_cast<std::uint32_t>(feature.stableId);
const std::int64_t nx=NoiseCoordinateQ16(x,wave)+SeedOffsetQ16(featureSeed,1700);
const std::int64_t ny=NoiseCoordinateQ16(y,wave)+SeedOffsetQ16(featureSeed,2900);
const std::int32_t noise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(featureSeed,4100),3);
cap+=MulQ16(MulQ16(noise,feature.parametersSubvoxels[0]),fade);
}
const std::int64_t shelf=CliffShelfDensity(feature,*recipe_,*plan_,x,y,z,out);
out.densitySubvoxels=SmoothMinSubvoxels(out.densitySubvoxels,shelf,
4*WorldAssemblySubvoxelsPerVoxel);
}
}
out.groundHeightSubvoxels=ground;
out.capHeightSubvoxels=cap;
out.biomeId=primary.stableId;
out.neighborBiomeId=neighbor.stableId;
out.biomeWeightQ16=selection.primaryWeightQ16;
out.densitySubvoxels=std::min(z-ground,cap-z);
for(const auto& feature:plan_->features)
{
if(feature.type!=2)continue;
const auto& lo=feature.minimumSubvoxels;const auto& hi=feature.maximumSubvoxels;
if(x<lo[0]||x>hi[0]||y<lo[1]||y>hi[1]||z<lo[2]||z>hi[2])continue;
const std::int64_t depth=std::min({x-lo[0],hi[0]-x,y-lo[1],hi[1]-y,z-lo[2],hi[2]-z});
out.densitySubvoxels=std::min(out.densitySubvoxels,-depth);
}
out.waterSurfaceSubvoxels=plan_->waterSurfaceSubvoxels;
if(plan_->waterEnabled && out.densitySubvoxels>=0 && z>ground
&&z<plan_->waterSurfaceSubvoxels && ground<plan_->waterSurfaceSubvoxels)
out.waterFillSubvoxels=std::min(z-ground,plan_->waterSurfaceSubvoxels-z);
return out;
}
const std::int64_t warpX = MulQ16(recipe_->biomeWarpSubvoxels,
Noise2D(x, y, recipe_->biomeWarpWavelengthSubvoxels, recipe_->seed, 0x574152505f585f31ull));
const std::int64_t warpY = MulQ16(recipe_->biomeWarpSubvoxels,
Noise2D(x, y, recipe_->biomeWarpWavelengthSubvoxels, recipe_->seed, 0x574152505f595f31ull));
const std::int64_t warpedX = x + warpX, warpedY = y + warpY;
const std::int64_t cellX = FloorDiv(warpedX, recipe_->biomeCellSizeSubvoxels);
const std::int64_t cellY = FloorDiv(warpedY, recipe_->biomeCellSizeSubvoxels);
std::uint64_t nearestDistance = std::numeric_limits<std::uint64_t>::max();
std::uint64_t secondDistance = std::numeric_limits<std::uint64_t>::max();
std::uint32_t nearestId = 0, secondId = 0;
const std::uint32_t jitterSeed = recipe_->seed ^ 0xa341316cu;
const std::int64_t cellSize = recipe_->biomeCellSizeSubvoxels;
for (std::int64_t cy = cellY - 2; cy <= cellY + 2; ++cy)
for (std::int64_t cx = cellX - 2; cx <= cellX + 2; ++cx)
{
const std::uint64_t h = CoordinateHash(jitterSeed, cx, cy, 0x42494f4d455f5349ull);
const std::int64_t jitterX = static_cast<std::int64_t>((h & 0xffffu)) * (cellSize / 2) / 65535 - cellSize / 4;
const std::int64_t jitterY = static_cast<std::int64_t>(((h >> 16) & 0xffffu)) * (cellSize / 2) / 65535 - cellSize / 4;
const std::int64_t siteX = cx * cellSize + cellSize / 2 + jitterX;
const std::int64_t siteY = cy * cellSize + cellSize / 2 + jitterY;
const std::int64_t dx = warpedX - siteX, dy = warpedY - siteY;
const std::uint64_t distance = static_cast<std::uint64_t>(dx * dx + dy * dy);
const std::uint32_t biomeId = recipe_->biomes[CoordinateHash(jitterSeed, cx, cy,
0x42494f4d455f4944ull) % recipe_->biomes.size()].stableId;
if (distance < nearestDistance)
{
secondDistance = nearestDistance; secondId = nearestId;
nearestDistance = distance; nearestId = biomeId;
}
else if (distance < secondDistance && biomeId != nearestId)
{ secondDistance = distance; secondId = biomeId; }
}
const auto findBiome = [&](std::uint32_t id) -> const WorldAssemblyBiomeProfileV1*
{
const auto found = std::find_if(recipe_->biomes.begin(), recipe_->biomes.end(),
[id](const auto& biome) { return biome.stableId == id; });
return found == recipe_->biomes.end() ? nullptr : &*found;
};
const auto* primary = findBiome(nearestId);
const auto* secondary = findBiome(secondId ? secondId : nearestId);
if (!primary || !secondary) return out;
std::uint32_t primaryWeight = 65536;
if (recipe_->biomeBorderBlendSubvoxels > 0 && nearestDistance < secondDistance)
{
const std::uint64_t difference = secondDistance - nearestDistance;
const std::uint64_t denominator = static_cast<std::uint64_t>(2 * cellSize)
* static_cast<std::uint64_t>(recipe_->biomeBorderBlendSubvoxels);
primaryWeight = SmoothQ16(RatioQ16(difference, denominator));
}
const std::int64_t primaryHeight = ProfileHeight(*primary, warpedX, warpedY, recipe_->seed);
const std::int64_t secondaryHeight = ProfileHeight(*secondary, warpedX, warpedY, recipe_->seed);
out.groundHeightSubvoxels = (primaryHeight * primaryWeight
+ secondaryHeight * (65536 - primaryWeight)) / 65536;
out.biomeId = primary->stableId;
out.neighborBiomeId = secondary->stableId;
out.biomeWeightQ16 = primaryWeight;
out.capHeightSubvoxels = plan_->capHeightSubvoxels + MulQ16(plan_->capUndulationSubvoxels,
Noise2D(x, y, plan_->capWavelengthSubvoxels, recipe_->seed, 0x4341505f554e4431ull));
out.densitySubvoxels = std::min(z - out.groundHeightSubvoxels,
out.capHeightSubvoxels - z);
if(recipe_->evaluatorVersion>=2&&plan_->waterEnabled&&out.densitySubvoxels>=0
&&z>out.groundHeightSubvoxels&&z<plan_->waterSurfaceSubvoxels
&&out.groundHeightSubvoxels<plan_->waterSurfaceSubvoxels)
out.waterFillSubvoxels=std::min(z-out.groundHeightSubvoxels,
plan_->waterSurfaceSubvoxels-z);
return out;
}
void Heightfield2D::resetThreadColumnCacheStats() noexcept
{ world_assembly_detail::HeightfieldColumnStats={}; }
HeightfieldColumnCacheStats Heightfield2D::threadColumnCacheStats() noexcept
{ return world_assembly_detail::HeightfieldColumnStats; }
HeightfieldBoxClass Heightfield2D::classifyLatticeBox(std::int64_t minimumX,
std::int64_t minimumY,std::int64_t minimumZ,std::int64_t maximumX,
std::int64_t maximumY,std::int64_t maximumZ,std::int32_t stepVoxels) const noexcept
{
using namespace world_assembly_detail;
if(!isValid()||stepVoxels<=0||minimumX>maximumX||minimumY>maximumY||minimumZ>maximumZ
||OutsideRange(minimumX)||OutsideRange(minimumY)||OutsideRange(minimumZ)
||OutsideRange(maximumX)||OutsideRange(maximumY)||OutsideRange(maximumZ))
return HeightfieldBoxClass::unknown;
for(const auto& feature:plan_->features)
{
if(feature.type!=2&&feature.type!=4)continue;
if(feature.minimumSubvoxels[0]<=maximumX&&feature.maximumSubvoxels[0]>=minimumX
&&feature.minimumSubvoxels[1]<=maximumY&&feature.maximumSubvoxels[1]>=minimumY
&&feature.minimumSubvoxels[2]<=maximumZ&&feature.maximumSubvoxels[2]>=minimumZ)
return HeightfieldBoxClass::mixed;
}
const std::int64_t stride=static_cast<std::int64_t>(stepVoxels)
*WorldAssemblySubvoxelsPerVoxel;
if((maximumX-minimumX)%stride!=0||(maximumY-minimumY)%stride!=0
||(maximumX-minimumX)/stride>2048||(maximumY-minimumY)/stride>2048)
return HeightfieldBoxClass::unknown;
std::int64_t groundMin=std::numeric_limits<std::int64_t>::max();
std::int64_t groundMax=std::numeric_limits<std::int64_t>::min();
std::int64_t capMin=std::numeric_limits<std::int64_t>::max();
std::int64_t capMax=std::numeric_limits<std::int64_t>::min();
for(std::int64_t y=minimumY;;y+=stride)
{
for(std::int64_t x=minimumX;;x+=stride)
{
const auto column=point(x,y,0);
groundMin=std::min(groundMin,column.groundHeightSubvoxels);
groundMax=std::max(groundMax,column.groundHeightSubvoxels);
capMin=std::min(capMin,column.capHeightSubvoxels);
capMax=std::max(capMax,column.capHeightSubvoxels);
if(x==maximumX)break;
}
if(y==maximumY)break;
}
if(minimumZ>groundMax&&maximumZ<capMin)return HeightfieldBoxClass::allAir;
if(maximumZ<groundMin||minimumZ>capMax)return HeightfieldBoxClass::allSolid;
return HeightfieldBoxClass::mixed;
}
FieldInterval Heightfield2D::interval(std::int64_t minX, std::int64_t minY,
std::int64_t minZ, std::int64_t maxX, std::int64_t maxY, std::int64_t maxZ) const noexcept
{
@@ -1361,16 +1528,23 @@ FieldInterval Heightfield2D::interval(std::int64_t minX, std::int64_t minY,
{
for(const auto& feature:plan_->features)
{
if(feature.type!=2)continue;
if(feature.type!=2&&feature.type!=4)continue;
bool overlap=true;
for(std::size_t axis=0;axis<3;++axis)
overlap=overlap&&feature.minimumSubvoxels[axis]<=std::array<std::int64_t,3>{maxX,maxY,maxZ}[axis]
&&feature.maximumSubvoxels[axis]>=std::array<std::int64_t,3>{minX,minY,minZ}[axis];
if(!overlap)continue;
const std::int64_t depth=std::min({(feature.maximumSubvoxels[0]-feature.minimumSubvoxels[0])/2,
(feature.maximumSubvoxels[1]-feature.minimumSubvoxels[1])/2,
(feature.maximumSubvoxels[2]-feature.minimumSubvoxels[2])/2});
out.minimumDensitySubvoxels=std::min(out.minimumDensitySubvoxels,-depth);
if(feature.type==2)
{
const std::int64_t depth=std::min({
(feature.maximumSubvoxels[0]-feature.minimumSubvoxels[0])/2,
(feature.maximumSubvoxels[1]-feature.minimumSubvoxels[1])/2,
(feature.maximumSubvoxels[2]-feature.minimumSubvoxels[2])/2});
out.minimumDensitySubvoxels=std::min(out.minimumDensitySubvoxels,-depth);
}
else
out.minimumDensitySubvoxels=std::min(out.minimumDensitySubvoxels,
-feature.parametersSubvoxels[2]-WorldAssemblySubvoxelsPerVoxel);
}
}
return out;
@@ -1416,6 +1590,8 @@ FieldDetailEnvelope Heightfield2D::detailEnvelope() const noexcept
(feature.maximumSubvoxels[0]-feature.minimumSubvoxels[0])/2,
(feature.maximumSubvoxels[1]-feature.minimumSubvoxels[1])/2,
(feature.maximumSubvoxels[2]-feature.minimumSubvoxels[2])/2}));
if(feature.type==4)maximum=std::max(maximum,feature.parametersSubvoxels[2]
+WorldAssemblySubvoxelsPerVoxel);
if(feature.type==3)maximum=std::max(maximum,std::abs(feature.parametersSubvoxels[0]));
}
out.maximumAbsoluteDisplacementSubvoxels = maximum;
@@ -1540,7 +1716,7 @@ WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(const Heightfield2D& block
}
for(const auto& feature:plan->features)
{
if(feature.type!=2)continue;
if(feature.type!=2&&feature.type!=4)continue;
const std::int64_t x=feature.minimumSubvoxels[0]
+(feature.maximumSubvoxels[0]-feature.minimumSubvoxels[0])/2;
const std::int64_t y=feature.minimumSubvoxels[1]
@@ -1563,7 +1739,7 @@ WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(const Heightfield2D& block
||result.biomeBorderBoxes>0;
const bool cliffCoverage=!hasCliffProfile||result.cliffBoxes>0;
const bool exceptionCoverage=std::none_of(plan->features.begin(),plan->features.end(),
[](const auto& feature){return feature.type==2;})||result.exceptionBoxes>0;
[](const auto& feature){return feature.type==2||feature.type==4;})||result.exceptionBoxes>0;
result.passed = result.boxesTested >= boxCount && result.negativeCoordinateBoxes>0
&& borderCoverage&&cliffCoverage&&result.capBoxes>0&&exceptionCoverage
&& result.missedBounds == 0