wip(standalone): GPU field step 2 -- far-field shapes/strata/relief in shared C++/HLSL, GPU field+mesh
Seven LOD2-8 tiles: RX 7900 XTX field+meshing 0.335 ms vs CPU generateTile 15.81 ms; repeat runs bit-identical; CPU W1/W8 44/44 hashes unchanged. Not yet a real gain: rock/air signs still come from CPU masks for every sample. Viewer does not draw GPU tiles yet; no whole-view timing. 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
d4b4377d30
commit
b61ccff549
11 files changed
+2361
-9
No files matched your search
@@ -6,6 +6,7 @@
|
||||
#include "VoxelNoise.h"
|
||||
#include "VoxelStrateManager.h"
|
||||
#include "VoxelForgeCore/VoxelForgeCore.h"
|
||||
#include "VoxelForgeFieldShared.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -326,6 +327,7 @@ public:
|
||||
// Route validation is world-wide. Detail regions reuse the same authored route data and
|
||||
// do not need to repeat the 5,642-point world audit for every entering tile group.
|
||||
if(bValidatePassageRoutes)ValidatePassageRoutes();
|
||||
RebuildSharedFieldPrimitives();
|
||||
Built=true;
|
||||
if(bIncludeFarWorld)BuildFarWorld();
|
||||
const uint64 WordBytes=static_cast<uint64>(RockWords.size()+CandidateWords.size())*sizeof(uint64);
|
||||
@@ -432,6 +434,7 @@ public:
|
||||
Stats.PrimitiveCount=Primitives.size();
|
||||
if(Primitives.empty())
|
||||
{Error="owner shape extraction produced no rooms, tunnels, or passages";return false;}
|
||||
RebuildSharedFieldPrimitives();
|
||||
Built=true;
|
||||
return true;
|
||||
}
|
||||
@@ -461,6 +464,128 @@ public:
|
||||
OutIndices.erase(std::unique(OutIndices.begin(),OutIndices.end()),OutIndices.end());
|
||||
}
|
||||
|
||||
bool ExportFarFieldTileInput(const FIntVector& Origin,int32 Cells,int32 Step,
|
||||
VoxelForgeCore::FarFieldTileInput& Out,std::string& Error) const
|
||||
{
|
||||
if(!FarBuilt||Cells<=0||Cells>32||Step<=0)
|
||||
{Error="far field export requires a built far world and a bounded coarse tile";return false;}
|
||||
const int32 Dim=Cells+3;
|
||||
const size_t SampleCount=static_cast<size_t>(Dim)*Dim*Dim;
|
||||
std::vector<std::vector<uint32>> PerSample(SampleCount);
|
||||
std::vector<uint8> Used(Primitives.size(),0);
|
||||
Out={};Out.origin={Origin.X,Origin.Y,Origin.Z};Out.cells=Cells;Out.step=Step;Out.sampleDim=Dim;
|
||||
if(LayerRanges.empty())
|
||||
{Out.strataLo=-100000.0f;Out.strataHi=100000.0f;}
|
||||
else
|
||||
{Out.strataLo=static_cast<float>(LayerRanges[0].Lo);Out.strataHi=static_cast<float>(LayerRanges[0].Hi);}
|
||||
Out.strataBlend=StrataCaveBlendRadius;
|
||||
if(Owner&&Owner->Manager)
|
||||
{
|
||||
const double TileZ0=Origin.Z,TileZ1=Origin.Z+static_cast<double>(Cells)*Step;
|
||||
for(const FStrateSlot& Slot:Owner->Manager->GetLayout())
|
||||
{
|
||||
const FStrateGenerationParams Params=Owner->Manager->GetGenerationParams(
|
||||
FIntVector(0,0,(Slot.TopChunkZ+Slot.BottomChunkZ)/2));
|
||||
const float HalfWidth=static_cast<float>(std::max(8.0,
|
||||
static_cast<double>(Params.BoundarySealThickness)+8.0));
|
||||
const float Boundaries[2]={static_cast<float>(Slot.BottomChunkZ)*CHUNK_SIZE,
|
||||
static_cast<float>(Slot.TopChunkZ+1)*CHUNK_SIZE};
|
||||
for(const float Z:Boundaries)
|
||||
if(Z+HalfWidth>=TileZ0&&Z-HalfWidth<=TileZ1)
|
||||
Out.stratumBoundaries.push_back({Z,HalfWidth});
|
||||
}
|
||||
std::sort(Out.stratumBoundaries.begin(),Out.stratumBoundaries.end(),
|
||||
[](const auto& A,const auto& B){return A.z<B.z;});
|
||||
Out.stratumBoundaries.erase(std::unique(Out.stratumBoundaries.begin(),
|
||||
Out.stratumBoundaries.end(),[](const auto& A,const auto& B)
|
||||
{return std::abs(A.z-B.z)<0.01f;}),Out.stratumBoundaries.end());
|
||||
}
|
||||
Out.sampleShapeRanges.resize(SampleCount);
|
||||
Out.rockAirSigns.assign((SampleCount+31u)/32u,0u);
|
||||
Out.protectedAir.assign((SampleCount+31u)/32u,0u);
|
||||
|
||||
size_t SampleIndex=0;
|
||||
for(int32 Z=-1;Z<=Cells+1;++Z)
|
||||
for(int32 Y=-1;Y<=Cells+1;++Y)
|
||||
for(int32 X=-1;X<=Cells+1;++X,++SampleIndex)
|
||||
{
|
||||
const int32 WX=Origin.X+X*Step,WY=Origin.Y+Y*Step,WZ=Origin.Z+Z*Step;
|
||||
if(WX>=Min[0]&&WX<=Max[0]&&WY>=Min[1]&&WY<=Max[1]&&WZ>=Min[2]&&WZ<=Max[2])
|
||||
{Error="tile lattice intersects the near analytic/detail region; this export is far-field only";return false;}
|
||||
const float Density=DensityAtLattice(WX,WY,WZ);
|
||||
if(Density>=0.0f)Out.rockAirSigns[SampleIndex>>5]|=1u<<(SampleIndex&31u);
|
||||
if(IsInsideCriticalAir(WX,WY,WZ))
|
||||
{
|
||||
Out.protectedAir[SampleIndex>>5]|=1u<<(SampleIndex&31u);
|
||||
++Out.protectedSampleCount;
|
||||
}
|
||||
const auto Found=SpatialBins.find(PointBin(WX,WY,WZ));
|
||||
if(Found==SpatialBins.end())continue;
|
||||
auto& Ids=PerSample[SampleIndex];
|
||||
Ids.assign(Found->second.begin(),Found->second.end());
|
||||
for(const uint32 Id:Ids)if(Id<Used.size())Used[Id]=1;
|
||||
}
|
||||
|
||||
std::vector<uint32> Remap(Primitives.size(),std::numeric_limits<uint32>::max());
|
||||
Out.shapes.reserve(std::count(Used.begin(),Used.end(),uint8{1}));
|
||||
for(uint32 Id=0;Id<Used.size();++Id)
|
||||
{
|
||||
if(!Used[Id])continue;
|
||||
Remap[Id]=static_cast<uint32>(Out.shapes.size());
|
||||
const VF_FieldPrimitive& S=SharedFieldPrimitives[Id];
|
||||
VoxelForgeCore::FarFieldPrimitiveGpu G{};
|
||||
G.flags=S.flags;
|
||||
for(int32 A=0;A<3;++A)
|
||||
{
|
||||
G.minVoxel[A]=S.minVoxel[A];G.maxVoxel[A]=S.maxVoxel[A];
|
||||
G.center[A]=static_cast<float>(S.center[A]);
|
||||
G.capsuleStart[A]=static_cast<float>(S.capsuleStart[A]);
|
||||
G.capsuleDelta[A]=static_cast<float>(S.capsuleDelta[A]);
|
||||
G.roundedHalfExtent[A]=static_cast<float>(S.roundedHalfExtent[A]);
|
||||
}
|
||||
for(int32 I=0;I<9;++I)G.q[I]=static_cast<float>(S.q[I]);
|
||||
G.minRadius=static_cast<float>(S.minRadius);
|
||||
G.capsuleAxisLengthSquared=static_cast<float>(S.capsuleAxisLengthSquared);
|
||||
G.capsuleAxisLength=static_cast<float>(S.capsuleAxisLength);
|
||||
G.capsuleStartRadius=static_cast<float>(S.capsuleStartRadius);
|
||||
G.capsuleEndRadius=static_cast<float>(S.capsuleEndRadius);
|
||||
G.profileStartT=static_cast<float>(S.profileStartT);G.profileEndT=static_cast<float>(S.profileEndT);
|
||||
G.profileStartRadius=static_cast<float>(S.profileStartRadius);G.profileEndRadius=static_cast<float>(S.profileEndRadius);
|
||||
G.radiusSlopeStart=static_cast<float>(S.radiusSlopeStart);G.radiusSlopeEnd=static_cast<float>(S.radiusSlopeEnd);
|
||||
G.rounding=static_cast<float>(S.rounding);
|
||||
G.blendRadius=static_cast<float>(S.blendRadius);G.floorBlendRadius=static_cast<float>(S.floorBlendRadius);
|
||||
G.floorCenterZ=static_cast<float>(S.floorCenterZ);G.floorSlopeX=static_cast<float>(S.floorSlopeX);
|
||||
G.floorSlopeY=static_cast<float>(S.floorSlopeY);
|
||||
G.floorCurveOriginX=static_cast<float>(S.floorCurveOriginX);G.floorCurveOriginY=static_cast<float>(S.floorCurveOriginY);
|
||||
G.floorCurveDeltaX=static_cast<float>(S.floorCurveDeltaX);G.floorCurveDeltaY=static_cast<float>(S.floorCurveDeltaY);
|
||||
G.floorCurveLength=static_cast<float>(S.floorCurveLength);
|
||||
G.floorCurveStartZ=static_cast<float>(S.floorCurveStartZ);G.floorCurveEndZ=static_cast<float>(S.floorCurveEndZ);
|
||||
G.floorCurveTangentStart=static_cast<float>(S.floorCurveTangentStart);
|
||||
G.floorCurveTangentEnd=static_cast<float>(S.floorCurveTangentEnd);
|
||||
G.floorCurveStartBlend=static_cast<float>(S.floorCurveStartBlend);
|
||||
G.floorCurveEndBlend=static_cast<float>(S.floorCurveEndBlend);
|
||||
Out.shapes.push_back(G);
|
||||
}
|
||||
uint64 CandidateCount=0;
|
||||
for(const auto& Ids:PerSample)CandidateCount+=Ids.size();
|
||||
if(CandidateCount>std::numeric_limits<uint32>::max())
|
||||
{Error="far tile candidate list exceeds the GPU 32-bit offset limit";return false;}
|
||||
Out.sampleShapeIds.reserve(static_cast<size_t>(CandidateCount));
|
||||
for(size_t I=0;I<PerSample.size();++I)
|
||||
{
|
||||
const auto& Ids=PerSample[I];
|
||||
Out.sampleShapeRanges[I]={static_cast<uint32>(Out.sampleShapeIds.size()),
|
||||
static_cast<uint32>(Ids.size())};
|
||||
for(const uint32 Id:Ids)
|
||||
{
|
||||
if(Id>=Remap.size()||Remap[Id]==std::numeric_limits<uint32>::max())
|
||||
{Error="far tile shape remap is inconsistent";return false;}
|
||||
Out.sampleShapeIds.push_back(Remap[Id]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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); }
|
||||
|
||||
@@ -505,6 +630,7 @@ public:
|
||||
if(FarBuilt)return;
|
||||
auto Stage=std::chrono::steady_clock::now();
|
||||
BuildFarParentShapes();
|
||||
RebuildSharedFieldPrimitives();
|
||||
Stats.FarPrimitiveSeconds=std::chrono::duration<double>(std::chrono::steady_clock::now()-Stage).count();
|
||||
Stage=std::chrono::steady_clock::now();
|
||||
BuildSpatialBins(SpatialBinSize,false);
|
||||
@@ -1407,6 +1533,64 @@ private:
|
||||
static std::array<double,3> Cross(const std::array<double,3>& A,const std::array<double,3>& B)
|
||||
{return {A[1]*B[2]-A[2]*B[1],A[2]*B[0]-A[0]*B[2],A[0]*B[1]-A[1]*B[0]};}
|
||||
|
||||
static VF_FieldPrimitive MakeSharedFieldPrimitive(const FPrimitive& P)
|
||||
{
|
||||
VF_FieldPrimitive Out{};
|
||||
if(P.bCapsule)Out.flags|=VF_FIELD_PRIMITIVE_CAPSULE;
|
||||
if(P.bSmoothCapsuleRadius)Out.flags|=VF_FIELD_PRIMITIVE_SMOOTH_CAPSULE;
|
||||
if(P.bRoundedBox)Out.flags|=VF_FIELD_PRIMITIVE_ROUNDED_BOX;
|
||||
if(P.bHasFloorClip)Out.flags|=VF_FIELD_PRIMITIVE_HAS_FLOOR;
|
||||
if(P.bCurvedFloorClip)Out.flags|=VF_FIELD_PRIMITIVE_CURVED_FLOOR;
|
||||
if(P.bCarve)Out.flags|=VF_FIELD_PRIMITIVE_CARVE;
|
||||
if(P.bCritical)Out.flags|=VF_FIELD_PRIMITIVE_CRITICAL;
|
||||
for(int32 A=0;A<3;++A)
|
||||
{
|
||||
Out.minVoxel[A]=P.Min[A];Out.maxVoxel[A]=P.Max[A];
|
||||
Out.center[A]=P.CenterSub[A]/16.0;
|
||||
Out.capsuleStart[A]=P.CapsuleStartWorld[A];
|
||||
Out.capsuleDelta[A]=P.CapsuleAxisDelta[A];
|
||||
Out.roundedHalfExtent[A]=P.RoundedHalfExtent[A];
|
||||
}
|
||||
for(int32 I=0;I<3;++I)for(int32 J=0;J<3;++J)
|
||||
Out.q[I*3+J]=static_cast<double>(P.Q[I][J])/QScale;
|
||||
Out.minRadius=P.MinRadius;
|
||||
Out.capsuleAxisLengthSquared=P.CapsuleAxisLengthSquared;
|
||||
Out.capsuleAxisLength=P.CapsuleAxisLength;
|
||||
Out.capsuleStartRadius=P.CapsuleStartRadius;
|
||||
Out.capsuleEndRadius=P.CapsuleEndRadius;
|
||||
Out.profileStartT=P.CapsuleProfileStartT;
|
||||
Out.profileEndT=P.CapsuleProfileEndT;
|
||||
Out.profileStartRadius=P.CapsuleProfileStartRadius;
|
||||
Out.profileEndRadius=P.CapsuleProfileEndRadius;
|
||||
Out.radiusSlopeStart=P.CapsuleRadiusSlopeStart;
|
||||
Out.radiusSlopeEnd=P.CapsuleRadiusSlopeEnd;
|
||||
Out.rounding=P.Rounding;
|
||||
Out.blendRadius=P.BlendRadius;
|
||||
Out.floorBlendRadius=P.FloorBlendRadius;
|
||||
Out.floorCenterZ=P.FloorCenterZ;
|
||||
Out.floorSlopeX=P.FloorSlopeX;
|
||||
Out.floorSlopeY=P.FloorSlopeY;
|
||||
Out.floorCurveOriginX=P.FloorCurveOriginX;
|
||||
Out.floorCurveOriginY=P.FloorCurveOriginY;
|
||||
Out.floorCurveDeltaX=P.FloorCurveDeltaX;
|
||||
Out.floorCurveDeltaY=P.FloorCurveDeltaY;
|
||||
Out.floorCurveLength=P.FloorCurveLength;
|
||||
Out.floorCurveStartZ=P.FloorCurveStartZ;
|
||||
Out.floorCurveEndZ=P.FloorCurveEndZ;
|
||||
Out.floorCurveTangentStart=P.FloorCurveTangentStart;
|
||||
Out.floorCurveTangentEnd=P.FloorCurveTangentEnd;
|
||||
Out.floorCurveStartBlend=P.FloorCurveStartBlend;
|
||||
Out.floorCurveEndBlend=P.FloorCurveEndBlend;
|
||||
return Out;
|
||||
}
|
||||
|
||||
void RebuildSharedFieldPrimitives()
|
||||
{
|
||||
SharedFieldPrimitives.clear();SharedFieldPrimitives.reserve(Primitives.size());
|
||||
for(const FPrimitive& P:Primitives)
|
||||
SharedFieldPrimitives.push_back(MakeSharedFieldPrimitive(P));
|
||||
}
|
||||
|
||||
void AddEllipsoid(const FVector& Center,const std::array<double,3>& Radii,
|
||||
const std::array<double,3>& Axis0={1,0,0},
|
||||
const std::array<double,3>& Axis1={0,1,0},
|
||||
@@ -3633,6 +3817,7 @@ private:
|
||||
for(uint32 Id:*QueryItems)
|
||||
{
|
||||
const FPrimitive& P=Primitives[Id];
|
||||
const VF_FieldPrimitive& Shared=SharedFieldPrimitives[Id];
|
||||
if(P.bCarve)
|
||||
{
|
||||
if(P.bCritical&&X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1]
|
||||
@@ -3642,22 +3827,22 @@ private:
|
||||
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 Distance=VF_FieldPrimitiveDistance(Shared,X,Y,Z);
|
||||
if(P.bHasFloorClip)
|
||||
{
|
||||
const double Floor=FloorAt(P,X,Y);
|
||||
Distance=SmoothMaxDistance(Distance,static_cast<float>(Floor-Z),
|
||||
const double Floor=VF_FieldPrimitiveFloorAt(Shared,X,Y);
|
||||
Distance=VF_FieldSmoothMax(Distance,static_cast<float>(Floor-Z),
|
||||
static_cast<float>(P.FloorBlendRadius));
|
||||
}
|
||||
Cave=SmoothMinDistance(Cave,Distance,static_cast<float>(P.BlendRadius));
|
||||
Cave=VF_FieldSmoothMin(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));
|
||||
Overlay=std::max(Overlay,-VF_FieldPrimitiveDistance(Shared,X,Y,Z));
|
||||
}
|
||||
}
|
||||
if(OutCriticalAir)*OutCriticalAir=bCriticalAir;
|
||||
return SmoothMinDistance(Base,std::max(Cave,Overlay),StrataCaveBlendRadius);
|
||||
return VF_FieldSmoothMin(Base,std::max(Cave,Overlay),StrataCaveBlendRadius);
|
||||
}
|
||||
|
||||
struct FOwnerNoiseLineContext
|
||||
@@ -4094,6 +4279,7 @@ private:
|
||||
std::vector<FInterval> LayerRanges;
|
||||
std::vector<FLayerField> LayerFields;
|
||||
std::vector<FPrimitive> Primitives;
|
||||
std::vector<VF_FieldPrimitive> SharedFieldPrimitives;
|
||||
std::vector<FFarConnectorSegment> FarConnectorSegments;
|
||||
std::unordered_map<FBinKey,std::vector<uint32>,FBinHash> FarConnectorSpatialBins;
|
||||
std::vector<FEditBrush> EditBrushes;
|
||||
|
||||
@@ -733,6 +733,24 @@ namespace VoxelForgeCore
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<FarFieldTileInput> World::exportFarFieldTile(const TileKey& key,
|
||||
std::string& error) const
|
||||
{
|
||||
if(!impl_||!impl_->owner||!impl_->shape||!impl_->shape->IsBuilt())
|
||||
{error="world must be compiled before exporting a far field tile";return std::nullopt;}
|
||||
if(key.lod<impl_->configuration.fullResolutionLodCount||key.lod>8)
|
||||
{error="far field export accepts only coarse LOD tiles";return std::nullopt;}
|
||||
if(!impl_->shape->HasFarWorld())
|
||||
{error="world must include the far parent/connector shapes before export";return std::nullopt;}
|
||||
const int32 extent=CHUNK_SIZE<<key.lod;
|
||||
const int32 cells=std::max(1,impl_->owner->CoarseTileCells);
|
||||
const int32 step=std::max(1,extent/cells);
|
||||
FarFieldTileInput result;
|
||||
if(!impl_->shape->ExportFarFieldTileInput(ToNative(key).OriginVoxels(),cells,step,result,error))
|
||||
return std::nullopt;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool World::auditDetailPassing() const { return impl_&&impl_->shape&&impl_->shape->IsDetailAuditPassing(); }
|
||||
bool World::auditGridStairIntegrity() const
|
||||
{ return impl_&&impl_->shape&&impl_->shape->AuditGridStairIntegrity(); }
|
||||
|
||||
@@ -7,22 +7,182 @@
|
||||
#define VF_FIELD_INLINE
|
||||
typedef int VF_FieldI32;
|
||||
typedef uint VF_FieldU32;
|
||||
typedef float VF_FieldReal;
|
||||
#define VF_FIELD_CAST_I32(v) int(v)
|
||||
#define VF_FIELD_CAST_U32(v) uint(v)
|
||||
#define VF_FIELD_CAST_F32(v) float(v)
|
||||
#define VF_FIELD_FLOOR(v) floor(v)
|
||||
#define VF_FIELD_ABS(v) abs(v)
|
||||
#define VF_FIELD_SQRT(v) sqrt(v)
|
||||
#define VF_FIELD_MIN(a,b) min(a,b)
|
||||
#define VF_FIELD_MAX(a,b) max(a,b)
|
||||
#define VF_FIELD_CLAMP(v,a,b) clamp(v,a,b)
|
||||
#define VF_FIELD_LERP(a,b,t) lerp(a,b,t)
|
||||
#else
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#define VF_FIELD_INLINE inline
|
||||
typedef std::int32_t VF_FieldI32;
|
||||
typedef std::uint32_t VF_FieldU32;
|
||||
typedef double VF_FieldReal;
|
||||
#define VF_FIELD_CAST_I32(v) static_cast<VF_FieldI32>(v)
|
||||
#define VF_FIELD_CAST_U32(v) static_cast<VF_FieldU32>(v)
|
||||
#define VF_FIELD_CAST_F32(v) static_cast<float>(v)
|
||||
#define VF_FIELD_FLOOR(v) std::floor(v)
|
||||
#define VF_FIELD_ABS(v) std::abs(v)
|
||||
#define VF_FIELD_SQRT(v) std::sqrt(v)
|
||||
#define VF_FIELD_MIN(a,b) std::min(a,b)
|
||||
#define VF_FIELD_MAX(a,b) std::max(a,b)
|
||||
#define VF_FIELD_CLAMP(v,a,b) std::clamp(v,a,b)
|
||||
#define VF_FIELD_LERP(a,b,t) std::lerp(a,b,t)
|
||||
#endif
|
||||
|
||||
// High-level far-field primitive data. The CPU instance uses double intermediates to retain the
|
||||
// reference path's precision; HLSL instantiates the same fields and routines as float. The packed
|
||||
// upload record is a separate float-only API type with the same semantic members.
|
||||
struct VF_FieldPrimitive
|
||||
{
|
||||
VF_FieldI32 flags;
|
||||
VF_FieldI32 minVoxel[3], maxVoxel[3];
|
||||
VF_FieldReal center[3], q[9], minRadius;
|
||||
VF_FieldReal capsuleStart[3], capsuleDelta[3];
|
||||
VF_FieldReal capsuleAxisLengthSquared, capsuleAxisLength;
|
||||
VF_FieldReal capsuleStartRadius, capsuleEndRadius;
|
||||
VF_FieldReal profileStartT, profileEndT, profileStartRadius, profileEndRadius;
|
||||
VF_FieldReal radiusSlopeStart, radiusSlopeEnd;
|
||||
VF_FieldReal roundedHalfExtent[3], rounding;
|
||||
VF_FieldReal blendRadius, floorBlendRadius;
|
||||
VF_FieldReal floorCenterZ, floorSlopeX, floorSlopeY;
|
||||
VF_FieldReal floorCurveOriginX, floorCurveOriginY;
|
||||
VF_FieldReal floorCurveDeltaX, floorCurveDeltaY, floorCurveLength;
|
||||
VF_FieldReal floorCurveStartZ, floorCurveEndZ;
|
||||
VF_FieldReal floorCurveTangentStart, floorCurveTangentEnd;
|
||||
VF_FieldReal floorCurveStartBlend, floorCurveEndBlend;
|
||||
};
|
||||
|
||||
static const VF_FieldI32 VF_FIELD_PRIMITIVE_CAPSULE = 1;
|
||||
static const VF_FieldI32 VF_FIELD_PRIMITIVE_SMOOTH_CAPSULE = 2;
|
||||
static const VF_FieldI32 VF_FIELD_PRIMITIVE_ROUNDED_BOX = 4;
|
||||
static const VF_FieldI32 VF_FIELD_PRIMITIVE_HAS_FLOOR = 8;
|
||||
static const VF_FieldI32 VF_FIELD_PRIMITIVE_CURVED_FLOOR = 16;
|
||||
static const VF_FieldI32 VF_FIELD_PRIMITIVE_CARVE = 32;
|
||||
static const VF_FieldI32 VF_FIELD_PRIMITIVE_CRITICAL = 64;
|
||||
|
||||
VF_FIELD_INLINE VF_FieldReal VF_FieldEvaluateFloorProfile(VF_FieldReal a, VF_FieldReal b,
|
||||
VF_FieldReal tangentA, VF_FieldReal tangentB, VF_FieldReal length,
|
||||
VF_FieldReal startBlend, VF_FieldReal endBlend, VF_FieldReal s)
|
||||
{
|
||||
const VF_FieldReal secant = (b - a) / length;
|
||||
if (s < 0.0) return a + tangentA * s;
|
||||
if (s > length) return b + tangentB * (s - length);
|
||||
VF_FieldReal result = VF_FIELD_LERP(a, b, s / length);
|
||||
if (startBlend > 0.0 && s < startBlend)
|
||||
{
|
||||
const VF_FieldReal t = s / startBlend;
|
||||
const VF_FieldReal correction = (tangentA - secant) * startBlend
|
||||
* (t - 2.0 * t * t + t * t * t);
|
||||
result += correction;
|
||||
}
|
||||
if (endBlend > 0.0 && length - s < endBlend)
|
||||
{
|
||||
const VF_FieldReal t = (length - s) / endBlend;
|
||||
const VF_FieldReal correction = (tangentB - secant) * endBlend
|
||||
* (t - 2.0 * t * t + t * t * t);
|
||||
result -= correction;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
VF_FIELD_INLINE VF_FieldReal VF_FieldPrimitiveFloorAt(VF_FieldPrimitive p,
|
||||
VF_FieldReal x, VF_FieldReal y)
|
||||
{
|
||||
if ((p.flags & VF_FIELD_PRIMITIVE_CURVED_FLOOR) != 0)
|
||||
{
|
||||
const VF_FieldReal t = ((x - p.floorCurveOriginX) * p.floorCurveDeltaX
|
||||
+ (y - p.floorCurveOriginY) * p.floorCurveDeltaY)
|
||||
/ (p.floorCurveLength * p.floorCurveLength);
|
||||
return VF_FieldEvaluateFloorProfile(p.floorCurveStartZ, p.floorCurveEndZ,
|
||||
p.floorCurveTangentStart, p.floorCurveTangentEnd, p.floorCurveLength,
|
||||
p.floorCurveStartBlend, p.floorCurveEndBlend, t * p.floorCurveLength);
|
||||
}
|
||||
return p.floorCenterZ + p.floorSlopeX * (x - p.center[0])
|
||||
+ p.floorSlopeY * (y - p.center[1]);
|
||||
}
|
||||
|
||||
VF_FIELD_INLINE float VF_FieldCapsuleRadiusAtT(VF_FieldPrimitive p,
|
||||
VF_FieldReal t, VF_FieldReal axisLength)
|
||||
{
|
||||
t = VF_FIELD_CLAMP(t, 0.0, 1.0);
|
||||
if ((p.flags & VF_FIELD_PRIMITIVE_SMOOTH_CAPSULE) == 0)
|
||||
return VF_FIELD_CAST_F32(p.capsuleStartRadius
|
||||
+ (p.capsuleEndRadius - p.capsuleStartRadius) * t);
|
||||
const VF_FieldReal t0 = p.profileStartT, t1 = p.profileEndT;
|
||||
if (t < t0)
|
||||
return VF_FIELD_CAST_F32(p.profileStartRadius
|
||||
+ p.radiusSlopeStart * axisLength * (t - t0));
|
||||
if (t > t1)
|
||||
return VF_FIELD_CAST_F32(p.profileEndRadius
|
||||
+ p.radiusSlopeEnd * axisLength * (t - t1));
|
||||
const VF_FieldReal spanT = VF_FIELD_MAX(1.0e-9, t1 - t0);
|
||||
const VF_FieldReal u = VF_FIELD_CLAMP((t - t0) / spanT, 0.0, 1.0);
|
||||
const VF_FieldReal u2 = u * u, u3 = u2 * u;
|
||||
const VF_FieldReal spanDistance = axisLength * spanT;
|
||||
const VF_FieldReal h00 = 2.0 * u3 - 3.0 * u2 + 1.0;
|
||||
const VF_FieldReal h10 = u3 - 2.0 * u2 + u;
|
||||
const VF_FieldReal h01 = -2.0 * u3 + 3.0 * u2;
|
||||
const VF_FieldReal h11 = u3 - u2;
|
||||
return VF_FIELD_CAST_F32(h00 * p.profileStartRadius
|
||||
+ h10 * spanDistance * p.radiusSlopeStart
|
||||
+ h01 * p.profileEndRadius
|
||||
+ h11 * spanDistance * p.radiusSlopeEnd);
|
||||
}
|
||||
|
||||
VF_FIELD_INLINE float VF_FieldPrimitiveDistance(VF_FieldPrimitive p,
|
||||
VF_FieldReal x, VF_FieldReal y, VF_FieldReal z)
|
||||
{
|
||||
if ((p.flags & VF_FIELD_PRIMITIVE_CAPSULE) != 0)
|
||||
{
|
||||
const VF_FieldReal position[3] = {x, y, z};
|
||||
VF_FieldReal v[3], d[3], vd = 0.0;
|
||||
for (VF_FieldI32 axis = 0; axis < 3; ++axis)
|
||||
{
|
||||
v[axis] = position[axis] - p.capsuleStart[axis];
|
||||
d[axis] = p.capsuleDelta[axis];
|
||||
vd += v[axis] * d[axis];
|
||||
}
|
||||
const VF_FieldReal d2 = p.capsuleAxisLengthSquared;
|
||||
const VF_FieldReal t = d2 > 1.0e-12 ? VF_FIELD_CLAMP(vd / d2, 0.0, 1.0) : 0.0;
|
||||
VF_FieldReal distanceSquared = 0.0;
|
||||
for (VF_FieldI32 axis = 0; axis < 3; ++axis)
|
||||
{
|
||||
const VF_FieldReal e = v[axis] - t * d[axis];
|
||||
distanceSquared += e * e;
|
||||
}
|
||||
const float radius = VF_FieldCapsuleRadiusAtT(p, t, p.capsuleAxisLength);
|
||||
return VF_FIELD_CAST_F32(VF_FIELD_SQRT(distanceSquared) - radius);
|
||||
}
|
||||
if ((p.flags & VF_FIELD_PRIMITIVE_ROUNDED_BOX) != 0)
|
||||
{
|
||||
const VF_FieldReal d[3] = {
|
||||
VF_FIELD_ABS(x - p.center[0]) - p.roundedHalfExtent[0],
|
||||
VF_FIELD_ABS(y - p.center[1]) - p.roundedHalfExtent[1],
|
||||
VF_FIELD_ABS(z - p.center[2]) - p.roundedHalfExtent[2]};
|
||||
const VF_FieldReal ox = VF_FIELD_MAX(d[0], 0.0);
|
||||
const VF_FieldReal oy = VF_FIELD_MAX(d[1], 0.0);
|
||||
const VF_FieldReal oz = VF_FIELD_MAX(d[2], 0.0);
|
||||
const VF_FieldReal outside = VF_FIELD_SQRT(ox * ox + oy * oy + oz * oz);
|
||||
const VF_FieldReal inside = VF_FIELD_MIN(VF_FIELD_MAX(d[0], VF_FIELD_MAX(d[1], d[2])), 0.0);
|
||||
return VF_FIELD_CAST_F32(outside + inside - p.rounding);
|
||||
}
|
||||
const VF_FieldReal d[3] = {x - p.center[0], y - p.center[1], z - p.center[2]};
|
||||
VF_FieldReal q = 0.0;
|
||||
for (VF_FieldI32 i = 0; i < 3; ++i)
|
||||
for (VF_FieldI32 j = 0; j < 3; ++j)
|
||||
q += p.q[i * 3 + j] * d[i] * d[j] * 256.0;
|
||||
return VF_FIELD_CAST_F32((VF_FIELD_SQRT(VF_FIELD_MAX(0.0, q)) - 1.0) * p.minRadius);
|
||||
}
|
||||
|
||||
VF_FIELD_INLINE VF_FieldU32 VF_FieldMix(VF_FieldU32 x)
|
||||
{
|
||||
x ^= x >> 16;
|
||||
@@ -94,7 +254,7 @@ VF_FIELD_INLINE float VF_FieldSmoothUnit(float value)
|
||||
#if defined(VF_FIELD_HLSL)
|
||||
value = clamp(value, 0.0f, 1.0f);
|
||||
#else
|
||||
value = value < 0.0f ? 0.0f : (value > 1.0f ? 1.0f : value);
|
||||
value = std::clamp(value, 0.0f, 1.0f);
|
||||
#endif
|
||||
return value * value * (3.0f - 2.0f * value);
|
||||
}
|
||||
@@ -102,7 +262,11 @@ VF_FIELD_INLINE float VF_FieldSmoothUnit(float value)
|
||||
VF_FIELD_INLINE float VF_FieldSmoothMin(float a, float b, float width)
|
||||
{
|
||||
if (width <= 0.0f)
|
||||
#if defined(VF_FIELD_HLSL)
|
||||
return a < b ? a : b;
|
||||
#else
|
||||
return std::min(a, b);
|
||||
#endif
|
||||
const float delta = a - b;
|
||||
#if defined(VF_FIELD_HLSL)
|
||||
const float absolute = abs(delta);
|
||||
@@ -110,12 +274,17 @@ VF_FIELD_INLINE float VF_FieldSmoothMin(float a, float b, float width)
|
||||
const float h = max(width - absolute, 0.0f) / width;
|
||||
#else
|
||||
const float absolute = std::abs(delta);
|
||||
const float minimum = a < b ? a : b;
|
||||
const float h = (width - absolute) > 0.0f ? (width - absolute) / width : 0.0f;
|
||||
const float minimum = std::min(a, b);
|
||||
const float h = std::max(width - absolute, 0.0f) / width;
|
||||
#endif
|
||||
return minimum - (h * h * h) * (width / 6.0f);
|
||||
}
|
||||
|
||||
VF_FIELD_INLINE float VF_FieldSmoothMax(float a, float b, float width)
|
||||
{
|
||||
return -VF_FieldSmoothMin(-a, -b, width);
|
||||
}
|
||||
|
||||
// CPU and GPU use the same edge-root rule. The optional midpoint is the exact analytic
|
||||
// field evaluation used by the reference LOD0 mesher for roots close to an edge midpoint.
|
||||
VF_FIELD_INLINE float VF_FieldRefineRoot(float d1, float d2, float midpoint,
|
||||
@@ -171,3 +340,9 @@ VF_FIELD_INLINE float VF_FieldRefineRoot(float d1, float d2, float midpoint,
|
||||
#undef VF_FIELD_CAST_U32
|
||||
#undef VF_FIELD_CAST_F32
|
||||
#undef VF_FIELD_FLOOR
|
||||
#undef VF_FIELD_ABS
|
||||
#undef VF_FIELD_SQRT
|
||||
#undef VF_FIELD_MIN
|
||||
#undef VF_FIELD_MAX
|
||||
#undef VF_FIELD_CLAMP
|
||||
#undef VF_FIELD_LERP
|
||||
Reference in new issue
Block a user