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

+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