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
+26
-10
@@ -79,14 +79,15 @@ Pass `analyticOnly = false` to retain the reference packed-page compiler. Packed
|
||||
parent-cell coordinates and an 8-voxel apron; each covers a 128-voxel cube or one larger coarse
|
||||
tile. This path remains available for parity checks and workloads that need the packed lattice.
|
||||
|
||||
## World assembler manifest v1 (prototype; evaluator v1 and v2)
|
||||
## World assembler manifest v1 (evaluator v1–v4)
|
||||
|
||||
`ResolvedWorldSnapshot::FromJson` accepts an optional `world_assembly` object alongside the owner
|
||||
snapshot. The object is the shipped-plan payload shape: a fixed-scale recipe and finite-strate plan
|
||||
with hashes, no generated mesh or voxel samples. The current evaluator-v2 review payload is
|
||||
[`round2/world_assembly.json`](../Research/WorldAssemblerM1/round2/world_assembly.json); the
|
||||
evaluator-v1 sample remains at [`round1-manifest.json`](../Research/WorldAssemblerM1/round1-manifest.json).
|
||||
Round 4 still needs to connect this payload to the season asset/hash path.
|
||||
with hashes, no generated mesh or voxel samples. The current review payload is written to
|
||||
[`round4/world_assembly.json`](../Research/WorldAssemblerM1/round4/world_assembly.json).
|
||||
Evaluator 1–3 payloads remain readable with their original hashes and field semantics. Evaluator 4
|
||||
adds a minimum two-voxel shelf thickness and a versioned route support contract; it requires at
|
||||
least two route waypoints.
|
||||
|
||||
Every coordinate and scalar uses signed integer units of 1/256 voxel (`units_per_voxel: 256`),
|
||||
independent of the plugin's 25 cm Core voxel and of tile size. The schema version is 1; evaluator
|
||||
@@ -96,8 +97,10 @@ order and hashed as 4- or 8-byte little-endian words with FNV-1a 64. Hashes are
|
||||
lowercase hexadecimal characters; JSON key order and whitespace do not affect them. The root,
|
||||
recipe, plan and each stratum carry a content hash. The recipe stores its seed, warped biome-map
|
||||
settings and one to eight biome profiles; the plan stores finite-strate extents, cap settings,
|
||||
stable-ID features, fixed-season water and route points. Bounds, feature count and route count are
|
||||
validated before a manifest is accepted.
|
||||
stable-ID features, fixed-season water, route points and (for evaluator 4) player-capsule, grade,
|
||||
support and sweep-spacing constraints. Route points are fixed-scale capsule-center locations with
|
||||
stable IDs and a protected automatic-change corridor radius. Bounds, feature count, route count, route
|
||||
IDs, corridor widths and finite-strate capsule extents are validated before a manifest is accepted.
|
||||
|
||||
Evaluator 2 adds climate-based biome assignment, output-space blending of both ground and cap
|
||||
profiles, per-biome relief and height warp, beach width, cliff limits, and optional per-biome cap
|
||||
@@ -120,8 +123,11 @@ neighbor biome IDs, Q16 blend weight and signed density. Evaluator 2 also return
|
||||
surface and water-fill sample values. Negative density is solid. Its contract reads `biome` and
|
||||
writes `height`, `rock`, `water` and `waterSurface`; route and `surfaceTags` remain declared channels
|
||||
for later blocks. The detail envelope reports maximum absolute displacement and minimum feature
|
||||
size. Influence is global in XY and limited to the finite stratum's Z extent, except for the
|
||||
explicitly bounded evaluator-v2 feature boxes.
|
||||
size. Evaluator 4 defines the shelf's minimum solid thickness as two Core voxels (0.5 m) and applies
|
||||
its feather to the horizontal footprint so it cannot thin the roof-to-floor section.
|
||||
`AuditHeightfieldShelfThickness` samples shelf sections against the pre-feature field and includes
|
||||
the previous sub-voxel taper as a negative control. Influence is global in XY and limited to the
|
||||
finite stratum's Z extent, except for the explicitly bounded feature boxes.
|
||||
|
||||
`interval(minXQ, minYQ, minZQ, maxXQ, maxYQ, maxZQ)` returns a conservative density range or
|
||||
`known == false`; callers must keep evaluating when it is unknown. The current heightfield range
|
||||
@@ -131,9 +137,19 @@ negative-coordinate and overhang boxes, and includes a deliberately false one-si
|
||||
negative control. A pass means no sampled point escaped and the negative control was rejected; it
|
||||
is an audit of this block implementation, not a proof for future blocks.
|
||||
|
||||
`AuditHeightfieldRoute` sweeps the recorded route through the final composed field using the
|
||||
versioned player capsule (0.34 m radius, 1.76 m height), checks a 15-degree maximum grade, samples
|
||||
capsule clearance at no more than half-voxel intervals, and probes support below five foot locations
|
||||
at each swept center. Automatic changes must be composed first and pass this audit before the plan is
|
||||
accepted. Player carve/fill edits remain unrestricted. This validates analytic field collision and
|
||||
support; it does not cook an Unreal physics mesh.
|
||||
|
||||
## Queries and tiles
|
||||
|
||||
`densityAt(position)` returns signed density; negative is solid and nonnegative is air.
|
||||
`densityAt(position)` returns signed density; negative is solid and nonnegative is air. Server and
|
||||
authority code can compile/query a `World` and run `AuditHeightfieldRoute` without calling
|
||||
`generateTile`; generated render meshes are not part of this query path. Unreal physics collision
|
||||
cooking remains the adapter's separate readiness step.
|
||||
`isSolidAt` wraps that sign test. `isSolidAlongSegment(start, end, spacing)` samples evenly along
|
||||
the segment with no interval wider than `spacing` voxels; it returns true if any sample is solid.
|
||||
`classifyBox(origin, step, cells)` returns `allSolid`, `allAir` or `mixed` for mesher tile
|
||||
|
||||
@@ -82,6 +82,8 @@ namespace VoxelForgeCore
|
||||
double meshingMilliseconds = 0.0;
|
||||
bool tileWasClassified = false;
|
||||
bool meshingSkipped = false;
|
||||
bool featureRefinedResolution = false;
|
||||
std::uint64_t farCapTrianglesSuppressed = 0;
|
||||
};
|
||||
|
||||
struct TileMesh
|
||||
@@ -126,7 +128,7 @@ namespace VoxelForgeCore
|
||||
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
|
||||
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
||||
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 1;
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 3;
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 4;
|
||||
|
||||
enum class FieldChannel : std::uint32_t
|
||||
{
|
||||
@@ -193,11 +195,26 @@ namespace VoxelForgeCore
|
||||
|
||||
struct WorldAssemblyRoutePointV1
|
||||
{
|
||||
// Stable ID and fixed-scale capsule-center waypoint. Radius is the protected
|
||||
// automatic-change corridor around this centerline point.
|
||||
std::uint64_t stableFeatureId = 0;
|
||||
std::array<std::int64_t, 3> positionSubvoxels{};
|
||||
std::int64_t radiusSubvoxels = 0;
|
||||
};
|
||||
|
||||
struct WorldAssemblyRouteConstraintsV1
|
||||
{
|
||||
std::uint32_t version = 0;
|
||||
std::int64_t capsuleRadiusSubvoxels = 0;
|
||||
std::int64_t capsuleHeightSubvoxels = 0;
|
||||
std::int64_t maximumGradeQ16 = 0;
|
||||
std::int64_t sweepSpacingSubvoxels = 0;
|
||||
std::int64_t supportProbeRadiusSubvoxels = 0;
|
||||
std::int64_t supportSearchDepthSubvoxels = 0;
|
||||
std::int64_t minimumClearanceSubvoxels = 0;
|
||||
std::uint32_t minimumSupportProbes = 0;
|
||||
};
|
||||
|
||||
struct WorldAssemblyFeatureV1
|
||||
{
|
||||
// Stable numeric type IDs are part of the plan contract: 1 is a heightfield cliff plane,
|
||||
@@ -228,6 +245,7 @@ namespace VoxelForgeCore
|
||||
std::int64_t waterSurfaceSubvoxels = 0;
|
||||
bool waterEnabled = false;
|
||||
std::vector<WorldAssemblyFeatureV1> features;
|
||||
WorldAssemblyRouteConstraintsV1 routeConstraints;
|
||||
std::vector<WorldAssemblyRoutePointV1> route;
|
||||
std::uint64_t contentHash = 0;
|
||||
};
|
||||
@@ -357,10 +375,50 @@ namespace VoxelForgeCore
|
||||
bool passed = false;
|
||||
};
|
||||
|
||||
struct WorldAssemblyShelfThicknessAuditResult
|
||||
{
|
||||
std::uint64_t featuresTested = 0;
|
||||
std::uint64_t columnsTested = 0;
|
||||
std::uint64_t activeColumns = 0;
|
||||
std::uint64_t negativeRuns = 0;
|
||||
std::uint64_t subVoxelColumns = 0;
|
||||
std::uint64_t subVoxelRuns = 0;
|
||||
std::uint64_t legacySubVoxelColumns = 0;
|
||||
std::uint64_t legacySubVoxelRuns = 0;
|
||||
std::int64_t minimumObservedThicknessSubvoxels = 0;
|
||||
std::int64_t minimumThicknessColumnXSubvoxels = 0;
|
||||
std::int64_t minimumThicknessColumnYSubvoxels = 0;
|
||||
std::int64_t minimumThicknessGroundSubvoxels = 0;
|
||||
std::int64_t minimumThicknessBottomSubvoxels = 0;
|
||||
std::int64_t minimumThicknessTopSubvoxels = 0;
|
||||
bool negativeControlDetected = false;
|
||||
bool passed = false;
|
||||
};
|
||||
|
||||
struct WorldAssemblyRouteAuditResult
|
||||
{
|
||||
std::uint64_t segmentsTested = 0;
|
||||
std::uint64_t centerSamples = 0;
|
||||
std::uint64_t capsuleFieldSamples = 0;
|
||||
std::uint64_t blockedCapsuleSamples = 0;
|
||||
std::uint64_t supportProbeSamples = 0;
|
||||
std::uint64_t unsupportedCenterSamples = 0;
|
||||
std::uint64_t gradeFailures = 0;
|
||||
std::int64_t minimumClearanceSubvoxels = 0;
|
||||
std::int64_t maximumSupportDepthSubvoxels = 0;
|
||||
bool passed = false;
|
||||
};
|
||||
|
||||
VFCORE_API WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(
|
||||
const Heightfield2D& block, std::uint32_t boxCount = 256,
|
||||
std::uint64_t seed = 0x574f524c44415331ull);
|
||||
|
||||
VFCORE_API WorldAssemblyShelfThicknessAuditResult AuditHeightfieldShelfThickness(
|
||||
const Heightfield2D& block);
|
||||
|
||||
VFCORE_API WorldAssemblyRouteAuditResult AuditHeightfieldRoute(
|
||||
const Heightfield2D& block);
|
||||
|
||||
class VFCORE_API ResolvedWorldSnapshot
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -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