standalone: whole view under one second -- 0.955 s cold @8 workers, full payload, world unchanged
- Eight-sample X batching of fine density rows, reuse of solved Z intervals in the candidate mask, AVX2 marching-cubes cell classification. LOD0/1 density-grid time -61/-62%; interval/sign stage 140 -> 97 ms; candidate-axis allocation -18 MiB. Shape stage ~413 ms, detail band ~219 ms. - W1/W8 hashes equal across 44 fields and equal to round 8; routes 5642/5642; look and far-survival audits pass; bad-shell control still fails; six fixed captures pixel-identical. Remote-view recapture pending (camera 500,0,0). Verified by me, interleaved (8 workers, cold, BelowNormal 0xFF00, 2,226 tiles, 110.9 MiB payload): 1.012 / 0.927 / 0.926 s vs round 8 1.100 / 1.099 / 1.088 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
16612acba4
commit
2656d81ac9
4 files changed
+667
-109
No files matched your search
@@ -22,6 +22,7 @@
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// Standalone-only world compiler. It compiles the runtime room/tunnel/passages into
|
||||
@@ -510,6 +511,55 @@ public:
|
||||
static_cast<float>(Z));
|
||||
}
|
||||
|
||||
void DensityAtLatticeX8(int32 XStart,int32 XStep,int32 Y,int32 Z,
|
||||
int32 Count,float OutDensity[8]) const
|
||||
{
|
||||
Count=std::clamp(Count,0,8);
|
||||
if(Count==0)return;
|
||||
bool bFast=EditBrushes.empty()&&Y>=Min[1]&&Y<=Max[1]
|
||||
&&Z>=Min[2]&&Z<=Max[2]
|
||||
&&XStart>=Min[0]
|
||||
&&XStart+static_cast<int64>(Count-1)*XStep<=Max[0];
|
||||
if(bFast)
|
||||
{
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
if(IsForcedFarAirSample(XStart+Lane*XStep,Y,Z))
|
||||
{bFast=false;break;}
|
||||
}
|
||||
if(!bFast)
|
||||
{
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t ColumnStart=static_cast<size_t>(Y-Min[1])*Size[0]
|
||||
+static_cast<size_t>(XStart-Min[0]);
|
||||
const size_t Zi=static_cast<size_t>(Z-Min[2]);
|
||||
const size_t WordOffset=Zi>>6;
|
||||
const uint64 Mask=uint64{1}<<(Zi&63u);
|
||||
const size_t NodeBase=Zi*static_cast<size_t>(Size[1])*Size[0]+ColumnStart;
|
||||
constexpr float ProxyBandEdge=DetailSupremum+0.125f;
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
{
|
||||
const size_t Column=ColumnStart+static_cast<size_t>(Lane*XStep);
|
||||
const size_t Word=Column*WordsPerZ+WordOffset;
|
||||
const bool bRock=(RockWords[Word]&Mask)!=0;
|
||||
float Density;
|
||||
if(CandidateWords[Word]&Mask)
|
||||
{
|
||||
const int8 Proxy=SurfaceDistanceQ[NodeBase
|
||||
+static_cast<size_t>(Lane*XStep)];
|
||||
Density=static_cast<float>(Proxy)*(1.0f/16.0f);
|
||||
if((Density<0.0f)!=bRock)
|
||||
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/16.0f);
|
||||
}
|
||||
else
|
||||
Density=bRock?-ProxyBandEdge:ProxyBandEdge;
|
||||
OutDensity[Lane]=Density;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z)
|
||||
{
|
||||
@@ -2378,17 +2428,44 @@ private:
|
||||
void BuildPackedRockIntervals(FParallelTiming& Timing)
|
||||
{
|
||||
const int32 NX=Size[0],NY=Size[1],NZ=Size[2];
|
||||
ParallelLines(static_cast<size_t>(NX)*NY,[&](size_t Line)
|
||||
CandidateAxisWords[2].assign(static_cast<size_t>(NX)*NY*WordsPerZ,0);
|
||||
struct FIntervalScratch
|
||||
{
|
||||
std::vector<FInterval> Carve,Solid,ProtectedAir;
|
||||
};
|
||||
std::array<FIntervalScratch,16> WorkerScratch;
|
||||
for(int32 Worker=0;Worker<WorkerCount;++Worker)
|
||||
{
|
||||
WorkerScratch[static_cast<size_t>(Worker)].Carve.reserve(24);
|
||||
WorkerScratch[static_cast<size_t>(Worker)].Solid.reserve(8);
|
||||
WorkerScratch[static_cast<size_t>(Worker)].ProtectedAir.reserve(4);
|
||||
}
|
||||
ParallelLines(static_cast<size_t>(NX)*NY,[&](size_t Line,int32 Worker)
|
||||
{
|
||||
const int32 X=Min[0]+static_cast<int32>(Line%NX),Y=Min[1]+static_cast<int32>(Line/NX);
|
||||
std::vector<FInterval> Carve,Solid,ProtectedAir;
|
||||
Carve.reserve(24);Solid.reserve(8);ProtectedAir.reserve(4);
|
||||
FIntervalScratch& Scratch=WorkerScratch[static_cast<size_t>(Worker)];
|
||||
std::vector<FInterval>& Carve=Scratch.Carve;
|
||||
std::vector<FInterval>& Solid=Scratch.Solid;
|
||||
std::vector<FInterval>& ProtectedAir=Scratch.ProtectedAir;
|
||||
Carve.clear();Solid.clear();ProtectedAir.clear();
|
||||
const int32 Fixed[3]={X,Y,0};
|
||||
for(uint32 Id:GetLineCandidates(2,X,Y))
|
||||
{
|
||||
const FPrimitive& P=Primitives[Id];FInterval R;
|
||||
if(SolveLine(P,2,Fixed,R))
|
||||
{
|
||||
if(!P.bCritical)
|
||||
{
|
||||
const size_t CandidateBase=Line*WordsPerZ;
|
||||
const int32 CandidateLo=std::max(Min[2],R.Lo-DetailBand);
|
||||
const int32 CandidateHi=std::min(Max[2],R.Lo+DetailBand);
|
||||
const int32 CandidateLo2=std::max(Min[2],R.Hi-DetailBand);
|
||||
const int32 CandidateHi2=std::min(Max[2],R.Hi+DetailBand);
|
||||
FillBits(CandidateAxisWords[2],CandidateBase,
|
||||
CandidateLo-Min[2],CandidateHi-Min[2],true);
|
||||
FillBits(CandidateAxisWords[2],CandidateBase,
|
||||
CandidateLo2-Min[2],CandidateHi2-Min[2],true);
|
||||
}
|
||||
R.Lo=std::max(R.Lo,Min[2])-Min[2];R.Hi=std::min(R.Hi,Max[2])-Min[2];
|
||||
if(R.Lo<=R.Hi)
|
||||
{
|
||||
@@ -2407,7 +2484,7 @@ private:
|
||||
for(const FInterval& R:Solid)FillBits(RockWords,Base,R.Lo,R.Hi,true);
|
||||
// Critical passage air is the final authored override, including at landing floors.
|
||||
for(const FInterval& R:ProtectedAir)FillBits(RockWords,Base,R.Lo,R.Hi,false);
|
||||
},&Timing);
|
||||
},&Timing,nullptr,16);
|
||||
}
|
||||
|
||||
void BuildCandidateBands(FParallelTiming& AxisTiming,FParallelTiming& MergeTiming,
|
||||
@@ -2416,6 +2493,7 @@ private:
|
||||
const int32 N[3]={Size[0],Size[1],Size[2]};
|
||||
for(int32 Axis=0;Axis<3;++Axis)
|
||||
{
|
||||
if(Axis==2)continue;
|
||||
const int32 A=(Axis+1)%3,B=(Axis+2)%3;
|
||||
const size_t Lines=static_cast<size_t>(Size[A])*Size[B];
|
||||
const size_t Words=(static_cast<size_t>(N[Axis])+63u)/64u;
|
||||
@@ -2437,10 +2515,10 @@ private:
|
||||
FillBits(CandidateAxisWords[Axis],Base,Lo-Min[Axis],Hi-Min[Axis],true);
|
||||
FillBits(CandidateAxisWords[Axis],Base,Lo2-Min[Axis],Hi2-Min[Axis],true);
|
||||
}
|
||||
},&AxisTiming);
|
||||
},&AxisTiming,nullptr,16);
|
||||
}
|
||||
const auto MergeStart=std::chrono::steady_clock::now();
|
||||
CandidateWords=CandidateAxisWords[2];
|
||||
CandidateWords=std::move(CandidateAxisWords[2]);
|
||||
for(int32 Axis=0;Axis<2;++Axis)
|
||||
{
|
||||
const int32 A=(Axis+1)%3,B=(Axis+2)%3;
|
||||
@@ -2465,7 +2543,7 @@ private:
|
||||
.fetch_or(uint64{1}<<(Zi&63u),std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
},&MergeTiming);
|
||||
},&MergeTiming,nullptr,16);
|
||||
}
|
||||
const int32 LayerLo=LayerRanges.empty()?1:LayerRanges[0].Lo;
|
||||
const int32 LayerHi=LayerRanges.empty()?0:LayerRanges[0].Hi;
|
||||
@@ -2500,6 +2578,18 @@ private:
|
||||
const int32 Y=Min[1]+static_cast<int32>(Line/Size[0]);
|
||||
FDetailNoiseLineContext NoiseLine(static_cast<float>(X),static_cast<float>(Y),
|
||||
static_cast<uint32>(Owner->Seed));
|
||||
const FLayerField* LineField=nullptr;
|
||||
bool bLineFieldKnown=false;
|
||||
int32 LineFieldLo=0,LineFieldHi=-1;
|
||||
const auto FindLineLayerField=[&](double Z)->const FLayerField*
|
||||
{
|
||||
if(bLineFieldKnown&&LineField&&Z>=LineFieldLo&&Z<=LineFieldHi)
|
||||
return LineField;
|
||||
LineField=FindLayerField(Z);
|
||||
bLineFieldKnown=LineField!=nullptr;
|
||||
if(LineField){LineFieldLo=LineField->Lo;LineFieldHi=LineField->Hi;}
|
||||
return LineField;
|
||||
};
|
||||
const int32 QueryBinX=FloorDiv(X,ActiveSpatialBinSize);
|
||||
const int32 QueryBinY=FloorDiv(Y,ActiveSpatialBinSize);
|
||||
int32 CachedQueryBinZ=std::numeric_limits<int32>::min();
|
||||
@@ -2511,80 +2601,114 @@ private:
|
||||
for(size_t W=0;W<WordsPerZ;++W)
|
||||
{
|
||||
uint64 Bits=CandidateWords[WordBase+W];
|
||||
const uint64 OriginalBits=Bits;
|
||||
uint64 ValidBits=0;
|
||||
int32 BatchZ[8]{};
|
||||
int32 BatchCount=0,BatchQueryBinZ=std::numeric_limits<int32>::min();
|
||||
const FBinItems* BatchBinItems=nullptr;
|
||||
const auto ProcessBatch=[&]()
|
||||
{
|
||||
if(BatchCount==0)return;
|
||||
FEffectiveRockSample Samples[8];
|
||||
int32 ProfileDistanceSamples=0;
|
||||
if(bProfile)
|
||||
{
|
||||
for(int32 Lane=0;Lane<BatchCount;++Lane)
|
||||
if((Profile.Candidates++&255u)==0)++ProfileDistanceSamples;
|
||||
}
|
||||
const auto DistanceStart=ProfileDistanceSamples>0
|
||||
?Clock::now():Clock::time_point{};
|
||||
EffectiveRockDistanceZ8(X,Y,BatchZ,BatchCount,BatchBinItems,Samples);
|
||||
if(ProfileDistanceSamples>0)
|
||||
{
|
||||
Profile.DistanceSampleNanoseconds+=static_cast<uint64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
Clock::now()-DistanceStart).count());
|
||||
Profile.DistanceSamples+=static_cast<uint64>(BatchCount);
|
||||
}
|
||||
for(int32 Lane=0;Lane<BatchCount;++Lane)
|
||||
{
|
||||
const int32 Z=BatchZ[Lane];
|
||||
const size_t Zi=static_cast<size_t>(Z-Min[2]);
|
||||
++LocalCandidates;
|
||||
const FEffectiveRockSample& Sample=Samples[Lane];
|
||||
const float SDF=Sample.Distance;
|
||||
if(std::abs(SDF)>DetailSupremum+0.125f)continue;
|
||||
const float SurfaceSDF=Sample.SurfaceSDF;
|
||||
const float CaveInfluence=Sample.CaveInfluence;
|
||||
const float FloorInfluence=Sample.FloorInfluence;
|
||||
const bool bCriticalAir=Sample.bCriticalAir;
|
||||
float DetailFade=DetailBandFade(SDF);
|
||||
float MacroValue=0.0f;
|
||||
bool bMacroKnown=false;
|
||||
const FLayerField* DetailField=nullptr;
|
||||
bool bDetailFieldKnown=false;
|
||||
if(DetailVariant==EDetailVariant::Coherent
|
||||
&&std::abs(SDF)>DetailBandInnerRadius)
|
||||
{
|
||||
MacroValue=NoiseLine.Macro.Sample(static_cast<float>(Z));
|
||||
bMacroKnown=true;
|
||||
DetailField=FindLineLayerField(Z);
|
||||
bDetailFieldKnown=true;
|
||||
DetailFade=DetailBandFade(SDF,LocalDetailBandEdge(MacroValue,
|
||||
SurfaceSDF,CaveInfluence,FloorInfluence,DetailField));
|
||||
}
|
||||
if(DetailFade<=0.0f&&bProfile)++Profile.LocalBandSkips;
|
||||
const bool bTimeDetail=DetailFade>0.0f&&bProfile
|
||||
&&((Profile.DetailEvaluations&63u)==0);
|
||||
const auto DetailStart=bTimeDetail?Clock::now():Clock::time_point{};
|
||||
float Displaced=SDF;
|
||||
if(DetailFade>0.0f)
|
||||
{
|
||||
if(bProfile)++Profile.DetailEvaluations;
|
||||
Displaced+=DetailNoiseOnLine(X,Y,Z,Sample.SurfaceSDF,
|
||||
CaveInfluence,FloorInfluence,NoiseLine,bTimeDetail?&Profile:nullptr,
|
||||
DetailField,bDetailFieldKnown,bMacroKnown,MacroValue)*DetailFade;
|
||||
}
|
||||
if(bTimeDetail)
|
||||
{
|
||||
Profile.DetailSampleNanoseconds+=static_cast<uint64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
Clock::now()-DetailStart).count());
|
||||
++Profile.DetailSamples;
|
||||
}
|
||||
if(bCriticalAir)Displaced=std::min(Displaced,-1.0f/16.0f);
|
||||
const bool bNewRock=Displaced>=0.0f;
|
||||
const bool bOldRock=((RockWords[WordBase+(Zi>>6)]>>(Zi&63u))&1u)!=0;
|
||||
if(bNewRock!=bOldRock)
|
||||
{
|
||||
RockWords[WordBase+(Zi>>6)]^=uint64{1}<<(Zi&63u);
|
||||
++LocalFlips;
|
||||
if(bProfile)++Profile.Flips;
|
||||
}
|
||||
const size_t Node=(Zi*static_cast<size_t>(Size[1])
|
||||
+static_cast<size_t>(Y-Min[1]))*Size[0]
|
||||
+static_cast<size_t>(X-Min[0]);
|
||||
StoreSurfaceDistance(Node,-Displaced);
|
||||
ValidBits|=uint64{1}<<(Zi&63u);
|
||||
++LocalProxies;
|
||||
}
|
||||
BatchCount=0;
|
||||
};
|
||||
while(Bits)
|
||||
{
|
||||
const uint32 Bit=std::countr_zero(Bits);Bits&=Bits-1;
|
||||
const size_t Zi=W*64+Bit;if(Zi>=static_cast<size_t>(Size[2]))continue;
|
||||
const int32 Z=Min[2]+static_cast<int32>(Zi);
|
||||
++LocalCandidates;
|
||||
const int32 QueryBinZ=FloorDiv(Z,ActiveSpatialBinSize);
|
||||
if(BatchCount>0&&QueryBinZ!=BatchQueryBinZ)ProcessBatch();
|
||||
if(QueryBinZ!=CachedQueryBinZ)
|
||||
{
|
||||
const auto Bin=SpatialBins.find({QueryBinX,QueryBinY,QueryBinZ});
|
||||
CachedQueryBinItems=Bin==SpatialBins.end()?nullptr:&Bin->second;
|
||||
CachedQueryBinZ=QueryBinZ;
|
||||
}
|
||||
const bool bTimeDistance=bProfile&&((Profile.Candidates++&255u)==0);
|
||||
const auto DistanceStart=bTimeDistance?Clock::now():Clock::time_point{};
|
||||
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
|
||||
bool bFloorSurface=false,bCriticalAir=false;float CaveInfluence=0.0f,FloorInfluence=0.0f;
|
||||
const float SDF=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF,
|
||||
&bFloorSurface,&CaveInfluence,&FloorInfluence,&bCriticalAir,
|
||||
CachedQueryBinItems,true);
|
||||
if(bTimeDistance)
|
||||
{
|
||||
Profile.DistanceSampleNanoseconds+=static_cast<uint64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now()-DistanceStart).count());
|
||||
++Profile.DistanceSamples;
|
||||
}
|
||||
if(std::abs(SDF)>DetailSupremum+0.125f)continue;
|
||||
float DetailFade=DetailBandFade(SDF);
|
||||
const FLayerField* DetailField=nullptr;
|
||||
bool bDetailFieldKnown=false;
|
||||
if(DetailVariant==EDetailVariant::Coherent
|
||||
&&std::abs(SDF)>DetailBandInnerRadius)
|
||||
{
|
||||
const float MacroValue=NoiseLine.Macro.Sample(static_cast<float>(Z));
|
||||
DetailField=FindLayerField(Z);
|
||||
bDetailFieldKnown=true;
|
||||
DetailFade=DetailBandFade(SDF,LocalDetailBandEdge(MacroValue,SurfaceSDF,
|
||||
CaveInfluence,FloorInfluence,DetailField));
|
||||
}
|
||||
if(DetailFade<=0.0f&&bProfile)++Profile.LocalBandSkips;
|
||||
const bool bTimeDetail=DetailFade>0.0f&&bProfile
|
||||
&&((Profile.DetailEvaluations&63u)==0);
|
||||
const auto DetailStart=bTimeDetail?Clock::now():Clock::time_point{};
|
||||
float Displaced=SDF;
|
||||
if(DetailFade>0.0f)
|
||||
{
|
||||
if(bProfile)++Profile.DetailEvaluations;
|
||||
Displaced+=DetailNoiseOnLine(X,Y,Z,SurfaceSDF,CaveInfluence,
|
||||
FloorInfluence,NoiseLine,bTimeDetail?&Profile:nullptr,
|
||||
DetailField,bDetailFieldKnown)*DetailFade;
|
||||
}
|
||||
if(bTimeDetail)
|
||||
{
|
||||
Profile.DetailSampleNanoseconds+=static_cast<uint64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now()-DetailStart).count());
|
||||
++Profile.DetailSamples;
|
||||
}
|
||||
if(bCriticalAir)Displaced=std::min(Displaced,-1.0f/16.0f);
|
||||
const bool bNewRock=Displaced>=0.0f;
|
||||
const bool bOldRock=((RockWords[WordBase+(Zi>>6)]>>(Zi&63u))&1u)!=0;
|
||||
if(bNewRock!=bOldRock)
|
||||
{
|
||||
RockWords[WordBase+(Zi>>6)]^=uint64{1}<<(Zi&63u);
|
||||
++LocalFlips;
|
||||
if(bProfile)++Profile.Flips;
|
||||
}
|
||||
const size_t Node=(Zi*static_cast<size_t>(Size[1])+static_cast<size_t>(Y-Min[1]))
|
||||
*Size[0]+static_cast<size_t>(X-Min[0]);
|
||||
StoreSurfaceDistance(Node,-Displaced);
|
||||
ValidBits|=uint64{1}<<Bit;
|
||||
++LocalProxies;
|
||||
if(BatchCount==0)BatchQueryBinZ=QueryBinZ,BatchBinItems=CachedQueryBinItems;
|
||||
BatchZ[BatchCount++]=Z;
|
||||
if(BatchCount==8)ProcessBatch();
|
||||
}
|
||||
CandidateWords[WordBase+W]=ValidBits;
|
||||
ProcessBatch();
|
||||
if(OriginalBits!=ValidBits)CandidateWords[WordBase+W]=ValidBits;
|
||||
}
|
||||
CandidateCount.fetch_add(LocalCandidates,std::memory_order_relaxed);
|
||||
ProxyCount.fetch_add(LocalProxies,std::memory_order_relaxed);
|
||||
@@ -2853,6 +2977,220 @@ private:
|
||||
return RockDistance;
|
||||
}
|
||||
|
||||
struct FEffectiveRockSample
|
||||
{
|
||||
float Distance=0.0f;
|
||||
float SurfaceSDF=0.0f;
|
||||
float CaveInfluence=0.0f;
|
||||
float FloorInfluence=0.0f;
|
||||
ESurfaceFamily Family=ESurfaceFamily::Strata;
|
||||
bool bFloorSurface=false;
|
||||
bool bCriticalAir=false;
|
||||
};
|
||||
|
||||
void ShapeDistanceZ8(const FPrimitive& P,int32 X,int32 Y,const int32 Z[8],int32 Count,
|
||||
float OutDistance[8]) const
|
||||
{
|
||||
const __m256d Zero=_mm256_setzero_pd();
|
||||
const __m256d One=_mm256_set1_pd(1.0);
|
||||
const __m256d SignMask=_mm256_set1_pd(-0.0);
|
||||
for(int32 Base=0;Base<Count;Base+=4)
|
||||
{
|
||||
double ZLane[4];
|
||||
const int32 Lanes=std::min(4,Count-Base);
|
||||
for(int32 Lane=0;Lane<4;++Lane)
|
||||
ZLane[Lane]=static_cast<double>(Z[Base+std::min(Lane,Lanes-1)]);
|
||||
const __m256d VZ=_mm256_setr_pd(ZLane[0],ZLane[1],ZLane[2],ZLane[3]);
|
||||
__m256d Distance;
|
||||
if(P.bCapsule)
|
||||
{
|
||||
const double VX=static_cast<double>(X)-P.CapsuleStartWorld[0];
|
||||
const double VY=static_cast<double>(Y)-P.CapsuleStartWorld[1];
|
||||
const __m256d VXv=_mm256_set1_pd(VX);
|
||||
const __m256d VYv=_mm256_set1_pd(VY);
|
||||
const __m256d VZv=_mm256_sub_pd(VZ,_mm256_set1_pd(P.CapsuleStartWorld[2]));
|
||||
__m256d VD=_mm256_mul_pd(VXv,_mm256_set1_pd(P.CapsuleAxisDelta[0]));
|
||||
VD=_mm256_add_pd(VD,_mm256_mul_pd(VYv,_mm256_set1_pd(P.CapsuleAxisDelta[1])));
|
||||
VD=_mm256_add_pd(VD,_mm256_mul_pd(VZv,_mm256_set1_pd(P.CapsuleAxisDelta[2])));
|
||||
const __m256d T=P.CapsuleAxisLengthSquared>1.0e-12
|
||||
?_mm256_max_pd(Zero,_mm256_min_pd(One,
|
||||
_mm256_div_pd(VD,_mm256_set1_pd(P.CapsuleAxisLengthSquared))))
|
||||
:Zero;
|
||||
const __m256d EX=_mm256_sub_pd(VXv,
|
||||
_mm256_mul_pd(T,_mm256_set1_pd(P.CapsuleAxisDelta[0])));
|
||||
const __m256d EY=_mm256_sub_pd(VYv,
|
||||
_mm256_mul_pd(T,_mm256_set1_pd(P.CapsuleAxisDelta[1])));
|
||||
const __m256d EZ=_mm256_sub_pd(VZv,
|
||||
_mm256_mul_pd(T,_mm256_set1_pd(P.CapsuleAxisDelta[2])));
|
||||
__m256d DistanceSquared=_mm256_mul_pd(EX,EX);
|
||||
DistanceSquared=_mm256_add_pd(DistanceSquared,_mm256_mul_pd(EY,EY));
|
||||
DistanceSquared=_mm256_add_pd(DistanceSquared,_mm256_mul_pd(EZ,EZ));
|
||||
const __m256d Root=_mm256_sqrt_pd(DistanceSquared);
|
||||
double TValues[4],RootValues[4];
|
||||
_mm256_storeu_pd(TValues,T);
|
||||
_mm256_storeu_pd(RootValues,Root);
|
||||
for(int32 Lane=0;Lane<Lanes;++Lane)
|
||||
OutDistance[Base+Lane]=static_cast<float>(RootValues[Lane]
|
||||
-CapsuleRadiusAtT(P,TValues[Lane],P.CapsuleAxisLength));
|
||||
continue;
|
||||
}
|
||||
if(P.bRoundedBox)
|
||||
{
|
||||
const double DX=std::abs(static_cast<double>(X)-P.CenterSub[0]/16.0)
|
||||
-P.RoundedHalfExtent[0];
|
||||
const double DY=std::abs(static_cast<double>(Y)-P.CenterSub[1]/16.0)
|
||||
-P.RoundedHalfExtent[1];
|
||||
const double DZCenter=P.CenterSub[2]/16.0;
|
||||
const __m256d DZv=_mm256_sub_pd(_mm256_andnot_pd(SignMask,
|
||||
_mm256_sub_pd(VZ,_mm256_set1_pd(DZCenter))),
|
||||
_mm256_set1_pd(P.RoundedHalfExtent[2]));
|
||||
const __m256d DXv=_mm256_set1_pd(DX),DYv=_mm256_set1_pd(DY);
|
||||
const __m256d Ox=_mm256_max_pd(DXv,Zero),Oy=_mm256_max_pd(DYv,Zero);
|
||||
const __m256d Oz=_mm256_max_pd(DZv,Zero);
|
||||
__m256d OutsideSquared=_mm256_mul_pd(Ox,Ox);
|
||||
OutsideSquared=_mm256_add_pd(OutsideSquared,_mm256_mul_pd(Oy,Oy));
|
||||
OutsideSquared=_mm256_add_pd(OutsideSquared,_mm256_mul_pd(Oz,Oz));
|
||||
const __m256d MaxXY=_mm256_max_pd(DXv,DYv);
|
||||
const __m256d MaxQ=_mm256_max_pd(MaxXY,DZv);
|
||||
const __m256d Inside=_mm256_min_pd(MaxQ,Zero);
|
||||
Distance=_mm256_sub_pd(_mm256_add_pd(_mm256_sqrt_pd(OutsideSquared),Inside),
|
||||
_mm256_set1_pd(P.Rounding));
|
||||
}
|
||||
else
|
||||
{
|
||||
const __m256d D[3]={
|
||||
_mm256_set1_pd(static_cast<double>(X)-P.CenterSub[0]/16.0),
|
||||
_mm256_set1_pd(static_cast<double>(Y)-P.CenterSub[1]/16.0),
|
||||
_mm256_sub_pd(VZ,_mm256_set1_pd(P.CenterSub[2]/16.0))};
|
||||
__m256d Q=_mm256_setzero_pd();
|
||||
for(int32 I=0;I<3;++I)for(int32 J=0;J<3;++J)
|
||||
{
|
||||
const double Coefficient=static_cast<double>(P.Q[I][J])/QScale;
|
||||
__m256d Term=_mm256_mul_pd(_mm256_set1_pd(Coefficient),D[I]);
|
||||
Term=_mm256_mul_pd(Term,D[J]);
|
||||
Term=_mm256_mul_pd(Term,_mm256_set1_pd(256.0));
|
||||
Q=_mm256_add_pd(Q,Term);
|
||||
}
|
||||
Distance=_mm256_mul_pd(_mm256_sub_pd(
|
||||
_mm256_sqrt_pd(_mm256_max_pd(Q,Zero)),_mm256_set1_pd(1.0)),
|
||||
_mm256_set1_pd(P.MinRadius));
|
||||
}
|
||||
double Values[4];
|
||||
_mm256_storeu_pd(Values,Distance);
|
||||
for(int32 Lane=0;Lane<Lanes;++Lane)
|
||||
OutDistance[Base+Lane]=static_cast<float>(Values[Lane]);
|
||||
}
|
||||
}
|
||||
|
||||
void EffectiveRockDistanceZ8(int32 X,int32 Y,const int32 Z[8],int32 Count,
|
||||
const FBinItems* BinItems,FEffectiveRockSample Out[8]) const
|
||||
{
|
||||
float Base[8],Cave[8],Overlay[8],CaveFloorInfluence[8];
|
||||
bool CriticalAir[8]{};
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
{
|
||||
Base[Lane]=StrataDistance(Z[Lane]);
|
||||
Cave[Lane]=100000.0f;
|
||||
Overlay[Lane]=-100000.0f;
|
||||
CaveFloorInfluence[Lane]=0.0f;
|
||||
}
|
||||
if(BinItems)
|
||||
{
|
||||
for(uint32 Id:*BinItems)
|
||||
{
|
||||
const FPrimitive& P=Primitives[Id];
|
||||
bool Eligible[8]{};
|
||||
bool AnyEligible=false;
|
||||
if(P.bCarve)
|
||||
{
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
{
|
||||
const int32 ZValue=Z[Lane];
|
||||
if(P.bCritical&&X>=P.Min[0]&&X<=P.Max[0]
|
||||
&&Y>=P.Min[1]&&Y<=P.Max[1]
|
||||
&&ZValue>=P.Min[2]&&ZValue<=P.Max[2]
|
||||
&&ShapeContains(P,X,Y,ZValue))
|
||||
CriticalAir[Lane]=true;
|
||||
if(X<P.Min[0]-DetailBand||X>P.Max[0]+DetailBand
|
||||
||Y<P.Min[1]-DetailBand||Y>P.Max[1]+DetailBand
|
||||
||ZValue<P.Min[2]-DetailBand||ZValue>P.Max[2]+DetailBand)
|
||||
continue;
|
||||
Eligible[Lane]=true;
|
||||
AnyEligible=true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
{
|
||||
const int32 ZValue=Z[Lane];
|
||||
Eligible[Lane]=X>=P.Min[0]&&X<=P.Max[0]
|
||||
&&Y>=P.Min[1]&&Y<=P.Max[1]
|
||||
&&ZValue>=P.Min[2]&&ZValue<=P.Max[2];
|
||||
AnyEligible=AnyEligible||Eligible[Lane];
|
||||
}
|
||||
}
|
||||
if(!AnyEligible)continue;
|
||||
|
||||
float Distances[8];
|
||||
ShapeDistanceZ8(P,X,Y,Z,Count,Distances);
|
||||
if(P.bCarve)
|
||||
{
|
||||
const double Floor=P.bHasFloorClip?FloorAt(P,X,Y):0.0;
|
||||
const float MetadataWidth=static_cast<float>(std::max(P.BlendRadius,6.0));
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
{
|
||||
if(!Eligible[Lane])continue;
|
||||
float Distance=Distances[Lane];
|
||||
float PrimitiveFloorInfluence=0.0f;
|
||||
if(P.bHasFloorClip)
|
||||
{
|
||||
const float FloorT=(static_cast<float>(std::abs(Z[Lane]-Floor))
|
||||
-static_cast<float>(P.FloorBlendRadius))
|
||||
/static_cast<float>(std::max(DetailBand*2,1));
|
||||
PrimitiveFloorInfluence=1.0f-SmoothUnit(FloorT);
|
||||
Distance=SmoothMaxDistance(Distance,static_cast<float>(Floor-Z[Lane]),
|
||||
static_cast<float>(P.FloorBlendRadius));
|
||||
}
|
||||
const float PrimitiveWeight=SmoothUnit(0.5f
|
||||
+(Cave[Lane]-Distance)/(2.0f*MetadataWidth));
|
||||
const float Combined=SmoothMinDistance(Cave[Lane],Distance,
|
||||
static_cast<float>(P.BlendRadius));
|
||||
CaveFloorInfluence[Lane]=std::lerp(CaveFloorInfluence[Lane],
|
||||
PrimitiveFloorInfluence,PrimitiveWeight);
|
||||
Cave[Lane]=Combined;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
if(Eligible[Lane])Overlay[Lane]=std::max(Overlay[Lane],-Distances[Lane]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
{
|
||||
const float CarveOrOverlay=std::max(Cave[Lane],Overlay[Lane]);
|
||||
const ESurfaceFamily Family=Base[Lane]<=CarveOrOverlay?ESurfaceFamily::Strata:
|
||||
(Cave[Lane]>=Overlay[Lane]?ESurfaceFamily::Cave:ESurfaceFamily::SolidOverlay);
|
||||
constexpr float FamilyBlendWidth=6.0f;
|
||||
const float CaveVsOverlay=SmoothUnit(0.5f
|
||||
+(Cave[Lane]-Overlay[Lane])/(2.0f*FamilyBlendWidth));
|
||||
const float CarveInfluence=SmoothUnit(0.5f
|
||||
+(Base[Lane]-CarveOrOverlay)/(2.0f*FamilyBlendWidth));
|
||||
const float CaveInfluence=CarveInfluence*CaveVsOverlay;
|
||||
const float FloorInfluence=CaveInfluence*CaveFloorInfluence[Lane];
|
||||
Out[Lane].Distance=SmoothMinDistance(Base[Lane],CarveOrOverlay,StrataCaveBlendRadius);
|
||||
Out[Lane].Family=Family;
|
||||
Out[Lane].SurfaceSDF=Family==ESurfaceFamily::Strata?Base[Lane]:
|
||||
(Family==ESurfaceFamily::Cave?Cave[Lane]:Overlay[Lane]);
|
||||
Out[Lane].CaveInfluence=CaveInfluence;
|
||||
Out[Lane].FloorInfluence=FloorInfluence;
|
||||
Out[Lane].bFloorSurface=FloorInfluence>=0.5f;
|
||||
Out[Lane].bCriticalAir=CriticalAir[Lane];
|
||||
}
|
||||
}
|
||||
|
||||
float CoarseRockDistance(double X,double Y,double Z,bool* OutCriticalAir) const
|
||||
{
|
||||
const float Base=StrataDistance(Z);
|
||||
@@ -3065,8 +3403,8 @@ private:
|
||||
{
|
||||
if(NewZ0==Z0+1)
|
||||
{
|
||||
Corners[0]=Corners[4];Corners[1]=Corners[5];
|
||||
Corners[2]=Corners[6];Corners[3]=Corners[7];
|
||||
Corners[0]=Corners[4];Corners[2]=Corners[6];
|
||||
Corners[1]=Corners[5];Corners[3]=Corners[7];
|
||||
SetPlane(NewZ0+1,true);
|
||||
}
|
||||
else
|
||||
@@ -3092,14 +3430,15 @@ private:
|
||||
Fine(X,Y,6,Seed^0xA511E9B3u)
|
||||
{}
|
||||
|
||||
float SurfaceReliefAt(float Z)
|
||||
float SurfaceReliefAt(float Z,bool bMacroKnown=false,float KnownMacro=0.0f)
|
||||
{
|
||||
const float MacroValue=Macro.Sample(Z);
|
||||
const float MacroValue=bMacroKnown?KnownMacro:Macro.Sample(Z);
|
||||
const float MidValue=Mid.Sample(Z);
|
||||
const float FineValue=Fine.Sample(Z);
|
||||
return 1.00f*MacroValue+(0.80f+0.25f*MacroValue)
|
||||
*(0.85f*MidValue+0.18f*FineValue);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
float DetailNoise(double X,double Y,double Z,float SurfaceSDF,
|
||||
@@ -3127,13 +3466,14 @@ private:
|
||||
FDetailNoiseLineContext& NoiseLine,
|
||||
FDetailWorkerProfile* Profile=nullptr,
|
||||
const FLayerField* KnownField=nullptr,
|
||||
bool bFieldKnown=false) const
|
||||
bool bFieldKnown=false,bool bMacroKnown=false,
|
||||
float KnownMacro=0.0f) const
|
||||
{
|
||||
if(DetailVariant!=EDetailVariant::Coherent)
|
||||
return DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,FloorInfluence);
|
||||
using Clock=std::chrono::steady_clock;
|
||||
const auto ReliefStart=Profile?Clock::now():Clock::time_point{};
|
||||
const float Relief=NoiseLine.SurfaceReliefAt(static_cast<float>(Z));
|
||||
const float Relief=NoiseLine.SurfaceReliefAt(static_cast<float>(Z),bMacroKnown,KnownMacro);
|
||||
if(Profile)
|
||||
Profile->ReliefSampleNanoseconds+=static_cast<uint64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now()-ReliefStart).count());
|
||||
@@ -3225,7 +3565,7 @@ private:
|
||||
}
|
||||
|
||||
template<class Fn> void ParallelLines(size_t Count,Fn&& Work,FParallelTiming* Timing=nullptr,
|
||||
std::array<double,16>* WorkerActiveSeconds=nullptr) const
|
||||
std::array<double,16>* WorkerActiveSeconds=nullptr,size_t WorkBatch=1) const
|
||||
{
|
||||
const auto WallStart=std::chrono::steady_clock::now();
|
||||
if(WorkerCount<=1||Count<512)
|
||||
@@ -3247,6 +3587,7 @@ private:
|
||||
}
|
||||
return;
|
||||
}
|
||||
WorkBatch=std::max<size_t>(WorkBatch,1);
|
||||
std::atomic<size_t> Next{0};std::atomic<uint64> WorkerNanoseconds{0};
|
||||
std::vector<std::thread> Threads;Threads.reserve(WorkerCount);
|
||||
for(int32 W=0;W<WorkerCount;++W)
|
||||
@@ -3255,9 +3596,14 @@ private:
|
||||
const auto WorkerStart=std::chrono::steady_clock::now();
|
||||
for(;;)
|
||||
{
|
||||
const size_t I=Next.fetch_add(1,std::memory_order_relaxed);if(I>=Count)break;
|
||||
if constexpr(std::is_invocable_v<Fn&,size_t,int32>)Work(I,W);
|
||||
else Work(I);
|
||||
const size_t First=Next.fetch_add(WorkBatch,std::memory_order_relaxed);
|
||||
if(First>=Count)break;
|
||||
const size_t Last=First+std::min(WorkBatch,Count-First);
|
||||
for(size_t I=First;I<Last;++I)
|
||||
{
|
||||
if constexpr(std::is_invocable_v<Fn&,size_t,int32>)Work(I,W);
|
||||
else Work(I);
|
||||
}
|
||||
}
|
||||
const uint64 WorkerNs=static_cast<uint64>(std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now()-WorkerStart).count());
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "VoxelMarchingCubesMesher.h"
|
||||
#include "MarchingCubesTables.h"
|
||||
#include "StandaloneShapeWorld.h"
|
||||
#include "VoxelDensityProfile.h"
|
||||
#if WITH_EDITOR
|
||||
#include "VoxelRedefinedDensityField.h"
|
||||
@@ -14,6 +15,7 @@
|
||||
#include "HAL/IConsoleManager.h"
|
||||
#include "Misc/CommandLine.h"
|
||||
#include <atomic>
|
||||
#include <immintrin.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -940,8 +942,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const int32 GZ = GzLo + BrickZ + Z;
|
||||
const int32 LocalIndex =
|
||||
((GZ + 1) * MDim + (GY + 1)) * MDim + (GX + 1);
|
||||
DensityGrid[LocalIndex] =
|
||||
BrickValues[(Z * BrickSize.Y + Y) * BrickSize.X + X];
|
||||
const float Density=BrickValues[(Z * BrickSize.Y + Y) * BrickSize.X + X];
|
||||
DensityGrid[LocalIndex]=Density;
|
||||
}
|
||||
if (OutDensitySampleCount != nullptr)
|
||||
{
|
||||
@@ -1022,6 +1024,9 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
// lower overhead when a tile spans only one room-graph region.
|
||||
const int64 TileExtentVoxels = static_cast<int64>(Step) * CellsPerAxis;
|
||||
const bool bUseVerticalCacheTraversal = TileExtentVoxels > CHUNK_SIZE * 16;
|
||||
const FStandaloneShapeWorld* ActiveShapeWorld=FStandaloneShapeWorldAccess::GetActive();
|
||||
const bool bUseShapeWorldXBatch=ActiveShapeWorld!=nullptr
|
||||
&&!bUseSharedDensityGrid&&!bCapturePreEditGrid;
|
||||
if (bUseVerticalCacheTraversal)
|
||||
{
|
||||
for (const std::vector<int32>& YGroup : YGroups)
|
||||
@@ -1058,9 +1063,54 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
|
||||
return FVoxelMeshData();
|
||||
}
|
||||
for (const int32 gx : XGroup)
|
||||
if(bUseShapeWorldXBatch)
|
||||
{
|
||||
SampleGridPoint(gx,gy,gz);
|
||||
for(size_t Offset=0;Offset<XGroup.size();)
|
||||
{
|
||||
const int32 BatchCount=static_cast<int32>(std::min<size_t>(
|
||||
8,XGroup.size()-Offset));
|
||||
const int32 GX0=XGroup[Offset];
|
||||
bool bContiguous=true;
|
||||
for(int32 Lane=1;Lane<BatchCount;++Lane)
|
||||
bContiguous&=XGroup[Offset+static_cast<size_t>(Lane)]==GX0+Lane;
|
||||
if(!bContiguous)
|
||||
{
|
||||
for(int32 Lane=0;Lane<BatchCount;++Lane)
|
||||
SampleGridPoint(XGroup[Offset+static_cast<size_t>(Lane)],gy,gz);
|
||||
Offset+=static_cast<size_t>(BatchCount);
|
||||
continue;
|
||||
}
|
||||
|
||||
float Densities[8]{};
|
||||
ActiveShapeWorld->DensityAtLatticeX8(
|
||||
OriginVoxels.X+GX0*Step,Step,
|
||||
OriginVoxels.Y+gy*Step,OriginVoxels.Z+gz*Step,
|
||||
BatchCount,Densities);
|
||||
for(int32 Lane=0;Lane<BatchCount;++Lane)
|
||||
{
|
||||
const int32 GX=GX0+Lane;
|
||||
const int32 LocalIndex=
|
||||
((gz+1)*MDim+(gy+1))*MDim+(GX+1);
|
||||
DensityGrid[LocalIndex]=Densities[Lane];
|
||||
}
|
||||
const int32 LastGX=GX0+BatchCount-1;
|
||||
const int32 ReachBlockX=FMath::Clamp(LastGX,0,CellsPerAxis-1)
|
||||
/VoxelGenLOD::TileReachBlockCells;
|
||||
const int32 ReachBlockY=FMath::Clamp(gy,0,CellsPerAxis-1)
|
||||
/VoxelGenLOD::TileReachBlockCells;
|
||||
const int32 ReachBlockZ=FMath::Clamp(gz,0,CellsPerAxis-1)
|
||||
/VoxelGenLOD::TileReachBlockCells;
|
||||
VoxelGenLOD::TileReachBlockIndex=
|
||||
((ReachBlockZ*ReachBlocksPerAxis)+ReachBlockY)
|
||||
*ReachBlocksPerAxis+ReachBlockX;
|
||||
if(OutDensitySampleCount)
|
||||
*OutDensitySampleCount+=BatchCount;
|
||||
Offset+=static_cast<size_t>(BatchCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(const int32 gx:XGroup)SampleGridPoint(gx,gy,gz);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1219,12 +1269,103 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const bool bProfileMesher = VoxelDensityProfile::IsCycleTimingEnabled();
|
||||
uint64 CellClassificationCalls = 0;
|
||||
uint64 SurfaceCellCalls = 0;
|
||||
uint8 CaseIndexRow[CHUNK_SIZE]{};
|
||||
uint8 SurfaceRow[CHUNK_SIZE]{};
|
||||
uint8 SampleClassificationRow[CHUNK_SIZE]{};
|
||||
for (int32 cz = CzLo; cz <= CzHi; cz++) // bande de strate : cf. CzLo/CzHi plus haut
|
||||
{
|
||||
if (ShouldAbortWork()) return FVoxelMeshData();
|
||||
for (int32 cy = 0; cy < CellsPerAxis; cy++)
|
||||
{
|
||||
if (ShouldAbortWork()) return FVoxelMeshData();
|
||||
FMemory::Memzero(SampleClassificationRow, sizeof(SampleClassificationRow));
|
||||
const uint64 ClassificationRowStart = bProfileMesher
|
||||
? FPlatformTime::Cycles64() : 0;
|
||||
int32 ClassificationRowCalls = 0;
|
||||
for (int32 GroupX = 0; GroupX < CellsPerAxis; GroupX += 8)
|
||||
{
|
||||
const int32 GroupCount = FMath::Min(8, CellsPerAxis - GroupX);
|
||||
if (bUseProofBlocks
|
||||
&& BlockSkip[((cz / 8) * BlocksPerAxis + (cy / 8)) * BlocksPerAxis
|
||||
+ (GroupX / 8)] != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GroupCount == 8)
|
||||
{
|
||||
const float* Row00 = DensityGrid.GetData()
|
||||
+ ((cz + 1) * MDim + (cy + 1)) * MDim + GroupX + 1;
|
||||
const float* Row01 = Row00 + MDim;
|
||||
const float* Row10 = Row00 + MDim * MDim;
|
||||
const float* Row11 = Row10 + MDim;
|
||||
const __m256 Iso = _mm256_set1_ps(IsoLevel);
|
||||
const int32 Masks[8] = {
|
||||
_mm256_movemask_ps(_mm256_cmp_ps(_mm256_loadu_ps(Row00), Iso, _CMP_GE_OQ)),
|
||||
_mm256_movemask_ps(_mm256_cmp_ps(_mm256_loadu_ps(Row00 + 1), Iso, _CMP_GE_OQ)),
|
||||
_mm256_movemask_ps(_mm256_cmp_ps(_mm256_loadu_ps(Row01 + 1), Iso, _CMP_GE_OQ)),
|
||||
_mm256_movemask_ps(_mm256_cmp_ps(_mm256_loadu_ps(Row01), Iso, _CMP_GE_OQ)),
|
||||
_mm256_movemask_ps(_mm256_cmp_ps(_mm256_loadu_ps(Row10), Iso, _CMP_GE_OQ)),
|
||||
_mm256_movemask_ps(_mm256_cmp_ps(_mm256_loadu_ps(Row10 + 1), Iso, _CMP_GE_OQ)),
|
||||
_mm256_movemask_ps(_mm256_cmp_ps(_mm256_loadu_ps(Row11 + 1), Iso, _CMP_GE_OQ)),
|
||||
_mm256_movemask_ps(_mm256_cmp_ps(_mm256_loadu_ps(Row11), Iso, _CMP_GE_OQ)),
|
||||
};
|
||||
for (int32 Lane = 0; Lane < 8; ++Lane)
|
||||
{
|
||||
const int32 CellX = GroupX + Lane;
|
||||
int32 CaseIndex = 0;
|
||||
for (int32 Corner = 0; Corner < 8; ++Corner)
|
||||
{
|
||||
CaseIndex |= ((Masks[Corner] >> Lane) & 1) << Corner;
|
||||
}
|
||||
CaseIndexRow[CellX] = static_cast<uint8>(CaseIndex);
|
||||
SurfaceRow[CellX] = MCEdgeTable[CaseIndex] != 0;
|
||||
SampleClassificationRow[CellX] = bProfileMesher
|
||||
&& VoxelDensityProfile::ShouldSample(
|
||||
VoxelDensityProfile::EBucket::MesherCellClassification);
|
||||
++ClassificationRowCalls;
|
||||
++CellClassificationCalls;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int32 Lane = 0; Lane < GroupCount; ++Lane)
|
||||
{
|
||||
const int32 CellX = GroupX + Lane;
|
||||
int32 CaseIndex = 0;
|
||||
for (int32 Corner = 0; Corner < 8; ++Corner)
|
||||
{
|
||||
const float Density = SampleG(
|
||||
CellX + CornerOffsets[Corner].X,
|
||||
cy + CornerOffsets[Corner].Y,
|
||||
cz + CornerOffsets[Corner].Z);
|
||||
if (Density >= IsoLevel) CaseIndex |= 1 << Corner;
|
||||
}
|
||||
CaseIndexRow[CellX] = static_cast<uint8>(CaseIndex);
|
||||
SurfaceRow[CellX] = MCEdgeTable[CaseIndex] != 0;
|
||||
SampleClassificationRow[CellX] = bProfileMesher
|
||||
&& VoxelDensityProfile::ShouldSample(
|
||||
VoxelDensityProfile::EBucket::MesherCellClassification);
|
||||
++ClassificationRowCalls;
|
||||
++CellClassificationCalls;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bProfileMesher && ClassificationRowCalls > 0)
|
||||
{
|
||||
const uint64 RowCycles = FPlatformTime::Cycles64() - ClassificationRowStart;
|
||||
const uint64 PerCellCycles = RowCycles
|
||||
/ static_cast<uint64>(ClassificationRowCalls);
|
||||
for (int32 cx = 0; cx < CellsPerAxis; ++cx)
|
||||
{
|
||||
if (SampleClassificationRow[cx])
|
||||
{
|
||||
VoxelDensityProfile::AddSampledMeasurement(
|
||||
VoxelDensityProfile::EBucket::MesherCellClassification,
|
||||
PerCellCycles);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int32 cx = 0; cx < CellsPerAxis; cx++)
|
||||
{
|
||||
if (bUseProofBlocks
|
||||
@@ -1232,37 +1373,19 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const bool bSampleCell = bProfileMesher
|
||||
&& VoxelDensityProfile::ShouldSample(
|
||||
VoxelDensityProfile::EBucket::MesherCellClassification);
|
||||
const uint64 ClassificationStart = bSampleCell
|
||||
? FPlatformTime::Cycles64() : 0;
|
||||
// PASSE 1 : densités aux 8 coins + index de cas MC SEULEMENT.
|
||||
// ~70% des cellules d'un chunk sont tout-roc ou tout-air (aucune surface) ;
|
||||
// on les rejette ICI, AVANT de payer les 8 positions + 8 gradients (48 lectures
|
||||
// grille + maths vectorielles). Sortie bit-identique : positions et gradients ne
|
||||
// servent qu'aux cellules réellement traversées par l'isosurface.
|
||||
const bool bSampleCell = SampleClassificationRow[cx] != 0;
|
||||
float Densities[8];
|
||||
int32 CaseIndex = 0;
|
||||
for (int32 i = 0; i < 8; i++)
|
||||
{
|
||||
const float D = SampleG(cx + CornerOffsets[i].X,
|
||||
cy + CornerOffsets[i].Y,
|
||||
cz + CornerOffsets[i].Z);
|
||||
Densities[i] = D;
|
||||
if (D >= IsoLevel) CaseIndex |= (1 << i);
|
||||
}
|
||||
|
||||
const bool bHasSurface = MCEdgeTable[CaseIndex] != 0;
|
||||
if (bSampleCell)
|
||||
{
|
||||
VoxelDensityProfile::AddSampledMeasurement(
|
||||
VoxelDensityProfile::EBucket::MesherCellClassification,
|
||||
FPlatformTime::Cycles64() - ClassificationStart);
|
||||
}
|
||||
++CellClassificationCalls;
|
||||
const int32 CaseIndex = CaseIndexRow[cx];
|
||||
const bool bHasSurface = SurfaceRow[cx] != 0;
|
||||
if (!bHasSurface) continue; // Pas de surface ici → skip
|
||||
++SurfaceCellCalls;
|
||||
// Density values are needed only after classification, for edge interpolation.
|
||||
for (int32 i = 0; i < 8; i++)
|
||||
{
|
||||
Densities[i] = SampleG(cx + CornerOffsets[i].X,
|
||||
cy + CornerOffsets[i].Y,
|
||||
cz + CornerOffsets[i].Z);
|
||||
}
|
||||
|
||||
// PASSE 2 : positions + gradients aux 8 coins (uniquement si surface présente).
|
||||
const uint64 GradientStart = bSampleCell
|
||||
|
||||
Reference in new issue
Block a user