wip(assembler): round L29 -- strate transitions honoured at the bedrock/ceiling interface
EVoxelStrateTransition from the upper strate shapes the bedrock(N)/ceiling(N+1) interface in the shared composition layer: Hard flat; Gradient = smooth 2D hash surface (16 m owner width, about 4 m measured undulation); Interleaved = 3D domain-warped fingered overlap within the width. Each band keeps >= 2 Core voxels solid; faces return to the seam near passage mouths. Interface audit over 512 m: 0 hole columns for all modes; wider-than-declared and too-thin controls rejected. All passage, route, proof and validator audits pass; deterministic Clang = GCC. Evaluator 15. Cost +4% SW, +1.3% FI vs L28. Open: ring-like artifact on the FI floor capture; transition look unverified. 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
f23ab7b9fa
commit
63c7e563d0
16 files changed
+794
-42
No files matched your search
@@ -250,7 +250,7 @@ namespace VoxelForgeCore
|
||||
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
||||
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2;
|
||||
// Version 14 adds shared passage columns for long native open-air crossings.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 14;
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 15;
|
||||
|
||||
enum class FieldChannel : std::uint32_t
|
||||
{
|
||||
@@ -639,6 +639,18 @@ namespace VoxelForgeCore
|
||||
std::vector<ResolvedLayoutAnchorV2> anchors;
|
||||
};
|
||||
|
||||
/** Per-column resolved bands for one adjacent bedrock/ceiling interface.
|
||||
* crossingDepthMicrometres is the actual overlap depth between the two facing bands.
|
||||
*/
|
||||
struct ResolvedLayoutTransitionBandsV1
|
||||
{
|
||||
std::int64_t crossingDepthMicrometres = 0;
|
||||
std::int64_t upperBedrockBottomMicrometres = 0;
|
||||
std::int64_t upperBedrockTopMicrometres = 0;
|
||||
std::int64_t lowerCeilingBottomMicrometres = 0;
|
||||
std::int64_t lowerCeilingTopMicrometres = 0;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutIssueV2
|
||||
{
|
||||
std::string code;
|
||||
@@ -730,6 +742,17 @@ namespace VoxelForgeCore
|
||||
std::string_view json, std::string& error);
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve the deterministic band faces at one XY column. Gradient shares one smooth
|
||||
* displaced face; Interleaved moves the faces inward by a total crossing no larger than
|
||||
* the declared width. Both bands retain authored thickness and touch or overlap, so no
|
||||
* air gap can form.
|
||||
*/
|
||||
VFCORE_API ResolvedLayoutTransitionBandsV1 EvaluateResolvedLayoutTransitionBandsV1(
|
||||
const ResolvedLayoutStratumV2& upper, const ResolvedLayoutStratumV2& lower,
|
||||
std::int32_t worldSeed, std::int64_t layoutChunkSizeMicrometres,
|
||||
std::int64_t xMicrometres, std::int64_t yMicrometres);
|
||||
|
||||
struct ResolvedLayoutValidatorAuditV3
|
||||
{
|
||||
std::uint32_t controlsRun = 0;
|
||||
|
||||
@@ -13,6 +13,97 @@ namespace VoxelForgeCore
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr std::int64_t TransitionQ16 = 1ll << 16;
|
||||
|
||||
void TransitionHashWord(std::uint64_t& hash, std::uint64_t word)
|
||||
{
|
||||
for (int byte = 0; byte < 8; ++byte)
|
||||
{
|
||||
hash ^= static_cast<std::uint8_t>(word >> (byte * 8));
|
||||
hash *= 1099511628211ull;
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t TransitionHash(std::int32_t seed, std::int32_t interfaceIndex,
|
||||
std::int64_t x, std::int64_t y, std::int64_t z, std::uint32_t salt)
|
||||
{
|
||||
std::uint64_t hash = 14695981039346656037ull;
|
||||
TransitionHashWord(hash, static_cast<std::uint32_t>(seed));
|
||||
TransitionHashWord(hash, static_cast<std::uint32_t>(interfaceIndex));
|
||||
TransitionHashWord(hash, static_cast<std::uint64_t>(x));
|
||||
TransitionHashWord(hash, static_cast<std::uint64_t>(y));
|
||||
TransitionHashWord(hash, static_cast<std::uint64_t>(z));
|
||||
TransitionHashWord(hash, salt);
|
||||
hash ^= hash >> 30; hash *= 0xbf58476d1ce4e5b9ull;
|
||||
hash ^= hash >> 27; hash *= 0x94d049bb133111ebull;
|
||||
hash ^= hash >> 31;
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::int64_t TransitionNoiseValue(std::int32_t seed, std::int32_t interfaceIndex,
|
||||
std::int64_t x, std::int64_t y, std::int64_t z, std::uint32_t salt)
|
||||
{
|
||||
const std::uint32_t bits = static_cast<std::uint32_t>(
|
||||
TransitionHash(seed, interfaceIndex, x, y, z, salt) >> 32);
|
||||
return static_cast<std::int64_t>(bits >> 15) - TransitionQ16;
|
||||
}
|
||||
|
||||
std::int64_t TransitionFloorDiv(std::int64_t value, std::int64_t divisor)
|
||||
{
|
||||
const std::int64_t quotient = value / divisor;
|
||||
const std::int64_t remainder = value % divisor;
|
||||
return quotient - (remainder < 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
std::int64_t TransitionFade(std::int64_t valueQ16)
|
||||
{
|
||||
valueQ16 = std::clamp(valueQ16, std::int64_t{0}, TransitionQ16);
|
||||
const std::int64_t square = (valueQ16 * valueQ16) / TransitionQ16;
|
||||
return (square * (3 * TransitionQ16 - 2 * valueQ16)) / TransitionQ16;
|
||||
}
|
||||
|
||||
std::int64_t TransitionLerp(std::int64_t a, std::int64_t b, std::int64_t tQ16)
|
||||
{ return a + ((b - a) * tQ16) / TransitionQ16; }
|
||||
|
||||
std::int64_t TransitionValueNoise2D(std::int32_t seed, std::int32_t interfaceIndex,
|
||||
std::int64_t x, std::int64_t y, std::int64_t cell, std::uint32_t salt)
|
||||
{
|
||||
const std::int64_t ix = TransitionFloorDiv(x, cell), iy = TransitionFloorDiv(y, cell);
|
||||
const std::int64_t tx = TransitionFade(((x - ix * cell) * TransitionQ16) / cell);
|
||||
const std::int64_t ty = TransitionFade(((y - iy * cell) * TransitionQ16) / cell);
|
||||
const auto at = [&](std::int64_t dx, std::int64_t dy)
|
||||
{ return TransitionNoiseValue(seed, interfaceIndex, ix + dx, iy + dy, 0, salt); };
|
||||
return TransitionLerp(TransitionLerp(at(0, 0), at(1, 0), tx),
|
||||
TransitionLerp(at(0, 1), at(1, 1), tx), ty);
|
||||
}
|
||||
|
||||
std::int64_t TransitionValueNoise3D(std::int32_t seed, std::int32_t interfaceIndex,
|
||||
std::int64_t x, std::int64_t y, std::int64_t z, std::int64_t cell,
|
||||
std::uint32_t salt)
|
||||
{
|
||||
const std::int64_t ix = TransitionFloorDiv(x, cell), iy = TransitionFloorDiv(y, cell);
|
||||
const std::int64_t iz = TransitionFloorDiv(z, cell);
|
||||
const std::int64_t tx = TransitionFade(((x - ix * cell) * TransitionQ16) / cell);
|
||||
const std::int64_t ty = TransitionFade(((y - iy * cell) * TransitionQ16) / cell);
|
||||
const std::int64_t tz = TransitionFade(((z - iz * cell) * TransitionQ16) / cell);
|
||||
const auto at = [&](std::int64_t dx, std::int64_t dy, std::int64_t dz)
|
||||
{ return TransitionNoiseValue(seed, interfaceIndex, ix + dx, iy + dy, iz + dz, salt); };
|
||||
const std::int64_t z0 = TransitionLerp(
|
||||
TransitionLerp(at(0, 0, 0), at(1, 0, 0), tx),
|
||||
TransitionLerp(at(0, 1, 0), at(1, 1, 0), tx), ty);
|
||||
const std::int64_t z1 = TransitionLerp(
|
||||
TransitionLerp(at(0, 0, 1), at(1, 0, 1), tx),
|
||||
TransitionLerp(at(0, 1, 1), at(1, 1, 1), tx), ty);
|
||||
return TransitionLerp(z0, z1, tz);
|
||||
}
|
||||
|
||||
std::int64_t LayoutTransitionLimit(const ResolvedLayoutStratumV2& stratum)
|
||||
{
|
||||
if (stratum.transitionMode == "Hard" || stratum.transitionFallsBackToHard)
|
||||
return 0;
|
||||
return std::max<std::int64_t>(0, stratum.transitionWidthMicrometres);
|
||||
}
|
||||
|
||||
std::string LayoutEscape(std::string_view value)
|
||||
{
|
||||
std::string out="\"";
|
||||
@@ -41,12 +132,6 @@ 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 ResolvedLayoutStratumV2& stratum)
|
||||
{
|
||||
if(stratum.transitionMode=="Hard"||stratum.transitionFallsBackToHard)return 0;
|
||||
return stratum.transitionWidthMicrometres
|
||||
+(stratum.transitionMode=="Interleaved"?stratum.transitionWarpAmplitudeMicrometres:0);
|
||||
}
|
||||
std::string LayoutBandJson(const ResolvedLayoutBandV2& band)
|
||||
{
|
||||
std::ostringstream out;out<<"{\"kind\":"<<LayoutEscape(band.kind)
|
||||
@@ -205,6 +290,84 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
ResolvedLayoutTransitionBandsV1 EvaluateResolvedLayoutTransitionBandsV1(
|
||||
const ResolvedLayoutStratumV2& upper, const ResolvedLayoutStratumV2& lower,
|
||||
std::int32_t worldSeed, std::int64_t layoutChunkSizeMicrometres,
|
||||
std::int64_t xMicrometres, std::int64_t yMicrometres)
|
||||
{
|
||||
ResolvedLayoutTransitionBandsV1 result;
|
||||
const std::int64_t seam = upper.bedrock.bottomMicrometres;
|
||||
const std::int64_t upperThickness = upper.bedrock.topMicrometres - seam;
|
||||
const std::int64_t lowerThickness = lower.ceiling.topMicrometres
|
||||
- lower.ceiling.bottomMicrometres;
|
||||
result.upperBedrockBottomMicrometres = seam;
|
||||
result.upperBedrockTopMicrometres = seam + std::max<std::int64_t>(0, upperThickness);
|
||||
result.lowerCeilingBottomMicrometres = seam - std::max<std::int64_t>(0, lowerThickness);
|
||||
result.lowerCeilingTopMicrometres = seam;
|
||||
|
||||
const std::int64_t width = upper.transitionFallsBackToHard
|
||||
? 0 : std::max<std::int64_t>(0, upper.transitionWidthMicrometres);
|
||||
if (upper.transitionMode == "Hard" || width == 0
|
||||
|| (upper.transitionMode != "Gradient" && upper.transitionMode != "Interleaved")
|
||||
|| !upper.bedrock.presentInCurrentField || !lower.ceiling.presentInCurrentField
|
||||
|| upperThickness <= 0 || lowerThickness <= 0)
|
||||
return result;
|
||||
|
||||
if (width <= 0) return result;
|
||||
|
||||
std::int64_t noise = 0;
|
||||
if (upper.transitionMode == "Gradient")
|
||||
{
|
||||
const std::int64_t wavelength = std::max<std::int64_t>(
|
||||
1000000, std::max<std::int64_t>(1, layoutChunkSizeMicrometres) * 4);
|
||||
noise = TransitionValueNoise2D(worldSeed, upper.strateIndex,
|
||||
xMicrometres, yMicrometres, wavelength, 0x47524144u);
|
||||
}
|
||||
else
|
||||
{
|
||||
// A face crossing wider than the sum of the authored bands would separate
|
||||
// their far edges and leave an air slab. Limit the physical crossing to the
|
||||
// smaller of the declared transition width and the combined band thickness.
|
||||
const std::int64_t maximumContinuousCrossing=upperThickness+lowerThickness;
|
||||
const std::int64_t crossingLimit=std::min(width,maximumContinuousCrossing);
|
||||
const std::int64_t cell = std::max<std::int64_t>(1000000,
|
||||
std::max<std::int64_t>(1, layoutChunkSizeMicrometres) * 2);
|
||||
const std::int64_t warp = std::min(crossingLimit / 2, cell / 2);
|
||||
const std::int64_t warpX = (TransitionValueNoise3D(worldSeed, upper.strateIndex,
|
||||
xMicrometres, yMicrometres, seam, cell, 0x49574158u) * warp) / TransitionQ16;
|
||||
const std::int64_t warpY = (TransitionValueNoise3D(worldSeed, upper.strateIndex,
|
||||
xMicrometres, yMicrometres, seam, cell, 0x49574159u) * warp) / TransitionQ16;
|
||||
const std::int64_t warpZ = (TransitionValueNoise3D(worldSeed, upper.strateIndex,
|
||||
xMicrometres, yMicrometres, seam, cell, 0x4957415au) * warp) / TransitionQ16;
|
||||
noise = TransitionValueNoise3D(worldSeed, upper.strateIndex,
|
||||
xMicrometres + warpX, yMicrometres + warpY, seam + warpZ,
|
||||
cell, 0x494e544cu);
|
||||
|
||||
// Interleaved means physical crossing: the two faces enter each other's
|
||||
// bands by a total depth in [0, declared width]. Split that overlap across
|
||||
// both faces so neither stratum owns the entire displacement. Since both
|
||||
// resulting intervals are solid and overlap, the transition cannot open a gap.
|
||||
const std::int64_t overlap = ((noise + TransitionQ16) * crossingLimit)
|
||||
/ (2 * TransitionQ16);
|
||||
const std::int64_t upperInset = overlap / 2;
|
||||
const std::int64_t lowerInset = overlap - upperInset;
|
||||
result.crossingDepthMicrometres = overlap;
|
||||
result.upperBedrockBottomMicrometres = seam - upperInset;
|
||||
result.upperBedrockTopMicrometres = result.upperBedrockBottomMicrometres + upperThickness;
|
||||
result.lowerCeilingTopMicrometres = seam + lowerInset;
|
||||
result.lowerCeilingBottomMicrometres = result.lowerCeilingTopMicrometres - lowerThickness;
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::int64_t displacement = (noise * (width / 8)) / TransitionQ16;
|
||||
const std::int64_t interfaceZ = seam + displacement;
|
||||
result.upperBedrockBottomMicrometres = interfaceZ;
|
||||
result.upperBedrockTopMicrometres = interfaceZ + upperThickness;
|
||||
result.lowerCeilingBottomMicrometres = interfaceZ - lowerThickness;
|
||||
result.lowerCeilingTopMicrometres = interfaceZ;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ResolvedLayoutSnapshotV3::ToJson() const
|
||||
{
|
||||
const std::string body=LayoutBodyJson(*this);
|
||||
@@ -216,7 +379,8 @@ bool ResolvedLayoutSnapshotV3::finalize(std::string& error)
|
||||
{
|
||||
error.clear();
|
||||
if(schemaVersion!=ResolvedLayoutSchemaVersionV3||worldIdentity.empty()||strates.empty()
|
||||
||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0)
|
||||
||coreVoxelSizeMicrometres==0||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
|
||||
@@ -257,7 +421,8 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
{
|
||||
issues.clear();error.clear();
|
||||
if(schemaVersion!=ResolvedLayoutSchemaVersionV3||worldIdentity.empty()||strates.empty()
|
||||
||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0)
|
||||
||coreVoxelSizeMicrometres==0||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)});};
|
||||
@@ -271,6 +436,8 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
if(s.transitionInterpenetrationMicrometres>effectiveTransitionWidth
|
||||
||s.transitionInterpenetrationMicrometres<0)
|
||||
add("transition_overlap_exceeds_width",s.stableId+" transition interpenetration exceeds its declared width");
|
||||
if(s.transitionWidthMicrometres<0||s.transitionBlendChunks<0)
|
||||
add("invalid_transition_width",s.stableId+" has a negative transition width");
|
||||
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");
|
||||
@@ -290,6 +457,10 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
{
|
||||
if(b.kind!=expected||b.topMicrometres<=b.bottomMicrometres)
|
||||
add("wrong_order",s.stableId+" has an invalid "+std::string(expected)+" band");
|
||||
const std::int64_t minimumThickness=2ll*coreVoxelSizeMicrometres;
|
||||
if(b.topMicrometres-b.bottomMicrometres<minimumThickness)
|
||||
add("band_thinner_than_minimum",s.stableId+" "+std::string(expected)
|
||||
+" is thinner than the two-Core-voxel minimum");
|
||||
if(b.protectedBand&&b.diggable)
|
||||
add("invalid_protection",s.stableId+" band is both protected and diggable");
|
||||
if(b.solidDensityQ16<=0)
|
||||
@@ -1055,6 +1226,11 @@ ResolvedLayoutValidatorAuditV3 AuditResolvedLayoutValidatorV3()
|
||||
auto missingCeiling=base;missingCeiling.strates[0].ceiling.presentInCurrentField=false;expectReject("missing ceiling",missingCeiling);
|
||||
auto wrongOrder=base;wrongOrder.strates[0].content.kind="bedrock";expectReject("wrong band order",wrongOrder);
|
||||
auto largeOverlap=base;largeOverlap.strates[0].transitionInterpenetrationMicrometres=4000001;expectReject("interpenetration exceeds transition width",largeOverlap);
|
||||
auto thinBand=base;
|
||||
thinBand.strates[0].bedrock.topMicrometres=thinBand.strates[0].bedrock.bottomMicrometres
|
||||
+2ll*base.coreVoxelSizeMicrometres-1;
|
||||
thinBand.strates[0].content.bottomMicrometres=thinBand.strates[0].bedrock.topMicrometres;
|
||||
expectReject("bedrock band below minimum thickness",thinBand);
|
||||
auto genericOverlap=base;genericOverlap.strates[0].bedrock.bottomMicrometres-=1;expectReject("nominal band overlap",genericOverlap);
|
||||
auto genericGap=base;genericGap.strates[0].bedrock.bottomMicrometres+=1;expectReject("nominal band gap",genericGap);
|
||||
result.passed=result.controlsRun==result.controlsRejectedAsExpected&&result.failures.empty();return result;
|
||||
|
||||
@@ -144,11 +144,96 @@ public:
|
||||
const VoxelForgeCore::ResolvedLayoutStratumV2* FindResolvedLayoutStratum(int32 StrateIndex) const
|
||||
{
|
||||
if(!ResolvedLayout)return nullptr;
|
||||
if(StrateIndex>=0&&static_cast<std::size_t>(StrateIndex)<ResolvedLayout->strates.size())
|
||||
{
|
||||
const auto& direct=ResolvedLayout->strates[static_cast<std::size_t>(StrateIndex)];
|
||||
if(direct.strateIndex==StrateIndex)return &direct;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
const VoxelForgeCore::ResolvedLayoutStratumV2* FindResolvedLayoutStratumByOrder(int32 Order) const
|
||||
{
|
||||
if(!ResolvedLayout||Order<0)return nullptr;
|
||||
if(static_cast<std::size_t>(Order)<ResolvedLayout->strates.size())
|
||||
{
|
||||
const auto& direct=ResolvedLayout->strates[static_cast<std::size_t>(Order)];
|
||||
if(direct.order==Order)return &direct;
|
||||
}
|
||||
const auto it=std::find_if(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(),
|
||||
[Order](const auto& item){return item.order==Order;});
|
||||
return it==ResolvedLayout->strates.end()?nullptr:&*it;
|
||||
}
|
||||
|
||||
bool IsResolvedLayoutTransitionActive(const VoxelForgeCore::ResolvedLayoutStratumV2& Upper) const
|
||||
{
|
||||
return Upper.transitionMode!="Hard"&&!Upper.transitionFallsBackToHard
|
||||
&&Upper.transitionWidthMicrometres>0
|
||||
&&Upper.transitionHonouredInCore;
|
||||
}
|
||||
|
||||
std::int64_t ResolvedTransitionMaximumFaceDisplacementMicrometres(
|
||||
const VoxelForgeCore::ResolvedLayoutStratumV2& Upper,
|
||||
const VoxelForgeCore::ResolvedLayoutStratumV2& Lower) const
|
||||
{
|
||||
const std::int64_t width=std::max<std::int64_t>(0,Upper.transitionWidthMicrometres);
|
||||
if(Upper.transitionMode=="Gradient")return width/8;
|
||||
if(Upper.transitionMode!="Interleaved")return 0;
|
||||
const std::int64_t upperThickness=std::max<std::int64_t>(0,
|
||||
Upper.bedrock.topMicrometres-Upper.bedrock.bottomMicrometres);
|
||||
const std::int64_t lowerThickness=std::max<std::int64_t>(0,
|
||||
Lower.ceiling.topMicrometres-Lower.ceiling.bottomMicrometres);
|
||||
const std::int64_t maximumCrossing=std::min(width,upperThickness+lowerThickness);
|
||||
return (maximumCrossing+1)/2;
|
||||
}
|
||||
|
||||
std::int64_t ResolvedTransitionPassageWeightQ16(
|
||||
const VoxelForgeCore::ResolvedLayoutStratumV2& Upper,
|
||||
const VoxelForgeCore::ResolvedLayoutStratumV2& Lower,
|
||||
std::int64_t XMicrometres,std::int64_t YMicrometres) const
|
||||
{
|
||||
constexpr std::int64_t Q16=1ll<<16;
|
||||
constexpr std::int64_t SupportMarginMicrometres=1000000;
|
||||
constexpr std::int64_t FadeWidthMicrometres=2000000;
|
||||
if(!ResolvedLayout||!Owner||Owner->CoreVoxelSizeMicrons==0)return Q16;
|
||||
const std::int64_t seam=Upper.bedrock.bottomMicrometres;
|
||||
const std::int64_t coreMicrometres=Owner->CoreVoxelSizeMicrons;
|
||||
const std::int64_t subvoxelsPerVoxel=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t weight=Q16;
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
if(passage.upperStrateIndex!=Upper.strateIndex
|
||||
||passage.lowerStrateIndex!=Lower.strateIndex)continue;
|
||||
const std::int64_t mouthRadiusMicrometres=std::max<std::int64_t>(0,
|
||||
passage.mouthRadiusSubvoxels*coreMicrometres/subvoxelsPerVoxel);
|
||||
const std::int64_t protectedRadius=mouthRadiusMicrometres
|
||||
+SupportMarginMicrometres;
|
||||
for(const auto* mouth:{&passage.upperMouthSubvoxels,&passage.lowerMouthSubvoxels})
|
||||
{
|
||||
const std::int64_t mouthZMicrometres=mouth->at(2)*coreMicrometres
|
||||
/subvoxelsPerVoxel;
|
||||
if(std::abs(mouthZMicrometres-seam)
|
||||
>Upper.transitionWidthMicrometres/2+mouthRadiusMicrometres)continue;
|
||||
const double dx=static_cast<double>(XMicrometres-mouth->at(0)*coreMicrometres
|
||||
/subvoxelsPerVoxel);
|
||||
const double dy=static_cast<double>(YMicrometres-mouth->at(1)*coreMicrometres
|
||||
/subvoxelsPerVoxel);
|
||||
const std::int64_t fadeEnd=protectedRadius+FadeWidthMicrometres;
|
||||
const double distanceSquared=dx*dx+dy*dy;
|
||||
if(distanceSquared>=static_cast<double>(fadeEnd)*fadeEnd)continue;
|
||||
if(distanceSquared<=static_cast<double>(protectedRadius)*protectedRadius)return 0;
|
||||
const double distance=std::sqrt(distanceSquared);
|
||||
const double t=static_cast<double>(static_cast<std::int64_t>(distance)
|
||||
-protectedRadius)/FadeWidthMicrometres;
|
||||
const double smooth=t*t*(3.0-2.0*t);
|
||||
weight=std::min(weight,static_cast<std::int64_t>(std::llround(smooth*Q16)));
|
||||
}
|
||||
}
|
||||
return weight;
|
||||
}
|
||||
|
||||
bool IsInsideResolvedStrateBoundaryBand(float Z) const
|
||||
{
|
||||
if(!ResolvedLayout||!Owner||!Owner->Manager||!std::isfinite(Z))return false;
|
||||
@@ -160,6 +245,21 @@ public:
|
||||
const auto solidAt=[&](const VoxelForgeCore::ResolvedLayoutBandV2& band)
|
||||
{return band.presentInCurrentField&&zMicrometres>=band.bottomMicrometres
|
||||
&&zMicrometres<band.topMicrometres;};
|
||||
const auto mayTouchTransition=[&](const VoxelForgeCore::ResolvedLayoutStratumV2* Upper,
|
||||
const VoxelForgeCore::ResolvedLayoutStratumV2* Lower)
|
||||
{
|
||||
if(!Upper||!Lower||!IsResolvedLayoutTransitionActive(*Upper))return false;
|
||||
const auto maxDisplacement=ResolvedTransitionMaximumFaceDisplacementMicrometres(
|
||||
*Upper,*Lower);
|
||||
const double minimum=std::min(Upper->bedrock.bottomMicrometres,
|
||||
Lower->ceiling.bottomMicrometres)-maxDisplacement;
|
||||
const double maximum=std::max(Upper->bedrock.topMicrometres,
|
||||
Lower->ceiling.topMicrometres)+maxDisplacement;
|
||||
return zMicrometres>=minimum&&zMicrometres<maximum;
|
||||
};
|
||||
const auto* lower=FindResolvedLayoutStratumByOrder(stratum->order+1);
|
||||
const auto* upper=stratum->order>0?FindResolvedLayoutStratumByOrder(stratum->order-1):nullptr;
|
||||
if(mayTouchTransition(stratum,lower)||mayTouchTransition(upper,stratum))return true;
|
||||
return solidAt(stratum->ceiling)||solidAt(stratum->bedrock);
|
||||
}
|
||||
|
||||
@@ -175,8 +275,68 @@ public:
|
||||
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;
|
||||
|
||||
bool bCeilingHandledByTransition=false,bBedrockHandledByTransition=false;
|
||||
const auto applyInterface=[&](const VoxelForgeCore::ResolvedLayoutStratumV2* upper,
|
||||
const VoxelForgeCore::ResolvedLayoutStratumV2* lower)
|
||||
{
|
||||
if(!upper||!lower||!IsResolvedLayoutTransitionActive(*upper))return false;
|
||||
const auto maxDisplacement=ResolvedTransitionMaximumFaceDisplacementMicrometres(
|
||||
*upper,*lower);
|
||||
const double minimum=std::min(upper->bedrock.bottomMicrometres,
|
||||
lower->ceiling.bottomMicrometres)-maxDisplacement;
|
||||
const double maximum=std::max(upper->bedrock.topMicrometres,
|
||||
lower->ceiling.topMicrometres)+maxDisplacement;
|
||||
if(zMicrometres<minimum||zMicrometres>=maximum)return false;
|
||||
if(upper==stratum)bBedrockHandledByTransition=true;
|
||||
if(lower==stratum)bCeilingHandledByTransition=true;
|
||||
const std::int64_t xMicrometres=static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(X)*Owner->CoreVoxelSizeMicrons));
|
||||
const std::int64_t yMicrometres=static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(Y)*Owner->CoreVoxelSizeMicrons));
|
||||
const auto bands=VoxelForgeCore::EvaluateResolvedLayoutTransitionBandsV1(*upper,*lower,
|
||||
Owner->Seed,ResolvedLayout->strateLayoutChunkSizeMicrometres,
|
||||
xMicrometres,yMicrometres);
|
||||
auto resolvedBands=bands;
|
||||
const std::int64_t transitionWeight=ResolvedTransitionPassageWeightQ16(
|
||||
*upper,*lower,xMicrometres,yMicrometres);
|
||||
constexpr std::int64_t TransitionQ16=1ll<<16;
|
||||
if(transitionWeight<TransitionQ16)
|
||||
{
|
||||
const std::int64_t seam=upper->bedrock.bottomMicrometres;
|
||||
const std::int64_t upperThickness=upper->bedrock.topMicrometres-seam;
|
||||
const std::int64_t lowerThickness=lower->ceiling.topMicrometres
|
||||
-lower->ceiling.bottomMicrometres;
|
||||
const auto scaled=[&](std::int64_t value)
|
||||
{return (value*transitionWeight)/TransitionQ16;};
|
||||
resolvedBands.upperBedrockBottomMicrometres=seam+scaled(
|
||||
bands.upperBedrockBottomMicrometres-seam);
|
||||
resolvedBands.upperBedrockTopMicrometres=
|
||||
resolvedBands.upperBedrockBottomMicrometres+upperThickness;
|
||||
resolvedBands.lowerCeilingTopMicrometres=seam+scaled(
|
||||
bands.lowerCeilingTopMicrometres-seam);
|
||||
resolvedBands.lowerCeilingBottomMicrometres=
|
||||
resolvedBands.lowerCeilingTopMicrometres-lowerThickness;
|
||||
resolvedBands.crossingDepthMicrometres=std::max<std::int64_t>(0,
|
||||
resolvedBands.lowerCeilingTopMicrometres
|
||||
-resolvedBands.upperBedrockBottomMicrometres);
|
||||
}
|
||||
const bool upperBedrock=zMicrometres>=resolvedBands.upperBedrockBottomMicrometres
|
||||
&&zMicrometres<resolvedBands.upperBedrockTopMicrometres;
|
||||
const bool lowerCeiling=zMicrometres>=resolvedBands.lowerCeilingBottomMicrometres
|
||||
&&zMicrometres<resolvedBands.lowerCeilingTopMicrometres;
|
||||
if(!upperBedrock&&!lowerCeiling)return false;
|
||||
const auto& band=upperBedrock?upper->bedrock:lower->ceiling;
|
||||
density=-std::max(0.05f,static_cast<float>(band.solidDensityQ16)/65536.0f);
|
||||
return true;
|
||||
};
|
||||
|
||||
const auto* lower=FindResolvedLayoutStratumByOrder(stratum->order+1);
|
||||
const auto* upper=stratum->order>0?FindResolvedLayoutStratumByOrder(stratum->order-1):nullptr;
|
||||
if(applyInterface(stratum,lower)||applyInterface(upper,stratum))return density;
|
||||
const auto* band=(!bCeilingHandledByTransition&&solidAt(stratum->ceiling))
|
||||
?&stratum->ceiling:(!bBedrockHandledByTransition&&solidAt(stratum->bedrock))
|
||||
?&stratum->bedrock:nullptr;
|
||||
if(!band)return density;
|
||||
return -std::max(0.05f,static_cast<float>(band->solidDensityQ16)/65536.0f);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user