feat(core): slab + sparse motifs, slice 1 -- flat field, monolith, platform at 0.52 s (L56); research R25
Motif vocabulary v3: version-1 records keyed by (seed, strate, stable ID) with route keepouts, band clearance and pairwise spacing. A rounded capped-cylinder monolith (r 1.4 m, 24 m tall) and a rounded-box plinth, smooth-unioned into a flatField floor with a 0.55 m geometric rounding (measured fillets 0.73-1.17 m). Exact radial/box interval bounds, a BVH, isTileShapeFree aware (0 brute-force misses, false-proof controls rejected). Final-field route passes; whole-surface audit covers foot, cap and platform edges. Cold whole view 0.524 s. Owner world unchanged. fast/medium/full PASS (Clang and GCC). Research R25 (self-checked): FloatingIslands LOD0 column cache conflicts. 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
aee1487410
commit
4e64b75044
15 files changed
+1560
-12
No files matched your search
@@ -165,8 +165,11 @@ namespace
|
||||
{
|
||||
const auto value=static_cast<std::uint8_t>(kind);
|
||||
return value>=static_cast<std::uint8_t>(ResolvedSdfStampKindV1::carveEllipsoid)
|
||||
&&value<=static_cast<std::uint8_t>(ResolvedSdfStampKindV1::fillDais);
|
||||
&&value<=static_cast<std::uint8_t>(ResolvedSdfStampKindV1::fillPlatform);
|
||||
}
|
||||
bool LayoutStampIsMotif(ResolvedSdfStampKindV1 kind)
|
||||
{return kind==ResolvedSdfStampKindV1::fillMonolith
|
||||
||kind==ResolvedSdfStampKindV1::fillPlatform;}
|
||||
double LayoutStampPointSegmentDistance(const std::array<std::int64_t,3>& point,
|
||||
const std::array<std::int64_t,3>& a,const std::array<std::int64_t,3>& b)
|
||||
{
|
||||
@@ -453,7 +456,9 @@ namespace
|
||||
{
|
||||
if(stamp.strateIndex!=vein.strateIndex
|
||||
||(stamp.kind!=ResolvedSdfStampKindV1::fillDais
|
||||
&&stamp.kind!=ResolvedSdfStampKindV1::fillPillar))continue;
|
||||
&&stamp.kind!=ResolvedSdfStampKindV1::fillPillar
|
||||
&&stamp.kind!=ResolvedSdfStampKindV1::fillMonolith
|
||||
&&stamp.kind!=ResolvedSdfStampKindV1::fillPlatform))continue;
|
||||
bool overlaps=true;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
overlaps=overlaps&&vein.boundsMinimumMicrometres[axis]
|
||||
@@ -581,7 +586,13 @@ namespace
|
||||
<<",\"irregularity_amplitude_um\":"<<stamp.irregularityAmplitudeMicrometres
|
||||
<<",\"irregularity_wavelength_um\":"<<stamp.irregularityWavelengthMicrometres
|
||||
<<",\"irregularity_seed\":"<<stamp.irregularitySeed
|
||||
<<",\"yaw_quarter_turns\":"<<static_cast<unsigned>(stamp.yawQuarterTurns)<<'}';
|
||||
<<",\"yaw_quarter_turns\":"<<static_cast<unsigned>(stamp.yawQuarterTurns);
|
||||
if(s.stampVocabularyVersion>=ResolvedLayoutStampVocabularyVersionV3)
|
||||
out<<",\"rounding_radius_um\":"<<stamp.roundingRadiusMicrometres
|
||||
<<",\"minimum_spacing_um\":"<<stamp.minimumSpacingMicrometres
|
||||
<<",\"attachment_policy\":"<<static_cast<unsigned>(stamp.attachmentPolicy)
|
||||
<<",\"floor_um\":"<<stamp.floorMicrometres;
|
||||
out<<'}';
|
||||
}
|
||||
out<<']';
|
||||
}
|
||||
@@ -1107,8 +1118,9 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
add("stamp_vocabulary_version","stamp vocabulary version is set without any stamp records");
|
||||
}
|
||||
else if(stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV1
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV2)
|
||||
add("stamp_vocabulary_version","resolved stamps require supported vocabulary version 1 or 2");
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV2
|
||||
&&stampVocabularyVersion!=ResolvedLayoutStampVocabularyVersionV3)
|
||||
add("stamp_vocabulary_version","resolved stamps require supported vocabulary version 1, 2 or 3");
|
||||
std::vector<std::string> stampIds;
|
||||
for(const auto& stamp:stamps)
|
||||
{
|
||||
@@ -1116,7 +1128,9 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
||std::find(stampIds.begin(),stampIds.end(),stamp.stableId)!=stampIds.end())
|
||||
add("stamp_identity","stamp has an empty or duplicate stable identity");
|
||||
else stampIds.push_back(stamp.stableId);
|
||||
if(!LayoutStampKindValid(stamp.kind)||stamp.yawQuarterTurns>3)
|
||||
const bool motif=LayoutStampIsMotif(stamp.kind);
|
||||
if(!LayoutStampKindValid(stamp.kind)||stamp.yawQuarterTurns>3
|
||||
||(motif&&stampVocabularyVersion<ResolvedLayoutStampVocabularyVersionV3))
|
||||
add("stamp_kind","stamp has an unsupported kind or orientation");
|
||||
bool validExtents=true;
|
||||
for(const auto extent:stamp.halfExtentsMicrometres)
|
||||
@@ -1130,8 +1144,33 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
||(stamp.irregularityAmplitudeMicrometres>0
|
||||
&&stamp.irregularityWavelengthMicrometres<=0))
|
||||
add("stamp_parameters",stamp.stableId+" has invalid or unbounded geometry parameters");
|
||||
const std::int64_t pad=stamp.smoothWidthMicrometres
|
||||
+stamp.irregularityAmplitudeMicrometres;
|
||||
if(motif)
|
||||
{
|
||||
const auto minimumExtent=*std::min_element(stamp.halfExtentsMicrometres.begin(),
|
||||
stamp.halfExtentsMicrometres.end());
|
||||
const bool monolith=stamp.kind==ResolvedSdfStampKindV1::fillMonolith;
|
||||
const bool shapeValid=monolith
|
||||
?stamp.halfExtentsMicrometres[0]==stamp.halfExtentsMicrometres[1]
|
||||
&&stamp.halfExtentsMicrometres[0]>=800000
|
||||
&&stamp.halfExtentsMicrometres[0]<=2000000
|
||||
&&stamp.halfExtentsMicrometres[2]>=6000000
|
||||
&&stamp.halfExtentsMicrometres[2]<=16000000
|
||||
:stamp.halfExtentsMicrometres[0]>=1000000
|
||||
&&stamp.halfExtentsMicrometres[1]>=1000000;
|
||||
if(stampVocabularyVersion<ResolvedLayoutStampVocabularyVersionV3
|
||||
||stamp.roundingRadiusMicrometres<500000
|
||||
||stamp.roundingRadiusMicrometres>minimumExtent
|
||||
||stamp.minimumSpacingMicrometres<0
|
||||
||stamp.minimumSpacingMicrometres>100000000
|
||||
||stamp.attachmentPolicy!=1
|
||||
||stamp.positionMicrometres[2]
|
||||
!=stamp.floorMicrometres+stamp.halfExtentsMicrometres[2]
|
||||
||!shapeValid||stamp.irregularityAmplitudeMicrometres!=0)
|
||||
add("stamp_motif_parameters",stamp.stableId+" has invalid kind, size, fillet or floor attachment");
|
||||
}
|
||||
const std::int64_t pad=motif
|
||||
?(stamp.smoothWidthMicrometres+5)/6+stamp.irregularityAmplitudeMicrometres
|
||||
:stamp.smoothWidthMicrometres+stamp.irregularityAmplitudeMicrometres;
|
||||
const std::array<std::int64_t,3> extents={
|
||||
stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?1:0]+pad,
|
||||
stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?0:1]+pad,
|
||||
@@ -1146,6 +1185,35 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
if(!LayoutStampSafe(*this,stamp,&safetyReason))
|
||||
add("stamp_placement",stamp.stableId+" is unsafe: "+safetyReason);
|
||||
}
|
||||
if(stampVocabularyVersion>=ResolvedLayoutStampVocabularyVersionV3)
|
||||
{
|
||||
for(std::size_t i=0;i<stamps.size();++i)
|
||||
{
|
||||
if(!LayoutStampIsMotif(stamps[i].kind))continue;
|
||||
for(std::size_t j=0;j<i;++j)
|
||||
{
|
||||
if(!LayoutStampIsMotif(stamps[j].kind)
|
||||
||stamps[i].strateIndex!=stamps[j].strateIndex)continue;
|
||||
double gapSquared=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const auto gap=stamps[i].boundsMinimumMicrometres[axis]
|
||||
>stamps[j].boundsMaximumMicrometres[axis]
|
||||
?stamps[i].boundsMinimumMicrometres[axis]
|
||||
-stamps[j].boundsMaximumMicrometres[axis]
|
||||
:stamps[j].boundsMinimumMicrometres[axis]
|
||||
>stamps[i].boundsMaximumMicrometres[axis]
|
||||
?stamps[j].boundsMinimumMicrometres[axis]
|
||||
-stamps[i].boundsMaximumMicrometres[axis]:0;
|
||||
gapSquared+=static_cast<double>(gap)*gap;
|
||||
}
|
||||
const auto required=std::max(stamps[i].minimumSpacingMicrometres,
|
||||
stamps[j].minimumSpacingMicrometres);
|
||||
if(gapSquared<static_cast<double>(required)*required)
|
||||
add("stamp_motif_spacing",stamps[i].stableId+" is too close to "+stamps[j].stableId);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(cavernRooms.empty()&&cavernEdges.empty())
|
||||
{
|
||||
if(cavernGraphVocabularyVersion!=0)
|
||||
@@ -1393,6 +1461,10 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
||||
stamp.smoothWidthMicrometres=LayoutInt(v,"smooth_width_um");
|
||||
stamp.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
|
||||
stamp.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
|
||||
stamp.roundingRadiusMicrometres=LayoutInt(v,"rounding_radius_um");
|
||||
stamp.minimumSpacingMicrometres=LayoutInt(v,"minimum_spacing_um");
|
||||
stamp.attachmentPolicy=static_cast<std::uint8_t>(LayoutInt(v,"attachment_policy"));
|
||||
stamp.floorMicrometres=LayoutInt(v,"floor_um");
|
||||
stamp.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
|
||||
stamp.yawQuarterTurns=static_cast<std::uint8_t>(LayoutInt(v,"yaw_quarter_turns"));
|
||||
s.stamps.push_back(std::move(stamp));
|
||||
@@ -1607,6 +1679,149 @@ bool ResolveResolvedLayoutStampsV2(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
layout=std::move(candidate);return true;
|
||||
}
|
||||
|
||||
bool ResolveResolvedLayoutStampsV3(ResolvedLayoutSnapshotV3& layout,std::int32_t seed,
|
||||
std::span<const ResolvedLayoutStampRequestV1> requests,std::string& error)
|
||||
{
|
||||
error.clear();
|
||||
if(requests.empty())
|
||||
{
|
||||
ResolvedLayoutSnapshotV3 candidate=layout;
|
||||
candidate.stampVocabularyVersion=0;candidate.stamps.clear();
|
||||
if(!candidate.finalize(error))return false;
|
||||
layout=std::move(candidate);return true;
|
||||
}
|
||||
std::vector<ResolvedLayoutStampRequestV1> ordered(requests.begin(),requests.end());
|
||||
std::sort(ordered.begin(),ordered.end(),[](const auto& a,const auto& b)
|
||||
{return a.stableId<b.stableId;});
|
||||
ResolvedLayoutSnapshotV3 candidate=layout;
|
||||
candidate.stampVocabularyVersion=ResolvedLayoutStampVocabularyVersionV3;
|
||||
candidate.stamps.clear();candidate.stamps.reserve(ordered.size());
|
||||
for(std::size_t requestIndex=0;requestIndex<ordered.size();++requestIndex)
|
||||
{
|
||||
const auto& request=ordered[requestIndex];
|
||||
if(request.stableId.empty()||(requestIndex>0
|
||||
&&ordered[requestIndex-1].stableId==request.stableId))
|
||||
{error="motif requests require non-empty unique stable IDs";return false;}
|
||||
if(!LayoutStampIsMotif(request.kind)||request.yawQuarterTurns>3
|
||||
||request.placementRadiusMicrometres<0
|
||||
||request.placementRadiusMicrometres>100000000
|
||||
||request.smoothWidthMicrometres<0||request.smoothWidthMicrometres>10000000
|
||||
||request.irregularityAmplitudeMicrometres!=0
|
||||
||request.irregularityWavelengthMicrometres<0
|
||||
||request.irregularityWavelengthMicrometres>100000000
|
||||
||request.roundingRadiusMicrometres<500000
|
||||
||request.minimumSpacingMicrometres<0
|
||||
||request.minimumSpacingMicrometres>100000000
|
||||
||request.attachmentPolicy!=1)
|
||||
{error="motif request "+request.stableId+" has invalid or unbounded parameters";return false;}
|
||||
if(std::any_of(request.halfExtentsMicrometres.begin(),request.halfExtentsMicrometres.end(),
|
||||
[](std::int64_t extent){return extent<500000||extent>100000000;}))
|
||||
{error="motif request "+request.stableId+" has an extent outside 0.5..100 m";return false;}
|
||||
const bool monolith=request.kind==ResolvedSdfStampKindV1::fillMonolith;
|
||||
const auto minimumExtent=*std::min_element(request.halfExtentsMicrometres.begin(),
|
||||
request.halfExtentsMicrometres.end());
|
||||
const bool dimensionsValid=monolith
|
||||
?request.halfExtentsMicrometres[0]==request.halfExtentsMicrometres[1]
|
||||
&&request.halfExtentsMicrometres[0]>=800000
|
||||
&&request.halfExtentsMicrometres[0]<=2000000
|
||||
&&request.halfExtentsMicrometres[2]>=6000000
|
||||
&&request.halfExtentsMicrometres[2]<=16000000
|
||||
:request.halfExtentsMicrometres[0]>=1000000
|
||||
&&request.halfExtentsMicrometres[1]>=1000000;
|
||||
if(!dimensionsValid||request.roundingRadiusMicrometres>minimumExtent)
|
||||
{error="motif request "+request.stableId+" has a shape size outside its authored family";return false;}
|
||||
const auto stratum=std::find_if(candidate.strates.begin(),candidate.strates.end(),
|
||||
[&](const auto& value){return value.strateIndex==request.strateIndex;});
|
||||
if(stratum==candidate.strates.end())
|
||||
{error="motif request "+request.stableId+" names an unknown strate";return false;}
|
||||
for(const auto coordinate:request.anchorPositionMicrometres)
|
||||
if(coordinate<-1000000000000ll||coordinate>1000000000000ll)
|
||||
{error="motif request "+request.stableId+" has an unbounded anchor";return false;}
|
||||
if(request.floorMicrometres<-1000000000000ll
|
||||
||request.floorMicrometres>1000000000000ll)
|
||||
{error="motif request "+request.stableId+" has an unbounded floor anchor";return false;}
|
||||
|
||||
const std::uint64_t baseSeed=LayoutStampSeed(seed,request.strateIndex,request.stableId)
|
||||
&0x001fffffffffffffull;
|
||||
std::uint64_t state=baseSeed;
|
||||
const auto randomOffset=[&](std::uint64_t bits)
|
||||
{
|
||||
const std::uint64_t span=static_cast<std::uint64_t>(
|
||||
request.placementRadiusMicrometres)*2+1;
|
||||
return request.placementRadiusMicrometres==0?std::int64_t{0}
|
||||
:static_cast<std::int64_t>(bits%span)-request.placementRadiusMicrometres;
|
||||
};
|
||||
bool placed=false;
|
||||
const std::uint32_t attempts=request.placementRadiusMicrometres==0?1u:512u;
|
||||
for(std::uint32_t attempt=0;attempt<attempts;++attempt)
|
||||
{
|
||||
ResolvedLayoutStampV1 stamp;stamp.stableId=request.stableId;
|
||||
stamp.strateIndex=request.strateIndex;stamp.kind=request.kind;
|
||||
stamp.halfExtentsMicrometres=request.halfExtentsMicrometres;
|
||||
stamp.smoothWidthMicrometres=request.smoothWidthMicrometres;
|
||||
stamp.irregularityAmplitudeMicrometres=0;
|
||||
stamp.irregularityWavelengthMicrometres=request.irregularityWavelengthMicrometres;
|
||||
stamp.roundingRadiusMicrometres=request.roundingRadiusMicrometres;
|
||||
stamp.minimumSpacingMicrometres=request.minimumSpacingMicrometres;
|
||||
stamp.attachmentPolicy=request.attachmentPolicy;
|
||||
stamp.floorMicrometres=request.floorMicrometres;
|
||||
stamp.irregularitySeed=baseSeed;stamp.yawQuarterTurns=request.yawQuarterTurns;
|
||||
const std::array<std::int64_t,2> offset={
|
||||
randomOffset(LayoutStampNext(state)),randomOffset(LayoutStampNext(state))};
|
||||
if(request.placementRadiusMicrometres>0)
|
||||
{
|
||||
const long double radius=request.placementRadiusMicrometres;
|
||||
const long double distanceSquared=static_cast<long double>(offset[0])*offset[0]
|
||||
+static_cast<long double>(offset[1])*offset[1];
|
||||
if(distanceSquared>radius*radius)continue;
|
||||
}
|
||||
stamp.positionMicrometres={request.anchorPositionMicrometres[0]+offset[0],
|
||||
request.anchorPositionMicrometres[1]+offset[1],
|
||||
request.floorMicrometres+request.halfExtentsMicrometres[2]};
|
||||
const std::int64_t pad=(stamp.smoothWidthMicrometres+5)/6;
|
||||
const std::array<std::int64_t,3> extents={
|
||||
stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?1:0]+pad,
|
||||
stamp.halfExtentsMicrometres[stamp.yawQuarterTurns%2?0:1]+pad,
|
||||
stamp.halfExtentsMicrometres[2]+pad};
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
stamp.boundsMinimumMicrometres[axis]=stamp.positionMicrometres[axis]-extents[axis];
|
||||
stamp.boundsMaximumMicrometres[axis]=stamp.positionMicrometres[axis]+extents[axis];
|
||||
}
|
||||
if(!LayoutStampSafe(candidate,stamp))continue;
|
||||
bool spaced=true;
|
||||
for(const auto& prior:candidate.stamps)
|
||||
{
|
||||
if(!LayoutStampIsMotif(prior.kind)||prior.strateIndex!=stamp.strateIndex)continue;
|
||||
double gapSquared=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const auto gap=stamp.boundsMinimumMicrometres[axis]
|
||||
>prior.boundsMaximumMicrometres[axis]
|
||||
?stamp.boundsMinimumMicrometres[axis]-prior.boundsMaximumMicrometres[axis]
|
||||
:prior.boundsMinimumMicrometres[axis]
|
||||
>stamp.boundsMaximumMicrometres[axis]
|
||||
?prior.boundsMinimumMicrometres[axis]-stamp.boundsMaximumMicrometres[axis]:0;
|
||||
gapSquared+=static_cast<double>(gap)*gap;
|
||||
}
|
||||
const auto required=std::max(stamp.minimumSpacingMicrometres,
|
||||
prior.minimumSpacingMicrometres);
|
||||
if(gapSquared<static_cast<double>(required)*required)
|
||||
{spaced=false;break;}
|
||||
}
|
||||
if(!spaced)continue;
|
||||
candidate.stamps.push_back(std::move(stamp));placed=true;break;
|
||||
}
|
||||
if(!placed)
|
||||
{error="motif request "+request.stableId+" has no deterministic route-, band- and spacing-safe placement";return false;}
|
||||
}
|
||||
if(!candidate.finalize(error))return false;
|
||||
std::vector<ResolvedLayoutIssueV2> issues;std::string strictError;
|
||||
if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,strictError))
|
||||
{error="strict stamp vocabulary-3 validation failed: "+strictError;return false;}
|
||||
layout=std::move(candidate);return true;
|
||||
}
|
||||
|
||||
bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t seed,
|
||||
std::span<const ResolvedCavernRoomV1> rooms,
|
||||
std::span<const ResolvedCavernEdgeV1> edges,std::string& error)
|
||||
|
||||
@@ -3281,6 +3281,7 @@ private:
|
||||
float smoothWidthVoxels=0.0f;
|
||||
float irregularityAmplitudeVoxels=0.0f;
|
||||
float irregularityWavelengthVoxels=0.0f;
|
||||
float roundingRadiusVoxels=0.0f;
|
||||
std::uint64_t irregularitySeed=0;
|
||||
std::uint8_t quarterTurns=0;
|
||||
};
|
||||
@@ -3452,6 +3453,9 @@ private:
|
||||
runtime.irregularityWavelengthVoxels=static_cast<float>(
|
||||
static_cast<double>(stamp.irregularityWavelengthMicrometres)
|
||||
/Owner->CoreVoxelSizeMicrons);
|
||||
runtime.roundingRadiusVoxels=static_cast<float>(
|
||||
static_cast<double>(stamp.roundingRadiusMicrometres)
|
||||
/Owner->CoreVoxelSizeMicrons);
|
||||
runtime.irregularitySeed=stamp.irregularitySeed;
|
||||
runtime.quarterTurns=stamp.yawQuarterTurns;
|
||||
StampRuntime.push_back(runtime);
|
||||
@@ -5370,6 +5374,7 @@ public:
|
||||
else ++Counts.MixedBoxes;
|
||||
return Result;
|
||||
};
|
||||
std::vector<std::uint32_t> stampCandidates;
|
||||
if(!StampSpatialBounds.empty())
|
||||
{
|
||||
constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
@@ -5381,8 +5386,18 @@ public:
|
||||
static_cast<std::int64_t>(BMax[0])*Q,
|
||||
static_cast<std::int64_t>(BMax[1])*Q,
|
||||
static_cast<std::int64_t>(BMax[2])*Q};
|
||||
if(!BuildStampTileList(minimum,maximum).empty())
|
||||
return Finish(EVoxelTileClass::Mixed,true);
|
||||
stampCandidates=BuildStampTileList(minimum,maximum);
|
||||
for(const std::uint32_t index:stampCandidates)
|
||||
{
|
||||
if(index>=StampRuntime.size())return Finish(EVoxelTileClass::Mixed,true);
|
||||
const auto& stamp=StampRuntime[index];
|
||||
if(!stamp.record||(stamp.record->kind
|
||||
!=VoxelForgeCore::ResolvedSdfStampKindV1::fillMonolith
|
||||
&&stamp.record->kind
|
||||
!=VoxelForgeCore::ResolvedSdfStampKindV1::fillPlatform)
|
||||
||!SurfacePlan||stamp.record->strateIndex!=SurfacePlan->strateIndex)
|
||||
return Finish(EVoxelTileClass::Mixed,true);
|
||||
}
|
||||
}
|
||||
if(!CavernRuntime.empty())
|
||||
{
|
||||
@@ -5471,6 +5486,44 @@ public:
|
||||
{
|
||||
if(!WorldAssembly||!SurfacePlan||!SurfaceField)
|
||||
return Finish(EVoxelTileClass::Mixed,true);
|
||||
if(!stampCandidates.empty())
|
||||
{
|
||||
constexpr std::int64_t Scale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const auto interval=SurfaceField->Evaluator.interval(
|
||||
static_cast<std::int64_t>(BMin[0])*Scale,
|
||||
static_cast<std::int64_t>(BMin[1])*Scale,
|
||||
static_cast<std::int64_t>(BMin[2])*Scale,
|
||||
static_cast<std::int64_t>(BMax[0])*Scale,
|
||||
static_cast<std::int64_t>(BMax[1])*Scale,
|
||||
static_cast<std::int64_t>(BMax[2])*Scale);
|
||||
if(!interval.known||interval.minimumDensitySubvoxels
|
||||
>interval.maximumDensitySubvoxels)
|
||||
return Finish(EVoxelTileClass::Mixed,true);
|
||||
FMetricInterval composed{
|
||||
static_cast<double>(interval.minimumDensitySubvoxels)/Scale,
|
||||
static_cast<double>(interval.maximumDensitySubvoxels)/Scale,true};
|
||||
for(const std::uint32_t index:stampCandidates)
|
||||
{
|
||||
const auto& stamp=StampRuntime[index];
|
||||
const auto feature=StampDistanceInterval(stamp,BMin,BMax);
|
||||
if(!feature.known)return Finish(EVoxelTileClass::Mixed,true);
|
||||
composed.minimum=SmoothMinDistance(static_cast<float>(composed.minimum),
|
||||
static_cast<float>(feature.minimum),stamp.smoothWidthVoxels);
|
||||
composed.maximum=SmoothMinDistance(static_cast<float>(composed.maximum),
|
||||
static_cast<float>(feature.maximum),stamp.smoothWidthVoxels);
|
||||
}
|
||||
constexpr double outwardPadding=2.0e-5;
|
||||
composed.minimum=std::nextafter(composed.minimum-outwardPadding,
|
||||
-std::numeric_limits<double>::infinity());
|
||||
composed.maximum=std::nextafter(composed.maximum+outwardPadding,
|
||||
std::numeric_limits<double>::infinity());
|
||||
if(!std::isfinite(composed.minimum)||!std::isfinite(composed.maximum)
|
||||
||composed.minimum>composed.maximum)
|
||||
return Finish(EVoxelTileClass::Mixed,true);
|
||||
if(composed.minimum>=0.0)return Finish(EVoxelTileClass::AllAir);
|
||||
if(composed.maximum<0.0)return Finish(EVoxelTileClass::AllSolid);
|
||||
return Finish(EVoxelTileClass::Mixed);
|
||||
}
|
||||
// classifyLatticeBox takes the exact extrema of ground/cap columns at every
|
||||
// MC lattice XY in this box. Since density is min(z-ground, cap-z), those extrema
|
||||
// bound every lattice sample in Z without a noise amplitude guess.
|
||||
@@ -6503,6 +6556,19 @@ private:
|
||||
+std::max(vertical,0.0f)*std::max(vertical,0.0f));
|
||||
return outside+std::min(std::max(radial,vertical),0.0f);
|
||||
}
|
||||
static float StampRoundedCappedCylinderSdf(float x,float y,float z,
|
||||
const std::array<float,3>& halfSize,float rounding)
|
||||
{
|
||||
const float radius=halfSize[0];
|
||||
if(!(radius>0.0f&&halfSize[0]==halfSize[1]&&halfSize[2]>0.0f)
|
||||
||rounding<0.0f||rounding>std::min(radius,halfSize[2]))
|
||||
return std::numeric_limits<float>::infinity();
|
||||
const float radial=std::sqrt(x*x+y*y)-(radius-rounding);
|
||||
const float vertical=std::abs(z)-(halfSize[2]-rounding);
|
||||
const float outside=std::sqrt(std::max(radial,0.0f)*std::max(radial,0.0f)
|
||||
+std::max(vertical,0.0f)*std::max(vertical,0.0f));
|
||||
return outside+std::min(std::max(radial,vertical),0.0f)-rounding;
|
||||
}
|
||||
static float StampArchSdf(float x,float y,float z,
|
||||
const std::array<float,3>& halfSize)
|
||||
{
|
||||
@@ -6533,6 +6599,12 @@ private:
|
||||
distance=StampArchSdf(localX,localY,localZ,stamp.halfExtentsVoxels);break;
|
||||
case VoxelForgeCore::ResolvedSdfStampKindV1::fillPillar:
|
||||
distance=StampPillarSdf(localX,localY,localZ,stamp.halfExtentsVoxels);break;
|
||||
case VoxelForgeCore::ResolvedSdfStampKindV1::fillMonolith:
|
||||
distance=StampRoundedCappedCylinderSdf(localX,localY,localZ,
|
||||
stamp.halfExtentsVoxels,stamp.roundingRadiusVoxels);break;
|
||||
case VoxelForgeCore::ResolvedSdfStampKindV1::fillPlatform:
|
||||
distance=StampRoundedBoxSdf(localX,localY,localZ,
|
||||
stamp.halfExtentsVoxels,stamp.roundingRadiusVoxels);break;
|
||||
case VoxelForgeCore::ResolvedSdfStampKindV1::fillDais:
|
||||
if(ResolvedLayout&&ResolvedLayout->stampVocabularyVersion
|
||||
>=VoxelForgeCore::ResolvedLayoutStampVocabularyVersionV2)
|
||||
@@ -6550,6 +6622,93 @@ private:
|
||||
*stamp.irregularityAmplitudeVoxels;
|
||||
return distance;
|
||||
}
|
||||
|
||||
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 double StampRoundedBoxSdfDouble(const std::array<double,3>& absolute,
|
||||
const std::array<double,3>& halfSize,double rounding)
|
||||
{
|
||||
const double qx=absolute[0]-halfSize[0]+rounding;
|
||||
const double qy=absolute[1]-halfSize[1]+rounding;
|
||||
const double qz=absolute[2]-halfSize[2]+rounding;
|
||||
const double outside=std::sqrt(std::max(qx,0.0)*std::max(qx,0.0)
|
||||
+std::max(qy,0.0)*std::max(qy,0.0)
|
||||
+std::max(qz,0.0)*std::max(qz,0.0));
|
||||
return outside+std::min(std::max({qx,qy,qz}),0.0)-rounding;
|
||||
}
|
||||
FMetricInterval StampDistanceInterval(const FStampRuntime& stamp,
|
||||
const int32 bMin[3],const int32 bMax[3]) const
|
||||
{
|
||||
if(!stamp.record||(stamp.record->kind
|
||||
!=VoxelForgeCore::ResolvedSdfStampKindV1::fillMonolith
|
||||
&&stamp.record->kind!=VoxelForgeCore::ResolvedSdfStampKindV1::fillPlatform))
|
||||
return {0.0,0.0,false};
|
||||
std::array<double,3> localMinimum{},localMaximum{};
|
||||
const std::array<double,3> center={stamp.positionVoxels[0],
|
||||
stamp.positionVoxels[1],stamp.positionVoxels[2]};
|
||||
const auto axisRange=[&](std::size_t axis)
|
||||
{
|
||||
return std::array<double,2>{static_cast<double>(bMin[axis]),
|
||||
static_cast<double>(bMax[axis])};
|
||||
};
|
||||
const auto xRange=axisRange(0),yRange=axisRange(1),zRange=axisRange(2);
|
||||
switch(stamp.quarterTurns&3u)
|
||||
{
|
||||
case 1:
|
||||
localMinimum={yRange[0]-center[1],center[0]-xRange[1],zRange[0]-center[2]};
|
||||
localMaximum={yRange[1]-center[1],center[0]-xRange[0],zRange[1]-center[2]};
|
||||
break;
|
||||
case 2:
|
||||
localMinimum={center[0]-xRange[1],center[1]-yRange[1],zRange[0]-center[2]};
|
||||
localMaximum={center[0]-xRange[0],center[1]-yRange[0],zRange[1]-center[2]};
|
||||
break;
|
||||
case 3:
|
||||
localMinimum={center[1]-yRange[1],xRange[0]-center[0],zRange[0]-center[2]};
|
||||
localMaximum={center[1]-yRange[0],xRange[1]-center[0],zRange[1]-center[2]};
|
||||
break;
|
||||
default:
|
||||
localMinimum={xRange[0]-center[0],yRange[0]-center[1],zRange[0]-center[2]};
|
||||
localMaximum={xRange[1]-center[0],yRange[1]-center[1],zRange[1]-center[2]};
|
||||
break;
|
||||
}
|
||||
std::array<double,3> absoluteMinimum{},absoluteMaximum{};
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
absoluteMinimum[axis]=StampAbsMinimum(localMinimum[axis],localMaximum[axis]);
|
||||
absoluteMaximum[axis]=std::max(std::abs(localMinimum[axis]),
|
||||
std::abs(localMaximum[axis]));
|
||||
}
|
||||
double minimum=0.0,maximum=0.0;
|
||||
if(stamp.record->kind==VoxelForgeCore::ResolvedSdfStampKindV1::fillMonolith)
|
||||
{
|
||||
const double radialMinimum=std::hypot(absoluteMinimum[0],absoluteMinimum[1]);
|
||||
const double radialMaximum=std::hypot(absoluteMaximum[0],absoluteMaximum[1]);
|
||||
const double round=stamp.roundingRadiusVoxels;
|
||||
const double side=stamp.halfExtentsVoxels[0]-round;
|
||||
const double cap=stamp.halfExtentsVoxels[2]-round;
|
||||
const auto eval=[&](double radial,double vertical)
|
||||
{
|
||||
const double qr=radial-side,qz=vertical-cap;
|
||||
return std::hypot(std::max(qr,0.0),std::max(qz,0.0))
|
||||
+std::min(std::max(qr,qz),0.0)-round;
|
||||
};
|
||||
minimum=eval(radialMinimum,absoluteMinimum[2]);
|
||||
maximum=eval(radialMaximum,absoluteMaximum[2]);
|
||||
}
|
||||
else if(stamp.record->kind==VoxelForgeCore::ResolvedSdfStampKindV1::fillPlatform)
|
||||
{
|
||||
const std::array<double,3> half={stamp.halfExtentsVoxels[0],
|
||||
stamp.halfExtentsVoxels[1],stamp.halfExtentsVoxels[2]};
|
||||
minimum=StampRoundedBoxSdfDouble(absoluteMinimum,half,stamp.roundingRadiusVoxels);
|
||||
maximum=StampRoundedBoxSdfDouble(absoluteMaximum,half,stamp.roundingRadiusVoxels);
|
||||
}
|
||||
else return {0.0,0.0,false};
|
||||
constexpr double outwardPadding=2.0e-5;
|
||||
return {std::nextafter(minimum-outwardPadding,
|
||||
-std::numeric_limits<double>::infinity()),
|
||||
std::nextafter(maximum+outwardPadding,
|
||||
std::numeric_limits<double>::infinity()),true};
|
||||
}
|
||||
float ApplyResolvedSdfStamps(float density,float X,float Y,float Z) const
|
||||
{
|
||||
if(StampRuntime.empty()||!Owner||Owner->CoreVoxelSizeMicrons==0)return density;
|
||||
|
||||
Reference in new issue
Block a user