wip(standalone): speed round -- interrupted by a PC hard reset at 10:16 (Kernel-Power 41)

Verified before the reset (density + full mesh hashes equal at 1/8/16 workers, routes 5642/5642,
look audits green):
- merged field lookups, parallel mask merge, compact inline spatial bins, line-local relief hash
  cache, lattice-specific mesher entry, fused sign+magnitude evaluation, far connectors as one
  capsule each (181,836 -> 16,491 far primitives).
- crater edit (sphere+box+capsule, one transaction across LOD0/1): 15-27 ms; player 3x3x3
  neighbourhood meshes ready ~18 ms after streaming starts.
NOT yet verified (in the tree, unhashed): standalone-only skips of UV/colour arrays and of the
ceiling/ground material grouping (best cold whole view 0.958 s @16 workers) and a far-bin reserve
tweak. Those skips drop data the plugin's materials may need: not a game-path win as-is.

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-04 12:18:48 +02:00
1 parent 97c0455939
commit ee5b05b357
16 files changed
+1011 -156

No files matched your search

+540 -69
View File
@@ -17,6 +17,7 @@
#include <limits>
#include <memory>
#include <mutex>
#include <immintrin.h>
#include <thread>
#include <unordered_map>
#include <vector>
@@ -28,6 +29,24 @@ class FStandaloneShapeWorld
{
public:
enum class EDetailVariant : uint8 { Coherent, Smooth, PerVoxel };
struct FEditBrush
{
enum class EShape : uint8 { Sphere, Capsule, RoundedBox };
enum class EOperation : uint8 { Carve, AddRock };
EShape Shape=EShape::Sphere;
EOperation Operation=EOperation::Carve;
std::array<double,3> A{};
std::array<double,3> B{};
std::array<double,3> HalfExtent{};
double Radius=0.0;
double Rounding=0.0;
};
struct FParallelTiming
{
double WallSeconds = 0.0;
double WorkerSeconds = 0.0;
};
struct FBuildStats
{
@@ -55,6 +74,15 @@ public:
double MaxUnmarkedFloorDirectionSlopeDelta = 0.0;
double MaxMarkedFloorSlopeDelta = 0.0;
double MaxFloorCurveDeviation = 0.0;
double LocalPrimitiveSeconds = 0.0;
double LocalLineBinSeconds = 0.0;
double CandidateMergeSeconds = 0.0;
double FarPrimitiveSeconds = 0.0;
double FarBinSeconds = 0.0;
FParallelTiming RockIntervalLines;
FParallelTiming CandidateAxisLines;
FParallelTiming CandidateMergeLines;
FParallelTiming DetailBandLines;
uint64 CurvedFloorSegments = 0;
uint64 MarkedFloorSlopeJoins = 0;
double LegacySegmentRadiusStep = 0.0;
@@ -70,6 +98,10 @@ public:
int32 CenterSub[3]{};
int32 CapsuleStartSub[3]{};
int32 CapsuleEndSub[3]{};
double CapsuleStartWorld[3]{};
double CapsuleAxisDelta[3]{};
double CapsuleAxisLengthSquared = 0.0;
double CapsuleAxisLength = 0.0;
int64 Q[3][3]{};
int32 Min[3]{};
int32 Max[3]{};
@@ -123,6 +155,81 @@ public:
return static_cast<size_t>(H);
}
};
struct FBinItems
{
static constexpr size_t InlineCapacity=4;
struct FHeapStorage {uint32* Data=nullptr;uint32 Capacity=0;uint32 Reserved=0;};
union FStorage
{
std::array<uint32,InlineCapacity> Inline;
FHeapStorage Heap;
FStorage():Inline{}{}
~FStorage(){}
} Storage;
uint32 Count=0;
uint32 bHeap=0;
FBinItems()=default;
FBinItems(const FBinItems&)=delete;
FBinItems& operator=(const FBinItems&)=delete;
FBinItems(FBinItems&& Other) noexcept {MoveFrom(Other);}
FBinItems& operator=(FBinItems&& Other) noexcept
{
if(this!=&Other){Release();MoveFrom(Other);}
return *this;
}
~FBinItems(){Release();}
void push_back(uint32 Id)
{
if(!bHeap&&Count<InlineCapacity)
{
Storage.Inline[Count++]=Id;
return;
}
if(!bHeap)
{
const std::array<uint32,InlineCapacity> Previous=Storage.Inline;
Storage.Heap={new uint32[8],8,0};
std::copy(Previous.begin(),Previous.end(),Storage.Heap.Data);
bHeap=1;
}
else if(Count==Storage.Heap.Capacity)
{
const uint32 NewCapacity=Storage.Heap.Capacity*2;
uint32* NewData=new uint32[NewCapacity];
std::copy_n(Storage.Heap.Data,Count,NewData);
delete[] Storage.Heap.Data;
Storage.Heap={NewData,NewCapacity,0};
}
Storage.Heap.Data[Count++]=Id;
}
size_t size() const {return Count;}
size_t capacity() const {return bHeap?Storage.Heap.Capacity:InlineCapacity;}
const uint32* begin() const {return bHeap?Storage.Heap.Data:Storage.Inline.data();}
const uint32* end() const {return begin()+Count;}
private:
void Release()
{
if(bHeap)delete[] Storage.Heap.Data;
bHeap=0;Count=0;
}
void MoveFrom(FBinItems& Other)
{
Count=Other.Count;bHeap=Other.bHeap;
if(bHeap)
{
Storage.Heap=Other.Storage.Heap;
Other.bHeap=0;Other.Count=0;
}
else
{
Storage.Inline=Other.Storage.Inline;
Other.Count=0;
}
}
};
static constexpr int32 QScale = 1 << 28;
static constexpr int32 SubPerVoxel = 16;
@@ -161,31 +268,62 @@ public:
auto Stage=Clock::now();
BuildLayerBounds();
auto Substage=Clock::now();
BuildOwnerPrimitives();
Stats.LocalPrimitiveSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
Substage=Clock::now();
if(FStandaloneResearchTunnels::Enabled.load(std::memory_order_acquire))
BuildRoundTunnelPrimitives();
BuildSpatialBins(LineBinSize);
Stats.LocalLineBinSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
Stats.ShapeSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
Stats.PrimitiveCount=Primitives.size();
if(Primitives.empty())
{Error="owner shape extraction produced no rooms, tunnels, or passages";return false;}
Stage=Clock::now();
const size_t NZ=static_cast<size_t>(Size[2]);
WordsPerZ=(NZ+63u)/64u;
RockWords.assign(static_cast<size_t>(Size[0])*Size[1]*WordsPerZ,0);
SurfaceDistanceQ=std::make_unique_for_overwrite<int8[]>(static_cast<size_t>(NodeCount));
BuildPackedRockIntervals();
BuildCandidateBands();
BuildPackedRockIntervals(Stats.RockIntervalLines);
BuildCandidateBands(Stats.CandidateAxisLines,Stats.CandidateMergeLines,
Stats.CandidateMergeSeconds);
Stats.IntervalSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
Stage=Clock::now();
ApplyDetailBand();
ApplyDetailBand(Stats.DetailBandLines);
Stats.SurfaceSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
ValidatePassageRoutes();
Built=true;
if(bIncludeFarWorld)BuildFarWorld();
const uint64 WordBytes=static_cast<uint64>(RockWords.size()+CandidateWords.size())*sizeof(uint64);
uint64 CandidateAxisBytes=0;
for(const auto& AxisWords:CandidateAxisWords)
CandidateAxisBytes+=static_cast<uint64>(AxisWords.capacity())*sizeof(uint64);
uint64 BinReferences=0,BinCapacity=0,InlineBins=0,OverflowBins=0;
for(const auto& Bin:SpatialBins)
{
BinReferences+=Bin.second.size();BinCapacity+=Bin.second.capacity();
if(Bin.second.size()<=FBinItems::InlineCapacity)++InlineBins;
else ++OverflowBins;
}
std::printf("[ShapeProfile] local_primitives=%.3fms line_bins=%.3fms rock_lines_wall=%.3fms rock_lines_worker=%.3fms candidate_axes_wall=%.3fms candidate_axes_worker=%.3fms candidate_merge_wall=%.3fms candidate_merge_worker=%.3fms candidate_merge_serial=%.3fms detail_lines_wall=%.3fms detail_lines_worker=%.3fms\n",
Stats.LocalPrimitiveSeconds*1000.0,Stats.LocalLineBinSeconds*1000.0,
Stats.RockIntervalLines.WallSeconds*1000.0,Stats.RockIntervalLines.WorkerSeconds*1000.0,
Stats.CandidateAxisLines.WallSeconds*1000.0,Stats.CandidateAxisLines.WorkerSeconds*1000.0,
Stats.CandidateMergeLines.WallSeconds*1000.0,Stats.CandidateMergeLines.WorkerSeconds*1000.0,
Stats.CandidateMergeSeconds*1000.0,Stats.DetailBandLines.WallSeconds*1000.0,
Stats.DetailBandLines.WorkerSeconds*1000.0);
std::printf("[ShapeAllocation] words=%.2fMiB candidate_axis_words=%.2fMiB proxy=%.2fMiB primitives=%.2fMiB spatial_bins=%llu spatial_bin_refs=%llu spatial_bin_capacity=%llu inline_bins=%llu overflow_bins=%llu far_primitive_build=%.3fms far_bin_rebuild=%.3fms\n",
static_cast<double>(WordBytes)/1048576.0,static_cast<double>(CandidateAxisBytes)/1048576.0,
static_cast<double>(Stats.SignNodes*sizeof(int8))/1048576.0,
static_cast<double>(Primitives.capacity()*sizeof(FPrimitive))/1048576.0,
static_cast<unsigned long long>(SpatialBins.size()),
static_cast<unsigned long long>(BinReferences),
static_cast<unsigned long long>(BinCapacity),
static_cast<unsigned long long>(InlineBins),
static_cast<unsigned long long>(OverflowBins),Stats.FarPrimitiveSeconds*1000.0,
Stats.FarBinSeconds*1000.0);
std::printf("[ShapeCompile] primitives=%llu rooms=%llu tunnels=%llu passages=%llu nodes=%llu packed=%.2f MiB surface_proxy_nodes=%llu proxy_address_capacity=%.2f MiB workers=%d\n",
static_cast<unsigned long long>(Stats.PrimitiveCount),
static_cast<unsigned long long>(Stats.RoomCount),
@@ -243,15 +381,51 @@ public:
bool DebugCriticalAirAt(double X,double Y,double Z) const { return IsInsideCriticalAir(X,Y,Z); }
bool DebugSolidOverlayAt(double X,double Y,double Z) const { return IsInsideSolidOverlay(X,Y,Z); }
void SetEditTransaction(std::vector<FEditBrush> Brushes)
{
EditBrushes=std::move(Brushes);
EditBoundsMin={MAX_int32,MAX_int32,MAX_int32};
EditBoundsMax={MIN_int32,MIN_int32,MIN_int32};
for(const FEditBrush& Brush:EditBrushes)
{
double Extent[3]{};
if(Brush.Shape==FEditBrush::EShape::Sphere)
for(int32 Axis=0;Axis<3;++Axis)Extent[Axis]=Brush.Radius;
else if(Brush.Shape==FEditBrush::EShape::Capsule)
for(int32 Axis=0;Axis<3;++Axis)Extent[Axis]=Brush.Radius;
else
for(int32 Axis=0;Axis<3;++Axis)Extent[Axis]=Brush.HalfExtent[Axis]+Brush.Rounding;
for(int32 Axis=0;Axis<3;++Axis)
{
const double Lo=Brush.Shape==FEditBrush::EShape::Capsule
?std::min(Brush.A[Axis],Brush.B[Axis])-Extent[Axis]
:Brush.A[Axis]-Extent[Axis];
const double Hi=Brush.Shape==FEditBrush::EShape::Capsule
?std::max(Brush.A[Axis],Brush.B[Axis])+Extent[Axis]
:Brush.A[Axis]+Extent[Axis];
(&EditBoundsMin.X)[Axis]=std::min((&EditBoundsMin.X)[Axis],static_cast<int32>(std::floor(Lo)));
(&EditBoundsMax.X)[Axis]=std::max((&EditBoundsMax.X)[Axis],static_cast<int32>(std::ceil(Hi)));
}
}
}
bool HasEditTransaction() const { return !EditBrushes.empty(); }
FIntVector GetEditBoundsMin() const { return EditBoundsMin; }
FIntVector GetEditBoundsMax() const { return EditBoundsMax; }
void BuildFarWorld()
{
if(FarBuilt)return;
const auto Start=std::chrono::steady_clock::now();
auto Stage=std::chrono::steady_clock::now();
BuildFarParentShapes();
BuildSpatialBins(SpatialBinSize);
Stats.FarShapeSeconds=std::chrono::duration<double>(std::chrono::steady_clock::now()-Start).count();
Stats.FarPrimitiveSeconds=std::chrono::duration<double>(std::chrono::steady_clock::now()-Stage).count();
Stage=std::chrono::steady_clock::now();
BuildSpatialBins(SpatialBinSize,false);
Stats.FarBinSeconds=std::chrono::duration<double>(std::chrono::steady_clock::now()-Stage).count();
Stats.FarShapeSeconds=Stats.FarPrimitiveSeconds+Stats.FarBinSeconds;
Stats.PrimitiveCount=Primitives.size();FarBuilt=true;
std::printf("[FarShapeStage] %.3f ms primitives=%llu\n",Stats.FarShapeSeconds*1000.0,
std::printf("[FarShapeStage] parent_shapes=%.3f ms spatial_bins=%.3f ms total=%.3f ms primitives=%llu\n",
Stats.FarPrimitiveSeconds*1000.0,Stats.FarBinSeconds*1000.0,Stats.FarShapeSeconds*1000.0,
static_cast<unsigned long long>(Stats.PrimitiveCount));
std::fflush(stdout);
}
@@ -263,51 +437,85 @@ public:
int32 IX=0,IY=0,IZ=0;
const bool bInteger=NearInteger(X,IX)&&NearInteger(Y,IY)&&NearInteger(Z,IZ);
const bool bPacked=bInteger&&Contains(IX,IY,IZ);
const bool bRock=bPacked?IsRock(IX,IY,IZ):EvaluateFast(X,Y,Z);
if(bPacked)
return PackedDensityAt(IX,IY,IZ,IsRock(IX,IY,IZ),X,Y,Z);
return AnalyticDensityAt(X,Y,Z);
}
float DensityAtLattice(int32 X,int32 Y,int32 Z) const
{
const bool bPacked=Contains(X,Y,Z);
if(bPacked)
return PackedDensityAt(X,Y,Z,IsRock(X,Y,Z),static_cast<float>(X),
static_cast<float>(Y),static_cast<float>(Z));
return AnalyticDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z));
}
private:
float PackedDensityAt(int32 X,int32 Y,int32 Z,bool bRock,float FX,float FY,float FZ) const
{
const size_t Column=static_cast<size_t>(Y-Min[1])*Size[0]+static_cast<size_t>(X-Min[0]);
const size_t Zi=static_cast<size_t>(Z-Min[2]);
const size_t Word=Column*WordsPerZ+(Zi>>6);
const uint64 Mask=uint64{1}<<(Zi&63u);
if(CandidateWords[Word]&Mask)
{
const size_t Column=static_cast<size_t>(IY-Min[1])*Size[0]+static_cast<size_t>(IX-Min[0]);
const size_t Zi=static_cast<size_t>(IZ-Min[2]);
const size_t Word=Column*WordsPerZ+(Zi>>6);
const uint64 Mask=uint64{1}<<(Zi&63u);
if(CandidateWords[Word]&Mask)
{
const size_t Node=Zi*static_cast<size_t>(Size[1])*Size[0]+Column;
const int8 Proxy=SurfaceDistanceQ[Node];
float 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);
return Density;
}
// Match the fixed fallback to the edge of the stored proxy band. The
// displacement is tapered to zero there, so this avoids a magnitude
// jump along the candidate bit-mask boundary.
constexpr float ProxyBandEdge=DetailSupremum+0.125f;
return bRock?-ProxyBandEdge:ProxyBandEdge;
const size_t Node=Zi*static_cast<size_t>(Size[1])*Size[0]+Column;
const int8 Proxy=SurfaceDistanceQ[Node];
float 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);
return ApplyEditBrushes(Density,FX,FY,FZ);
}
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
bool bFloorSurface=false;float CaveInfluence=0.0f,FloorInfluence=0.0f;
float RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF,
&bFloorSurface,&CaveInfluence,&FloorInfluence);
// Match the fixed fallback to the edge of the stored proxy band. The
// displacement is tapered to zero there, so this avoids a magnitude
// jump along the candidate bit-mask boundary.
constexpr float ProxyBandEdge=DetailSupremum+0.125f;
return ApplyEditBrushes(bRock?-ProxyBandEdge:ProxyBandEdge,FX,FY,FZ);
}
float AnalyticDensityAt(float X,float Y,float Z) const
{
const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]
&&Z>=Min[2]&&Z<=Max[2];
const bool bCriticalAir=IsInsideCriticalAir(X,Y,Z);
if(!bInsideFine)
{
bool bCriticalAir=false;
float RockDistance=CoarseRockDistance(X,Y,Z,&bCriticalAir);
if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f);
const bool bRock=RockDistance>=0.0f;
float Density=-RockDistance;
if((Density<0.0f)!=bRock||std::abs(Density)<0.05f)
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),0.05f);
return ApplyEditBrushes(Density,X,Y,Z);
}
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
bool bFloorSurface=false,bCriticalAir=false;float CaveInfluence=0.0f,FloorInfluence=0.0f;
float RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF,
&bFloorSurface,&CaveInfluence,&FloorInfluence,&bCriticalAir);
if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth
&&std::abs(RockDistance)<=DetailSupremum+0.125f)
RockDistance+=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,FloorInfluence)
*DetailBandFade(RockDistance);
if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f);
const bool bRock=RockDistance>=0.0f;
float Density=-RockDistance;
if((Density<0.0f)!=bRock||std::abs(Density)<0.05f)
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),0.05f);
return Density;
return ApplyEditBrushes(Density,X,Y,Z);
}
public:
EVoxelTileClass ClassifyBox(const FIntVector& Origin,int32 Step,int32 Cells) const
{
const int32 Pad=std::max(Step,1);
int32 BMin[3]={Origin.X-Pad,Origin.Y-Pad,Origin.Z-Pad};
int32 BMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step,Origin.Z+(Cells+1)*Step};
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
&&EditBoundsMin.Z<=BMax[2]&&EditBoundsMax.Z>=BMin[2])
return EVoxelTileClass::Mixed;
for(int32 BZ=FloorDiv(BMin[2],ActiveSpatialBinSize);BZ<=FloorDiv(BMax[2],ActiveSpatialBinSize);++BZ)
for(int32 BY=FloorDiv(BMin[1],ActiveSpatialBinSize);BY<=FloorDiv(BMax[1],ActiveSpatialBinSize);++BY)
for(int32 BX=FloorDiv(BMin[0],ActiveSpatialBinSize);BX<=FloorDiv(BMax[0],ActiveSpatialBinSize);++BX)
@@ -655,6 +863,50 @@ public:
}
private:
float ApplyEditBrushes(float Density,double X,double Y,double Z) const
{
if(EditBrushes.empty()||X<EditBoundsMin.X||X>EditBoundsMax.X
||Y<EditBoundsMin.Y||Y>EditBoundsMax.Y||Z<EditBoundsMin.Z||Z>EditBoundsMax.Z)
return Density;
const double P[3]={X,Y,Z};
for(const FEditBrush& Brush:EditBrushes)
{
double Distance=0.0;
if(Brush.Shape==FEditBrush::EShape::Sphere)
{
const double DX=X-Brush.A[0],DY=Y-Brush.A[1],DZ=Z-Brush.A[2];
Distance=DeterministicSqrt(DX*DX+DY*DY+DZ*DZ)-Brush.Radius;
}
else if(Brush.Shape==FEditBrush::EShape::Capsule)
{
double AB[3]{},AP[3]{},LengthSq=0.0,Dot=0.0;
for(int32 Axis=0;Axis<3;++Axis)
{AB[Axis]=Brush.B[Axis]-Brush.A[Axis];AP[Axis]=P[Axis]-Brush.A[Axis];LengthSq+=AB[Axis]*AB[Axis];Dot+=AB[Axis]*AP[Axis];}
const double T=LengthSq>1.0e-12?std::clamp(Dot/LengthSq,0.0,1.0):0.0;
double D2=0.0;
for(int32 Axis=0;Axis<3;++Axis)
{const double D=AP[Axis]-T*AB[Axis];D2+=D*D;}
Distance=DeterministicSqrt(D2)-Brush.Radius;
}
else
{
double OutsideSq=0.0,MaxQ=-std::numeric_limits<double>::infinity();
for(int32 Axis=0;Axis<3;++Axis)
{
const double Q=std::abs(P[Axis]-Brush.A[Axis])-Brush.HalfExtent[Axis];
const double Outside=std::max(Q,0.0);
OutsideSq+=Outside*Outside;MaxQ=std::max(MaxQ,Q);
}
Distance=DeterministicSqrt(OutsideSq)+std::min(MaxQ,0.0)-Brush.Rounding;
}
if(Brush.Operation==FEditBrush::EOperation::Carve)
Density=std::max(Density,static_cast<float>(-Distance));
else
Density=std::min(Density,static_cast<float>(Distance));
}
return Density;
}
struct FAxisBins { int32 MinA=0,MinB=0,CountA=0,CountB=0; std::vector<std::vector<uint32>> Items; };
struct FLayerField
{
@@ -671,6 +923,12 @@ private:
Out=static_cast<int32>(R);return true;
}
static double DeterministicSqrt(double Value)
{
if(!(Value>0.0))return 0.0;
return _mm_cvtsd_f64(_mm_sqrt_sd(_mm_setzero_pd(),_mm_set_sd(Value)));
}
bool Contains(int32 X,int32 Y,int32 Z) const
{return X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]&&Z>=Min[2]&&Z<=Max[2];}
@@ -883,14 +1141,21 @@ private:
P.CapsuleStartRadius=ExtendedRadiusA;P.CapsuleEndRadius=ExtendedRadiusB;
P.MinRadius=std::min(ExtendedRadiusA,ExtendedRadiusB);P.BlendRadius=ActiveBlendRadius;
const double R=std::max(ExtendedRadiusA,ExtendedRadiusB);
double AxisLengthSquared=0.0;
for(int32 Axis=0;Axis<3;++Axis)
{
P.CapsuleStartSub[Axis]=static_cast<int32>(std::llround(Start[Axis]*SubPerVoxel));
P.CapsuleEndSub[Axis]=static_cast<int32>(std::llround(End[Axis]*SubPerVoxel));
P.CapsuleStartWorld[Axis]=P.CapsuleStartSub[Axis]/static_cast<double>(SubPerVoxel);
const double CapsuleEndWorld=P.CapsuleEndSub[Axis]/static_cast<double>(SubPerVoxel);
P.CapsuleAxisDelta[Axis]=CapsuleEndWorld-P.CapsuleStartWorld[Axis];
AxisLengthSquared+=P.CapsuleAxisDelta[Axis]*P.CapsuleAxisDelta[Axis];
P.CenterSub[Axis]=static_cast<int32>(std::llround((Start[Axis]+End[Axis])*0.5*SubPerVoxel));
P.Min[Axis]=static_cast<int32>(std::floor(std::min(Start[Axis],End[Axis])-R))-1;
P.Max[Axis]=static_cast<int32>(std::ceil(std::max(Start[Axis],End[Axis])+R))+1;
}
P.CapsuleAxisLengthSquared=AxisLengthSquared;
P.CapsuleAxisLength=std::sqrt(std::max(0.0,AxisLengthSquared));
if(bFloorClip)
{
const double FloorSlope=(FloorB-FloorA)/Length;
@@ -1584,12 +1849,20 @@ private:
ActiveBlendRadius=PreviousBlendRadius;
}
void BuildSpatialBins(int32 BinResolution)
void BuildSpatialBins(int32 BinResolution,bool bBuildAxisBins=true)
{
SpatialBins.clear();
CriticalAirBins.clear();
ActiveSpatialBinSize=BinResolution;
for(int32 Axis=0;Axis<3;++Axis)
// Primitive bounds overlap multiple cells. Reserve from the measured bins-per-primitive
// density for each grid instead of repeatedly rehashing from a 2x primitive guess.
const size_t BinsPerPrimitive=bBuildAxisBins?96u:24u;
SpatialBins.reserve(std::max<size_t>(Primitives.size()*BinsPerPrimitive,1u));
if(!bBuildAxisBins)
{
CriticalAirBins.reserve(Primitives.size()/16+1);
}
if(bBuildAxisBins)for(int32 Axis=0;Axis<3;++Axis)
{
FAxisBins& B=AxisBins[Axis];
const int32 A=(Axis+1)%3,C=(Axis+2)%3;
@@ -1681,7 +1954,8 @@ private:
const int32 MinY=static_cast<int32>(std::floor(std::min(A.Y,B.Y)-64.0));
const int32 MaxY=static_cast<int32>(std::ceil(std::max(A.Y,B.Y)+64.0));
if(MaxX>=Min[0]&&MinX<=Max[0]&&MaxY>=Min[1]&&MinY<=Max[1])continue;
AddCapsule(A,B,64.0f,true);
AddTaperedCapsule(A,B,64.0f,64.0f,true);
Primitives.back().BlendRadius=0.0;
}
}
if(!Parents.empty())
@@ -1787,16 +2061,16 @@ private:
double V[3],D[3],D2=0.0,VD=0.0;
for(int32 A=0;A<3;++A)
{
const double Start=P.CapsuleStartSub[A]/static_cast<double>(SubPerVoxel);
const double End=P.CapsuleEndSub[A]/static_cast<double>(SubPerVoxel);
V[A]=Position[A]-Start;D[A]=End-Start;D2+=D[A]*D[A];VD+=V[A]*D[A];
V[A]=Position[A]-P.CapsuleStartWorld[A];
D[A]=P.CapsuleAxisDelta[A];
VD+=V[A]*D[A];
}
const double AxisLength=std::sqrt(std::max(0.0,D2));
D2=P.CapsuleAxisLengthSquared;
const double T=D2>1.0e-12?std::clamp(VD/D2,0.0,1.0):0.0;
double DistanceSquared=0.0;
for(int32 A=0;A<3;++A)
{const double E=V[A]-T*D[A];DistanceSquared+=E*E;}
const double Radius=CapsuleRadiusAtT(P,T,AxisLength);
const double Radius=CapsuleRadiusAtT(P,T,P.CapsuleAxisLength);
return static_cast<float>(std::sqrt(DistanceSquared)-Radius);
}
if(P.bRoundedBox)
@@ -1949,7 +2223,7 @@ private:
}
}
void BuildPackedRockIntervals()
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)
@@ -1981,10 +2255,11 @@ 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);
}
void BuildCandidateBands()
void BuildCandidateBands(FParallelTiming& AxisTiming,FParallelTiming& MergeTiming,
double& MergeSeconds)
{
const int32 N[3]={Size[0],Size[1],Size[2]};
for(int32 Axis=0;Axis<3;++Axis)
@@ -2010,14 +2285,15 @@ private:
FillBits(CandidateAxisWords[Axis],Base,Lo-Min[Axis],Hi-Min[Axis],true);
FillBits(CandidateAxisWords[Axis],Base,Lo2-Min[Axis],Hi2-Min[Axis],true);
}
});
},&AxisTiming);
}
CandidateWords.assign(RockWords.size(),0);
for(int32 Axis=0;Axis<3;++Axis)
const auto MergeStart=std::chrono::steady_clock::now();
CandidateWords=CandidateAxisWords[2];
for(int32 Axis=0;Axis<2;++Axis)
{
const int32 A=(Axis+1)%3,B=(Axis+2)%3;
const size_t Words=(static_cast<size_t>(Size[Axis])+63u)/64u;
for(size_t Line=0;Line<static_cast<size_t>(Size[A])*Size[B];++Line)
ParallelLines(static_cast<size_t>(Size[A])*Size[B],[&,Axis,A,B,Words](size_t Line)
{
const int32 CA=Min[A]+static_cast<int32>(Line%Size[A]);
const int32 CB=Min[B]+static_cast<int32>(Line/Size[A]);
@@ -2033,10 +2309,11 @@ private:
const size_t Column=static_cast<size_t>(Coord[1]-Min[1])*Size[0]
+static_cast<size_t>(Coord[0]-Min[0]);
const size_t Zi=static_cast<size_t>(Coord[2]-Min[2]);
CandidateWords[Column*WordsPerZ+(Zi>>6)]|=uint64{1}<<(Zi&63u);
std::atomic_ref<uint64>(CandidateWords[Column*WordsPerZ+(Zi>>6)])
.fetch_or(uint64{1}<<(Zi&63u),std::memory_order_relaxed);
}
}
}
},&MergeTiming);
}
const int32 LayerLo=LayerRanges.empty()?1:LayerRanges[0].Lo;
const int32 LayerHi=LayerRanges.empty()?0:LayerRanges[0].Hi;
@@ -2053,15 +2330,20 @@ private:
FillBits(CandidateWords,Base,Lo1,Hi1,true);
}
}
const double MergeElapsed=std::chrono::duration<double>(
std::chrono::steady_clock::now()-MergeStart).count();
MergeSeconds=std::max(0.0,MergeElapsed-MergeTiming.WallSeconds);
}
void ApplyDetailBand()
void ApplyDetailBand(FParallelTiming& Timing)
{
std::atomic<uint64> CandidateCount{0},ProxyCount{0},FlipCount{0};
ParallelLines(static_cast<size_t>(Size[0])*Size[1],[&](size_t Line)
{
const int32 X=Min[0]+static_cast<int32>(Line%Size[0]);
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));
uint64 LocalCandidates=0,LocalProxies=0,LocalFlips=0;
const size_t WordBase=Line*WordsPerZ;
for(size_t W=0;W<WordsPerZ;++W)
@@ -2074,13 +2356,13 @@ private:
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 bool bCriticalAir=IsInsideCriticalAir(X,Y,Z);
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
bool bFloorSurface=false;float CaveInfluence=0.0f,FloorInfluence=0.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);
&bFloorSurface,&CaveInfluence,&FloorInfluence,&bCriticalAir);
if(std::abs(SDF)>DetailSupremum+0.125f)continue;
float Displaced=SDF+DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,FloorInfluence)
float Displaced=SDF+DetailNoiseOnLine(X,Y,Z,SurfaceSDF,CaveInfluence,
FloorInfluence,NoiseLine)
*DetailBandFade(SDF);
if(bCriticalAir)Displaced=std::min(Displaced,-1.0f/16.0f);
const bool bNewRock=Displaced>=0.0f;
@@ -2101,7 +2383,7 @@ private:
CandidateCount.fetch_add(LocalCandidates,std::memory_order_relaxed);
ProxyCount.fetch_add(LocalProxies,std::memory_order_relaxed);
FlipCount.fetch_add(LocalFlips,std::memory_order_relaxed);
});
},&Timing);
Stats.SurfaceCandidates=CandidateCount.load(std::memory_order_relaxed);
Stats.SurfaceProxyNodes=ProxyCount.load(std::memory_order_relaxed);
Stats.DetailFlips=FlipCount.load(std::memory_order_relaxed);
@@ -2238,13 +2520,53 @@ private:
float* OutSurfaceSDF=nullptr,
bool* OutFloorSurface=nullptr,
float* OutCaveInfluence=nullptr,
float* OutFloorInfluence=nullptr) const
float* OutFloorInfluence=nullptr,
bool* OutCriticalAir=nullptr) const
{
const float Base=StrataDistance(Z);
float Cave=100000.0f;
float Overlay=-100000.0f;
float CaveFloorInfluence=0.0f;
const float Cave=CaveDistance(X,Y,Z,nullptr,
(OutFloorSurface||OutFloorInfluence)?&CaveFloorInfluence:nullptr);
const float Overlay=SolidOverlayDistance(X,Y,Z);
bool bCriticalAir=false;
const auto Bin=SpatialBins.find(PointBin(X,Y,Z));
if(Bin!=SpatialBins.end())
{
for(uint32 Id:Bin->second)
{
const FPrimitive& P=Primitives[Id];
if(P.bCarve)
{
if(P.bCritical&&X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1]
&&Z>=P.Min[2]&&Z<=P.Max[2]&&ShapeContains(P,
static_cast<int32>(X),static_cast<int32>(Y),static_cast<int32>(Z)))
bCriticalAir=true;
if(X<P.Min[0]-DetailBand||X>P.Max[0]+DetailBand
||Y<P.Min[1]-DetailBand||Y>P.Max[1]+DetailBand
||Z<P.Min[2]-DetailBand||Z>P.Max[2]+DetailBand)continue;
float Distance=ShapeDistance(P,X,Y,Z);
float PrimitiveFloorInfluence=0.0f;
if(P.bHasFloorClip)
{
const double Floor=FloorAt(P,X,Y);
const float FloorT=(static_cast<float>(std::abs(Z-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),
static_cast<float>(P.FloorBlendRadius));
}
const float MetadataWidth=static_cast<float>(std::max(P.BlendRadius,6.0));
const float PrimitiveWeight=SmoothUnit(0.5f+(Cave-Distance)/(2.0f*MetadataWidth));
const float Combined=SmoothMinDistance(Cave,Distance,static_cast<float>(P.BlendRadius));
CaveFloorInfluence=std::lerp(CaveFloorInfluence,PrimitiveFloorInfluence,PrimitiveWeight);
Cave=Combined;
}
else if(X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1]
&&Z>=P.Min[2]&&Z<=P.Max[2])
Overlay=std::max(Overlay,-ShapeDistance(P,X,Y,Z));
}
}
if(OutCriticalAir)*OutCriticalAir=bCriticalAir;
const float CarveOrOverlay=std::max(Cave,Overlay);
const ESurfaceFamily Family=Base<=CarveOrOverlay?ESurfaceFamily::Strata:
(Cave>=Overlay?ESurfaceFamily::Cave:ESurfaceFamily::SolidOverlay);
@@ -2265,6 +2587,44 @@ private:
return RockDistance;
}
float CoarseRockDistance(double X,double Y,double Z,bool* OutCriticalAir) const
{
const float Base=StrataDistance(Z);
float Cave=100000.0f,Overlay=-100000.0f;
bool bCriticalAir=false;
const auto Bin=SpatialBins.find(PointBin(X,Y,Z));
if(Bin!=SpatialBins.end())
{
for(uint32 Id:Bin->second)
{
const FPrimitive& P=Primitives[Id];
if(P.bCarve)
{
if(P.bCritical&&X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1]
&&Z>=P.Min[2]&&Z<=P.Max[2]&&ShapeContains(P,
static_cast<int32>(X),static_cast<int32>(Y),static_cast<int32>(Z)))
bCriticalAir=true;
if(X<P.Min[0]-DetailBand||X>P.Max[0]+DetailBand
||Y<P.Min[1]-DetailBand||Y>P.Max[1]+DetailBand
||Z<P.Min[2]-DetailBand||Z>P.Max[2]+DetailBand)continue;
float Distance=ShapeDistance(P,X,Y,Z);
if(P.bHasFloorClip)
{
const double Floor=FloorAt(P,X,Y);
Distance=SmoothMaxDistance(Distance,static_cast<float>(Floor-Z),
static_cast<float>(P.FloorBlendRadius));
}
Cave=SmoothMinDistance(Cave,Distance,static_cast<float>(P.BlendRadius));
}
else if(X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1]
&&Z>=P.Min[2]&&Z<=P.Max[2])
Overlay=std::max(Overlay,-ShapeDistance(P,X,Y,Z));
}
}
if(OutCriticalAir)*OutCriticalAir=bCriticalAir;
return SmoothMinDistance(Base,std::max(Cave,Overlay),StrataCaveBlendRadius);
}
float OwnerCaveRoughness(double X,double Y,double Z,float SurfaceSDF,
const FStrateGenerationParams& Params,
float CaveInfluence) const
@@ -2334,6 +2694,77 @@ private:
return 1.00f*Macro+(0.80f+0.25f*Macro)*(0.85f*Mid+0.18f*Fine);
}
struct FValueNoiseLineContext
{
int32 X0=0,Y0=0,Z0=std::numeric_limits<int32>::min();
int32 Wavelength=1;
uint32 Seed=0;
float Inv=1.0f,TX=0.0f,TY=0.0f;
std::array<float,8> Corners{};
FValueNoiseLineContext(float X,float Y,int32 InWavelength,uint32 InSeed)
:Wavelength(InWavelength),Seed(InSeed),Inv(1.0f/static_cast<float>(InWavelength))
{
const float FX=X*Inv,FY=Y*Inv;
X0=FMath::FloorToInt(FX);Y0=FMath::FloorToInt(FY);
TX=FStandaloneResearchTunnels::Fade(FX-static_cast<float>(X0));
TY=FStandaloneResearchTunnels::Fade(FY-static_cast<float>(Y0));
}
void SetPlane(int32 Z,bool bUpper)
{
const size_t I=bUpper?4u:0u;
Corners[I+0]=FStandaloneResearchTunnels::NoiseValue(X0,Y0,Z,Seed);
Corners[I+1]=FStandaloneResearchTunnels::NoiseValue(X0+1,Y0,Z,Seed);
Corners[I+2]=FStandaloneResearchTunnels::NoiseValue(X0,Y0+1,Z,Seed);
Corners[I+3]=FStandaloneResearchTunnels::NoiseValue(X0+1,Y0+1,Z,Seed);
}
float Sample(float Z)
{
const float FZ=Z*Inv;
const int32 NewZ0=FMath::FloorToInt(FZ);
const float TZ=FStandaloneResearchTunnels::Fade(FZ-static_cast<float>(NewZ0));
if(NewZ0!=Z0)
{
if(NewZ0==Z0+1)
{
Corners[0]=Corners[4];Corners[1]=Corners[5];
Corners[2]=Corners[6];Corners[3]=Corners[7];
SetPlane(NewZ0+1,true);
}
else
{
SetPlane(NewZ0,false);SetPlane(NewZ0+1,true);
}
Z0=NewZ0;
}
const float Z00=FMath::Lerp(Corners[0],Corners[1],TX);
const float Z10=FMath::Lerp(Corners[2],Corners[3],TX);
const float Z01=FMath::Lerp(Corners[4],Corners[5],TX);
const float Z11=FMath::Lerp(Corners[6],Corners[7],TX);
return FMath::Lerp(FMath::Lerp(Z00,Z10,TY),FMath::Lerp(Z01,Z11,TY),TZ);
}
};
struct FDetailNoiseLineContext
{
FValueNoiseLineContext Macro,Mid,Fine;
FDetailNoiseLineContext(float X,float Y,uint32 Seed)
:Macro(X,Y,32,Seed^0xB5297A4Du),Mid(X,Y,16,Seed^0x63D83595u),
Fine(X,Y,6,Seed^0xA511E9B3u)
{}
float SurfaceReliefAt(float Z)
{
const float MacroValue=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,
float CaveInfluence,float FloorInfluence) const
{
@@ -2353,13 +2784,27 @@ private:
return std::clamp((Relief+OwnerRelief*0.5f)*Contrast,-3.9f,3.9f);
}
float DetailNoiseOnLine(double X,double Y,double Z,float SurfaceSDF,
float CaveInfluence,float FloorInfluence,
FDetailNoiseLineContext& NoiseLine) const
{
if(DetailVariant!=EDetailVariant::Coherent)
return DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,FloorInfluence);
const float Relief=NoiseLine.SurfaceReliefAt(static_cast<float>(Z));
const FLayerField* Field=FindLayerField(Z);
const float OwnerRelief=Field?OwnerCaveRoughness(X,Y,Z,SurfaceSDF,Field->Params,
CaveInfluence):0.0f;
const float Contrast=1.35f-0.80f*std::clamp(CaveInfluence,0.0f,1.0f)
+1.85f*std::clamp(FloorInfluence,0.0f,1.0f);
return std::clamp((Relief+OwnerRelief*0.5f)*Contrast,-3.9f,3.9f);
}
bool EvaluateFast(double X,double Y,double Z) const
{
const bool bCriticalAir=IsInsideCriticalAir(X,Y,Z);
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
bool bFloorSurface=false;float CaveInfluence=0.0f,FloorInfluence=0.0f;
bool bFloorSurface=false,bCriticalAir=false;float CaveInfluence=0.0f,FloorInfluence=0.0f;
float RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF,
&bFloorSurface,&CaveInfluence,&FloorInfluence);
&bFloorSurface,&CaveInfluence,&FloorInfluence,&bCriticalAir);
const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]&&Z>=Min[2]&&Z<=Max[2];
if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth
&&std::abs(RockDistance)<=DetailSupremum+0.125f)
@@ -2421,19 +2866,44 @@ private:
return Count?PairAB/std::sqrt(std::max(1.0e-12,PairAA*PairBB)):0.0;
}
template<class Fn> void ParallelLines(size_t Count,Fn&& Work) const
template<class Fn> void ParallelLines(size_t Count,Fn&& Work,FParallelTiming* Timing=nullptr) const
{
const auto WallStart=std::chrono::steady_clock::now();
if(WorkerCount<=1||Count<512)
{for(size_t I=0;I<Count;++I)Work(I);return;}
std::atomic<size_t> Next{0};std::vector<std::thread> Threads;Threads.reserve(WorkerCount);
{
const auto WorkerStart=std::chrono::steady_clock::now();
for(size_t I=0;I<Count;++I)Work(I);
if(Timing)
{
const double WorkerSeconds=std::chrono::duration<double>(std::chrono::steady_clock::now()-WorkerStart).count();
Timing->WallSeconds+=std::chrono::duration<double>(std::chrono::steady_clock::now()-WallStart).count();
Timing->WorkerSeconds+=WorkerSeconds;
}
return;
}
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)
Threads.emplace_back([&](){for(;;){const size_t I=Next.fetch_add(1,std::memory_order_relaxed);if(I>=Count)break;Work(I);}});
Threads.emplace_back([&]()
{
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;Work(I);}
WorkerNanoseconds.fetch_add(static_cast<uint64>(std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now()-WorkerStart).count()),std::memory_order_relaxed);
});
for(std::thread& T:Threads)T.join();
if(Timing)
{
Timing->WallSeconds+=std::chrono::duration<double>(std::chrono::steady_clock::now()-WallStart).count();
Timing->WorkerSeconds+=static_cast<double>(WorkerNanoseconds.load(std::memory_order_relaxed))*1.0e-9;
}
}
FStandaloneOwnerWorld* Owner=nullptr;
int32 WorkerCount=6;
int32 Min[3]{},Max[3]{},ClipMin[3]{},ClipMax[3]{},Size[3]{};
FIntVector EditBoundsMin=FIntVector(MAX_int32,MAX_int32,MAX_int32);
FIntVector EditBoundsMax=FIntVector(MIN_int32,MIN_int32,MIN_int32);
int32 ActiveSpatialBinSize=LineBinSize;
double ActiveBlendRadius=0.0;
double ActiveFloorBlendRadius=0.0;
@@ -2444,9 +2914,10 @@ private:
std::vector<FInterval> LayerRanges;
std::vector<FLayerField> LayerFields;
std::vector<FPrimitive> Primitives;
std::vector<FEditBrush> EditBrushes;
std::array<FAxisBins,3> AxisBins;
std::unordered_map<FBinKey,std::vector<uint32>,FBinHash> SpatialBins;
std::unordered_map<FBinKey,std::vector<uint32>,FBinHash> CriticalAirBins;
std::unordered_map<FBinKey,FBinItems,FBinHash> SpatialBins;
std::unordered_map<FBinKey,FBinItems,FBinHash> CriticalAirBins;
std::vector<uint64> RockWords;
std::unique_ptr<int8[]> SurfaceDistanceQ;
std::array<std::vector<uint64>,3> CandidateAxisWords;