wip(assembler): M1 round 4 PARTIAL -- stopped at owner's request (GPU coil whine at night)
Unverified mid-round state (cliff plates / horizon slab / routes in progress). Resume from here. 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
2c20f9eaea
commit
20c6d86f0e
11 files changed
+839
-47
No files matched your search
@@ -967,11 +967,38 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
|
||||
};
|
||||
const int32 Base=static_cast<int32>(NewVertices.size());
|
||||
const double Xs[4]={X0,X1,X1,X0},Ys[4]={Y0,Y0,Y1,Y1};
|
||||
std::array<double,4> CornerZ{};
|
||||
std::array<FVector,4> CornerNormals{};
|
||||
bool bValidPatch=true;
|
||||
for(int32 Corner=0;Corner<4;++Corner)
|
||||
{
|
||||
const double Z=SurfaceZ(Xs[Corner],Ys[Corner]);
|
||||
NewVertices.emplace_back(Xs[Corner]*25.0,Ys[Corner]*25.0,Z*25.0);
|
||||
NewNormals.push_back(Normal);
|
||||
const double PredictedZ=SurfaceZ(Xs[Corner],Ys[Corner]);
|
||||
const std::vector<double> CornerCrossings=FindFarStratumCrossings(
|
||||
Shape,Xs[Corner],Ys[Corner],ZMin,ZMax);
|
||||
if(CornerCrossings.empty()){bValidPatch=false;break;}
|
||||
CornerZ[Corner]=*std::min_element(CornerCrossings.begin(),CornerCrossings.end(),
|
||||
[PredictedZ](double A,double B)
|
||||
{return std::abs(A-PredictedZ)<std::abs(B-PredictedZ);});
|
||||
const float CornerGX=Density(Xs[Corner]+2,Ys[Corner],CornerZ[Corner])
|
||||
-Density(Xs[Corner]-2,Ys[Corner],CornerZ[Corner]);
|
||||
const float CornerGY=Density(Xs[Corner],Ys[Corner]+2,CornerZ[Corner])
|
||||
-Density(Xs[Corner],Ys[Corner]-2,CornerZ[Corner]);
|
||||
const float CornerGZ=Density(Xs[Corner],Ys[Corner],CornerZ[Corner]+2)
|
||||
-Density(Xs[Corner],Ys[Corner],CornerZ[Corner]-2);
|
||||
CornerNormals[Corner]=FVector(CornerGX,CornerGY,CornerGZ);
|
||||
if(CornerNormals[Corner].SizeSquared()<1.0e-8)
|
||||
CornerNormals[Corner]=Normal;
|
||||
else CornerNormals[Corner].Normalize();
|
||||
}
|
||||
// A center crossing alone cannot justify a large tangent quad: it can
|
||||
// bridge air over an isolated far-LOD feature and draw as a horizon slab.
|
||||
// Keep the fallback only when the source field has a matching crossing
|
||||
// at every corner, and place each corner on that source crossing.
|
||||
if(!bValidPatch)continue;
|
||||
for(int32 Corner=0;Corner<4;++Corner)
|
||||
{
|
||||
NewVertices.emplace_back(Xs[Corner]*25.0,Ys[Corner]*25.0,CornerZ[Corner]*25.0);
|
||||
NewNormals.push_back(CornerNormals[Corner]);
|
||||
if(bHasUVs)NewUVs.emplace_back(static_cast<float>((Xs[Corner]-Origin.X)/256.0),
|
||||
static_cast<float>((Ys[Corner]-Origin.Y)/256.0));
|
||||
if(bHasColors)NewColors.push_back(Color);
|
||||
|
||||
@@ -627,12 +627,42 @@ namespace VoxelForgeCore
|
||||
mesher.bGenerateSkirts=world.bGenerateSkirts;mesher.SkirtCells=static_cast<float>(world.SkirtCells);
|
||||
mesher.LODOctaveDrop=world.LODOctaveDrop;
|
||||
const int32 level=key.lod,extent=CHUNK_SIZE<<level;
|
||||
const int32 cells=level<world.FullResClipLevels?CHUNK_SIZE:FMath::Max(1,world.CoarseTileCells);
|
||||
const int32 step=FMath::Max(1,extent/cells);
|
||||
int64 sampleCount=0;std::vector<float> densityValues;
|
||||
const FVoxelTileKey nativeKey=ToNative(key);
|
||||
const FIntVector origin=nativeKey.OriginVoxels();
|
||||
int32 cells=level<world.FullResClipLevels?CHUNK_SIZE:FMath::Max(1,world.CoarseTileCells);
|
||||
int32 step=FMath::Max(1,extent/cells);
|
||||
bool bFeatureRefinedResolution=false;
|
||||
if(impl_->worldAssembly&&level<=2)
|
||||
{
|
||||
constexpr std::int64_t Scale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::array<std::int64_t,3> tileMin={
|
||||
static_cast<std::int64_t>(origin.X)*Scale,
|
||||
static_cast<std::int64_t>(origin.Y)*Scale,
|
||||
static_cast<std::int64_t>(origin.Z)*Scale};
|
||||
const std::array<std::int64_t,3> tileMax={
|
||||
static_cast<std::int64_t>(origin.X+extent)*Scale,
|
||||
static_cast<std::int64_t>(origin.Y+extent)*Scale,
|
||||
static_cast<std::int64_t>(origin.Z+extent)*Scale};
|
||||
for(const auto& stratum:impl_->worldAssembly->plan.strates)
|
||||
for(const auto& feature:stratum.features)
|
||||
{
|
||||
if(feature.type!=4)continue;
|
||||
bool overlaps=true;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
overlaps=overlaps&&feature.minimumSubvoxels[axis]<=tileMax[axis]
|
||||
&&feature.maximumSubvoxels[axis]>=tileMin[axis];
|
||||
if(overlaps)
|
||||
{
|
||||
// A two-voxel shelf is only one sample wide at LOD1's normal step.
|
||||
// Mesh feature-intersecting tiles at the one-voxel Core lattice so
|
||||
// the full-resolution block cannot turn into a one-cell plate.
|
||||
cells=extent;step=1;bFeatureRefinedResolution=true;break;
|
||||
}
|
||||
}
|
||||
}
|
||||
int64 sampleCount=0;std::vector<float> densityValues;
|
||||
TileMesh out;out.key=key;
|
||||
out.diagnostics.featureRefinedResolution=bFeatureRefinedResolution;
|
||||
const bool bHasWorldAssembly=impl_->worldAssembly!=nullptr;
|
||||
const bool bNeedsFarShell=bHasWorldAssembly&&key.lod>=7
|
||||
&&(tileShape.HasFarWorld()||impl_->shape->HasFarWorld());
|
||||
@@ -691,6 +721,18 @@ namespace VoxelForgeCore
|
||||
AppendFarStratumShells(world,farShape,nativeKey,step,mesh);
|
||||
AppendFarConnectorShells(farShape,nativeKey,step,world.CoarseTileCells,mesh);
|
||||
}
|
||||
if(bHasWorldAssembly&&key.lod>=8&&mesh.NumCeilingTriangles>0)
|
||||
{
|
||||
// The finite sky cap remains in the composed field, queries, and local meshes.
|
||||
// At the coarsest clipmap level its large planar triangles read as a detached
|
||||
// horizon slab, so omit only that far render group after far-shell construction.
|
||||
const int32 KeptTriangleCount=std::max(0,mesh.Triangles.Num()/3-mesh.NumCeilingTriangles);
|
||||
const int32 KeptIndexCount=KeptTriangleCount*3;
|
||||
out.diagnostics.farCapTrianglesSuppressed=static_cast<std::uint64_t>(mesh.NumCeilingTriangles);
|
||||
mesh.Triangles.SetNum(KeptIndexCount);
|
||||
mesh.TriangleSkirtFaceMasks.SetNum(KeptTriangleCount);
|
||||
mesh.NumCeilingTriangles=0;
|
||||
}
|
||||
|
||||
out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
|
||||
if(bHasWorldAssembly)
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace VoxelForgeCore
|
||||
namespace world_assembly_detail
|
||||
{
|
||||
constexpr std::int64_t Q16 = 1ll << 16;
|
||||
constexpr std::int64_t MinimumCliffShelfThicknessSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel;
|
||||
constexpr std::int64_t MaxSerializedMagnitude = 1000000000000ll;
|
||||
constexpr std::size_t MaxBiomeCount = 8;
|
||||
constexpr std::size_t MaxStrateCount = 64;
|
||||
@@ -228,6 +229,60 @@ namespace world_assembly_detail
|
||||
|| !std::all_of(point.positionSubvoxels.begin(), point.positionSubvoxels.end(), ValidScalar))
|
||||
{ error = "world assembly route point has an invalid feature ID, position or radius"; return false; }
|
||||
}
|
||||
std::vector<std::uint64_t> routeIds;
|
||||
routeIds.reserve(stratum.route.size());
|
||||
for (const auto& point : stratum.route)
|
||||
{
|
||||
if (std::find(routeIds.begin(), routeIds.end(), point.stableFeatureId) != routeIds.end())
|
||||
{ error = "world assembly route point IDs must be unique"; return false; }
|
||||
routeIds.push_back(point.stableFeatureId);
|
||||
}
|
||||
for (std::size_t i = 1; i < stratum.route.size(); ++i)
|
||||
{
|
||||
std::uint64_t squared = 0;
|
||||
for (std::size_t axis = 0; axis < 3; ++axis)
|
||||
{
|
||||
const std::int64_t delta = stratum.route[i].positionSubvoxels[axis]
|
||||
- stratum.route[i - 1].positionSubvoxels[axis];
|
||||
if (std::abs(delta) > 4096 * WorldAssemblySubvoxelsPerVoxel)
|
||||
{ error = "world assembly route segment exceeds its bounded sweep length"; return false; }
|
||||
squared += static_cast<std::uint64_t>(delta * delta);
|
||||
}
|
||||
if (squared == 0)
|
||||
{ error = "world assembly route contains a zero-length segment"; return false; }
|
||||
}
|
||||
const auto& constraints = stratum.routeConstraints;
|
||||
if (constraints.version == 0)
|
||||
{
|
||||
if (constraints.capsuleRadiusSubvoxels != 0 || constraints.capsuleHeightSubvoxels != 0
|
||||
|| constraints.maximumGradeQ16 != 0 || constraints.sweepSpacingSubvoxels != 0
|
||||
|| constraints.supportProbeRadiusSubvoxels != 0
|
||||
|| constraints.supportSearchDepthSubvoxels != 0
|
||||
|| constraints.minimumClearanceSubvoxels != 0 || constraints.minimumSupportProbes != 0)
|
||||
{ error = "world assembly route constraints need a supported version"; return false; }
|
||||
}
|
||||
else if (constraints.version != 1
|
||||
|| !ValidScalar(constraints.capsuleRadiusSubvoxels)
|
||||
|| !ValidScalar(constraints.capsuleHeightSubvoxels)
|
||||
|| !ValidScalar(constraints.maximumGradeQ16)
|
||||
|| !ValidScalar(constraints.sweepSpacingSubvoxels)
|
||||
|| !ValidScalar(constraints.supportProbeRadiusSubvoxels)
|
||||
|| !ValidScalar(constraints.supportSearchDepthSubvoxels)
|
||||
|| !ValidScalar(constraints.minimumClearanceSubvoxels)
|
||||
|| constraints.capsuleRadiusSubvoxels <= 0
|
||||
|| constraints.capsuleHeightSubvoxels < 2 * constraints.capsuleRadiusSubvoxels
|
||||
|| constraints.maximumGradeQ16 <= 0 || constraints.maximumGradeQ16 > Q16
|
||||
|| constraints.sweepSpacingSubvoxels <= 0
|
||||
|| constraints.sweepSpacingSubvoxels > WorldAssemblySubvoxelsPerVoxel / 2
|
||||
|| constraints.supportProbeRadiusSubvoxels <= 0
|
||||
|| constraints.supportSearchDepthSubvoxels <= 0
|
||||
|| constraints.minimumClearanceSubvoxels < 0
|
||||
|| constraints.minimumSupportProbes == 0 || constraints.minimumSupportProbes > 5)
|
||||
{ error = "world assembly route support constraints are outside the supported range"; return false; }
|
||||
if (constraints.version == 1)
|
||||
for (const auto& point : stratum.route)
|
||||
if (point.radiusSubvoxels < constraints.capsuleRadiusSubvoxels)
|
||||
{ error = "world assembly route corridor is narrower than its player capsule"; return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -310,6 +365,20 @@ namespace world_assembly_detail
|
||||
for (const auto value : feature.maximumSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8);
|
||||
for (const auto value : feature.parametersSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8);
|
||||
}
|
||||
if (stratum.routeConstraints.version != 0)
|
||||
{
|
||||
const auto& constraints = stratum.routeConstraints;
|
||||
HashWord(hash, 0x524f5554455631ull, 8);
|
||||
HashWord(hash, constraints.version, 4);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.capsuleRadiusSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.capsuleHeightSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.maximumGradeQ16), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.sweepSpacingSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.supportProbeRadiusSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.supportSearchDepthSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(constraints.minimumClearanceSubvoxels), 8);
|
||||
HashWord(hash, constraints.minimumSupportProbes, 4);
|
||||
}
|
||||
HashWord(hash, stratum.route.size(), 4);
|
||||
for (const auto& point : stratum.route)
|
||||
{
|
||||
@@ -440,6 +509,24 @@ namespace world_assembly_detail
|
||||
{ error = "world assembly stratum has malformed optional evaluator-2 fields"; return false; }
|
||||
if(hasWaterBoolean)stratum.waterEnabled=waterEnabled.Boolean(false);
|
||||
if(hasWaterNumber)stratum.waterEnabled=waterEnabled.Number()!=0.0;
|
||||
const VFJson::Value& routeConstraintsValue = item.Get("route_constraints");
|
||||
if (!std::holds_alternative<std::nullptr_t>(routeConstraintsValue.Data))
|
||||
{
|
||||
auto& constraints = stratum.routeConstraints;
|
||||
std::uint32_t minimumSupportProbes = 0;
|
||||
if (!routeConstraintsValue.IsObject()
|
||||
|| !ReadU32(routeConstraintsValue.Get("version"), constraints.version)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("capsule_radius_q"), constraints.capsuleRadiusSubvoxels)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("capsule_height_q"), constraints.capsuleHeightSubvoxels)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("maximum_grade_q16"), constraints.maximumGradeQ16)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("sweep_spacing_q"), constraints.sweepSpacingSubvoxels)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("support_probe_radius_q"), constraints.supportProbeRadiusSubvoxels)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("support_search_depth_q"), constraints.supportSearchDepthSubvoxels)
|
||||
|| !ReadInteger(routeConstraintsValue.Get("minimum_clearance_q"), constraints.minimumClearanceSubvoxels)
|
||||
|| !ReadU32(routeConstraintsValue.Get("minimum_support_probes"), minimumSupportProbes))
|
||||
{ error = "world assembly route constraints are missing a fixed-scale field"; return false; }
|
||||
constraints.minimumSupportProbes = minimumSupportProbes;
|
||||
}
|
||||
for (const auto& featureValue : item.Get("features").AsArray())
|
||||
{
|
||||
WorldAssemblyFeatureV1 feature;
|
||||
@@ -1109,7 +1196,7 @@ namespace world_assembly_detail
|
||||
inline std::int64_t CliffShelfDensity(const WorldAssemblyFeatureV1& feature,
|
||||
const WorldAssemblyRecipeV1& recipe,const WorldAssemblyStratumPlanV1& plan,
|
||||
std::int64_t x,std::int64_t y,std::int64_t z,
|
||||
const HeightfieldPointSample& localColumn)
|
||||
const HeightfieldPointSample& localColumn,bool enforceMinimumThickness)
|
||||
{
|
||||
const auto& lo=feature.minimumSubvoxels;const auto& hi=feature.maximumSubvoxels;
|
||||
if(x<lo[0]||x>hi[0]||y<lo[1]||y>hi[1]||z<lo[2]||z>hi[2])
|
||||
@@ -1134,17 +1221,36 @@ namespace world_assembly_detail
|
||||
return std::numeric_limits<std::int64_t>::max()/4;
|
||||
const std::uint32_t vertical=SmoothQ16(static_cast<std::uint32_t>(
|
||||
std::clamp<std::int64_t>(aboveGround*Q16/height,0,Q16)));
|
||||
const std::int64_t sourceOffset=MulQ16(reach,vertical);
|
||||
// Evaluator v4 samples the source column on the shelf's root line. The offset
|
||||
// therefore follows horizontal progress through the block and stays fixed for
|
||||
// every z slice. Advancing it with z can make the shelf switch on at its top;
|
||||
// sampling the far end everywhere instead can pull a distant cliff cap across
|
||||
// the whole shelf. Earlier evaluators retain their height-dependent taper.
|
||||
const std::int64_t sourceOffset=enforceMinimumThickness
|
||||
?std::clamp<std::int64_t>(along,0,reach):MulQ16(reach,vertical);
|
||||
const std::int64_t sourceX=x-dirX*sourceOffset;
|
||||
const std::int64_t sourceY=y-dirY*sourceOffset;
|
||||
const HeightfieldPointSample source=CachedColumn(recipe,plan,sourceX,sourceY);
|
||||
if(source.groundHeightSubvoxels<=localColumn.groundHeightSubvoxels)
|
||||
return std::numeric_limits<std::int64_t>::max()/4;
|
||||
std::int64_t candidate=std::max({localColumn.groundHeightSubvoxels-z,
|
||||
z-source.groundHeightSubvoxels,-along,along-reach,
|
||||
std::abs(lateral)-halfWidth,-aboveGround,aboveGround-height});
|
||||
const std::uint32_t edgeFade=FeatureFadeQ16(feature,x,y,Feather);
|
||||
candidate+=static_cast<std::int64_t>(Q16-edgeFade)*Feather/Q16;
|
||||
const std::int64_t edgeInset=static_cast<std::int64_t>(Q16-edgeFade)*Feather/Q16;
|
||||
const std::int64_t sourceTop=enforceMinimumThickness
|
||||
?std::max(source.groundHeightSubvoxels,
|
||||
localColumn.groundHeightSubvoxels+MinimumCliffShelfThicknessSubvoxels)
|
||||
:source.groundHeightSubvoxels;
|
||||
std::int64_t candidate=std::max({localColumn.groundHeightSubvoxels-z,
|
||||
z-sourceTop,-aboveGround,aboveGround-height});
|
||||
if(enforceMinimumThickness)
|
||||
{
|
||||
// The feather narrows the shelf footprint. It must not offset the top and
|
||||
// bottom surfaces, or their vertical separation collapses into MC sheets.
|
||||
candidate=std::max({candidate,-along+edgeInset,along-reach+edgeInset,
|
||||
std::abs(lateral)-halfWidth+edgeInset});
|
||||
}
|
||||
else
|
||||
candidate=std::max({candidate,-along,along-reach,
|
||||
std::abs(lateral)-halfWidth})+edgeInset;
|
||||
return candidate;
|
||||
}
|
||||
|
||||
@@ -1192,6 +1298,26 @@ bool WorldAssemblyManifest::finalize(std::string& error)
|
||||
for (auto& stratum : plan.strates)
|
||||
{
|
||||
if (!world_assembly_detail::ValidateStratum(stratum, error)) return false;
|
||||
if (recipe.evaluatorVersion >= 4)
|
||||
{
|
||||
if (stratum.routeConstraints.version != 1 || stratum.route.size() < 2)
|
||||
{ error = "world assembly evaluator 4 requires a versioned entrance-to-exit route"; return false; }
|
||||
const std::int64_t bottomZ = static_cast<std::int64_t>(stratum.bottomChunkZ)
|
||||
* 32 * WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t topZ = (static_cast<std::int64_t>(stratum.topChunkZ) + 1)
|
||||
* 32 * WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t halfHeight = stratum.routeConstraints.capsuleHeightSubvoxels / 2;
|
||||
for (const auto& point : stratum.route)
|
||||
{
|
||||
if (point.positionSubvoxels[2] - halfHeight < bottomZ
|
||||
|| point.positionSubvoxels[2] + halfHeight > topZ)
|
||||
{ error = "world assembly route capsule escapes its finite stratum Z bounds"; return false; }
|
||||
}
|
||||
for (const auto& feature : stratum.features)
|
||||
if (feature.type == 4 && feature.parametersSubvoxels[2]
|
||||
< 2 * WorldAssemblySubvoxelsPerVoxel)
|
||||
{ error = "world assembly evaluator 4 cliff shelf is thinner than its two-voxel minimum"; return false; }
|
||||
}
|
||||
if (std::find(strateIds.begin(), strateIds.end(), stratum.strateIndex) != strateIds.end())
|
||||
{ error = "world assembly plan contains a duplicate strate index"; return false; }
|
||||
strateIds.push_back(stratum.strateIndex);
|
||||
@@ -1322,7 +1448,21 @@ std::string WorldAssemblyManifest::ToJson() const
|
||||
<< feature.parametersSubvoxels[0] << ',' << feature.parametersSubvoxels[1] << ','
|
||||
<< feature.parametersSubvoxels[2] << ',' << feature.parametersSubvoxels[3] << "]}";
|
||||
}
|
||||
out << "],\"route\":[";
|
||||
out << ']';
|
||||
if (stratum.routeConstraints.version != 0)
|
||||
{
|
||||
const auto& constraints = stratum.routeConstraints;
|
||||
out << ",\"route_constraints\":{\"version\":" << constraints.version
|
||||
<< ",\"capsule_radius_q\":" << constraints.capsuleRadiusSubvoxels
|
||||
<< ",\"capsule_height_q\":" << constraints.capsuleHeightSubvoxels
|
||||
<< ",\"maximum_grade_q16\":" << constraints.maximumGradeQ16
|
||||
<< ",\"sweep_spacing_q\":" << constraints.sweepSpacingSubvoxels
|
||||
<< ",\"support_probe_radius_q\":" << constraints.supportProbeRadiusSubvoxels
|
||||
<< ",\"support_search_depth_q\":" << constraints.supportSearchDepthSubvoxels
|
||||
<< ",\"minimum_clearance_q\":" << constraints.minimumClearanceSubvoxels
|
||||
<< ",\"minimum_support_probes\":" << constraints.minimumSupportProbes << '}';
|
||||
}
|
||||
out << ",\"route\":[";
|
||||
for (std::size_t p = 0; p < stratum.route.size(); ++p)
|
||||
{
|
||||
const auto& point = stratum.route[p];
|
||||
@@ -1391,7 +1531,8 @@ HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
|
||||
}
|
||||
else if(feature.type==4&&recipe_->evaluatorVersion>=3)
|
||||
{
|
||||
const std::int64_t shelf=CliffShelfDensity(feature,*recipe_,*plan_,x,y,z,out);
|
||||
const std::int64_t shelf=CliffShelfDensity(feature,*recipe_,*plan_,x,y,z,out,
|
||||
recipe_->evaluatorVersion>=4);
|
||||
out.densitySubvoxels=SmoothMinSubvoxels(out.densitySubvoxels,shelf,
|
||||
4*WorldAssemblySubvoxelsPerVoxel);
|
||||
}
|
||||
@@ -1592,6 +1733,9 @@ FieldDetailEnvelope Heightfield2D::detailEnvelope() const noexcept
|
||||
(feature.maximumSubvoxels[2]-feature.minimumSubvoxels[2])/2}));
|
||||
if(feature.type==4)maximum=std::max(maximum,feature.parametersSubvoxels[2]
|
||||
+WorldAssemblySubvoxelsPerVoxel);
|
||||
if(feature.type==4&&recipe_->evaluatorVersion>=4)
|
||||
minimumFeature=std::min(minimumFeature,
|
||||
world_assembly_detail::MinimumCliffShelfThicknessSubvoxels);
|
||||
if(feature.type==3)maximum=std::max(maximum,std::abs(feature.parametersSubvoxels[0]));
|
||||
}
|
||||
out.maximumAbsoluteDisplacementSubvoxels = maximum;
|
||||
@@ -1746,4 +1890,203 @@ WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(const Heightfield2D& block
|
||||
&& result.unknownBounds == 0 && result.negativeControlDetected;
|
||||
return result;
|
||||
}
|
||||
|
||||
WorldAssemblyShelfThicknessAuditResult AuditHeightfieldShelfThickness(const Heightfield2D& block)
|
||||
{
|
||||
WorldAssemblyShelfThicknessAuditResult result;
|
||||
const auto* recipe=block.recipe();
|
||||
const auto* plan=block.plan();
|
||||
if(!block.isValid()||!recipe||!plan||recipe->evaluatorVersion<4)return result;
|
||||
constexpr std::int64_t sampleStep=WorldAssemblySubvoxelsPerVoxel/4;
|
||||
constexpr std::int64_t xyStep=WorldAssemblySubvoxelsPerVoxel/2;
|
||||
std::int64_t minimumObserved=std::numeric_limits<std::int64_t>::max();
|
||||
for(std::size_t featureIndex=0;featureIndex<plan->features.size();++featureIndex)
|
||||
{
|
||||
const auto& feature=plan->features[featureIndex];
|
||||
if(feature.type!=4)continue;
|
||||
++result.featuresTested;
|
||||
auto baselinePlan=*plan;
|
||||
baselinePlan.features.erase(baselinePlan.features.begin()+featureIndex);
|
||||
const Heightfield2D baseline(*recipe,baselinePlan);
|
||||
const std::int64_t firstX=feature.minimumSubvoxels[0]+WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t lastX=feature.maximumSubvoxels[0]-WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t firstY=feature.minimumSubvoxels[1]+WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t lastY=feature.maximumSubvoxels[1]-WorldAssemblySubvoxelsPerVoxel;
|
||||
for(std::int64_t y=firstY;y<=lastY;y+=xyStep)
|
||||
for(std::int64_t x=firstX;x<=lastX;x+=xyStep)
|
||||
{
|
||||
++result.columnsTested;
|
||||
const std::int64_t ground=baseline.point(x,y,0).groundHeightSubvoxels;
|
||||
const std::int64_t range=std::max<std::int64_t>(feature.parametersSubvoxels[2],
|
||||
world_assembly_detail::MinimumCliffShelfThicknessSubvoxels)+sampleStep;
|
||||
std::int64_t currentRun=0,legacyCurrentRun=0;
|
||||
std::int64_t currentStart=0,legacyCurrentStart=0;
|
||||
bool columnActive=false,columnSubVoxel=false,legacyColumnSubVoxel=false;
|
||||
const auto recordRun=[&](std::int64_t run,std::int64_t start,bool legacy)
|
||||
{
|
||||
if(run<=0)return;
|
||||
if(legacy)
|
||||
{
|
||||
if(run<WorldAssemblySubvoxelsPerVoxel)
|
||||
{++result.legacySubVoxelRuns;legacyColumnSubVoxel=true;}
|
||||
return;
|
||||
}
|
||||
columnActive=true;
|
||||
++result.negativeRuns;
|
||||
if(run<minimumObserved)
|
||||
{
|
||||
minimumObserved=run;
|
||||
result.minimumThicknessColumnXSubvoxels=x;
|
||||
result.minimumThicknessColumnYSubvoxels=y;
|
||||
result.minimumThicknessGroundSubvoxels=ground;
|
||||
result.minimumThicknessBottomSubvoxels=start;
|
||||
result.minimumThicknessTopSubvoxels=start+run-sampleStep;
|
||||
}
|
||||
if(run<WorldAssemblySubvoxelsPerVoxel)
|
||||
{++result.subVoxelRuns;columnSubVoxel=true;}
|
||||
};
|
||||
for(std::int64_t dz=sampleStep;dz<=range;dz+=sampleStep)
|
||||
{
|
||||
const std::int64_t z=ground+dz;
|
||||
const auto baselineSample=baseline.point(x,y,z);
|
||||
if(baselineSample.densitySubvoxels<=0)
|
||||
{
|
||||
recordRun(currentRun,currentStart,false);currentRun=0;
|
||||
recordRun(legacyCurrentRun,legacyCurrentStart,true);legacyCurrentRun=0;
|
||||
continue;
|
||||
}
|
||||
const auto composedSample=block.point(x,y,z);
|
||||
if(composedSample.densitySubvoxels<0)
|
||||
{
|
||||
if(currentRun==0)currentStart=z;
|
||||
currentRun+=sampleStep;
|
||||
}
|
||||
else {recordRun(currentRun,currentStart,false);currentRun=0;}
|
||||
|
||||
const std::int64_t oldShelf=world_assembly_detail::CliffShelfDensity(feature,*recipe,baselinePlan,
|
||||
x,y,z,baselineSample,false);
|
||||
const std::int64_t legacyDensity=world_assembly_detail::SmoothMinSubvoxels(
|
||||
baselineSample.densitySubvoxels,oldShelf,4*WorldAssemblySubvoxelsPerVoxel);
|
||||
if(legacyDensity<0)
|
||||
{if(legacyCurrentRun==0)legacyCurrentStart=z;legacyCurrentRun+=sampleStep;}
|
||||
else {recordRun(legacyCurrentRun,legacyCurrentStart,true);legacyCurrentRun=0;}
|
||||
}
|
||||
recordRun(currentRun,currentStart,false);
|
||||
recordRun(legacyCurrentRun,legacyCurrentStart,true);
|
||||
if(columnActive)++result.activeColumns;
|
||||
if(columnSubVoxel)++result.subVoxelColumns;
|
||||
if(legacyColumnSubVoxel)++result.legacySubVoxelColumns;
|
||||
}
|
||||
}
|
||||
if(minimumObserved!=std::numeric_limits<std::int64_t>::max())
|
||||
result.minimumObservedThicknessSubvoxels=minimumObserved;
|
||||
result.negativeControlDetected=result.legacySubVoxelColumns>0;
|
||||
result.passed=result.featuresTested>0&&result.activeColumns>0
|
||||
&&result.minimumObservedThicknessSubvoxels>=world_assembly_detail::MinimumCliffShelfThicknessSubvoxels
|
||||
&&result.subVoxelRuns==0&&result.negativeControlDetected;
|
||||
return result;
|
||||
}
|
||||
|
||||
WorldAssemblyRouteAuditResult AuditHeightfieldRoute(const Heightfield2D& block)
|
||||
{
|
||||
WorldAssemblyRouteAuditResult result;
|
||||
const auto* plan=block.plan();
|
||||
if(!block.isValid()||!plan||plan->routeConstraints.version!=1||plan->route.size()<2)
|
||||
return result;
|
||||
using namespace world_assembly_detail;
|
||||
const auto& constraints=plan->routeConstraints;
|
||||
const std::int64_t radius=constraints.capsuleRadiusSubvoxels;
|
||||
const std::int64_t halfHeight=constraints.capsuleHeightSubvoxels/2;
|
||||
const std::int64_t axisHalf=std::max<std::int64_t>(0,halfHeight-radius);
|
||||
const std::int64_t spacing=std::min<std::int64_t>(constraints.sweepSpacingSubvoxels,
|
||||
WorldAssemblySubvoxelsPerVoxel/2);
|
||||
const std::int64_t supportSpacing=std::min<std::int64_t>(spacing,
|
||||
WorldAssemblySubvoxelsPerVoxel/4);
|
||||
std::int64_t minimumClearance=std::numeric_limits<std::int64_t>::max();
|
||||
std::int64_t maximumSupportDepth=0;
|
||||
const std::array<std::array<std::int64_t,2>,5> supportOffsets{{
|
||||
{{0,0}},{{constraints.supportProbeRadiusSubvoxels,0}},
|
||||
{{-constraints.supportProbeRadiusSubvoxels,0}},
|
||||
{{0,constraints.supportProbeRadiusSubvoxels}},
|
||||
{{0,-constraints.supportProbeRadiusSubvoxels}}}};
|
||||
|
||||
const auto sweepCapsule=[&](const std::array<std::int64_t,3>& center)
|
||||
{
|
||||
++result.centerSamples;
|
||||
const std::int64_t footZ=center[2]-halfHeight;
|
||||
const std::int64_t firstDz=-halfHeight+constraints.minimumClearanceSubvoxels;
|
||||
for(std::int64_t dz=firstDz;dz<=halfHeight;dz+=spacing)
|
||||
{
|
||||
const std::int64_t capOffset=std::max<std::int64_t>(0,std::abs(dz)-axisHalf);
|
||||
const std::int64_t radialSquared=radius*radius-capOffset*capOffset;
|
||||
if(radialSquared<0)continue;
|
||||
for(std::int64_t oy=-radius;oy<=radius;oy+=spacing)
|
||||
for(std::int64_t ox=-radius;ox<=radius;ox+=spacing)
|
||||
{
|
||||
if(ox*ox+oy*oy>radialSquared)continue;
|
||||
const auto sample=block.point(center[0]+ox,center[1]+oy,center[2]+dz);
|
||||
++result.capsuleFieldSamples;
|
||||
minimumClearance=std::min(minimumClearance,sample.densitySubvoxels);
|
||||
if(sample.densitySubvoxels<constraints.minimumClearanceSubvoxels)
|
||||
++result.blockedCapsuleSamples;
|
||||
}
|
||||
}
|
||||
std::uint32_t supportedProbes=0;
|
||||
for(const auto& offset:supportOffsets)
|
||||
{
|
||||
++result.supportProbeSamples;
|
||||
bool supported=false;
|
||||
for(std::int64_t depth=supportSpacing;
|
||||
depth<=constraints.supportSearchDepthSubvoxels;depth+=supportSpacing)
|
||||
{
|
||||
const auto sample=block.point(center[0]+offset[0],center[1]+offset[1],footZ-depth);
|
||||
if(sample.densitySubvoxels<0)
|
||||
{
|
||||
supported=true;
|
||||
maximumSupportDepth=std::max(maximumSupportDepth,depth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(supported)++supportedProbes;
|
||||
}
|
||||
if(supportedProbes<constraints.minimumSupportProbes)
|
||||
++result.unsupportedCenterSamples;
|
||||
};
|
||||
|
||||
for(std::size_t segment=1;segment<plan->route.size();++segment)
|
||||
{
|
||||
++result.segmentsTested;
|
||||
const auto& from=plan->route[segment-1].positionSubvoxels;
|
||||
const auto& to=plan->route[segment].positionSubvoxels;
|
||||
const std::int64_t dx=to[0]-from[0],dy=to[1]-from[1],dz=to[2]-from[2];
|
||||
const std::uint64_t horizontalSquared=static_cast<std::uint64_t>(dx*dx+dy*dy);
|
||||
const std::uint64_t horizontalLength=IntegerSqrt(horizontalSquared);
|
||||
const std::uint64_t rise=static_cast<std::uint64_t>(std::abs(dz));
|
||||
if(horizontalLength==0||rise*static_cast<std::uint64_t>(Q16)
|
||||
>horizontalLength*static_cast<std::uint64_t>(constraints.maximumGradeQ16))
|
||||
++result.gradeFailures;
|
||||
const std::uint64_t length=IntegerSqrt(horizontalSquared
|
||||
+static_cast<std::uint64_t>(dz*dz));
|
||||
const std::uint64_t sampleCount=std::max<std::uint64_t>(1,
|
||||
(length+static_cast<std::uint64_t>(spacing)-1)/static_cast<std::uint64_t>(spacing));
|
||||
const std::uint64_t first=segment==1?0:1;
|
||||
for(std::uint64_t i=first;i<=sampleCount;++i)
|
||||
{
|
||||
const auto interpolate=[&](std::size_t axis)
|
||||
{
|
||||
const std::int64_t delta=to[axis]-from[axis];
|
||||
return from[axis]+delta*static_cast<std::int64_t>(i)
|
||||
/static_cast<std::int64_t>(sampleCount);
|
||||
};
|
||||
sweepCapsule({interpolate(0),interpolate(1),interpolate(2)});
|
||||
}
|
||||
}
|
||||
result.minimumClearanceSubvoxels=minimumClearance==std::numeric_limits<std::int64_t>::max()
|
||||
?0:minimumClearance;
|
||||
result.maximumSupportDepthSubvoxels=maximumSupportDepth;
|
||||
result.passed=result.segmentsTested>0&&result.centerSamples>0
|
||||
&&result.blockedCapsuleSamples==0&&result.unsupportedCenterSamples==0
|
||||
&&result.gradeFailures==0;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user