streaming: near LODs first (walkable ring, LOD0, LOD1, LOD2, then far); exact passage trims (L78)
- Headless and Unreal schedulers drain the collision-critical ring, then LOD0-2, then far LODs at low priority. LOD0-2 ready (W8 medians): SW 4.36->1.76, FI 6.89->5.26, TN 4.88->3.22, VS 2.71->1.56 s. Whole view roughly unchanged (FI/VS slightly later). KNOWN RISK: far tiles wait until every near tile is ready, so far LODs may starve while moving fast (to address next). - Exact composer trims: tile-level upper-mouth candidate list, segment-index min/max rejection before lower_bound, 64-entry thread-local value-noise corner cache. Passage share 65 -> 63%. - World exact: 24/24 hash matrix + pinned L75 owner hash. Fast tier PASS. Linux UE host builds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
5e091d254c
commit
ec20994a8e
10 files changed
+718
-52
No files matched your search
@@ -201,6 +201,37 @@ public:
|
||||
passageColumns_=passageRoutes_;
|
||||
passageMouthSupports_=passageRoutes_;
|
||||
passageMouthSupportSegments_.resize(passageRoutes_.size());
|
||||
if(shape.ResolvedLayout&&!shape.ResolvedLayout->passages.empty())
|
||||
{
|
||||
constexpr double Q=static_cast<double>(
|
||||
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
const double qPerMetre=Q*1000000.0/
|
||||
std::max<std::uint32_t>(1,
|
||||
shape.ResolvedLayout->coreVoxelSizeMicrometres);
|
||||
const double horizontalReach=15.0*qPerMetre;
|
||||
const double verticalReach=8.0*qPerMetre;
|
||||
const bool failOpen=minimumSubvoxels_[0]>maximumSubvoxels_[0]
|
||||
||minimumSubvoxels_[1]>maximumSubvoxels_[1]
|
||||
||minimumSubvoxels_[2]>maximumSubvoxels_[2];
|
||||
for(std::size_t passageIndex=0;
|
||||
passageIndex<shape.ResolvedLayout->passages.size();++passageIndex)
|
||||
{
|
||||
if(failOpen)
|
||||
{
|
||||
upperMouthSupportPassages_.push_back(passageIndex);
|
||||
continue;
|
||||
}
|
||||
const auto& mouth=shape.ResolvedLayout->passages[
|
||||
passageIndex].upperMouthSubvoxels;
|
||||
if(static_cast<double>(mouth[0])+horizontalReach>=minimumSubvoxels_[0]
|
||||
&&static_cast<double>(mouth[0])-horizontalReach<=maximumSubvoxels_[0]
|
||||
&&static_cast<double>(mouth[1])+horizontalReach>=minimumSubvoxels_[1]
|
||||
&&static_cast<double>(mouth[1])-horizontalReach<=maximumSubvoxels_[1]
|
||||
&&static_cast<double>(mouth[2])+verticalReach>=minimumSubvoxels_[2]
|
||||
&&static_cast<double>(mouth[2])-verticalReach<=maximumSubvoxels_[2])
|
||||
upperMouthSupportPassages_.push_back(passageIndex);
|
||||
}
|
||||
}
|
||||
for(const auto& crossing:shape.PassageVoidCrossings)
|
||||
if(crossing.needsColumn
|
||||
&&crossing.bottomZSubvoxels<=maximumSubvoxels_[2]
|
||||
@@ -257,6 +288,10 @@ public:
|
||||
[](std::uint8_t value){return value!=0;});}
|
||||
bool hasRouteFeatureOverlap() const{return routeFeatureOverlap_;}
|
||||
bool hasColumnZOverlap() const{return columnZOverlap_;}
|
||||
bool mayContainUpperMouthSupport() const
|
||||
{return !upperMouthSupportPassages_.empty();}
|
||||
const std::vector<std::size_t>& upperMouthSupportPassages() const
|
||||
{return upperMouthSupportPassages_;}
|
||||
bool mayContainResolvedBoundaryBand() const{return mayContainResolvedBoundaryBand_;}
|
||||
bool mayContainTransitionPassageFade() const{return mayContainTransitionPassageFade_;}
|
||||
bool mayContainResolvedBoundaryBandAtZ(double zMicrometres) const
|
||||
@@ -282,6 +317,7 @@ public:
|
||||
std::vector<std::uint32_t> cavernFeatureIndices_;
|
||||
std::vector<std::uint8_t> passageRoutes_,passageColumns_,passageMouthSupports_;
|
||||
std::vector<std::vector<FCandidateSegment>> passageMouthSupportSegments_;
|
||||
std::vector<std::size_t> upperMouthSupportPassages_;
|
||||
std::size_t routeSegmentCandidateCount_=0;
|
||||
bool routeFeatureOverlap_=false,columnZOverlap_=false;
|
||||
bool mayContainResolvedBoundaryBand_=true;
|
||||
@@ -1004,6 +1040,11 @@ public:
|
||||
auto* passageProfile=passageTileScopeMatches
|
||||
?passageTileQueries->profileStats():nullptr;
|
||||
if(passageProfile)++passageProfile->passageComposerQueries;
|
||||
if(passageTileScopeMatches&&density>=0.0f
|
||||
&&!passageTileQueries->hasRouteFeatureOverlap()
|
||||
&&!passageTileQueries->hasColumnZOverlap()
|
||||
&&!passageTileQueries->hasAnyMouthSupport()
|
||||
&&!passageTileQueries->mayContainUpperMouthSupport())return density;
|
||||
bool queryNearUpperMouthSupport=false;
|
||||
const auto checkUpperMouth=[&](const auto& passage)
|
||||
{
|
||||
@@ -1015,10 +1056,22 @@ public:
|
||||
if(dx*dx+dy*dy<=reach*reach&&std::abs(dz)<=8.0*qPerMetre)
|
||||
queryNearUpperMouthSupport=true;
|
||||
};
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
if(passageTileScopeMatches)
|
||||
{
|
||||
checkUpperMouth(passage);
|
||||
if(queryNearUpperMouthSupport)break;
|
||||
for(const std::size_t passageIndex:
|
||||
passageTileQueries->upperMouthSupportPassages())
|
||||
{
|
||||
checkUpperMouth(ResolvedLayout->passages[passageIndex]);
|
||||
if(queryNearUpperMouthSupport)break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
checkUpperMouth(passage);
|
||||
if(queryNearUpperMouthSupport)break;
|
||||
}
|
||||
}
|
||||
if(passageTileScopeMatches&&density>=0.0f
|
||||
&&!passageTileQueries->hasRouteFeatureOverlap()
|
||||
@@ -3862,6 +3915,7 @@ private:
|
||||
{
|
||||
std::array<double,3> minimum{},maximum{};
|
||||
double maximumRadius=0.0;
|
||||
std::size_t minimumSegment=0,maximumSegment=0;
|
||||
std::vector<std::size_t> segments;
|
||||
int left=-1,right=-1;
|
||||
};
|
||||
@@ -4676,6 +4730,8 @@ private:
|
||||
node.maximumRadius=std::max({node.maximumRadius,
|
||||
static_cast<double>(a.radiusSubvoxels),static_cast<double>(b.radiusSubvoxels)});
|
||||
}
|
||||
node.minimumSegment=*std::min_element(segments.begin(),segments.end());
|
||||
node.maximumSegment=*std::max_element(segments.begin(),segments.end());
|
||||
node.segments=segments;
|
||||
std::sort(node.segments.begin(),node.segments.end());
|
||||
const int nodeIndex=static_cast<int>(PassageNearestNodes[passageIndex].size());
|
||||
@@ -5284,7 +5340,11 @@ private:
|
||||
if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return;
|
||||
const auto& nodes=PassageNearestNodes[passageIndex];
|
||||
const auto& node=nodes[static_cast<std::size_t>(nodeIndex)];
|
||||
const auto candidate=std::lower_bound(node.segments.begin(),node.segments.end(),first);
|
||||
if(node.maximumSegment<first||node.minimumSegment>=end)return;
|
||||
const bool allSegmentsInRange=first<=node.minimumSegment
|
||||
&&node.maximumSegment<end;
|
||||
const auto candidate=allSegmentsInRange?node.segments.begin()
|
||||
:std::lower_bound(node.segments.begin(),node.segments.end(),first);
|
||||
if(candidate==node.segments.end()||*candidate>=end)return;
|
||||
const double lowerBound=PassageNearestNodeLowerBound(node,qx,qy,qz);
|
||||
if(lowerBound>bestDistance)return;
|
||||
@@ -5325,7 +5385,11 @@ private:
|
||||
if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return false;
|
||||
const auto& nodes=PassageNearestNodes[passageIndex];
|
||||
const auto& node=nodes[static_cast<std::size_t>(nodeIndex)];
|
||||
const auto candidate=std::lower_bound(node.segments.begin(),node.segments.end(),first);
|
||||
if(node.maximumSegment<first||node.minimumSegment>=end)return false;
|
||||
const bool allSegmentsInRange=first<=node.minimumSegment
|
||||
&&node.maximumSegment<end;
|
||||
const auto candidate=allSegmentsInRange?node.segments.begin()
|
||||
:std::lower_bound(node.segments.begin(),node.segments.end(),first);
|
||||
if(candidate==node.segments.end()||*candidate>=end)return false;
|
||||
if(PassageNearestNodeLowerBound(node,qx,qy,qz)>signedDistanceLimit)return false;
|
||||
if(node.left<0)return true;
|
||||
@@ -5448,7 +5512,11 @@ private:
|
||||
if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return false;
|
||||
const auto& nodes=PassageNearestNodes[passageIndex];
|
||||
const auto& node=nodes[static_cast<std::size_t>(nodeIndex)];
|
||||
auto candidate=std::lower_bound(node.segments.begin(),node.segments.end(),first);
|
||||
if(node.maximumSegment<first||node.minimumSegment>=end)return false;
|
||||
const bool allSegmentsInRange=first<=node.minimumSegment
|
||||
&&node.maximumSegment<end;
|
||||
auto candidate=allSegmentsInRange?node.segments.begin()
|
||||
:std::lower_bound(node.segments.begin(),node.segments.end(),first);
|
||||
if(candidate==node.segments.end()||*candidate>=end)return false;
|
||||
const double xyReach=0.39*qPerMetre;
|
||||
if(qx+xyReach<node.minimum[0]||qx-xyReach>node.maximum[0]
|
||||
|
||||
@@ -1891,6 +1891,17 @@ namespace VoxelForgeCore
|
||||
std::vector<TileKey> output(tiles.begin(),tiles.end());
|
||||
std::sort(output.begin(),output.end(),[&](const TileKey& a,const TileKey& b)
|
||||
{
|
||||
const auto tier=[&](const TileKey& key)
|
||||
{
|
||||
if(key.lod==0&&std::abs(key.coordinate.x-centerChunk.x)<=1
|
||||
&&std::abs(key.coordinate.y-centerChunk.y)<=1
|
||||
&&std::abs(key.coordinate.z-centerChunk.z)<=1)return 0;
|
||||
return key.lod<=2?1:2;
|
||||
};
|
||||
const int tierA=tier(a),tierB=tier(b);
|
||||
if(tierA!=tierB)return tierA<tierB;
|
||||
if(tierA==1&&a.lod!=b.lod)return a.lod<b.lod;
|
||||
if(tierA==2&&a.lod!=b.lod)return a.lod>b.lod;
|
||||
const double da=TileDistanceSquared(a,centerChunk),db=TileDistanceSquared(b,centerChunk);
|
||||
if(da!=db)return da<db;
|
||||
if(a.lod!=b.lod)return a.lod<b.lod;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#define VF_FIELD_CLAMP(v,a,b) clamp(v,a,b)
|
||||
#define VF_FIELD_LERP(a,b,t) lerp(a,b,t)
|
||||
#else
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
@@ -244,11 +245,70 @@ VF_FIELD_INLINE float VF_FieldValueNoise(float x, float y, float z,
|
||||
return VF_FieldLerp(VF_FieldLerp(z00, z10, ty), VF_FieldLerp(z01, z11, ty), tz);
|
||||
}
|
||||
|
||||
#if !defined(VF_FIELD_HLSL)
|
||||
struct VF_FieldValueNoiseCacheEntry
|
||||
{
|
||||
VF_FieldI32 x=0,y=0,z=0,wavelength=0;
|
||||
VF_FieldU32 seed=0;
|
||||
bool valid=false;
|
||||
std::array<float,8> corners{};
|
||||
};
|
||||
|
||||
// Reuse the eight lattice values while adjacent voxel samples remain in the same
|
||||
// value-noise cell. Only the integer hash lookups are shared; interpolation keeps
|
||||
// the same inputs and operation order as VF_FieldValueNoise.
|
||||
VF_FIELD_INLINE float VF_FieldCachedValueNoise(float x,float y,float z,
|
||||
VF_FieldI32 wavelength,VF_FieldU32 seed)
|
||||
{
|
||||
static thread_local std::array<VF_FieldValueNoiseCacheEntry,64> cache{};
|
||||
const float inv=1.0f/VF_FIELD_CAST_F32(wavelength);
|
||||
const float fx=x*inv,fy=y*inv,fz=z*inv;
|
||||
const VF_FieldI32 x0=VF_FIELD_CAST_I32(VF_FIELD_FLOOR(fx));
|
||||
const VF_FieldI32 y0=VF_FIELD_CAST_I32(VF_FIELD_FLOOR(fy));
|
||||
const VF_FieldI32 z0=VF_FIELD_CAST_I32(VF_FIELD_FLOOR(fz));
|
||||
const VF_FieldU32 cellHash=VF_FieldMix(seed
|
||||
^VF_FIELD_CAST_U32(x0)*0x68E31DA4u
|
||||
^VF_FIELD_CAST_U32(y0)*0x1B56C4E9u
|
||||
^VF_FIELD_CAST_U32(z0)*0x9E3779B9u
|
||||
^VF_FIELD_CAST_U32(wavelength)*0x85EBCA77u);
|
||||
auto& entry=cache[cellHash&(cache.size()-1)];
|
||||
if(!entry.valid||entry.x!=x0||entry.y!=y0||entry.z!=z0
|
||||
||entry.wavelength!=wavelength||entry.seed!=seed)
|
||||
{
|
||||
entry.x=x0;entry.y=y0;entry.z=z0;entry.wavelength=wavelength;
|
||||
entry.seed=seed;entry.valid=true;
|
||||
entry.corners={
|
||||
VF_FieldNoiseValue(x0,y0,z0,seed),
|
||||
VF_FieldNoiseValue(x0+1,y0,z0,seed),
|
||||
VF_FieldNoiseValue(x0,y0+1,z0,seed),
|
||||
VF_FieldNoiseValue(x0+1,y0+1,z0,seed),
|
||||
VF_FieldNoiseValue(x0,y0,z0+1,seed),
|
||||
VF_FieldNoiseValue(x0+1,y0,z0+1,seed),
|
||||
VF_FieldNoiseValue(x0,y0+1,z0+1,seed),
|
||||
VF_FieldNoiseValue(x0+1,y0+1,z0+1,seed)};
|
||||
}
|
||||
const float tx=VF_FieldFade(fx-VF_FIELD_CAST_F32(x0));
|
||||
const float ty=VF_FieldFade(fy-VF_FIELD_CAST_F32(y0));
|
||||
const float tz=VF_FieldFade(fz-VF_FIELD_CAST_F32(z0));
|
||||
const float z00=VF_FieldLerp(entry.corners[0],entry.corners[1],tx);
|
||||
const float z10=VF_FieldLerp(entry.corners[2],entry.corners[3],tx);
|
||||
const float z01=VF_FieldLerp(entry.corners[4],entry.corners[5],tx);
|
||||
const float z11=VF_FieldLerp(entry.corners[6],entry.corners[7],tx);
|
||||
return VF_FieldLerp(VF_FieldLerp(z00,z10,ty),VF_FieldLerp(z01,z11,ty),tz);
|
||||
}
|
||||
#endif
|
||||
|
||||
VF_FIELD_INLINE float VF_FieldSurfaceRelief(float x, float y, float z, VF_FieldU32 seed)
|
||||
{
|
||||
#if defined(VF_FIELD_HLSL)
|
||||
const float macro = VF_FieldValueNoise(x, y, z, 32, seed ^ 0xB5297A4Du);
|
||||
const float mid = VF_FieldValueNoise(x, y, z, 16, seed ^ 0x63D83595u);
|
||||
const float fine = VF_FieldValueNoise(x, y, z, 4, seed ^ 0xA511E9B3u);
|
||||
#else
|
||||
const float macro=VF_FieldCachedValueNoise(x,y,z,32,seed^0xB5297A4Du);
|
||||
const float mid=VF_FieldCachedValueNoise(x,y,z,16,seed^0x63D83595u);
|
||||
const float fine=VF_FieldCachedValueNoise(x,y,z,4,seed^0xA511E9B3u);
|
||||
#endif
|
||||
return 1.20f * macro + (0.80f + 0.25f * macro) * (0.40f * mid + 0.60f * fine);
|
||||
}
|
||||
|
||||
@@ -335,12 +395,21 @@ VF_FIELD_INLINE float VF_FieldPassageWallRelief(float x,float y,float z,VF_Field
|
||||
{
|
||||
// Use long, non-commensurate rotated scales. The shared passage wall keeps broad,
|
||||
// uneven rock relief without a short wavelength that can read as repeated rings.
|
||||
#if defined(VF_FIELD_HLSL)
|
||||
const float broad=VF_FieldValueNoise(x+17.0f,y-23.0f,z+11.0f,43,
|
||||
seed^0xB5297A4Du);
|
||||
const float mid=VF_FieldValueNoise(y-7.0f,z+19.0f,x-31.0f,29,
|
||||
seed^0x63D83595u);
|
||||
const float fine=VF_FieldValueNoise(z+5.0f,x+29.0f,y-13.0f,19,
|
||||
seed^0xA511E9B3u);
|
||||
#else
|
||||
const float broad=VF_FieldCachedValueNoise(x+17.0f,y-23.0f,z+11.0f,43,
|
||||
seed^0xB5297A4Du);
|
||||
const float mid=VF_FieldCachedValueNoise(y-7.0f,z+19.0f,x-31.0f,29,
|
||||
seed^0x63D83595u);
|
||||
const float fine=VF_FieldCachedValueNoise(z+5.0f,x+29.0f,y-13.0f,19,
|
||||
seed^0xA511E9B3u);
|
||||
#endif
|
||||
return 0.50f*broad+0.35f*mid+0.15f*fine;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user