proto: continuous swept road deck; coarse-LOD proxies keep thin pillars intact (L143)
Road: one swept-deck stamp on a shared cubic-Hermite centerline (stamp vocabulary V5), 376.8 m, max 8.5 deg, walk audit clean, short-road control fails as expected. Pillars: regular fillPillar stamps get deterministic 24-sided capped-cylinder proxies once the LOD step exceeds their radius or half-height (applies to all worlds). Owner hashes unchanged (v227); five-fixture Clang/GCC determinism; fast tier green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
6cd9e4da76
commit
a4e214561f
19 files changed
+1167
-73
No files matched your search
@@ -892,6 +892,7 @@ namespace VoxelForgeCore
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV2 = 2;
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV3 = 3;
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV4 = 4;
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV5 = 5;
|
||||
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV1 = 1;
|
||||
inline constexpr std::uint32_t ResolvedLayoutOreVeinVocabularyVersionV1 = 1;
|
||||
enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict };
|
||||
@@ -918,6 +919,9 @@ namespace VoxelForgeCore
|
||||
std::array<std::int64_t, 3> anchorPositionMicrometres{};
|
||||
// Present only for fillSweptDeckSegment; defines its directed centerline.
|
||||
std::array<std::array<std::int64_t, 3>, 2> sweepEndpointsMicrometres{};
|
||||
// V5 permits one continuous swept deck to carry its complete sampled centerline.
|
||||
// Empty retains the V4 single-segment interpretation.
|
||||
std::vector<std::array<std::int64_t, 3>> sweepPathMicrometres;
|
||||
std::int64_t placementRadiusMicrometres = 0;
|
||||
std::array<std::int64_t, 3> halfExtentsMicrometres{};
|
||||
std::int64_t smoothWidthMicrometres = 0;
|
||||
@@ -938,6 +942,7 @@ namespace VoxelForgeCore
|
||||
ResolvedSdfStampKindV1 kind = ResolvedSdfStampKindV1::carveEllipsoid;
|
||||
std::array<std::int64_t, 3> positionMicrometres{};
|
||||
std::array<std::array<std::int64_t, 3>, 2> sweepEndpointsMicrometres{};
|
||||
std::vector<std::array<std::int64_t, 3>> sweepPathMicrometres;
|
||||
std::array<std::int64_t, 3> boundsMinimumMicrometres{};
|
||||
std::array<std::int64_t, 3> boundsMaximumMicrometres{};
|
||||
std::array<std::int64_t, 3> halfExtentsMicrometres{};
|
||||
|
||||
@@ -599,6 +599,7 @@ namespace
|
||||
<<",\"irregularity_seed\":"<<stamp.irregularitySeed
|
||||
<<",\"yaw_quarter_turns\":"<<static_cast<unsigned>(stamp.yawQuarterTurns);
|
||||
if(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
{
|
||||
out<<",\"sweep_endpoints_um\":[["
|
||||
<<stamp.sweepEndpointsMicrometres[0][0]<<','
|
||||
<<stamp.sweepEndpointsMicrometres[0][1]<<','
|
||||
@@ -606,6 +607,19 @@ namespace
|
||||
<<stamp.sweepEndpointsMicrometres[1][0]<<','
|
||||
<<stamp.sweepEndpointsMicrometres[1][1]<<','
|
||||
<<stamp.sweepEndpointsMicrometres[1][2]<<"]]";
|
||||
if(!stamp.sweepPathMicrometres.empty())
|
||||
{
|
||||
out<<",\"sweep_path_um\":[";
|
||||
for(std::size_t pointIndex=0;pointIndex<stamp.sweepPathMicrometres.size();
|
||||
++pointIndex)
|
||||
{
|
||||
if(pointIndex)out<<',';
|
||||
const auto& point=stamp.sweepPathMicrometres[pointIndex];
|
||||
out<<'['<<point[0]<<','<<point[1]<<','<<point[2]<<']';
|
||||
}
|
||||
out<<']';
|
||||
}
|
||||
}
|
||||
if(s.stampVocabularyVersion>=ResolvedLayoutStampVocabularyVersionV3)
|
||||
out<<",\"rounding_radius_um\":"<<stamp.roundingRadiusMicrometres
|
||||
<<",\"minimum_spacing_um\":"<<stamp.minimumSpacingMicrometres
|
||||
@@ -1143,8 +1157,9 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
else if(stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV1
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV2
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV3
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV4)
|
||||
add("stamp_vocabulary_version","resolved stamps require supported vocabulary version 1, 2, 3 or 4");
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV4
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV5)
|
||||
add("stamp_vocabulary_version","resolved stamps require supported vocabulary version 1 through 5");
|
||||
std::vector<std::string> stampIds;
|
||||
for(const auto& stamp:stamps)
|
||||
{
|
||||
@@ -1184,9 +1199,30 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
-stamp.sweepEndpointsMicrometres[0][axis];
|
||||
lengthSquared+=delta*delta;
|
||||
}
|
||||
if(!(lengthSquared>0.0)
|
||||
||std::sqrt(lengthSquared)>2.0*stamp.halfExtentsMicrometres[0]+1.0)
|
||||
if(stamp.sweepPathMicrometres.empty()&&(!(lengthSquared>0.0)
|
||||
||std::sqrt(lengthSquared)>2.0*stamp.halfExtentsMicrometres[0]+1.0))
|
||||
add("stamp_sweep",stamp.stableId+" has invalid endpoints or longitudinal extent");
|
||||
if(!stamp.sweepPathMicrometres.empty())
|
||||
{
|
||||
bool validPath=stampVocabularyVersion>=ResolvedLayoutStampVocabularyVersionV5
|
||||
&&stamp.sweepPathMicrometres.size()>=2
|
||||
&&stamp.sweepPathMicrometres.size()<=1025
|
||||
&&stamp.sweepPathMicrometres.front()==stamp.sweepEndpointsMicrometres[0]
|
||||
&&stamp.sweepPathMicrometres.back()==stamp.sweepEndpointsMicrometres[1];
|
||||
for(std::size_t pointIndex=0;pointIndex<stamp.sweepPathMicrometres.size();
|
||||
++pointIndex)
|
||||
{
|
||||
const auto& point=stamp.sweepPathMicrometres[pointIndex];
|
||||
for(const auto coordinate:point)
|
||||
validPath=validPath&&coordinate>=-1000000000000ll
|
||||
&&coordinate<=1000000000000ll;
|
||||
if(pointIndex>0)
|
||||
validPath=validPath
|
||||
&&stamp.sweepPathMicrometres[pointIndex-1][0]<point[0];
|
||||
}
|
||||
if(!validPath)add("stamp_sweep_path",stamp.stableId
|
||||
+" has an invalid V5 path or non-increasing longitudinal axis");
|
||||
}
|
||||
}
|
||||
if(motif)
|
||||
{
|
||||
@@ -1215,7 +1251,8 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
const std::int64_t pad=motif
|
||||
?(stamp.smoothWidthMicrometres+5)/6+stamp.irregularityAmplitudeMicrometres
|
||||
:stamp.smoothWidthMicrometres+stamp.irregularityAmplitudeMicrometres;
|
||||
if(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
if(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment
|
||||
&&stamp.sweepPathMicrometres.empty())
|
||||
{
|
||||
double radiusSquared=0.0;
|
||||
for(const auto extent:stamp.halfExtentsMicrometres)
|
||||
@@ -1235,6 +1272,29 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
add("stamp_bounds",stamp.stableId+" conservative sweep bounds do not match its parameters");
|
||||
}
|
||||
}
|
||||
else if(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
{
|
||||
double radiusSquared=0.0;
|
||||
for(std::size_t axis=1;axis<3;++axis)
|
||||
{
|
||||
const double expanded=static_cast<double>(stamp.halfExtentsMicrometres[axis]+pad);
|
||||
radiusSquared+=expanded*expanded;
|
||||
}
|
||||
const auto radius=static_cast<std::int64_t>(std::ceil(std::sqrt(radiusSquared)));
|
||||
std::array<std::int64_t,3> minimum=stamp.sweepPathMicrometres.front();
|
||||
std::array<std::int64_t,3> maximum=minimum;
|
||||
for(const auto& point:stamp.sweepPathMicrometres)
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
minimum[axis]=std::min(minimum[axis],point[axis]);
|
||||
maximum[axis]=std::max(maximum[axis],point[axis]);
|
||||
}
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
if(stamp.boundsMinimumMicrometres[axis]!=minimum[axis]-radius
|
||||
||stamp.boundsMaximumMicrometres[axis]!=maximum[axis]+radius)
|
||||
add("stamp_bounds",stamp.stableId
|
||||
+" conservative continuous-sweep bounds do not match its path");
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::array<std::int64_t,3> extents={
|
||||
@@ -1556,6 +1616,14 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
||||
for(std::size_t axis=0;axis<3&&axis<values.size();++axis)
|
||||
stamp.sweepEndpointsMicrometres[endpoint][axis]=values[axis].Integer();
|
||||
}
|
||||
for(const auto& value:v.Get("sweep_path_um").AsArray())
|
||||
{
|
||||
std::array<std::int64_t,3> point{};
|
||||
const auto& values=value.AsArray();
|
||||
for(std::size_t axis=0;axis<3&&axis<values.size();++axis)
|
||||
point[axis]=values[axis].Integer();
|
||||
stamp.sweepPathMicrometres.push_back(point);
|
||||
}
|
||||
readTriplet("bounds_min_um",stamp.boundsMinimumMicrometres);
|
||||
readTriplet("bounds_max_um",stamp.boundsMaximumMicrometres);
|
||||
readTriplet("half_extents_um",stamp.halfExtentsMicrometres);
|
||||
@@ -1678,10 +1746,15 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
std::sort(ordered.begin(),ordered.end(),[](const auto& a,const auto& b)
|
||||
{return a.stableId<b.stableId;});
|
||||
ResolvedLayoutSnapshotV3 candidate=layout;
|
||||
const bool hasContinuousSweptDeck=std::any_of(ordered.begin(),ordered.end(),[](const auto& request)
|
||||
{return request.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment
|
||||
&&!request.sweepPathMicrometres.empty();});
|
||||
const bool hasSweptDeck=std::any_of(ordered.begin(),ordered.end(),[](const auto& request)
|
||||
{return request.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment;});
|
||||
candidate.stampVocabularyVersion=hasSweptDeck
|
||||
?ResolvedLayoutStampVocabularyVersionV4:ResolvedLayoutStampVocabularyVersionV1;
|
||||
candidate.stampVocabularyVersion=hasContinuousSweptDeck
|
||||
?ResolvedLayoutStampVocabularyVersionV5
|
||||
:hasSweptDeck?ResolvedLayoutStampVocabularyVersionV4
|
||||
:ResolvedLayoutStampVocabularyVersionV1;
|
||||
candidate.stamps.clear();candidate.stamps.reserve(ordered.size());
|
||||
for(std::size_t requestIndex=0;requestIndex<ordered.size();++requestIndex)
|
||||
{
|
||||
@@ -1712,7 +1785,8 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
for(const auto coordinate:request.anchorPositionMicrometres)
|
||||
if(coordinate<-1000000000000ll||coordinate>1000000000000ll)
|
||||
{error="stamp request "+request.stableId+" has an unbounded anchor";return false;}
|
||||
if(request.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
if(request.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment
|
||||
&&request.sweepPathMicrometres.empty())
|
||||
{
|
||||
double lengthSquared=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
@@ -1729,6 +1803,25 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
||std::sqrt(lengthSquared)>2.0*request.halfExtentsMicrometres[0]+1.0)
|
||||
{error="stamp request "+request.stableId+" has invalid swept-segment extents";return false;}
|
||||
}
|
||||
if(request.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment
|
||||
&&!request.sweepPathMicrometres.empty())
|
||||
{
|
||||
if(request.sweepPathMicrometres.size()<2
|
||||
||request.sweepPathMicrometres.size()>1025
|
||||
||request.sweepPathMicrometres.front()!=request.sweepEndpointsMicrometres[0]
|
||||
||request.sweepPathMicrometres.back()!=request.sweepEndpointsMicrometres[1])
|
||||
{error="stamp request "+request.stableId+" has an invalid continuous deck path";return false;}
|
||||
for(std::size_t pointIndex=0;pointIndex<request.sweepPathMicrometres.size();
|
||||
++pointIndex)
|
||||
{
|
||||
const auto& point=request.sweepPathMicrometres[pointIndex];
|
||||
for(const auto coordinate:point)
|
||||
if(coordinate<-1000000000000ll||coordinate>1000000000000ll)
|
||||
{error="stamp request "+request.stableId+" has an unbounded path point";return false;}
|
||||
if(pointIndex>0&&request.sweepPathMicrometres[pointIndex-1][0]>=point[0])
|
||||
{error="stamp request "+request.stableId+" path X coordinates must increase strictly";return false;}
|
||||
}
|
||||
}
|
||||
|
||||
// The shared JSON value type stores numbers as IEEE-754 doubles; keep the
|
||||
// serialized seed in its exact integer range so the canonical round trip holds.
|
||||
@@ -1748,6 +1841,7 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
ResolvedLayoutStampV1 stamp;stamp.stableId=request.stableId;
|
||||
stamp.strateIndex=request.strateIndex;stamp.kind=request.kind;
|
||||
stamp.sweepEndpointsMicrometres=request.sweepEndpointsMicrometres;
|
||||
stamp.sweepPathMicrometres=request.sweepPathMicrometres;
|
||||
stamp.halfExtentsMicrometres=request.halfExtentsMicrometres;
|
||||
stamp.smoothWidthMicrometres=request.smoothWidthMicrometres;
|
||||
stamp.irregularityAmplitudeMicrometres=request.irregularityAmplitudeMicrometres;
|
||||
@@ -1769,7 +1863,8 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
stamp.positionMicrometres[axis]=request.anchorPositionMicrometres[axis]+offset[axis];
|
||||
const std::int64_t pad=stamp.smoothWidthMicrometres
|
||||
+stamp.irregularityAmplitudeMicrometres;
|
||||
if(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
if(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment
|
||||
&&stamp.sweepPathMicrometres.empty())
|
||||
{
|
||||
double radiusSquared=0.0;
|
||||
for(const auto extent:stamp.halfExtentsMicrometres)
|
||||
@@ -1788,6 +1883,30 @@ bool ResolveResolvedLayoutStampsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
stamp.boundsMaximumMicrometres[axis]=maximum+radius;
|
||||
}
|
||||
}
|
||||
else if(stamp.kind==ResolvedSdfStampKindV1::fillSweptDeckSegment)
|
||||
{
|
||||
double radiusSquared=0.0;
|
||||
for(std::size_t axis=1;axis<3;++axis)
|
||||
{
|
||||
const double expanded=static_cast<double>(stamp.halfExtentsMicrometres[axis]+pad);
|
||||
radiusSquared+=expanded*expanded;
|
||||
}
|
||||
const std::int64_t radius=static_cast<std::int64_t>(
|
||||
std::ceil(std::sqrt(radiusSquared)));
|
||||
std::array<std::int64_t,3> minimum=stamp.sweepPathMicrometres.front();
|
||||
std::array<std::int64_t,3> maximum=minimum;
|
||||
for(const auto& point:stamp.sweepPathMicrometres)
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
minimum[axis]=std::min(minimum[axis],point[axis]);
|
||||
maximum[axis]=std::max(maximum[axis],point[axis]);
|
||||
}
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
stamp.boundsMinimumMicrometres[axis]=minimum[axis]-radius;
|
||||
stamp.boundsMaximumMicrometres[axis]=maximum[axis]+radius;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::array<std::int64_t,3> extents={
|
||||
|
||||
@@ -3536,6 +3536,7 @@ private:
|
||||
const VoxelForgeCore::ResolvedLayoutStampV1* record=nullptr;
|
||||
std::array<float,3> positionVoxels{},halfExtentsVoxels{};
|
||||
std::array<std::array<float,3>,2> sweepEndpointsVoxels{};
|
||||
std::vector<std::array<float,3>> sweepPathVoxels;
|
||||
float smoothWidthVoxels=0.0f;
|
||||
float irregularityAmplitudeVoxels=0.0f;
|
||||
float irregularityWavelengthVoxels=0.0f;
|
||||
@@ -3710,6 +3711,15 @@ private:
|
||||
static_cast<double>(stamp.sweepEndpointsMicrometres[endpoint][axis])
|
||||
/Owner->CoreVoxelSizeMicrons);
|
||||
}
|
||||
runtime.sweepPathVoxels.reserve(stamp.sweepPathMicrometres.size());
|
||||
for(const auto& pointMicrometres:stamp.sweepPathMicrometres)
|
||||
{
|
||||
std::array<float,3> point{};
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
point[axis]=static_cast<float>(static_cast<double>(pointMicrometres[axis])
|
||||
/Owner->CoreVoxelSizeMicrons);
|
||||
runtime.sweepPathVoxels.push_back(point);
|
||||
}
|
||||
const std::size_t xAxis=stamp.yawQuarterTurns%2?1:0;
|
||||
const std::size_t yAxis=stamp.yawQuarterTurns%2?0:1;
|
||||
runtime.halfExtentsVoxels={
|
||||
@@ -5169,6 +5179,20 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
static std::int32_t& CurrentStampSamplingStep() noexcept
|
||||
{
|
||||
static thread_local std::int32_t Step=0;
|
||||
return Step;
|
||||
}
|
||||
struct FScopedStampSamplingStep
|
||||
{
|
||||
explicit FScopedStampSamplingStep(std::int32_t Step)
|
||||
:Previous(CurrentStampSamplingStep())
|
||||
{CurrentStampSamplingStep()=std::max(0,Step);}
|
||||
~FScopedStampSamplingStep(){CurrentStampSamplingStep()=Previous;}
|
||||
std::int32_t Previous=0;
|
||||
};
|
||||
|
||||
bool GetNativeSurfaceHeightsAt(float X,float Y,int32 ChunkZ,
|
||||
float& OutGroundZ,float& OutCeilingZ) const
|
||||
{
|
||||
@@ -7123,6 +7147,111 @@ private:
|
||||
const float top=std::max(StampEllipsoidSdf(x,y,z,halfSize),-z);
|
||||
return SmoothMinDistance(lower,top,round*2.0f);
|
||||
}
|
||||
static float StampContinuousSweptDeckSdf(const FStampRuntime& stamp,
|
||||
float x,float y,float z)
|
||||
{
|
||||
const auto& path=stamp.sweepPathVoxels;
|
||||
if(path.size()<2)return std::numeric_limits<float>::infinity();
|
||||
const float halfWidth=stamp.halfExtentsVoxels[1];
|
||||
const float halfThickness=stamp.halfExtentsVoxels[2];
|
||||
const float round=std::min(0.35f,std::min(halfWidth,halfThickness)*0.15f);
|
||||
const float searchMargin=halfWidth+halfThickness+2.0f;
|
||||
const auto first=std::lower_bound(path.begin(),path.end(),x-searchMargin,
|
||||
[](const auto& point,float value){return point[0]<value;});
|
||||
const auto last=std::upper_bound(first,path.end(),x+searchMargin,
|
||||
[](float value,const auto& point){return value<point[0];});
|
||||
const std::size_t firstIndex=static_cast<std::size_t>(first-path.begin());
|
||||
const std::size_t lastIndex=static_cast<std::size_t>(last-path.begin());
|
||||
const std::size_t segmentBegin=std::min(path.size()-2,
|
||||
firstIndex==0?std::size_t{0}:firstIndex-1);
|
||||
const std::size_t segmentEnd=std::min(path.size()-2,lastIndex);
|
||||
std::size_t nearestSegment=path.size();
|
||||
float nearestProjection=0.0f;
|
||||
float nearestDistanceSquared=std::numeric_limits<float>::infinity();
|
||||
for(std::size_t segment=segmentBegin;segment<=segmentEnd;++segment)
|
||||
{
|
||||
const auto& a=path[segment];const auto& b=path[segment+1];
|
||||
const float dx=b[0]-a[0],dy=b[1]-a[1];
|
||||
const float horizontalSquared=dx*dx+dy*dy;
|
||||
if(!(horizontalSquared>1.0e-10f))continue;
|
||||
const float projection=std::clamp(((x-a[0])*dx+(y-a[1])*dy)
|
||||
/horizontalSquared,0.0f,1.0f);
|
||||
const float nearestX=a[0]+projection*dx;
|
||||
const float nearestY=a[1]+projection*dy;
|
||||
const float offsetX=x-nearestX,offsetY=y-nearestY;
|
||||
const float distanceSquared=offsetX*offsetX+offsetY*offsetY;
|
||||
if(distanceSquared<nearestDistanceSquared
|
||||
||(distanceSquared==nearestDistanceSquared&&segment<nearestSegment))
|
||||
{
|
||||
nearestSegment=segment;
|
||||
nearestProjection=projection;
|
||||
nearestDistanceSquared=distanceSquared;
|
||||
}
|
||||
}
|
||||
if(nearestSegment>=path.size()-1)return std::numeric_limits<float>::infinity();
|
||||
const auto evalCenterline=[&](float parameter,std::array<float,3>& center,
|
||||
std::array<float,3>& tangent)
|
||||
{
|
||||
const auto& p0=path[nearestSegment];
|
||||
const auto& p1=path[nearestSegment+1];
|
||||
const auto& before=path[nearestSegment==0?nearestSegment:nearestSegment-1];
|
||||
const auto& after=path[nearestSegment+2<path.size()
|
||||
?nearestSegment+2:nearestSegment+1];
|
||||
const float t2=parameter*parameter,t3=t2*parameter;
|
||||
const float h00=2.0f*t3-3.0f*t2+1.0f;
|
||||
const float h10=t3-2.0f*t2+parameter;
|
||||
const float h01=-2.0f*t3+3.0f*t2;
|
||||
const float h11=t3-t2;
|
||||
const float d00=6.0f*t2-6.0f*parameter;
|
||||
const float d10=3.0f*t2-4.0f*parameter+1.0f;
|
||||
const float d01=-6.0f*t2+6.0f*parameter;
|
||||
const float d11=3.0f*t2-2.0f*parameter;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const float m0=nearestSegment==0?p1[axis]-p0[axis]
|
||||
:0.5f*(p1[axis]-before[axis]);
|
||||
const float m1=nearestSegment+2>=path.size()?p1[axis]-p0[axis]
|
||||
:0.5f*(after[axis]-p0[axis]);
|
||||
center[axis]=h00*p0[axis]+h10*m0+h01*p1[axis]+h11*m1;
|
||||
tangent[axis]=d00*p0[axis]+d10*m0+d01*p1[axis]+d11*m1;
|
||||
}
|
||||
};
|
||||
std::array<float,3> center{},tangent{};
|
||||
for(int refinement=0;refinement<2;++refinement)
|
||||
{
|
||||
evalCenterline(nearestProjection,center,tangent);
|
||||
const float derivativeSquared=tangent[0]*tangent[0]+tangent[1]*tangent[1];
|
||||
if(!(derivativeSquared>1.0e-10f))return std::numeric_limits<float>::infinity();
|
||||
const float offsetX=center[0]-x,offsetY=center[1]-y;
|
||||
nearestProjection=std::clamp(nearestProjection
|
||||
-(offsetX*tangent[0]+offsetY*tangent[1])/derivativeSquared,0.0f,1.0f);
|
||||
}
|
||||
evalCenterline(nearestProjection,center,tangent);
|
||||
const float tangentLength=std::sqrt(tangent[0]*tangent[0]
|
||||
+tangent[1]*tangent[1]+tangent[2]*tangent[2]);
|
||||
if(!(tangentLength>1.0e-8f))return std::numeric_limits<float>::infinity();
|
||||
for(float& component:tangent)component/=tangentLength;
|
||||
const float horizontalLength=std::sqrt(tangent[0]*tangent[0]
|
||||
+tangent[1]*tangent[1]);
|
||||
if(!(horizontalLength>1.0e-8f))return std::numeric_limits<float>::infinity();
|
||||
const std::array<float,3> side={-tangent[1]/horizontalLength,
|
||||
tangent[0]/horizontalLength,0.0f};
|
||||
const std::array<float,3> up={-tangent[2]*side[1],tangent[2]*side[0],
|
||||
tangent[0]*side[1]-tangent[1]*side[0]};
|
||||
const std::array<float,3> relative={x-center[0],y-center[1],z-center[2]};
|
||||
const auto dot=[&](const std::array<float,3>& axis)
|
||||
{return relative[0]*axis[0]+relative[1]*axis[1]+relative[2]*axis[2];};
|
||||
const float lateral=dot(side),vertical=dot(up);
|
||||
if((nearestSegment==0&&nearestProjection<=0.0f)
|
||||
||(nearestSegment+2==path.size()&&nearestProjection>=1.0f))
|
||||
return StampRoundedBoxSdf(dot(tangent),lateral,vertical,
|
||||
{halfThickness+round,halfWidth,halfThickness},round);
|
||||
const float qSide=std::abs(lateral)-halfWidth+round;
|
||||
const float qUp=std::abs(vertical)-halfThickness+round;
|
||||
const float outside=std::sqrt(std::max(qSide,0.0f)*std::max(qSide,0.0f)
|
||||
+std::max(qUp,0.0f)*std::max(qUp,0.0f));
|
||||
return outside+std::min(std::max(qSide,qUp),0.0f)-round;
|
||||
}
|
||||
float StampSdf(const FStampRuntime& stamp,float x,float y,float z) const
|
||||
{
|
||||
float localX=x-stamp.positionVoxels[0];
|
||||
@@ -7161,6 +7290,11 @@ private:
|
||||
break;
|
||||
case VoxelForgeCore::ResolvedSdfStampKindV1::fillSweptDeckSegment:
|
||||
{
|
||||
if(!stamp.sweepPathVoxels.empty())
|
||||
{
|
||||
distance=StampContinuousSweptDeckSdf(stamp,x,y,z);
|
||||
break;
|
||||
}
|
||||
std::array<float,3> tangent{},side{},up{},delta{};
|
||||
float lengthSquared=0.0f,horizontalSquared=0.0f;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
@@ -7199,6 +7333,15 @@ private:
|
||||
|
||||
static double StampAbsMinimum(double minimum,double maximum)
|
||||
{return minimum<=0.0&&maximum>=0.0?0.0:std::min(std::abs(minimum),std::abs(maximum));}
|
||||
static bool UsesCoarsePillarProxy(const FStampRuntime& stamp,std::int32_t sampleStep)
|
||||
{
|
||||
if(!stamp.record||stamp.record->kind
|
||||
!=VoxelForgeCore::ResolvedSdfStampKindV1::fillPillar
|
||||
||sampleStep<=0||stamp.irregularityAmplitudeVoxels>0.0f)return false;
|
||||
return static_cast<float>(sampleStep)>std::min(stamp.halfExtentsVoxels[0],
|
||||
stamp.halfExtentsVoxels[1])
|
||||
||static_cast<float>(sampleStep)>stamp.halfExtentsVoxels[2];
|
||||
}
|
||||
static double StampRoundedBoxSdfDouble(const std::array<double,3>& absolute,
|
||||
const std::array<double,3>& halfSize,double rounding)
|
||||
{
|
||||
@@ -7300,6 +7443,7 @@ private:
|
||||
if(index>=StampRuntime.size())return;
|
||||
const auto& runtime=StampRuntime[index];const auto& record=*runtime.record;
|
||||
if(record.strateIndex!=bound->StrateIndex)return;
|
||||
if(UsesCoarsePillarProxy(runtime,CurrentStampSamplingStep()))return;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
if(worldMicrometres[axis]<record.boundsMinimumMicrometres[axis]
|
||||
||worldMicrometres[axis]>record.boundsMaximumMicrometres[axis])return;
|
||||
@@ -7317,6 +7461,154 @@ private:
|
||||
apply(static_cast<std::uint32_t>(index));
|
||||
return composed;
|
||||
}
|
||||
|
||||
public:
|
||||
std::uint32_t AppendCoarsePillarProxies(FVoxelMeshData& mesh,
|
||||
const FIntVector& tileOrigin,std::int32_t tileExtent,std::int32_t sampleStep) const
|
||||
{
|
||||
if(!Owner||Owner->CoreVoxelSizeMicrons==0||tileExtent<=0||sampleStep<=0
|
||||
||StampRuntime.empty())return 0;
|
||||
constexpr int32 sideCount=24;
|
||||
constexpr double TwoPi=6.2831853071795864769;
|
||||
const int32 originalVertexCount=mesh.Vertices.Num();
|
||||
const int32 originalSurfaceVertices=std::clamp(mesh.NumSurfaceVertices,0,
|
||||
originalVertexCount);
|
||||
const bool hasNormals=mesh.Normals.Num()==originalVertexCount;
|
||||
const bool hasUvs=mesh.UVs.Num()==originalVertexCount;
|
||||
const bool hasColors=mesh.Colors.Num()==originalVertexCount;
|
||||
const double voxelCentimetres=Owner->CoreVoxelSizeMicrons/10000.0;
|
||||
const std::int64_t tileMinimum[3]={tileOrigin.X,tileOrigin.Y,tileOrigin.Z};
|
||||
const std::int64_t tileMaximum[3]={static_cast<std::int64_t>(tileOrigin.X)+tileExtent,
|
||||
static_cast<std::int64_t>(tileOrigin.Y)+tileExtent,
|
||||
static_cast<std::int64_t>(tileOrigin.Z)+tileExtent};
|
||||
std::vector<int32> proxyIndices;
|
||||
std::uint32_t proxyCount=0;
|
||||
const auto addVertex=[&](const FVector& position,const FVector& normal,
|
||||
const FVector2f& uv)
|
||||
{
|
||||
const int32 index=mesh.Vertices.Add(position);
|
||||
if(hasNormals)mesh.Normals.Add(normal);
|
||||
if(hasUvs)mesh.UVs.Add(uv);
|
||||
if(hasColors)mesh.Colors.Add(FColor::White);
|
||||
return index;
|
||||
};
|
||||
for(const auto& stamp:StampRuntime)
|
||||
{
|
||||
if(!UsesCoarsePillarProxy(stamp,sampleStep))continue;
|
||||
const auto& center=stamp.positionVoxels;
|
||||
if(center[0]<tileMinimum[0]||center[0]>=tileMaximum[0]
|
||||
||center[1]<tileMinimum[1]||center[1]>=tileMaximum[1]
|
||||
||center[2]<tileMinimum[2]||center[2]>=tileMaximum[2])continue;
|
||||
const double radiusX=stamp.halfExtentsVoxels[0];
|
||||
const double radiusY=stamp.halfExtentsVoxels[1];
|
||||
const double bottomVoxel=center[2]-stamp.halfExtentsVoxels[2]-1.0;
|
||||
const double topVoxel=center[2]+stamp.halfExtentsVoxels[2];
|
||||
const auto point=[&](double angle,double z)
|
||||
{
|
||||
return FVector((center[0]+radiusX*std::cos(angle))*voxelCentimetres,
|
||||
(center[1]+radiusY*std::sin(angle))*voxelCentimetres,
|
||||
z*voxelCentimetres);
|
||||
};
|
||||
const int32 capTopCenter=addVertex(FVector(center[0]*voxelCentimetres,
|
||||
center[1]*voxelCentimetres,topVoxel*voxelCentimetres),
|
||||
FVector(0.0,0.0,1.0),FVector2f(0.5f,0.5f));
|
||||
const int32 capBottomCenter=addVertex(FVector(center[0]*voxelCentimetres,
|
||||
center[1]*voxelCentimetres,bottomVoxel*voxelCentimetres),
|
||||
FVector(0.0,0.0,-1.0),FVector2f(0.5f,0.5f));
|
||||
for(int32 sideIndex=0;sideIndex<sideCount;++sideIndex)
|
||||
{
|
||||
const double u0=static_cast<double>(sideIndex)/sideCount;
|
||||
const double u1=static_cast<double>(sideIndex+1)/sideCount;
|
||||
const double a0=TwoPi*u0,a1=TwoPi*u1;
|
||||
const FVector n0=FVector(std::cos(a0)/radiusX,
|
||||
std::sin(a0)/radiusY,0.0).GetSafeNormal();
|
||||
const FVector n1=FVector(std::cos(a1)/radiusX,
|
||||
std::sin(a1)/radiusY,0.0).GetSafeNormal();
|
||||
const int32 bottomA=addVertex(point(a0,bottomVoxel),n0,FVector2f(u0,0.0f));
|
||||
const int32 bottomB=addVertex(point(a1,bottomVoxel),n1,FVector2f(u1,0.0f));
|
||||
const int32 topB=addVertex(point(a1,topVoxel),n1,FVector2f(u1,1.0f));
|
||||
const int32 topA=addVertex(point(a0,topVoxel),n0,FVector2f(u0,1.0f));
|
||||
proxyIndices.insert(proxyIndices.end(),{bottomA,bottomB,topB,
|
||||
bottomA,topB,topA});
|
||||
|
||||
const int32 topAIndex=addVertex(point(a0,topVoxel),FVector(0.0,0.0,1.0),
|
||||
FVector2f(static_cast<float>(0.5+0.5*std::cos(a0)),
|
||||
static_cast<float>(0.5+0.5*std::sin(a0))));
|
||||
const int32 topBIndex=addVertex(point(a1,topVoxel),FVector(0.0,0.0,1.0),
|
||||
FVector2f(static_cast<float>(0.5+0.5*std::cos(a1)),
|
||||
static_cast<float>(0.5+0.5*std::sin(a1))));
|
||||
proxyIndices.insert(proxyIndices.end(),{capTopCenter,topAIndex,topBIndex});
|
||||
|
||||
const int32 bottomAIndex=addVertex(point(a0,bottomVoxel),FVector(0.0,0.0,-1.0),
|
||||
FVector2f(static_cast<float>(0.5+0.5*std::cos(a0)),
|
||||
static_cast<float>(0.5+0.5*std::sin(a0))));
|
||||
const int32 bottomBIndex=addVertex(point(a1,bottomVoxel),FVector(0.0,0.0,-1.0),
|
||||
FVector2f(static_cast<float>(0.5+0.5*std::cos(a1)),
|
||||
static_cast<float>(0.5+0.5*std::sin(a1))));
|
||||
proxyIndices.insert(proxyIndices.end(),
|
||||
{capBottomCenter,bottomBIndex,bottomAIndex});
|
||||
}
|
||||
++proxyCount;
|
||||
}
|
||||
if(proxyIndices.empty())return 0;
|
||||
const int32 proxyVertexCount=mesh.Vertices.Num()-originalVertexCount;
|
||||
const auto insertProxyVertices=[&](auto& values)
|
||||
{
|
||||
if(values.Num()!=originalVertexCount+proxyVertexCount)return;
|
||||
using FArray=std::remove_reference_t<decltype(values)>;
|
||||
FArray reordered;
|
||||
reordered.Reserve(values.Num());
|
||||
for(int32 index=0;index<originalSurfaceVertices;++index)
|
||||
reordered.Add(values[index]);
|
||||
for(int32 index=originalVertexCount;index<values.Num();++index)
|
||||
reordered.Add(values[index]);
|
||||
for(int32 index=originalSurfaceVertices;index<originalVertexCount;++index)
|
||||
reordered.Add(values[index]);
|
||||
values=MoveTemp(reordered);
|
||||
};
|
||||
insertProxyVertices(mesh.Vertices);
|
||||
if(hasNormals)insertProxyVertices(mesh.Normals);
|
||||
if(hasUvs)insertProxyVertices(mesh.UVs);
|
||||
if(hasColors)insertProxyVertices(mesh.Colors);
|
||||
|
||||
const int32 oldTriangleCount=mesh.Triangles.Num()/3;
|
||||
const int32 ceilingStart=std::max(0,mesh.Triangles.Num()-mesh.NumCeilingTriangles*3);
|
||||
const int32 groundTriangleCount=ceilingStart/3;
|
||||
const int32 proxyTriangleCount=static_cast<int32>(proxyIndices.size()/3);
|
||||
TArray<int32> mergedIndices;
|
||||
mergedIndices.Reserve(mesh.Triangles.Num()+static_cast<int32>(proxyIndices.size()));
|
||||
for(int32 index=0;index<ceilingStart;++index)
|
||||
{
|
||||
const int32 oldIndex=mesh.Triangles[index];
|
||||
mergedIndices.Add(oldIndex>=originalSurfaceVertices
|
||||
?oldIndex+proxyVertexCount:oldIndex);
|
||||
}
|
||||
for(const int32 index:proxyIndices)
|
||||
mergedIndices.Add(originalSurfaceVertices+index-originalVertexCount);
|
||||
for(int32 index=ceilingStart;index<mesh.Triangles.Num();++index)
|
||||
{
|
||||
const int32 oldIndex=mesh.Triangles[index];
|
||||
mergedIndices.Add(oldIndex>=originalSurfaceVertices
|
||||
?oldIndex+proxyVertexCount:oldIndex);
|
||||
}
|
||||
mesh.Triangles=MoveTemp(mergedIndices);
|
||||
|
||||
TArray<uint8> mergedMasks;
|
||||
mergedMasks.Reserve(oldTriangleCount+proxyTriangleCount);
|
||||
for(int32 triangle=0;triangle<groundTriangleCount;++triangle)
|
||||
mergedMasks.Add(mesh.TriangleSkirtFaceMasks.IsValidIndex(triangle)
|
||||
?mesh.TriangleSkirtFaceMasks[triangle]:0);
|
||||
for(int32 triangle=0;triangle<proxyTriangleCount;++triangle)mergedMasks.Add(0);
|
||||
if(groundTriangleCount<oldTriangleCount)
|
||||
for(int32 triangle=groundTriangleCount;triangle<oldTriangleCount;++triangle)
|
||||
mergedMasks.Add(mesh.TriangleSkirtFaceMasks.IsValidIndex(triangle)
|
||||
?mesh.TriangleSkirtFaceMasks[triangle]:0);
|
||||
mesh.TriangleSkirtFaceMasks=MoveTemp(mergedMasks);
|
||||
mesh.NumSurfaceVertices=originalSurfaceVertices+proxyVertexCount;
|
||||
return proxyCount;
|
||||
}
|
||||
|
||||
private:
|
||||
float ApplyResolvedCavernGraph(float density,float X,float Y,float Z,
|
||||
float* nativeFloorSupport=nullptr) const
|
||||
{
|
||||
|
||||
@@ -2889,12 +2889,16 @@ namespace VoxelForgeCore
|
||||
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
||||
const auto MarchingCubesStart=std::chrono::steady_clock::now();
|
||||
FVoxelUniformBlockProofAuditStats BlockProofAudit;
|
||||
FVoxelMeshData mesh=mesher.GenerateMesh(origin,MeshStep,MeshCells,nullptr,
|
||||
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
||||
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr,
|
||||
options.profileMesherStages?&McStageTimings:nullptr,
|
||||
options.auditUniformBlockProofs?&BlockProofAudit:nullptr,
|
||||
options.forceFullDensityCapture);
|
||||
FVoxelMeshData mesh;
|
||||
{
|
||||
FStandaloneShapeWorld::FScopedStampSamplingStep stampSamplingStep(MeshStep);
|
||||
mesh=mesher.GenerateMesh(origin,MeshStep,MeshCells,nullptr,
|
||||
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
||||
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr,
|
||||
options.profileMesherStages?&McStageTimings:nullptr,
|
||||
options.auditUniformBlockProofs?&BlockProofAudit:nullptr,
|
||||
options.forceFullDensityCapture);
|
||||
}
|
||||
passageFeatureScope.copySweptTubeRuntimeCounts();
|
||||
if(options.auditUniformBlockProofs)
|
||||
{
|
||||
@@ -3107,6 +3111,13 @@ namespace VoxelForgeCore
|
||||
AppendStratumBoundaryFloors(world,farShape,nativeKey,step,mesh);
|
||||
AppendStratumBoundaryCeilings(world,farShape,nativeKey,step,mesh);
|
||||
}
|
||||
const std::uint32_t coarsePillarProxyCount=tileShape.AppendCoarsePillarProxies(
|
||||
mesh,origin,extent,MeshStep);
|
||||
if(coarsePillarProxyCount>0)
|
||||
std::printf("[CoarsePillarProxy] key=(%d,%d,%d,lod%d) sampling_step_vox=%d "
|
||||
"pillars=%u mode=exact_capped_cylinder status=EMITTED\n",
|
||||
key.coordinate.x,key.coordinate.y,key.coordinate.z,key.lod,MeshStep,
|
||||
coarsePillarProxyCount);
|
||||
l77RefineStage.Restore();
|
||||
|
||||
out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
|
||||
|
||||
Reference in new issue
Block a user