wip(L6, side branch): screen-space far gate; no-op cutoff removed; cost within noise vs L4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ED33VnqepD4YY6gDAAA832
This commit is contained in:
1 parent
b21a19d561
commit
aa67bd31f5
13 files changed
+469
-610
No files matched your search
@@ -137,6 +137,7 @@ namespace VoxelForgeCore
|
||||
double marchingCubesGradientNormalsMilliseconds = 0.0;
|
||||
double marchingCubesEdgeInterpolationMilliseconds = 0.0;
|
||||
double marchingCubesVertexDedupStreamMilliseconds = 0.0;
|
||||
double marchingCubesSkirtMilliseconds = 0.0;
|
||||
double marchingCubesOtherMilliseconds = 0.0;
|
||||
std::uint64_t marchingCubesGradientFallbackDensityCalls = 0;
|
||||
std::uint64_t marchingCubesEdgeMidpointDensityCalls = 0;
|
||||
@@ -146,11 +147,6 @@ namespace VoxelForgeCore
|
||||
double meshAssemblyMilliseconds = 0.0;
|
||||
double meshingMilliseconds = 0.0;
|
||||
double featureListMilliseconds = 0.0;
|
||||
std::array<std::uint64_t,8> visualDetailOctaveEvaluations{};
|
||||
std::array<std::uint64_t,8> visualDetailOctaveBudgetEligible{};
|
||||
std::array<std::uint64_t,8> visualDetailOctaveSkips{};
|
||||
std::array<double,8> visualDetailOctaveMilliseconds{};
|
||||
std::array<double,8> visualDetailOctaveEligibleMilliseconds{};
|
||||
double farShellMilliseconds = 0.0;
|
||||
double farStratumShellMilliseconds = 0.0;
|
||||
double farConnectorShellMilliseconds = 0.0;
|
||||
@@ -585,8 +581,6 @@ namespace VoxelForgeCore
|
||||
struct FieldInterval;
|
||||
struct FieldDetailEnvelope;
|
||||
struct FieldBlockContract;
|
||||
struct VisualDetailRecipeContractV1;
|
||||
struct VisualDetailCutoffV1;
|
||||
|
||||
class VFCORE_API FloatingIslandFieldV1
|
||||
{
|
||||
@@ -609,7 +603,6 @@ namespace VoxelForgeCore
|
||||
std::int64_t maximumZSubvoxels) const noexcept;
|
||||
FloatingIslandInfluenceBounds influenceBounds() const noexcept;
|
||||
FieldDetailEnvelope detailEnvelope() const noexcept;
|
||||
VisualDetailRecipeContractV1 visualDetailContract() const;
|
||||
FieldBlockContract contract() const noexcept;
|
||||
const FloatingIslandRecipeV1* recipe() const noexcept { return recipe_; }
|
||||
const FloatingIslandPlanV1* plan() const noexcept { return plan_; }
|
||||
@@ -659,12 +652,6 @@ namespace VoxelForgeCore
|
||||
std::uint64_t columnMisses = 0;
|
||||
std::uint64_t columnConflictMisses = 0;
|
||||
double columnComputeMilliseconds = 0.0;
|
||||
std::array<std::uint64_t,8> visualDetailOctaveEvaluations{};
|
||||
std::array<std::uint64_t,8> visualDetailOctaveBudgetEligible{};
|
||||
std::array<std::uint64_t,8> visualDetailOctaveSkips{};
|
||||
std::array<double,8> visualDetailOctaveMilliseconds{};
|
||||
std::array<double,8> visualDetailOctaveEligibleMilliseconds{};
|
||||
bool profileVisualDetailOctaves = false;
|
||||
bool captureCoordinates = false;
|
||||
bool coordinateCaptureComplete = true;
|
||||
std::vector<std::array<std::int64_t, 2>> evaluatedCoordinates;
|
||||
@@ -684,44 +671,6 @@ namespace VoxelForgeCore
|
||||
std::int64_t minimumFeatureSizeSubvoxels = 1;
|
||||
};
|
||||
|
||||
// A recipe may opt its display-only noise terms into this shared octave contract.
|
||||
// A missing declaration keeps the exact evaluator. The per-call amplitude includes
|
||||
// every downstream scale, so wavelength alone can never authorize dropping a term.
|
||||
struct VisualDetailOctaveTermV1
|
||||
{
|
||||
std::uint64_t stableId = 0;
|
||||
std::int64_t baseWavelengthSubvoxels = 0;
|
||||
std::int64_t maximumDisplacementSubvoxels = 0;
|
||||
std::uint32_t octaveCount = 0;
|
||||
std::uint32_t maximumNoiseAbsQ16 = 2u * 65536u;
|
||||
};
|
||||
|
||||
inline constexpr std::uint32_t CurrentVisualDetailCutoffContractVersion = 2;
|
||||
|
||||
struct VisualDetailRecipeContractV1
|
||||
{
|
||||
std::uint32_t version = CurrentVisualDetailCutoffContractVersion;
|
||||
std::vector<VisualDetailOctaveTermV1> terms;
|
||||
};
|
||||
|
||||
struct VisualDetailCutoffV1
|
||||
{
|
||||
std::int64_t pixelBudgetSubvoxels = 0;
|
||||
std::int64_t sampleStepSubvoxels = 0;
|
||||
std::uint32_t callBudgetDivisor = 64;
|
||||
bool apply = false;
|
||||
};
|
||||
|
||||
struct VisualDetailCutoffResultV1
|
||||
{
|
||||
std::uint32_t firstSkippedOctave = 0;
|
||||
std::int64_t maximumErrorSubvoxels = 0;
|
||||
};
|
||||
|
||||
VFCORE_API VisualDetailCutoffResultV1 FindVisualDetailTailCutoffV1(
|
||||
const VisualDetailOctaveTermV1& term,
|
||||
const VisualDetailCutoffV1& cutoff) noexcept;
|
||||
|
||||
struct FieldBlockContract
|
||||
{
|
||||
std::uint32_t readsChannels = 0;
|
||||
@@ -756,6 +705,10 @@ namespace VoxelForgeCore
|
||||
std::int64_t maximumZSubvoxels, std::int32_t stepVoxels) const noexcept;
|
||||
static void resetThreadColumnCacheStats(bool captureCoordinates = false,
|
||||
bool clearCache = false, std::uint32_t octaveDrop = 0) noexcept;
|
||||
static void setThreadColumnOctaveDrop(std::uint32_t octaveDrop) noexcept;
|
||||
static std::uint32_t threadColumnOctaveDrop() noexcept;
|
||||
static void setThreadColumnInterpolationStrideSubvoxels(std::int64_t stride) noexcept;
|
||||
static std::int64_t threadColumnInterpolationStrideSubvoxels() noexcept;
|
||||
static void setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept;
|
||||
static const HeightfieldColumnCacheStats& threadColumnCacheStats() noexcept;
|
||||
FieldInterval interval(std::int64_t minimumXSubvoxels,
|
||||
@@ -766,15 +719,6 @@ namespace VoxelForgeCore
|
||||
std::int64_t maximumZSubvoxels) const noexcept;
|
||||
FieldInfluenceBounds influenceBounds() const noexcept;
|
||||
FieldDetailEnvelope detailEnvelope() const noexcept;
|
||||
VisualDetailRecipeContractV1 visualDetailContract() const;
|
||||
static void setThreadVisualDetailCutoff(VisualDetailCutoffV1 cutoff) noexcept;
|
||||
static VisualDetailCutoffV1 threadVisualDetailCutoff() noexcept;
|
||||
static void setThreadColumnOctaveDrop(std::uint32_t octaveDrop) noexcept;
|
||||
static std::uint32_t threadColumnOctaveDrop() noexcept;
|
||||
static void setThreadVisualDetailProfileEnabled(bool enabled) noexcept;
|
||||
static bool threadVisualDetailProfileEnabled() noexcept;
|
||||
static void setThreadColumnInterpolationStrideSubvoxels(std::int64_t stride) noexcept;
|
||||
static std::int64_t threadColumnInterpolationStrideSubvoxels() noexcept;
|
||||
FieldBlockContract contract() const noexcept;
|
||||
const WorldAssemblyRecipeV1* recipe() const noexcept { return recipe_; }
|
||||
const WorldAssemblyStratumPlanV1* plan() const noexcept { return plan_; }
|
||||
@@ -1111,15 +1055,7 @@ namespace VoxelForgeCore
|
||||
// Incremente quand le plafond lointain change. Les hashes LOD4-8 incluent cette version;
|
||||
// densite et hashes LOD0-3 restent inchanges.
|
||||
std::uint32_t farHorizonVersion = 3;
|
||||
// Display mesh only. Zero preserves the exact field. Collision, queries and edits
|
||||
// never set this option.
|
||||
std::int64_t visualDetailPixelBudgetSubvoxels = 0;
|
||||
std::int32_t visualDetailSampleStepVoxels = 0;
|
||||
std::uint32_t visualDetailCallBudgetDivisor = 64;
|
||||
bool profileVisualDetailOctaves = false;
|
||||
bool visualDetailCutoffEnabled = false;
|
||||
std::uint32_t visualDetailContractVersion = 0;
|
||||
// Diagnostic controls used only by the Linux gate's historical negative controls.
|
||||
// Linux research controls for reproducing L5 negative controls. Defaults preserve output.
|
||||
std::uint32_t diagnosticGlobalOctaveDrop = 0;
|
||||
std::uint32_t diagnosticCellStride = 1;
|
||||
std::uint32_t diagnosticColumnInterpolationStride = 1;
|
||||
|
||||
@@ -1905,13 +1905,6 @@ FieldDetailEnvelope FloatingIslandFieldV1::detailEnvelope() const noexcept
|
||||
return result;
|
||||
}
|
||||
|
||||
VisualDetailRecipeContractV1 FloatingIslandFieldV1::visualDetailContract() const
|
||||
{
|
||||
// Current island geometry uses shape and connector terms without an octave declaration.
|
||||
// Returning an empty shared contract deliberately selects exact sampling.
|
||||
return {};
|
||||
}
|
||||
|
||||
FieldBlockContract FloatingIslandFieldV1::contract() const noexcept
|
||||
{
|
||||
FieldBlockContract result;
|
||||
|
||||
@@ -220,13 +220,6 @@ namespace VoxelForgeCore
|
||||
return Hash;
|
||||
}
|
||||
|
||||
std::uint64_t SaltVisualDetailHash(std::uint64_t Hash,std::uint32_t Version)
|
||||
{
|
||||
AddWord(Hash,0x5649534445544149ull,8);
|
||||
AddWord(Hash,Version,4);
|
||||
return Hash;
|
||||
}
|
||||
|
||||
FStandaloneShapeWorld::EDetailVariant ToNative(DetailVariant value)
|
||||
{
|
||||
switch(value)
|
||||
@@ -1337,25 +1330,6 @@ namespace VoxelForgeCore
|
||||
std::optional<TileMesh> World::generateTile(const TileKey& key,const TileGenerationOptions& options,
|
||||
std::string& error) const
|
||||
{
|
||||
struct FScopedHeightfieldVisualDetailState
|
||||
{
|
||||
VisualDetailCutoffV1 Cutoff=Heightfield2D::threadVisualDetailCutoff();
|
||||
std::uint32_t OctaveDrop=Heightfield2D::threadColumnOctaveDrop();
|
||||
bool bProfile=Heightfield2D::threadVisualDetailProfileEnabled();
|
||||
std::int64_t InterpolationStride=
|
||||
Heightfield2D::threadColumnInterpolationStrideSubvoxels();
|
||||
~FScopedHeightfieldVisualDetailState()
|
||||
{
|
||||
Heightfield2D::setThreadVisualDetailCutoff(Cutoff);
|
||||
Heightfield2D::setThreadColumnOctaveDrop(OctaveDrop);
|
||||
Heightfield2D::setThreadVisualDetailProfileEnabled(bProfile);
|
||||
Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(InterpolationStride);
|
||||
}
|
||||
} VisualDetailState;
|
||||
Heightfield2D::setThreadVisualDetailProfileEnabled(options.profileVisualDetailOctaves);
|
||||
if(options.visualDetailCutoffEnabled
|
||||
&&options.visualDetailContractVersion!=CurrentVisualDetailCutoffContractVersion)
|
||||
{error="visual-detail recipe contract version is unsupported";return std::nullopt;}
|
||||
// Fine owner tiles had zero direct-map conflict evictions in the cold audit. Keep their
|
||||
// single-probe path and use associativity for the coarse coordinate families that collide.
|
||||
// Les tuiles proches n'avaient aucun conflit; garder leur acces simple et associer les LOD grossiers.
|
||||
@@ -1712,19 +1686,20 @@ namespace VoxelForgeCore
|
||||
out.diagnostics.featureListMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-FeatureListStart).count();
|
||||
const ScopedWorldAssemblyFeatureQueriesV1 featureScope(featureQueries);
|
||||
VisualDetailCutoffV1 DisplayCutoff;
|
||||
DisplayCutoff.pixelBudgetSubvoxels=options.visualDetailPixelBudgetSubvoxels;
|
||||
DisplayCutoff.sampleStepSubvoxels=DisplayCutoff.pixelBudgetSubvoxels>0
|
||||
?static_cast<std::int64_t>(MeshStep)*WorldAssemblySubvoxelsPerVoxel:0;
|
||||
DisplayCutoff.callBudgetDivisor=std::max<std::uint32_t>(1,
|
||||
options.visualDetailCallBudgetDivisor);
|
||||
DisplayCutoff.apply=options.visualDetailCutoffEnabled;
|
||||
Heightfield2D::setThreadVisualDetailCutoff(DisplayCutoff);
|
||||
const bool bDiagnosticColumnSampling=options.diagnosticGlobalOctaveDrop!=0
|
||||
||options.diagnosticColumnInterpolationStride>1;
|
||||
const std::uint32_t PreviousOctaveDrop=bDiagnosticColumnSampling
|
||||
?Heightfield2D::threadColumnOctaveDrop():0;
|
||||
const std::int64_t PreviousInterpolationStride=bDiagnosticColumnSampling
|
||||
?Heightfield2D::threadColumnInterpolationStrideSubvoxels():0;
|
||||
if(bDiagnosticColumnSampling)
|
||||
{
|
||||
Heightfield2D::setThreadColumnOctaveDrop(options.diagnosticGlobalOctaveDrop);
|
||||
Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(
|
||||
options.diagnosticColumnInterpolationStride>1&&level>=3
|
||||
?static_cast<std::int64_t>(MeshStep)*WorldAssemblySubvoxelsPerVoxel
|
||||
*options.diagnosticColumnInterpolationStride:0);
|
||||
}
|
||||
const double ColumnMillisecondsBeforeMeshing=
|
||||
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
||||
const auto MarchingCubesStart=std::chrono::steady_clock::now();
|
||||
@@ -1732,6 +1707,11 @@ namespace VoxelForgeCore
|
||||
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
||||
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr,
|
||||
options.profileMesherStages?&McStageTimings:nullptr);
|
||||
if(bDiagnosticColumnSampling)
|
||||
{
|
||||
Heightfield2D::setThreadColumnOctaveDrop(PreviousOctaveDrop);
|
||||
Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(PreviousInterpolationStride);
|
||||
}
|
||||
const double MarchingCubesWallMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-MarchingCubesStart).count();
|
||||
const double ColumnMillisecondsAfterMeshing=
|
||||
@@ -1742,6 +1722,10 @@ namespace VoxelForgeCore
|
||||
ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing);
|
||||
if(options.profileMesherStages)
|
||||
{
|
||||
out.diagnostics.marchingCubesSkirtMilliseconds=McStageTimings.SkirtMilliseconds;
|
||||
out.diagnostics.marchingCubesExclusiveMilliseconds=std::max(0.0,
|
||||
out.diagnostics.marchingCubesExclusiveMilliseconds
|
||||
-out.diagnostics.marchingCubesSkirtMilliseconds);
|
||||
out.diagnostics.marchingCubesDensityGridMilliseconds=McStageTimings.DensityGridMilliseconds;
|
||||
out.diagnostics.marchingCubesCellClassificationMilliseconds=
|
||||
McStageTimings.CellClassificationMilliseconds;
|
||||
@@ -1845,12 +1829,6 @@ namespace VoxelForgeCore
|
||||
out.diagnostics.columnCacheHits=Columns.columnHits;
|
||||
out.diagnostics.columnCacheMisses=Columns.columnMisses;
|
||||
out.diagnostics.columnCacheConflictMisses=Columns.columnConflictMisses;
|
||||
out.diagnostics.visualDetailOctaveEvaluations=Columns.visualDetailOctaveEvaluations;
|
||||
out.diagnostics.visualDetailOctaveBudgetEligible=Columns.visualDetailOctaveBudgetEligible;
|
||||
out.diagnostics.visualDetailOctaveSkips=Columns.visualDetailOctaveSkips;
|
||||
out.diagnostics.visualDetailOctaveMilliseconds=Columns.visualDetailOctaveMilliseconds;
|
||||
out.diagnostics.visualDetailOctaveEligibleMilliseconds=
|
||||
Columns.visualDetailOctaveEligibleMilliseconds;
|
||||
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
|
||||
out.diagnostics.columnEvaluationExclusiveMilliseconds=Columns.columnComputeMilliseconds;
|
||||
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
|
||||
@@ -1920,21 +1898,6 @@ namespace VoxelForgeCore
|
||||
out.payloadHash=SaltFarHorizonHash(out.payloadHash,options.farHorizonVersion);
|
||||
out.topologyHash=SaltFarHorizonHash(out.topologyHash,options.farHorizonVersion);
|
||||
}
|
||||
if(key.lod>=3&&options.visualDetailCutoffEnabled
|
||||
&&options.visualDetailPixelBudgetSubvoxels>0)
|
||||
{
|
||||
const auto& DetailStats=Heightfield2D::threadColumnCacheStats();
|
||||
const bool bVisualDetailChanged=std::any_of(
|
||||
DetailStats.visualDetailOctaveSkips.begin(),
|
||||
DetailStats.visualDetailOctaveSkips.end(),
|
||||
[](std::uint64_t Skips){return Skips!=0;});
|
||||
if(bVisualDetailChanged)
|
||||
{
|
||||
out.meshHash=SaltVisualDetailHash(out.meshHash,options.visualDetailContractVersion);
|
||||
out.payloadHash=SaltVisualDetailHash(out.payloadHash,options.visualDetailContractVersion);
|
||||
out.topologyHash=SaltVisualDetailHash(out.topologyHash,options.visualDetailContractVersion);
|
||||
}
|
||||
}
|
||||
out.hasDensityHash=out.hasMeshHash=out.hasPayloadHash=out.hasTopologyHash=true;
|
||||
}
|
||||
out.diagnostics.meshAssemblyMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
|
||||
@@ -1031,91 +1031,19 @@ namespace world_assembly_detail
|
||||
// Audit-only override; normal tile generation resets this to zero before evaluating a recipe.
|
||||
// Surcharge reservee aux mesures; la generation remet zero avant d'evaluer une recette.
|
||||
inline thread_local std::uint32_t HeightfieldColumnOctaveDrop = 0;
|
||||
inline thread_local VisualDetailCutoffV1 HeightfieldVisualDetailCutoff{};
|
||||
inline thread_local bool HeightfieldVisualDetailProfileEnabled = false;
|
||||
inline thread_local std::int64_t HeightfieldColumnInterpolationStrideSubvoxels = 0;
|
||||
inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{};
|
||||
|
||||
inline std::int32_t FractalNoiseQ16(std::int64_t xQ16, std::int64_t yQ16,
|
||||
std::int64_t zQ16, std::uint32_t octaves, bool ridged = false,
|
||||
std::int64_t baseWavelengthSubvoxels = 0,
|
||||
std::int64_t maximumDisplacementSubvoxels = 0)
|
||||
std::int64_t zQ16, std::uint32_t octaves, bool ridged = false)
|
||||
{
|
||||
if(HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels<=0
|
||||
&&HeightfieldColumnOctaveDrop==0&&!HeightfieldVisualDetailProfileEnabled)
|
||||
{
|
||||
std::int64_t ExactTotal=0,ExactWeightSum=0,ExactWeight=Q16,ExactRidgeWeight=Q16;
|
||||
for(std::uint32_t Octave=0;Octave<octaves;++Octave)
|
||||
{
|
||||
const std::int64_t Scale=1ll<<Octave;
|
||||
const std::int64_t Noise=Perlin3DQ16(
|
||||
xQ16*Scale,yQ16*Scale,zQ16*Scale);
|
||||
std::int64_t Value=Noise;
|
||||
if(ridged)
|
||||
{
|
||||
const std::int64_t Scaled=Noise*5/4;
|
||||
const std::int64_t Folded=Q16-std::abs(Scaled);
|
||||
Value=MulQ16(MulQ16(Folded,Folded),ExactRidgeWeight);
|
||||
ExactRidgeWeight=std::clamp<std::int64_t>(Value*2,0,Q16);
|
||||
}
|
||||
ExactTotal+=MulQ16(Value,ExactWeight);
|
||||
ExactWeightSum+=ExactWeight;
|
||||
ExactWeight/=2;
|
||||
}
|
||||
if(ExactWeightSum==0)return 0;
|
||||
const std::int64_t ExactAverage=(ExactTotal*Q16)/ExactWeightSum;
|
||||
return static_cast<std::int32_t>(ridged?ExactAverage*2-Q16:ExactAverage);
|
||||
}
|
||||
std::int64_t total = 0, weightSum = 0, weight = Q16, ridgeWeight = Q16;
|
||||
std::uint32_t effectiveOctaves=octaves;
|
||||
if(HeightfieldColumnOctaveDrop!=0&&octaves!=0)
|
||||
effectiveOctaves=std::max(1u,octaves-std::min(octaves-1,HeightfieldColumnOctaveDrop));
|
||||
VisualDetailCutoffResultV1 DetailCutoff;
|
||||
const bool bDeclaredVisualTerm=baseWavelengthSubvoxels>0
|
||||
&&maximumDisplacementSubvoxels>0;
|
||||
if(HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels>0
|
||||
&&HeightfieldColumnOctaveDrop==0&&baseWavelengthSubvoxels>0
|
||||
&&maximumDisplacementSubvoxels>0)
|
||||
{
|
||||
const VisualDetailOctaveTermV1 Term{0,baseWavelengthSubvoxels,
|
||||
maximumDisplacementSubvoxels,octaves,2u*static_cast<std::uint32_t>(Q16)};
|
||||
DetailCutoff=FindVisualDetailTailCutoffV1(Term,HeightfieldVisualDetailCutoff);
|
||||
if(DetailCutoff.firstSkippedOctave>0&&HeightfieldVisualDetailCutoff.apply)
|
||||
effectiveOctaves=std::min(effectiveOctaves,DetailCutoff.firstSkippedOctave);
|
||||
}
|
||||
if(HeightfieldVisualDetailProfileEnabled&&bDeclaredVisualTerm
|
||||
&&DetailCutoff.firstSkippedOctave>0)
|
||||
for(std::uint32_t octave=DetailCutoff.firstSkippedOctave;
|
||||
octave<octaves&&octave<HeightfieldColumnStats.visualDetailOctaveBudgetEligible.size();++octave)
|
||||
{
|
||||
++HeightfieldColumnStats.visualDetailOctaveBudgetEligible[octave];
|
||||
if(HeightfieldVisualDetailCutoff.apply)
|
||||
++HeightfieldColumnStats.visualDetailOctaveSkips[octave];
|
||||
}
|
||||
std::int64_t fullWeightSum=0;
|
||||
if(DetailCutoff.firstSkippedOctave>0&&HeightfieldVisualDetailCutoff.apply)
|
||||
{
|
||||
std::int64_t fullWeight=Q16;
|
||||
for(std::uint32_t octave=0;octave<octaves;++octave)
|
||||
{fullWeightSum+=fullWeight;fullWeight/=2;}
|
||||
}
|
||||
for (std::uint32_t octave = 0; octave < effectiveOctaves; ++octave)
|
||||
{
|
||||
const std::int64_t scale = 1ll << octave;
|
||||
const auto OctaveStart=HeightfieldVisualDetailProfileEnabled&&bDeclaredVisualTerm
|
||||
?std::chrono::steady_clock::now():std::chrono::steady_clock::time_point{};
|
||||
const std::int64_t noise = Perlin3DQ16(xQ16 * scale,yQ16 * scale,zQ16 * scale);
|
||||
if(HeightfieldVisualDetailProfileEnabled&&bDeclaredVisualTerm
|
||||
&&octave<HeightfieldColumnStats.visualDetailOctaveEvaluations.size())
|
||||
{
|
||||
const double OctaveMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-OctaveStart).count();
|
||||
++HeightfieldColumnStats.visualDetailOctaveEvaluations[octave];
|
||||
HeightfieldColumnStats.visualDetailOctaveMilliseconds[octave]+=OctaveMilliseconds;
|
||||
if(DetailCutoff.firstSkippedOctave>0&&octave>=DetailCutoff.firstSkippedOctave)
|
||||
HeightfieldColumnStats.visualDetailOctaveEligibleMilliseconds[octave]
|
||||
+=OctaveMilliseconds;
|
||||
}
|
||||
std::int64_t value = noise;
|
||||
if (ridged)
|
||||
{
|
||||
@@ -1129,24 +1057,17 @@ namespace world_assembly_detail
|
||||
weight /= 2;
|
||||
}
|
||||
if (weightSum == 0) return 0;
|
||||
// The cutoff retains the original denominator: omitted zero-mean terms do not
|
||||
// renormalize the surviving terrain. The historical diagnostic drop above keeps
|
||||
// its old renormalized path so the quality gate can reproduce S13.
|
||||
const std::int64_t denominator=HeightfieldColumnOctaveDrop==0
|
||||
&&DetailCutoff.firstSkippedOctave>0&&HeightfieldVisualDetailCutoff.apply
|
||||
?fullWeightSum:weightSum;
|
||||
const std::int64_t average = (total * Q16) / denominator;
|
||||
const std::int64_t average = (total * Q16) / weightSum;
|
||||
return static_cast<std::int32_t>(ridged ? average * 2 - Q16 : average);
|
||||
}
|
||||
|
||||
inline std::int32_t ReliefQ16(std::uint32_t seed, std::int64_t x,
|
||||
std::int64_t y, std::int64_t wavelength, std::int64_t contrastQ16,
|
||||
std::int64_t maximumDisplacementSubvoxels=0)
|
||||
std::int64_t y, std::int64_t wavelength, std::int64_t contrastQ16)
|
||||
{
|
||||
const std::int32_t noise = FractalNoiseQ16(
|
||||
NoiseCoordinateQ16(x,wavelength)+SeedOffsetQ16(seed,730),
|
||||
NoiseCoordinateQ16(y,wavelength)+SeedOffsetQ16(seed,210),
|
||||
SeedOffsetQ16(seed,50),2,false,wavelength,maximumDisplacementSubvoxels);
|
||||
SeedOffsetQ16(seed,50),2);
|
||||
const std::int64_t raw = Q16/2 + noise/2;
|
||||
const std::int64_t contrasted = Q16/2 + MulQ16(raw-Q16/2,contrastQ16);
|
||||
return static_cast<std::int32_t>(SmoothQ16(static_cast<std::uint32_t>(
|
||||
@@ -1182,22 +1103,15 @@ namespace world_assembly_detail
|
||||
qx+=WarpDisplacement(wx,biome.heightWarpStrengthSubvoxels);
|
||||
qy+=WarpDisplacement(wy,biome.heightWarpStrengthSubvoxels);
|
||||
}
|
||||
const bool bDetailContractSafe=biome.cliffStrengthQ16==0;
|
||||
const std::int64_t reliefInfluence=(std::abs(biome.mountainAmplitudeSubvoxels)
|
||||
+std::abs(biome.continentAmplitudeSubvoxels)*11/40)
|
||||
*std::clamp<std::int64_t>(biome.reliefStrengthQ16,0,Q16)
|
||||
*std::max<std::int64_t>(0,biome.reliefContrastQ16)*3/(4*Q16*Q16);
|
||||
const std::int32_t relief=ReliefQ16(seed,x,y,biome.reliefWavelengthSubvoxels,
|
||||
biome.reliefContrastQ16,bDetailContractSafe?reliefInfluence:0);
|
||||
biome.reliefContrastQ16);
|
||||
const std::int32_t mountainous=static_cast<std::int32_t>(Q16
|
||||
+MulQ16(relief-Q16,biome.reliefStrengthQ16));
|
||||
if(outRelief)*outRelief=mountainous;
|
||||
const std::int64_t contX=NoiseCoordinateQ16(qx,biome.continentWavelengthSubvoxels)+SeedOffsetQ16(seed,310);
|
||||
const std::int64_t contY=NoiseCoordinateQ16(qy,biome.continentWavelengthSubvoxels)+SeedOffsetQ16(seed,570);
|
||||
const std::int64_t contZ=SeedOffsetQ16(seed,70);
|
||||
const std::int64_t continentInfluence=(std::abs(biome.continentAmplitudeSubvoxels)*775+999)/1000;
|
||||
const std::int32_t continent=FractalNoiseQ16(contX,contY,contZ,4,false,
|
||||
biome.continentWavelengthSubvoxels,bDetailContractSafe?continentInfluence:0);
|
||||
const std::int32_t continent=FractalNoiseQ16(contX,contY,contZ,4);
|
||||
const std::int64_t continentScale=Q16*45/100+MulQ16(mountainous,Q16*55/100);
|
||||
std::int64_t height=biome.baseHeightSubvoxels
|
||||
+MulQ16(MulQ16(continent,biome.continentAmplitudeSubvoxels),continentScale)/2;
|
||||
@@ -1206,19 +1120,14 @@ namespace world_assembly_detail
|
||||
const std::int64_t mountainX=NoiseCoordinateQ16(qx,biome.mountainWavelengthSubvoxels)+SeedOffsetQ16(seed,990);
|
||||
const std::int64_t mountainY=NoiseCoordinateQ16(qy,biome.mountainWavelengthSubvoxels)+SeedOffsetQ16(seed,770);
|
||||
const std::int64_t mountainZ=SeedOffsetQ16(seed,90);
|
||||
const std::int32_t ridge=FractalNoiseQ16(mountainX,mountainY,mountainZ,4,true,
|
||||
biome.mountainWavelengthSubvoxels,
|
||||
bDetailContractSafe?std::abs(biome.mountainAmplitudeSubvoxels)*2:0);
|
||||
const std::int32_t ridge=FractalNoiseQ16(mountainX,mountainY,mountainZ,4,true);
|
||||
const std::int64_t ridgeHeight=std::clamp<std::int64_t>((ridge+Q16)/2,0,Q16);
|
||||
height+=MulQ16(MulQ16(ridgeHeight,biome.mountainAmplitudeSubvoxels),mountainous);
|
||||
}
|
||||
const std::int64_t detailX=NoiseCoordinateQ16(x,biome.detailWavelengthSubvoxels)+11*Q16;
|
||||
const std::int64_t detailY=NoiseCoordinateQ16(y,biome.detailWavelengthSubvoxels)+22*Q16;
|
||||
const std::int64_t detailZ=SeedOffsetQ16(seed,130);
|
||||
const std::int32_t detailNoise=FractalNoiseQ16(detailX,detailY,detailZ,3,false,
|
||||
biome.detailWavelengthSubvoxels,
|
||||
bDetailContractSafe?std::abs(biome.detailAmplitudeSubvoxels):0);
|
||||
height+=MulQ16(detailNoise,biome.detailAmplitudeSubvoxels);
|
||||
height+=MulQ16(FractalNoiseQ16(detailX,detailY,detailZ,3),biome.detailAmplitudeSubvoxels);
|
||||
return height;
|
||||
}
|
||||
|
||||
@@ -1415,24 +1324,21 @@ namespace world_assembly_detail
|
||||
{
|
||||
const std::int64_t nx=NoiseCoordinateQ16(qx,swellWave)+SeedOffsetQ16(recipe.seed,190);
|
||||
const std::int64_t ny=NoiseCoordinateQ16(qy,swellWave)+13*Q16;
|
||||
const std::int32_t swell=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,50),3,
|
||||
false,swellWave,(std::abs(undulation)*5+3)/4);
|
||||
const std::int32_t swell=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,50),3);
|
||||
cap+=WarpDisplacement(swell,undulation);
|
||||
}
|
||||
if(detail>0)
|
||||
{
|
||||
const std::int64_t nx=NoiseCoordinateQ16(x,detailWave)+5*Q16;
|
||||
const std::int64_t ny=NoiseCoordinateQ16(y,detailWave)+6*Q16;
|
||||
const std::int32_t noise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,210),3,
|
||||
false,detailWave,(std::abs(detail)*5+3)/4);
|
||||
const std::int32_t noise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,210),3);
|
||||
cap-=WarpDisplacement(std::abs(noise),detail);
|
||||
}
|
||||
if(ridge>0)
|
||||
{
|
||||
const std::int64_t nx=NoiseCoordinateQ16(qx,ridgeWave)+31*Q16;
|
||||
const std::int64_t ny=NoiseCoordinateQ16(qy,ridgeWave)+47*Q16;
|
||||
const std::int32_t ridgeNoise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,110),4,
|
||||
true,ridgeWave,std::abs(ridge)*2);
|
||||
const std::int32_t ridgeNoise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,110),4,true);
|
||||
const std::int64_t hang=std::clamp<std::int64_t>((ridgeNoise+Q16)/2,0,Q16);
|
||||
cap-=MulQ16(hang,ridge);
|
||||
}
|
||||
@@ -1455,10 +1361,6 @@ namespace world_assembly_detail
|
||||
const WorldAssemblyStratumPlanV1* plan = nullptr;
|
||||
std::uint64_t recipeHash = 0, planHash = 0;
|
||||
std::int64_t x = 0, y = 0;
|
||||
std::int64_t detailBudgetSubvoxels = 0,detailStepSubvoxels = 0;
|
||||
std::uint32_t detailCallBudgetDivisor = 0;
|
||||
bool detailApply = false;
|
||||
std::uint32_t octaveDrop = 0;
|
||||
HeightfieldPointSample column{};
|
||||
};
|
||||
// Keep 65,536 entries in each worker cache; four ways tolerate coordinate families that
|
||||
@@ -1471,6 +1373,7 @@ namespace world_assembly_detail
|
||||
std::array<HeightfieldColumnCacheEntry, 65536>> HeightfieldColumnCache;
|
||||
inline thread_local VoxelForgeCoreThreadLocal::FLazy<
|
||||
std::array<std::uint8_t, HeightfieldColumnCacheSetCount>> HeightfieldColumnCacheNextWay;
|
||||
inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{};
|
||||
inline thread_local bool HeightfieldColumnCacheLegacyDirectMapped=false;
|
||||
|
||||
inline std::size_t HeightfieldColumnCacheSet(const WorldAssemblyRecipeV1& recipe,
|
||||
@@ -1480,19 +1383,9 @@ namespace world_assembly_detail
|
||||
// entropy from widely spaced coarse LOD coordinates in its low 16 bits.
|
||||
// Melanger tous les bits de coordonnee avant le masque; l'ancien hash perdait de
|
||||
// l'entropie sur les coordonnees espacees des LOD grossiers.
|
||||
const std::uint64_t DetailContext=HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels>0
|
||||
?static_cast<std::uint64_t>(HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels)
|
||||
^std::rotl(static_cast<std::uint64_t>(
|
||||
HeightfieldVisualDetailCutoff.sampleStepSubvoxels),11)
|
||||
^(static_cast<std::uint64_t>(
|
||||
HeightfieldVisualDetailCutoff.callBudgetDivisor)<<19)
|
||||
^(static_cast<std::uint64_t>(HeightfieldVisualDetailCutoff.apply)<<53)
|
||||
:0;
|
||||
std::uint64_t mixed=static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull
|
||||
^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,29)
|
||||
^recipe.contentHash^std::rotl(plan.contentHash,17)
|
||||
^DetailContext
|
||||
^(static_cast<std::uint64_t>(HeightfieldColumnOctaveDrop)<<37);
|
||||
^recipe.contentHash^std::rotl(plan.contentHash,17);
|
||||
mixed^=mixed>>30;
|
||||
mixed*=0xBF58476D1CE4E5B9ull;
|
||||
mixed^=mixed>>27;
|
||||
@@ -1538,8 +1431,7 @@ namespace world_assembly_detail
|
||||
const std::int64_t nx=NoiseCoordinateQ16(x,wave)+SeedOffsetQ16(featureSeed,1700);
|
||||
const std::int64_t ny=NoiseCoordinateQ16(y,wave)+SeedOffsetQ16(featureSeed,2900);
|
||||
const std::int32_t noise=FractalNoiseQ16(nx,ny,
|
||||
SeedOffsetQ16(featureSeed,4100),3,false,wave,
|
||||
std::abs(feature.parametersSubvoxels[0]));
|
||||
SeedOffsetQ16(featureSeed,4100),3);
|
||||
cap+=MulQ16(MulQ16(noise,feature.parametersSubvoxels[0]),fade);
|
||||
}
|
||||
}
|
||||
@@ -1650,14 +1542,8 @@ namespace world_assembly_detail
|
||||
C11.capHeightSubvoxels);
|
||||
return Out;
|
||||
}
|
||||
const std::int64_t DetailBudget=HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels;
|
||||
const bool bVisualContextActive=DetailBudget>0;
|
||||
const std::int64_t DetailStep=DetailBudget>0
|
||||
?HeightfieldVisualDetailCutoff.sampleStepSubvoxels:0;
|
||||
const std::uint32_t DetailDivisor=DetailBudget>0
|
||||
?HeightfieldVisualDetailCutoff.callBudgetDivisor:0;
|
||||
const bool DetailApply=DetailBudget>0&&HeightfieldVisualDetailCutoff.apply;
|
||||
const std::uint32_t OctaveDrop=HeightfieldColumnOctaveDrop;
|
||||
if(HeightfieldColumnOctaveDrop!=0)
|
||||
return EvaluateColumn(recipe,plan,x,y);
|
||||
auto& cache=HeightfieldColumnCache.Get();
|
||||
auto& nextWay=HeightfieldColumnCacheNextWay.Get();
|
||||
HeightfieldColumnCacheEntry* replacement=nullptr;
|
||||
@@ -1668,23 +1554,11 @@ namespace world_assembly_detail
|
||||
const std::uint64_t mixed=(static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull)
|
||||
^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,23)
|
||||
^static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&recipe))
|
||||
^std::rotl(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),41)
|
||||
^static_cast<std::uint64_t>(DetailBudget)
|
||||
^std::rotl(static_cast<std::uint64_t>(DetailStep),11)
|
||||
^(static_cast<std::uint64_t>(DetailDivisor)<<19)
|
||||
^(static_cast<std::uint64_t>(DetailApply)<<53)
|
||||
^(static_cast<std::uint64_t>(OctaveDrop)<<37);
|
||||
^std::rotl(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),41);
|
||||
replacement=&cache[static_cast<std::size_t>(mixed)&(cache.size()-1)];
|
||||
if(replacement->recipe==&recipe&&replacement->plan==&plan
|
||||
&&replacement->recipeHash==recipe.contentHash
|
||||
&&replacement->planHash==plan.contentHash&&replacement->x==x&&replacement->y==y
|
||||
&&replacement->octaveDrop==OctaveDrop
|
||||
&&(bVisualContextActive
|
||||
?replacement->detailBudgetSubvoxels==DetailBudget
|
||||
&&replacement->detailStepSubvoxels==DetailStep
|
||||
&&replacement->detailCallBudgetDivisor==DetailDivisor
|
||||
&&replacement->detailApply==DetailApply
|
||||
:replacement->detailBudgetSubvoxels==0))
|
||||
&&replacement->planHash==plan.contentHash&&replacement->x==x&&replacement->y==y)
|
||||
{++HeightfieldColumnStats.columnHits;return replacement->column;}
|
||||
if(replacement->recipe!=nullptr)++HeightfieldColumnStats.columnConflictMisses;
|
||||
}
|
||||
@@ -1698,14 +1572,7 @@ namespace world_assembly_detail
|
||||
auto& candidate=cache[first+way];
|
||||
if(candidate.recipe==&recipe&&candidate.plan==&plan
|
||||
&&candidate.recipeHash==recipe.contentHash&&candidate.planHash==plan.contentHash
|
||||
&&candidate.x==x&&candidate.y==y
|
||||
&&candidate.octaveDrop==OctaveDrop
|
||||
&&(bVisualContextActive
|
||||
?candidate.detailBudgetSubvoxels==DetailBudget
|
||||
&&candidate.detailStepSubvoxels==DetailStep
|
||||
&&candidate.detailCallBudgetDivisor==DetailDivisor
|
||||
&&candidate.detailApply==DetailApply
|
||||
:candidate.detailBudgetSubvoxels==0))
|
||||
&&candidate.x==x&&candidate.y==y)
|
||||
{++HeightfieldColumnStats.columnHits;return candidate.column;}
|
||||
if(candidate.recipe==nullptr&&replacementWay==HeightfieldColumnCacheWays)
|
||||
replacementWay=way;
|
||||
@@ -1736,8 +1603,7 @@ namespace world_assembly_detail
|
||||
HeightfieldColumnStats.coordinateCaptureComplete=false;
|
||||
}
|
||||
}
|
||||
entry={&recipe,&plan,recipe.contentHash,plan.contentHash,x,y,
|
||||
DetailBudget,DetailStep,DetailDivisor,DetailApply,OctaveDrop,column};
|
||||
entry={&recipe,&plan,recipe.contentHash,plan.contentHash,x,y,column};
|
||||
return column;
|
||||
}
|
||||
|
||||
@@ -1827,48 +1693,6 @@ namespace detail_world_assembly
|
||||
}
|
||||
}
|
||||
|
||||
VisualDetailCutoffResultV1 FindVisualDetailTailCutoffV1(
|
||||
const VisualDetailOctaveTermV1& Term,const VisualDetailCutoffV1& Cutoff) noexcept
|
||||
{
|
||||
VisualDetailCutoffResultV1 Result;
|
||||
if(Term.octaveCount<2||Term.baseWavelengthSubvoxels<=0
|
||||
||Term.maximumDisplacementSubvoxels<=0||Cutoff.pixelBudgetSubvoxels<=0
|
||||
||Cutoff.sampleStepSubvoxels<=0||Cutoff.callBudgetDivisor==0)return Result;
|
||||
std::int64_t TotalWeight=0,Weight=65536;
|
||||
for(std::uint32_t Octave=0;Octave<Term.octaveCount;++Octave)
|
||||
{TotalWeight+=Weight;Weight/=2;}
|
||||
if(TotalWeight<=0)return Result;
|
||||
const std::int64_t PerCallBudget=std::max<std::int64_t>(1,
|
||||
Cutoff.pixelBudgetSubvoxels/Cutoff.callBudgetDivisor);
|
||||
for(std::uint32_t First=1;First<Term.octaveCount;++First)
|
||||
{
|
||||
bool bSubSampled=true;std::int64_t TailWeight=0;Weight=65536;
|
||||
for(std::uint32_t Octave=0;Octave<Term.octaveCount;++Octave)
|
||||
{
|
||||
if(Octave>=First)
|
||||
{
|
||||
const std::int64_t Wave=Term.baseWavelengthSubvoxels
|
||||
/static_cast<std::int64_t>(1ull<<std::min<std::uint32_t>(Octave,62));
|
||||
bSubSampled=bSubSampled&&Wave<=2*Cutoff.sampleStepSubvoxels;
|
||||
TailWeight+=Weight;
|
||||
}
|
||||
Weight/=2;
|
||||
}
|
||||
if(!bSubSampled)continue;
|
||||
const long double Bound=static_cast<long double>(Term.maximumDisplacementSubvoxels)
|
||||
*Term.maximumNoiseAbsQ16*TailWeight
|
||||
/(65536.0L*TotalWeight);
|
||||
const std::int64_t Error=static_cast<std::int64_t>(std::ceil(Bound));
|
||||
if(Error<=PerCallBudget)
|
||||
{
|
||||
Result.firstSkippedOctave=First;
|
||||
Result.maximumErrorSubvoxels=Error;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
std::optional<WorldAssemblyManifest> WorldAssemblyManifest::FromJson(
|
||||
std::string_view json, std::string& error)
|
||||
{
|
||||
@@ -2264,11 +2088,6 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl
|
||||
Stats.columnMisses=0;
|
||||
Stats.columnConflictMisses=0;
|
||||
Stats.columnComputeMilliseconds=0.0;
|
||||
Stats.visualDetailOctaveEvaluations.fill(0);
|
||||
Stats.visualDetailOctaveBudgetEligible.fill(0);
|
||||
Stats.visualDetailOctaveSkips.fill(0);
|
||||
Stats.visualDetailOctaveMilliseconds.fill(0.0);
|
||||
Stats.visualDetailOctaveEligibleMilliseconds.fill(0.0);
|
||||
Stats.captureCoordinates=captureCoordinates;
|
||||
Stats.coordinateCaptureComplete=true;
|
||||
Stats.evaluatedCoordinates.clear();
|
||||
@@ -2277,32 +2096,18 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl
|
||||
void Heightfield2D::setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept
|
||||
{ world_assembly_detail::HeightfieldColumnCacheLegacyDirectMapped=enabled; }
|
||||
|
||||
void Heightfield2D::setThreadVisualDetailCutoff(VisualDetailCutoffV1 cutoff) noexcept
|
||||
{world_assembly_detail::HeightfieldVisualDetailCutoff=cutoff;}
|
||||
|
||||
VisualDetailCutoffV1 Heightfield2D::threadVisualDetailCutoff() noexcept
|
||||
{return world_assembly_detail::HeightfieldVisualDetailCutoff;}
|
||||
|
||||
void Heightfield2D::setThreadColumnOctaveDrop(std::uint32_t octaveDrop) noexcept
|
||||
{world_assembly_detail::HeightfieldColumnOctaveDrop=octaveDrop;}
|
||||
{ world_assembly_detail::HeightfieldColumnOctaveDrop=octaveDrop; }
|
||||
|
||||
std::uint32_t Heightfield2D::threadColumnOctaveDrop() noexcept
|
||||
{return world_assembly_detail::HeightfieldColumnOctaveDrop;}
|
||||
|
||||
void Heightfield2D::setThreadVisualDetailProfileEnabled(bool enabled) noexcept
|
||||
{
|
||||
world_assembly_detail::HeightfieldVisualDetailProfileEnabled=enabled;
|
||||
world_assembly_detail::HeightfieldColumnStats.profileVisualDetailOctaves=enabled;
|
||||
}
|
||||
|
||||
bool Heightfield2D::threadVisualDetailProfileEnabled() noexcept
|
||||
{return world_assembly_detail::HeightfieldVisualDetailProfileEnabled;}
|
||||
{ return world_assembly_detail::HeightfieldColumnOctaveDrop; }
|
||||
|
||||
void Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(std::int64_t stride) noexcept
|
||||
{world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels=std::max<std::int64_t>(0,stride);}
|
||||
{ world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels=std::max<std::int64_t>(0,stride); }
|
||||
|
||||
std::int64_t Heightfield2D::threadColumnInterpolationStrideSubvoxels() noexcept
|
||||
{return world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels;}
|
||||
{ return world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels; }
|
||||
|
||||
|
||||
const HeightfieldColumnCacheStats& Heightfield2D::threadColumnCacheStats() noexcept
|
||||
{ return world_assembly_detail::HeightfieldColumnStats; }
|
||||
@@ -2500,64 +2305,6 @@ FieldDetailEnvelope Heightfield2D::detailEnvelope() const noexcept
|
||||
return out;
|
||||
}
|
||||
|
||||
VisualDetailRecipeContractV1 Heightfield2D::visualDetailContract() const
|
||||
{
|
||||
VisualDetailRecipeContractV1 Contract;
|
||||
if(!isValid()||recipe_->evaluatorVersion<2)return Contract;
|
||||
const auto Add=[&](std::uint64_t Id,std::int64_t Wavelength,
|
||||
std::int64_t Amplitude,std::uint32_t Octaves)
|
||||
{
|
||||
if(Wavelength>0&&Amplitude>0)
|
||||
Contract.terms.push_back({Id,Wavelength,Amplitude,Octaves,
|
||||
2u*static_cast<std::uint32_t>(world_assembly_detail::Q16)});
|
||||
};
|
||||
for(const auto& Biome:recipe_->biomes)
|
||||
{
|
||||
if(Biome.cliffStrengthQ16==0)
|
||||
{
|
||||
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|1u,
|
||||
Biome.continentWavelengthSubvoxels,
|
||||
(std::abs(Biome.continentAmplitudeSubvoxels)*775+999)/1000,4);
|
||||
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|2u,
|
||||
Biome.mountainWavelengthSubvoxels,
|
||||
std::abs(Biome.mountainAmplitudeSubvoxels)*2,4);
|
||||
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|3u,
|
||||
Biome.detailWavelengthSubvoxels,
|
||||
std::abs(Biome.detailAmplitudeSubvoxels),3);
|
||||
const std::int64_t ReliefInfluence=(std::abs(Biome.mountainAmplitudeSubvoxels)
|
||||
+std::abs(Biome.continentAmplitudeSubvoxels)*11/40)
|
||||
*std::clamp<std::int64_t>(Biome.reliefStrengthQ16,0,world_assembly_detail::Q16)
|
||||
*std::max<std::int64_t>(0,Biome.reliefContrastQ16)*3
|
||||
/(4*world_assembly_detail::Q16*world_assembly_detail::Q16);
|
||||
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|4u,
|
||||
Biome.reliefWavelengthSubvoxels,ReliefInfluence,2);
|
||||
}
|
||||
const std::int64_t CapUndulation=Biome.hasCapProfile
|
||||
?Biome.capUndulationSubvoxels:plan_->capUndulationSubvoxels;
|
||||
const std::int64_t CapWave=Biome.hasCapProfile
|
||||
?Biome.capWavelengthSubvoxels:plan_->capWavelengthSubvoxels;
|
||||
const std::int64_t CapDetail=Biome.hasCapProfile
|
||||
?Biome.capDetailAmplitudeSubvoxels:plan_->capDetailAmplitudeSubvoxels;
|
||||
const std::int64_t CapDetailWave=Biome.hasCapProfile
|
||||
?Biome.capDetailWavelengthSubvoxels:plan_->capDetailWavelengthSubvoxels;
|
||||
const std::int64_t CapRidge=Biome.hasCapProfile
|
||||
?Biome.capRidgeAmplitudeSubvoxels:plan_->capRidgeAmplitudeSubvoxels;
|
||||
const std::int64_t CapRidgeWave=Biome.hasCapProfile
|
||||
?Biome.capRidgeWavelengthSubvoxels:plan_->capRidgeWavelengthSubvoxels;
|
||||
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|5u,CapWave,
|
||||
(std::abs(CapUndulation)*5+3)/4,3);
|
||||
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|6u,CapDetailWave,
|
||||
(std::abs(CapDetail)*5+3)/4,3);
|
||||
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|7u,CapRidgeWave,
|
||||
std::abs(CapRidge)*2,4);
|
||||
}
|
||||
for(const auto& Feature:plan_->features)
|
||||
if(Feature.type==3)
|
||||
Add(Feature.stableId,Feature.parametersSubvoxels[1],
|
||||
std::abs(Feature.parametersSubvoxels[0]),3);
|
||||
return Contract;
|
||||
}
|
||||
|
||||
FieldBlockContract Heightfield2D::contract() const noexcept
|
||||
{
|
||||
return {ChannelBit(FieldChannel::biome),
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Linux6 report
|
||||
|
||||
## Result
|
||||
|
||||
The screen-space gate rejects today’s far meshes at every sampled LOD3–8: the largest measured same-field error is 92.0 px, and the largest sampled swap pop is 70.3 px at LOD6→5. Same-LOD regeneration is exact, the reported swaps have no cracks or floaters, and no display mesh changed in this round.
|
||||
|
||||
Source base: `wip/l5-far-cutoff` at `b21a19d`; L4 reference: `7770074`.
|
||||
|
||||
## Screen-space method
|
||||
|
||||
For one surface tile nearest the L1 camera at each LOD, the gate rasterizes triangles on a 65×65 XY grid, excluding skirts. It compares the mesh hit to direct `Heightfield2D` ground/cap roots; slopes use ±1-voxel finite differences. For vertical residual `Δz` and slopes `sx,sy`, it computes normal distance `|Δz|/sqrt(1+sx²+sy²)`, projects that normal onto the camera’s screen plane, then divides by the one-pixel world distance at that LOD’s closest display distance. The view uses 90° horizontal FOV, 2560 px, yaw 0°, pitch −22°; one pixel is 0.000613592 rad. Same-LOD regeneration is the mesh reference for swaps; direct field roots are the reference for true surface error.
|
||||
|
||||
The known failure control is a 1 m cell on an 89.427° plane, shifted sideways by 5 mm (0.005 cell): its vertical metric says 8.488 px, while its true normal distance is 5 mm and projected screen error is 0.0849 px. This confirms that vertical height can badly overstate visible displacement.
|
||||
|
||||
| LOD | Closest distance (m) | Field error p50 / p99 / max (px) | Max normal distance (m) |
|
||||
|---:|---:|---:|---:|
|
||||
| 3 | 96 | 0.81 / 4.16 / 6.00 | 0.423 |
|
||||
| 4 | 192 | 1.01 / 5.12 / 7.93 | 0.952 |
|
||||
| 5 | 384 | 0.79 / 16.37 / 92.02 | 25.122 |
|
||||
| 6 | 768 | 0.51 / 9.75 / 70.34 | 37.644 |
|
||||
| 7 | 1,536 | 0.29 / 9.04 / 42.36 | 42.714 |
|
||||
| 8 | 3,072 | 0.83 / 18.03 / 47.18 | 93.175 |
|
||||
|
||||
| Swap | Distance (m) | Pop p50 / p99 / max (px) | Max normal distance (m) |
|
||||
|---|---:|---:|---:|
|
||||
| 3→2 | 96 | 0.61 / 3.77 / 6.05 | 0.367 |
|
||||
| 4→3 | 192 | 0.75 / 4.63 / 7.51 | 0.896 |
|
||||
| 5→4 | 384 | 0.00 / 9.58 / 31.78 | 8.139 |
|
||||
| 6→5 | 768 | 0.00 / 9.75 / 70.34 | 37.644 |
|
||||
| 7→6 | 1,536 | 0.00 / 9.04 / 42.36 | 42.714 |
|
||||
| 8→7 | 3,072 | 0.83 / 18.03 / 47.18 | 93.175 |
|
||||
|
||||
The tails are not confined to cliffs. The field maximum slopes at LOD5–8 are 8.34°, 14.09°, 6.76°, and 9.33°; LOD5’s p99 is 19.8 px below 30° and 21.1 px at 45–60°. The sampled LOD6→5 maximum is at `(-472, -1432, -463.151) m`; [the marked offscreen capture](captures/lod6_to_5_max_pop.jpg) shows that location at the 768 m swap distance. This is a sampled-tile result, not a whole-world supremum.
|
||||
|
||||
All same-LOD mesh comparisons at LOD3–8 pass with zero error. The S13 octave-drop and corrected stride-2 column-interpolation controls are rejected at every LOD3–8, as are missing displayed surface, 2 m floating surface, and missing fine-ring injections. The measured swap rows have zero cracks and floaters; they reject solely on displacement.
|
||||
|
||||
## Far worker time, W12
|
||||
|
||||
The L1 view has 2553 tiles. Clean-run phase totals for LOD3–8 are shown below. A separate temporary timer measured complete `FractalNoiseQ16` calls; “column outside” is clean column time minus that subtotal. The timer added 3–6% to worker time, so the split between those two column subphases is approximate; it has been removed from the final tree.
|
||||
|
||||
| LOD | Worker (s) | Column outside noise (ms) | Fractal/noise calls (ms) | MC (ms) | Ground refine (ms) | Cap refine (ms) | Skirts (ms) | Assembly (ms) |
|
||||
|---:|---:|---:|---:|---:|---:|---:|---:|---:|
|
||||
| 3 | 1.084 | 171 | 276 | 473 | 0 | 0 | 118 | 33 |
|
||||
| 4 | 2.581 | 1,000 | 534 | 440 | 0 | 304 | 196 | 102 |
|
||||
| 5 | 1.829 | 658 | 358 | 100 | 464 | 22 | 161 | 70 |
|
||||
| 6 | 2.761 | 1,608 | 284 | 59 | 385 | 58 | 162 | 70 |
|
||||
| 7 | 1.192 | 234 | 198 | 32 | 275 | 202 | 153 | 96 |
|
||||
| 8 | 2.224 | 264 | 469 | 256 | 424 | 299 | 333 | 177 |
|
||||
| **Total** | **11.671** | **3,934** | **2,119** | **1,360** | **1,548** | **885** | **1,123** | **548** |
|
||||
|
||||
Column work outside the fractal calls is the largest single phase (33.7% of far worker time); all column work is 51.9%. Ground and cap refinement total 2.43 worker-s, skirts 1.12 worker-s, MC 1.36 worker-s, and assembly 0.55 worker-s. The earlier 7–13% profile timed individual Perlin calls; the L6 subtotal times complete fractal calls, including their octave accumulation.
|
||||
|
||||
The listed subphases total 11.52 worker-s; about 0.15 worker-s remains in timer and per-tile residuals.
|
||||
|
||||
The overlap scan counted about 411,000 cross-worker neighboring duplicate XY evaluations at LOD3–8 (about 166,000 at LOD4 alone). Exact column reuse is the largest plausible lever because it targets the largest phase. I did not build it: no candidate has yet shown a screen-gated, greater-than-3% whole-view win. Stride-2 interpolation is not a safe substitute; its control fails at every far LOD.
|
||||
|
||||
## Cost with the feature off
|
||||
|
||||
Three interleaved W12 runs per binary, same L1 camera and forced generation. `whole_view_ready` is the comparison metric; the six whole-view hashes matched exactly.
|
||||
|
||||
| Binary | Runs (s) | Median |
|
||||
|---|---|---:|
|
||||
| L4 reference | 2.082, 2.180, 2.183 | 2.180 s |
|
||||
| L6 cleaned tree | 2.127, 2.185, 2.223 | 2.185 s (+0.2%) |
|
||||
|
||||
Run-to-run spread was about 0.10 s, so the 5 ms median difference is within noise. The no-op visual cutoff contract, plan, and CLI flag were removed. The opt-in research gate retains its negative controls; skirt timing uses the existing optional stage-timing pointer, which is null on the default path. Governor stayed `powersave`; runs used CPUs 0–11, W12, 6 GiB address-space limits, and normal priority. Peak runtime RSS was 403 MiB; peak build process RSS was 874 MiB (GCC).
|
||||
|
||||
## Determinism and hashes
|
||||
|
||||
Clang 19 `-ffp-contract=off` W1/W12 and GCC default W1/W12 produced the same hashes. LOD0–2, exact queries, and all whole-view hashes equal L4; no recipe bump is needed.
|
||||
|
||||
- Recipe / plan / manifest: `83b616a3f4fe781d` / `1ca84619c8600ee5` / `e55991385bf0ca29` (recipe version 1).
|
||||
- Whole view (density / mesh / payload / topology): `c10697a8f48380c7` / `e75c9119a1498cd5` / `49a9a8d1835a6c8c` / `edcb23288b5e8724`.
|
||||
- Full detail (density / mesh / payload / topology): `0ee0579bd15cbbe4` / `32e5d4964d610fee` / `1932b96ee4b1d910` / `69743c6bad3447dc`.
|
||||
- Exact queries (density / unclamped / detail / collision): `e4d4a8eb95b35933` / `e4d4a8eb95b35933` / `efcba20c0c70c415` / `e604823a249029bf`.
|
||||
- SHA-256 L4 binary / Clang L6 binary / GCC L6 binary: `6efb8f19a41ca3cd23aca9294ceee1e9cdd1d3dc8da23b3ee61ef8ae943aa763` / `3f5a4f88b0d911e8e66af9ef72e294926a5507a73d44fcfe868df27c666bf01e` / `de872e8fdaa9f40e8a6c096700fd998bcf35f232b06f2b4e35a374b4b0e9fa07`.
|
||||
- Capture JPEG SHA-256: `483b434f3ad752dd09b82cd1c78fe81ee8cd31803ecf4dcd3c2918a8ae5d66b1` (156 KiB).
|
||||
|
||||
## Failed or retried runs
|
||||
|
||||
- First Clang build failed on a missing view argument in `LinuxScreenSpaceGate.h`; fixed and rebuilt successfully.
|
||||
- First gate pass paired extra mesh hits incorrectly and reported same-LOD floaters at LOD5–8. The reference pairing was corrected; the final gate reports zero same-LOD displacement and no floaters.
|
||||
- A `/usr/bin/time -v` wrapper attempt exited 127 because that binary is unavailable; the runs were retried with `/proc` RSS monitoring.
|
||||
- The final quality gates exit 6 by design because measured displacement exceeds the 1 px rule. Builds, captures, determinism runs, and cost runs completed successfully. EGL could not write `~/.cache` in this environment, disabled shader caching, and rendered successfully on llvmpipe.
|
||||
|
||||
## Note for the owner
|
||||
|
||||
Today’s `assembler/world` remains at L4; this sampled view measures up to 92.0 px from the field and a 70.3 px LOD6→5 pop at the tested display distances, with the largest point marked in the capture. This round changes no default mesh or recipe output, removes the no-op cutoff, and leaves W12 cost within run-to-run noise.
|
||||
|
||||
## Orchestrator review (Claude)
|
||||
|
||||
- Cost re-run by me (interleaved W12, L1 view): L4 2.098/2.138/2.171 s vs L6 2.116/2.150/2.197 s
|
||||
(median +0.6%, within noise), density `c10697a8f48380c7`; the L6 binary SHA matches the report.
|
||||
Merged into `assembler/world` with L5 squashed in.
|
||||
- The marked LOD6→5 maximum sits on the **rim of a flat-topped hill (mesa edge)**, not on open gentle
|
||||
ground: the "8–14° slope" is the field slope at the sample, on the plateau top beside the drop. The
|
||||
coarse mesh rounds the rim by about a cell (16–32 m at LOD5–6), so the pop is a real **silhouette snap
|
||||
at sharp rims** (tens of px at 768 m), while p50 stays at 0 px. That is ordinary LOD-switch behaviour
|
||||
without geomorphing or cross-fade, so a fix belongs in rendering (Unreal), not in generation. The
|
||||
owner decides whether it is visible enough to matter.
|
||||
- Next speed lever, per the far breakdown: exact cross-tile column reuse (column work outside noise is
|
||||
34% of far worker time; ~411k duplicate XY evaluations at LOD3–8).
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
@@ -341,11 +341,12 @@ int main(int argc,char** argv)
|
||||
{
|
||||
if(argc<3)
|
||||
{
|
||||
std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--yaw-deg=N] [--pitch-deg=N]\n",argv[0]);
|
||||
std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N]\n",argv[0]);
|
||||
return 2;
|
||||
}
|
||||
int Width=2560,Height=1440;double Fov=90.0;
|
||||
bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0;
|
||||
bool HasCamera=false;std::array<double,3> Camera{};
|
||||
for(int I=3;I<argc;++I)
|
||||
{
|
||||
const std::string_view Arg(argv[I]);
|
||||
@@ -361,6 +362,14 @@ int main(int argc,char** argv)
|
||||
if(End==Value.c_str()||*End!='\0'||Fov<=1.0||Fov>=179.0)
|
||||
{std::fprintf(stderr,"Invalid horizontal FOV\n");return 2;}
|
||||
}
|
||||
else if(Arg.starts_with("--camera-m="))
|
||||
{
|
||||
const std::string Value(Arg.substr(11));
|
||||
if(std::sscanf(Value.c_str(),"%lf,%lf,%lf",&Camera[0],&Camera[1],&Camera[2])!=3
|
||||
||!std::isfinite(Camera[0])||!std::isfinite(Camera[1])||!std::isfinite(Camera[2]))
|
||||
{std::fprintf(stderr,"Invalid camera position\n");return 2;}
|
||||
HasCamera=true;
|
||||
}
|
||||
else if(Arg.starts_with("--yaw-deg=")||Arg.starts_with("--pitch-deg="))
|
||||
{
|
||||
const bool IsYaw=Arg.starts_with("--yaw-deg=");
|
||||
@@ -376,6 +385,7 @@ int main(int argc,char** argv)
|
||||
FScene Scene;std::string Error;
|
||||
if(!LoadScene(argv[1],Scene,Error))
|
||||
{std::fprintf(stderr,"[OffscreenCapture] FAIL: %s\n",Error.c_str());return 2;}
|
||||
if(HasCamera)Scene.CameraMeters=Camera;
|
||||
if(HasYaw)Scene.YawDegrees=Yaw;
|
||||
if(HasPitch)Scene.PitchDegrees=Pitch;
|
||||
std::printf("[OffscreenScene] voxels_per_meter=%.6f camera_m=(%.3f,%.3f,%.3f) yaw=%.2f pitch=%.2f vertices=%zu triangles=%llu\n",
|
||||
|
||||
@@ -22,6 +22,12 @@ namespace StandaloneScreenSpaceGate
|
||||
int Lod=0;
|
||||
std::uint64_t Samples=0,Missing=0,Floating=0;
|
||||
double P50=0.0,P99=0.0,Maximum=0.0;
|
||||
double VerticalP50=0.0,VerticalP99=0.0,VerticalMaximum=0.0;
|
||||
double MaximumNormalDistanceMeters=0.0,MaximumX=0.0,MaximumY=0.0;
|
||||
double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0;
|
||||
double MaximumSlopeDegrees=0.0;
|
||||
std::array<std::uint64_t,4> SlopeSamples{};
|
||||
std::array<double,4> SlopeP99{},SlopeMaximum{};
|
||||
bool Valid=false;
|
||||
};
|
||||
|
||||
@@ -30,9 +36,128 @@ namespace StandaloneScreenSpaceGate
|
||||
int CoarseLod=0;
|
||||
std::uint64_t Samples=0,Cracks=0,Floating=0;
|
||||
double P50=0.0,P99=0.0,Maximum=0.0;
|
||||
double VerticalP50=0.0,VerticalP99=0.0,VerticalMaximum=0.0;
|
||||
double MaximumNormalDistanceMeters=0.0,MaximumX=0.0,MaximumY=0.0;
|
||||
double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0;
|
||||
double MaximumSlopeDegrees=0.0;
|
||||
std::array<std::uint64_t,4> SlopeSamples{};
|
||||
std::array<double,4> SlopeP99{},SlopeMaximum{};
|
||||
bool Valid=false;
|
||||
};
|
||||
|
||||
struct FSurfaceRoot
|
||||
{
|
||||
double ZMeters=0.0;
|
||||
double SlopeX=0.0;
|
||||
double SlopeY=0.0;
|
||||
};
|
||||
|
||||
struct FViewBasis
|
||||
{
|
||||
std::array<double,3> Right{};
|
||||
std::array<double,3> Up{};
|
||||
};
|
||||
|
||||
struct FErrorAccumulator
|
||||
{
|
||||
std::vector<double> Pixels,VerticalPixels;
|
||||
std::array<std::vector<double>,4> BySlope;
|
||||
double Maximum=-1.0,MaximumVertical=0.0,MaximumNormalDistanceMeters=0.0;
|
||||
double MaximumX=0.0,MaximumY=0.0,MaximumSlopeDegrees=0.0;
|
||||
double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0;
|
||||
std::array<std::uint64_t,4> SlopeSamples{};
|
||||
};
|
||||
|
||||
inline double Percentile(std::vector<double>& Values,double P);
|
||||
|
||||
inline FViewBasis MakeViewBasis(double YawDegrees,double PitchDegrees)
|
||||
{
|
||||
constexpr double Pi=3.14159265358979323846;
|
||||
const double Yaw=YawDegrees*Pi/180.0,Pitch=PitchDegrees*Pi/180.0;
|
||||
const std::array<double,3> Forward={std::sin(Yaw)*std::cos(Pitch),
|
||||
std::cos(Yaw)*std::cos(Pitch),std::sin(Pitch)};
|
||||
FViewBasis Basis;
|
||||
Basis.Right={std::cos(Yaw),-std::sin(Yaw),0.0};
|
||||
Basis.Up={Basis.Right[1]*Forward[2]-Basis.Right[2]*Forward[1],
|
||||
Basis.Right[2]*Forward[0]-Basis.Right[0]*Forward[2],
|
||||
Basis.Right[0]*Forward[1]-Basis.Right[1]*Forward[0]};
|
||||
return Basis;
|
||||
}
|
||||
|
||||
inline std::size_t SlopeBin(double Degrees)
|
||||
{
|
||||
if(Degrees<30.0)return 0;
|
||||
if(Degrees<45.0)return 1;
|
||||
if(Degrees<60.0)return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
inline void AddError(FErrorAccumulator& Accumulator,double DeltaZMeters,
|
||||
const FSurfaceRoot& Root,double PixelBudgetMeters,const FViewBasis& View,
|
||||
double X,double Y)
|
||||
{
|
||||
constexpr double Pi=3.14159265358979323846;
|
||||
const double GradientSquared=Root.SlopeX*Root.SlopeX+Root.SlopeY*Root.SlopeY;
|
||||
const double NormalLength=std::sqrt(1.0+GradientSquared);
|
||||
const double NormalX=-Root.SlopeX/NormalLength;
|
||||
const double NormalY=-Root.SlopeY/NormalLength;
|
||||
const double NormalZ=1.0/NormalLength;
|
||||
const double NormalDistance=std::abs(DeltaZMeters)/NormalLength;
|
||||
const double ProjectedNormal=std::hypot(
|
||||
NormalX*View.Right[0]+NormalY*View.Right[1]+NormalZ*View.Right[2],
|
||||
NormalX*View.Up[0]+NormalY*View.Up[1]+NormalZ*View.Up[2]);
|
||||
const double ProjectedVertical=std::hypot(View.Right[2],View.Up[2]);
|
||||
const double NormalPixels=NormalDistance*ProjectedNormal/PixelBudgetMeters;
|
||||
const double VerticalPixels=std::abs(DeltaZMeters)*ProjectedVertical/PixelBudgetMeters;
|
||||
const double SlopeDegrees=std::atan(std::sqrt(GradientSquared))*180.0/Pi;
|
||||
Accumulator.Pixels.push_back(NormalPixels);
|
||||
Accumulator.VerticalPixels.push_back(VerticalPixels);
|
||||
const std::size_t Bin=SlopeBin(SlopeDegrees);
|
||||
Accumulator.BySlope[Bin].push_back(NormalPixels);
|
||||
++Accumulator.SlopeSamples[Bin];
|
||||
if(NormalPixels>Accumulator.Maximum)
|
||||
{
|
||||
Accumulator.Maximum=NormalPixels;
|
||||
Accumulator.MaximumVertical=VerticalPixels;
|
||||
Accumulator.MaximumNormalDistanceMeters=NormalDistance;
|
||||
Accumulator.MaximumX=X;Accumulator.MaximumY=Y;
|
||||
Accumulator.MaximumSlopeDegrees=SlopeDegrees;
|
||||
Accumulator.MaximumReferenceZ=Root.ZMeters;
|
||||
Accumulator.MaximumDisplayedZ=Root.ZMeters+DeltaZMeters;
|
||||
}
|
||||
}
|
||||
|
||||
inline void AddMissing(FErrorAccumulator& Accumulator)
|
||||
{
|
||||
Accumulator.Pixels.push_back(2.0);
|
||||
Accumulator.VerticalPixels.push_back(2.0);
|
||||
}
|
||||
|
||||
inline void FinalizeError(FErrorRow& Row,FErrorAccumulator& Accumulator)
|
||||
{
|
||||
Row.Samples=Accumulator.Pixels.size();
|
||||
Row.P50=Percentile(Accumulator.Pixels,0.50);
|
||||
Row.P99=Percentile(Accumulator.Pixels,0.99);
|
||||
Row.Maximum=Accumulator.Pixels.empty()?0.0
|
||||
:*std::max_element(Accumulator.Pixels.begin(),Accumulator.Pixels.end());
|
||||
Row.VerticalP50=Percentile(Accumulator.VerticalPixels,0.50);
|
||||
Row.VerticalP99=Percentile(Accumulator.VerticalPixels,0.99);
|
||||
Row.VerticalMaximum=Accumulator.VerticalPixels.empty()?0.0
|
||||
:*std::max_element(Accumulator.VerticalPixels.begin(),Accumulator.VerticalPixels.end());
|
||||
Row.MaximumNormalDistanceMeters=Accumulator.MaximumNormalDistanceMeters;
|
||||
Row.MaximumX=Accumulator.MaximumX;Row.MaximumY=Accumulator.MaximumY;
|
||||
Row.MaximumReferenceZ=Accumulator.MaximumReferenceZ;
|
||||
Row.MaximumDisplayedZ=Accumulator.MaximumDisplayedZ;
|
||||
Row.MaximumSlopeDegrees=Accumulator.MaximumSlopeDegrees;
|
||||
Row.SlopeSamples=Accumulator.SlopeSamples;
|
||||
for(std::size_t Bin=0;Bin<Accumulator.BySlope.size();++Bin)
|
||||
{
|
||||
Row.SlopeP99[Bin]=Percentile(Accumulator.BySlope[Bin],0.99);
|
||||
Row.SlopeMaximum[Bin]=Accumulator.BySlope[Bin].empty()?0.0
|
||||
:*std::max_element(Accumulator.BySlope[Bin].begin(),Accumulator.BySlope[Bin].end());
|
||||
}
|
||||
}
|
||||
|
||||
struct FHeightGrid
|
||||
{
|
||||
int Side=65;
|
||||
@@ -162,38 +287,51 @@ namespace StandaloneScreenSpaceGate
|
||||
return Grid;
|
||||
}
|
||||
|
||||
inline std::vector<double> ExactHeightRoots(const FHeightGrid& Grid,int Ix,int Iy,
|
||||
inline std::vector<FSurfaceRoot> ExactHeightRoots(const FHeightGrid& Grid,int Ix,int Iy,
|
||||
const FVoxelTileKey& Patch,double VoxelMeters,
|
||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly)
|
||||
{
|
||||
std::vector<double> Roots;
|
||||
std::vector<FSurfaceRoot> Roots;
|
||||
const double XSubvoxels=Grid.X(Ix)/VoxelMeters*256.0;
|
||||
const double YSubvoxels=Grid.Y(Iy)/VoxelMeters*256.0;
|
||||
const auto X=static_cast<std::int64_t>(std::llround(XSubvoxels));
|
||||
const auto Y=static_cast<std::int64_t>(std::llround(YSubvoxels));
|
||||
const double MinZ=static_cast<double>(Patch.OriginVoxels().Z);
|
||||
const double MaxZ=MinZ+Patch.ExtentVoxels();
|
||||
constexpr std::int64_t NormalProbeSubvoxels=256;
|
||||
for(const auto& Plan:Assembly.plan.strates)
|
||||
{
|
||||
VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan);
|
||||
if(!Field.isValid())continue;
|
||||
const auto Column=Field.point(static_cast<std::int64_t>(std::llround(XSubvoxels)),
|
||||
static_cast<std::int64_t>(std::llround(YSubvoxels)),0);
|
||||
const auto Column=Field.point(X,Y,0);
|
||||
if(Column.groundHeightSubvoxels>=Column.capHeightSubvoxels)continue;
|
||||
const double Ground=static_cast<double>(Column.groundHeightSubvoxels)/(256.0)*VoxelMeters;
|
||||
const double Cap=static_cast<double>(Column.capHeightSubvoxels)/(256.0)*VoxelMeters;
|
||||
const auto XMinus=Field.point(X-NormalProbeSubvoxels,Y,0);
|
||||
const auto XPlus=Field.point(X+NormalProbeSubvoxels,Y,0);
|
||||
const auto YMinus=Field.point(X,Y-NormalProbeSubvoxels,0);
|
||||
const auto YPlus=Field.point(X,Y+NormalProbeSubvoxels,0);
|
||||
const auto Slope=[&](std::int64_t Minus,std::int64_t Plus)
|
||||
{return static_cast<double>(Plus-Minus)/(2.0*NormalProbeSubvoxels);};
|
||||
const double Ground=static_cast<double>(Column.groundHeightSubvoxels)/256.0*VoxelMeters;
|
||||
const double Cap=static_cast<double>(Column.capHeightSubvoxels)/256.0*VoxelMeters;
|
||||
const double PlanMinZ=static_cast<double>(Plan.bottomChunkZ)*32.0*VoxelMeters;
|
||||
const double PlanMaxZ=static_cast<double>(Plan.topChunkZ+1)*32.0*VoxelMeters;
|
||||
if(Ground>=MinZ*VoxelMeters-0.25*VoxelMeters
|
||||
&&Ground<=MaxZ*VoxelMeters+0.25*VoxelMeters
|
||||
&&Ground>=PlanMinZ-0.25*VoxelMeters&&Ground<=PlanMaxZ+0.25*VoxelMeters)
|
||||
Roots.push_back(Ground);
|
||||
Roots.push_back({Ground,Slope(XMinus.groundHeightSubvoxels,
|
||||
XPlus.groundHeightSubvoxels),Slope(YMinus.groundHeightSubvoxels,
|
||||
YPlus.groundHeightSubvoxels)});
|
||||
if(Cap>=MinZ*VoxelMeters-0.25*VoxelMeters
|
||||
&&Cap<=MaxZ*VoxelMeters+0.25*VoxelMeters
|
||||
&&Cap>=PlanMinZ-0.25*VoxelMeters&&Cap<=PlanMaxZ+0.25*VoxelMeters)
|
||||
Roots.push_back(Cap);
|
||||
Roots.push_back({Cap,Slope(XMinus.capHeightSubvoxels,
|
||||
XPlus.capHeightSubvoxels),Slope(YMinus.capHeightSubvoxels,
|
||||
YPlus.capHeightSubvoxels)});
|
||||
}
|
||||
std::sort(Roots.begin(),Roots.end());
|
||||
Roots.erase(std::unique(Roots.begin(),Roots.end(),[](double A,double B)
|
||||
{return std::abs(A-B)<0.01;}),Roots.end());
|
||||
std::sort(Roots.begin(),Roots.end(),[](const FSurfaceRoot& A,const FSurfaceRoot& B)
|
||||
{return A.ZMeters<B.ZMeters;});
|
||||
Roots.erase(std::unique(Roots.begin(),Roots.end(),[](const FSurfaceRoot& A,
|
||||
const FSurfaceRoot& B){return std::abs(A.ZMeters-B.ZMeters)<0.01;}),Roots.end());
|
||||
return Roots;
|
||||
}
|
||||
|
||||
@@ -213,75 +351,87 @@ namespace StandaloneScreenSpaceGate
|
||||
|
||||
inline FErrorRow CompareToExact(const FHeightGrid& MeshGrid,const FVoxelTileKey& Patch,
|
||||
double VoxelMeters,double PixelBudgetMeters,
|
||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly)
|
||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly,const FViewBasis& View)
|
||||
{
|
||||
FErrorRow Row;Row.Lod=Patch.Level;Row.Valid=true;
|
||||
std::vector<double> Errors;
|
||||
FErrorAccumulator Accumulator;
|
||||
for(int Iy=0;Iy<MeshGrid.Side;++Iy)
|
||||
for(int Ix=0;Ix<MeshGrid.Side;++Ix)
|
||||
{
|
||||
const auto Roots=ExactHeightRoots(MeshGrid,Ix,Iy,Patch,VoxelMeters,Assembly);
|
||||
if(Roots.empty()&&MeshGrid.At(Ix,Iy).empty())continue;
|
||||
std::vector<bool> Used(MeshGrid.At(Ix,Iy).size(),false);
|
||||
for(double Root:Roots)
|
||||
for(const FSurfaceRoot& Root:Roots)
|
||||
{
|
||||
std::size_t HitIndex=0;double ErrorMeters=0.0;
|
||||
if(!MatchNearest(MeshGrid.At(Ix,Iy),Used,Root,HitIndex,ErrorMeters))
|
||||
if(!MatchNearest(MeshGrid.At(Ix,Iy),Used,Root.ZMeters,HitIndex,ErrorMeters))
|
||||
{
|
||||
++Row.Missing;
|
||||
Errors.push_back(2.0);
|
||||
AddMissing(Accumulator);
|
||||
continue;
|
||||
}
|
||||
Used[HitIndex]=true;
|
||||
Errors.push_back(ErrorMeters/PixelBudgetMeters);
|
||||
AddError(Accumulator,MeshGrid.At(Ix,Iy)[HitIndex]-Root.ZMeters,
|
||||
Root,PixelBudgetMeters,View,MeshGrid.X(Ix),MeshGrid.Y(Iy));
|
||||
++Row.Samples;
|
||||
}
|
||||
// Heightfield2D declares ground and cap roots only. Other mesh intersections may
|
||||
// be valid tunnel geometry; the exact-mesh comparison checks extra pieces.
|
||||
}
|
||||
Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99);
|
||||
Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end());
|
||||
Row.Samples=Errors.size();
|
||||
FinalizeError(Row,Accumulator);
|
||||
return Row;
|
||||
}
|
||||
|
||||
inline FErrorRow CompareToReference(const FHeightGrid& MeshGrid,
|
||||
const FHeightGrid& ReferenceGrid,int Lod,double PixelBudgetMeters)
|
||||
const FHeightGrid& ReferenceGrid,const FVoxelTileKey& Patch,double VoxelMeters,
|
||||
double PixelBudgetMeters,const VoxelForgeCore::WorldAssemblyManifest& Assembly,
|
||||
const FViewBasis& View)
|
||||
{
|
||||
FErrorRow Row;Row.Lod=Lod;Row.Valid=true;
|
||||
std::vector<double> Errors;
|
||||
FErrorRow Row;Row.Lod=Patch.Level;Row.Valid=true;
|
||||
FErrorAccumulator Accumulator;
|
||||
for(int Iy=0;Iy<ReferenceGrid.Side;++Iy)
|
||||
for(int Ix=0;Ix<ReferenceGrid.Side;++Ix)
|
||||
{
|
||||
const auto& Reference=ReferenceGrid.At(Ix,Iy);
|
||||
const auto& Mesh=MeshGrid.At(Ix,Iy);
|
||||
if(Reference.empty()&&Mesh.empty())continue;
|
||||
std::vector<bool> Used(Mesh.size(),false);
|
||||
for(double ReferenceZ:Reference)
|
||||
const auto Roots=ExactHeightRoots(ReferenceGrid,Ix,Iy,Patch,VoxelMeters,Assembly);
|
||||
std::vector<bool> UsedReference(Reference.size(),false),UsedMesh(Mesh.size(),false);
|
||||
for(std::size_t ReferenceIndex=0;ReferenceIndex<Reference.size();++ReferenceIndex)
|
||||
{
|
||||
std::size_t HitIndex=0;double ErrorMeters=0.0;
|
||||
if(!MatchNearest(Mesh,Used,ReferenceZ,HitIndex,ErrorMeters))
|
||||
const double ReferenceZ=Reference[ReferenceIndex];
|
||||
UsedReference[ReferenceIndex]=true;
|
||||
std::size_t MeshIndex=0;double MeshError=0.0;
|
||||
if(!MatchNearest(Mesh,UsedMesh,ReferenceZ,MeshIndex,MeshError))
|
||||
{
|
||||
++Row.Missing;Errors.push_back(2.0);continue;
|
||||
++Row.Missing;AddMissing(Accumulator);continue;
|
||||
}
|
||||
Used[HitIndex]=true;
|
||||
Errors.push_back(ErrorMeters/PixelBudgetMeters);
|
||||
UsedMesh[MeshIndex]=true;
|
||||
FSurfaceRoot Root{ReferenceZ,0.0,0.0};
|
||||
if(!Roots.empty())
|
||||
{
|
||||
const auto BestRoot=std::min_element(Roots.begin(),Roots.end(),
|
||||
[ReferenceZ](const FSurfaceRoot& A,const FSurfaceRoot& B)
|
||||
{return std::abs(A.ZMeters-ReferenceZ)<std::abs(B.ZMeters-ReferenceZ);});
|
||||
if(BestRoot!=Roots.end()&&std::abs(BestRoot->ZMeters-ReferenceZ)<1.0)
|
||||
Root=*BestRoot;
|
||||
}
|
||||
AddError(Accumulator,Mesh[MeshIndex]-Reference[ReferenceIndex],
|
||||
Root,PixelBudgetMeters,View,ReferenceGrid.X(Ix),ReferenceGrid.Y(Iy));
|
||||
}
|
||||
for(std::size_t I=0;I<Mesh.size();++I)
|
||||
if(!Used[I]){++Row.Floating;Errors.push_back(2.0);}
|
||||
if(!UsedMesh[I]){++Row.Floating;AddMissing(Accumulator);}
|
||||
}
|
||||
Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99);
|
||||
Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end());
|
||||
Row.Samples=Errors.size();
|
||||
FinalizeError(Row,Accumulator);
|
||||
return Row;
|
||||
}
|
||||
|
||||
inline FPopRow ComparePop(const FHeightGrid& Coarse,const FHeightGrid& Fine,
|
||||
const FVoxelTileKey& Patch,double VoxelMeters,double PixelBudgetMeters,
|
||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly)
|
||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly,const FViewBasis& View)
|
||||
{
|
||||
FPopRow Row;Row.CoarseLod=Patch.Level;Row.Valid=true;
|
||||
std::vector<double> Errors;
|
||||
FErrorAccumulator Accumulator;
|
||||
for(int Iy=0;Iy<Coarse.Side;++Iy)
|
||||
for(int Ix=0;Ix<Coarse.Side;++Ix)
|
||||
{
|
||||
@@ -291,25 +441,34 @@ namespace StandaloneScreenSpaceGate
|
||||
if(Roots.empty()&&CoarseHits.empty()&&FineHits.empty())continue;
|
||||
std::vector<bool> CoarseUsed(CoarseHits.size(),false);
|
||||
std::vector<bool> FineUsed(FineHits.size(),false);
|
||||
for(double Root:Roots)
|
||||
for(const FSurfaceRoot& Root:Roots)
|
||||
{
|
||||
std::size_t CoarseIndex=0,FineIndex=0;
|
||||
double CoarseError=0.0,FineError=0.0;
|
||||
if(!MatchNearest(CoarseHits,CoarseUsed,Root,CoarseIndex,CoarseError)
|
||||
||!MatchNearest(FineHits,FineUsed,Root,FineIndex,FineError))
|
||||
if(!MatchNearest(CoarseHits,CoarseUsed,Root.ZMeters,CoarseIndex,CoarseError)
|
||||
||!MatchNearest(FineHits,FineUsed,Root.ZMeters,FineIndex,FineError))
|
||||
{
|
||||
++Row.Cracks;Errors.push_back(2.0);continue;
|
||||
++Row.Cracks;AddMissing(Accumulator);continue;
|
||||
}
|
||||
CoarseUsed[CoarseIndex]=true;
|
||||
FineUsed[FineIndex]=true;
|
||||
Errors.push_back(std::abs(CoarseHits[CoarseIndex]-FineHits[FineIndex])
|
||||
/PixelBudgetMeters);
|
||||
AddError(Accumulator,CoarseHits[CoarseIndex]-FineHits[FineIndex],
|
||||
Root,PixelBudgetMeters,View,Coarse.X(Ix),Coarse.Y(Iy));
|
||||
++Row.Samples;
|
||||
}
|
||||
}
|
||||
Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99);
|
||||
Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end());
|
||||
Row.Samples=Errors.size();
|
||||
FErrorRow Stats;Stats.Lod=Patch.Level;Stats.Valid=true;
|
||||
FinalizeError(Stats,Accumulator);
|
||||
Row.Samples=Stats.Samples;Row.P50=Stats.P50;Row.P99=Stats.P99;Row.Maximum=Stats.Maximum;
|
||||
Row.VerticalP50=Stats.VerticalP50;Row.VerticalP99=Stats.VerticalP99;
|
||||
Row.VerticalMaximum=Stats.VerticalMaximum;
|
||||
Row.MaximumNormalDistanceMeters=Stats.MaximumNormalDistanceMeters;
|
||||
Row.MaximumX=Stats.MaximumX;Row.MaximumY=Stats.MaximumY;
|
||||
Row.MaximumReferenceZ=Stats.MaximumReferenceZ;
|
||||
Row.MaximumDisplayedZ=Stats.MaximumDisplayedZ;
|
||||
Row.MaximumSlopeDegrees=Stats.MaximumSlopeDegrees;
|
||||
Row.SlopeSamples=Stats.SlopeSamples;Row.SlopeP99=Stats.SlopeP99;
|
||||
Row.SlopeMaximum=Stats.SlopeMaximum;
|
||||
return Row;
|
||||
}
|
||||
|
||||
@@ -352,19 +511,52 @@ namespace StandaloneScreenSpaceGate
|
||||
return Distances;
|
||||
}
|
||||
|
||||
inline void PrintSlopeTail(const char* Kind,int Lod,const std::array<std::uint64_t,4>& Counts,
|
||||
const std::array<double,4>& P99,const std::array<double,4>& Maximum)
|
||||
{
|
||||
constexpr const char* Labels[4]={"lt30","30to45","45to60","ge60"};
|
||||
for(std::size_t Bin=0;Bin<Counts.size();++Bin)
|
||||
std::printf("[ScreenSpaceSlopeTail] kind=%s lod=%d slope_deg=%s samples=%llu p99_px=%.4f max_px=%.4f\n",
|
||||
Kind,Lod,Labels[Bin],static_cast<unsigned long long>(Counts[Bin]),P99[Bin],Maximum[Bin]);
|
||||
}
|
||||
|
||||
inline void PrintKnownFailureControl(double CellMeters,double DisplayDistanceMeters,
|
||||
const FViewBasis& View)
|
||||
{
|
||||
constexpr double Slope=100.0;
|
||||
const double LateralMeters=CellMeters/200.0;
|
||||
const double VerticalMeters=Slope*LateralMeters;
|
||||
const double NormalLength=std::sqrt(1.0+Slope*Slope);
|
||||
const double NormalDistance=VerticalMeters/NormalLength;
|
||||
const std::array<double,3> Normal={-Slope/NormalLength,0.0,1.0/NormalLength};
|
||||
const double ProjectedNormal=std::hypot(
|
||||
Normal[0]*View.Right[0]+Normal[1]*View.Right[1]+Normal[2]*View.Right[2],
|
||||
Normal[0]*View.Up[0]+Normal[1]*View.Up[1]+Normal[2]*View.Up[2]);
|
||||
const double PixelBudgetMeters=DisplayDistanceMeters*OnePixelRadians;
|
||||
const double VerticalPixels=VerticalMeters/PixelBudgetMeters;
|
||||
const double NormalPixels=NormalDistance*ProjectedNormal/PixelBudgetMeters;
|
||||
const bool Passed=VerticalPixels>4.0&&NormalPixels<1.0;
|
||||
std::printf("[ScreenSpaceKnownFailureControl] case=steep-plane-lateral-offset slope_deg=%.4f cell_m=%.6f lateral_m=%.6f cell_fraction=%.6f vertical_error_m=%.6f vertical_height_px=%.4f closest_normal_m=%.6f projected_normal_px=%.4f status=%s\n",
|
||||
std::atan(Slope)*180.0/3.14159265358979323846,CellMeters,LateralMeters,
|
||||
LateralMeters/CellMeters,VerticalMeters,VerticalPixels,NormalDistance,NormalPixels,
|
||||
Passed?"EXPECTED_VERTICAL_FALSE_POSITIVE":"CONTROL_FAILED");
|
||||
}
|
||||
|
||||
inline bool Run(const std::array<std::optional<FStandaloneCpuTile>,9>& SampleTiles,
|
||||
FStandaloneStreamingGenerator& Streamer,
|
||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly,
|
||||
const std::array<double,9>& Distances,std::uint32_t VoxelSizeMicrons,int MaxLod,
|
||||
const std::array<std::int64_t,9>& PixelBudgetsSubvoxels)
|
||||
const std::array<std::int64_t,9>& PixelBudgetsSubvoxels,
|
||||
double CameraYawDegrees,double CameraPitchDegrees)
|
||||
{
|
||||
const double VoxelMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
|
||||
const FViewBasis View=MakeViewBasis(CameraYawDegrees,CameraPitchDegrees);
|
||||
std::array<double,9> Budgets{};
|
||||
std::array<FErrorRow,9> ErrorRows{};
|
||||
std::array<FPopRow,9> PopRows{};
|
||||
bool GatePass=true;
|
||||
std::printf("[ScreenSpaceGate] angular_pixel=%.9f rad (90deg/2560) lod>=3_limit=1.0px sampling=65x65 skirts=excluded metric=vertical_height_error field_reference=direct-Heightfield2D-roots\n",
|
||||
OnePixelRadians);
|
||||
std::printf("[ScreenSpaceGate] angular_pixel=%.9f rad (horizontal_fov=90deg,width=2560) lod>=3_limit=1.0px sampling=65x65 skirts=excluded metric=local_surface_normal_projected field_reference=direct-Heightfield2D-roots normal_probe_vox=1 view_yaw_deg=%.3f view_pitch_deg=%.3f\n",
|
||||
OnePixelRadians,CameraYawDegrees,CameraPitchDegrees);
|
||||
for(int Lod=0;Lod<=MaxLod&&Lod<9;++Lod)
|
||||
{
|
||||
Budgets[Lod]=Distances[Lod]*OnePixelRadians;
|
||||
@@ -372,6 +564,8 @@ namespace StandaloneScreenSpaceGate
|
||||
Lod,Distances[Lod],Budgets[Lod],Budgets[Lod]*100.0,
|
||||
static_cast<long long>(PixelBudgetsSubvoxels[Lod]),Lod<3?"exact-2cm":"1px");
|
||||
}
|
||||
const double LOD3CellMeters=static_cast<double>(CHUNK_SIZE*(1<<3))*VoxelMeters/64.0;
|
||||
PrintKnownFailureControl(LOD3CellMeters,Distances[3],View);
|
||||
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0);
|
||||
for(int Lod=3;Lod<=MaxLod&&Lod<9;++Lod)
|
||||
{
|
||||
@@ -389,32 +583,43 @@ namespace StandaloneScreenSpaceGate
|
||||
const FStandaloneCpuTile* ExactPointer=&ExactTile;
|
||||
const std::span<const FStandaloneCpuTile* const> ExactSpan(&ExactPointer,1);
|
||||
const FHeightGrid ExactGrid=Rasterize(ExactSpan,ExactTile.Key,VoxelMeters);
|
||||
ErrorRows[Lod]=CompareToReference(CoarseGrid,ExactGrid,Lod,Budgets[Lod]);
|
||||
ErrorRows[Lod]=CompareToReference(CoarseGrid,ExactGrid,CoarseTile.Key,VoxelMeters,
|
||||
Budgets[Lod],Assembly,View);
|
||||
const FErrorRow& Error=ErrorRows[Lod];
|
||||
const bool LodPass=Error.Valid&&Error.Missing==0&&Error.Floating==0
|
||||
&&Error.Samples>=16&&Error.Maximum<=1.0;
|
||||
GatePass=GatePass&&LodPass;
|
||||
std::printf("[ScreenSpaceGateLOD] lod=%d key=(%d,%d,%d) samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n",
|
||||
std::printf("[ScreenSpaceGateLOD] lod=%d key=(%d,%d,%d) samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xy_m=(%.3f,%.3f) max_slope_deg=%.2f missing=%llu floating=%llu status=%s\n",
|
||||
Lod,CoarseTile.Key.Coord.X,CoarseTile.Key.Coord.Y,CoarseTile.Key.Coord.Z,
|
||||
static_cast<unsigned long long>(Error.Samples),Error.P50,Error.P99,Error.Maximum,
|
||||
Error.VerticalP50,Error.VerticalP99,Error.VerticalMaximum,
|
||||
Error.MaximumNormalDistanceMeters,Error.MaximumX,Error.MaximumY,
|
||||
Error.MaximumSlopeDegrees,
|
||||
static_cast<unsigned long long>(Error.Missing),static_cast<unsigned long long>(Error.Floating),
|
||||
LodPass?"PASS":"REJECT");
|
||||
PrintSlopeTail("same_lod",Lod,Error.SlopeSamples,Error.SlopeP99,Error.SlopeMaximum);
|
||||
|
||||
const FErrorRow FieldError=CompareToExact(CoarseGrid,CoarseTile.Key,VoxelMeters,
|
||||
Budgets[Lod],Assembly);
|
||||
Budgets[Lod],Assembly,View);
|
||||
const bool FieldPass=FieldError.Valid&&FieldError.Missing==0&&FieldError.Floating==0
|
||||
&&FieldError.Samples>=16&&FieldError.Maximum<=1.0;
|
||||
GatePass=GatePass&&FieldPass;
|
||||
std::printf("[ScreenSpaceGateFieldLOD] lod=%d samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n",
|
||||
std::printf("[ScreenSpaceGateFieldLOD] lod=%d samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xyz_m=(%.3f,%.3f,%.3f) max_slope_deg=%.2f missing=%llu floating=%llu status=%s\n",
|
||||
Lod,static_cast<unsigned long long>(FieldError.Samples),FieldError.P50,
|
||||
FieldError.P99,FieldError.Maximum,static_cast<unsigned long long>(FieldError.Missing),
|
||||
FieldError.P99,FieldError.Maximum,FieldError.VerticalP50,FieldError.VerticalP99,
|
||||
FieldError.VerticalMaximum,FieldError.MaximumNormalDistanceMeters,
|
||||
FieldError.MaximumX,FieldError.MaximumY,FieldError.MaximumDisplayedZ,
|
||||
FieldError.MaximumSlopeDegrees,
|
||||
static_cast<unsigned long long>(FieldError.Missing),
|
||||
static_cast<unsigned long long>(FieldError.Floating),FieldPass?"PASS":"REJECT");
|
||||
PrintSlopeTail("field",Lod,FieldError.SlopeSamples,FieldError.SlopeP99,
|
||||
FieldError.SlopeMaximum);
|
||||
|
||||
bool FaultControlsPass=true;
|
||||
const FHeightGrid MissingGrid(ExactGrid.Side,ExactGrid.MinimumX,ExactGrid.MaximumX,
|
||||
ExactGrid.MinimumY,ExactGrid.MaximumY);
|
||||
const FErrorRow MissingError=CompareToReference(MissingGrid,ExactGrid,Lod,
|
||||
Budgets[Lod]);
|
||||
const FErrorRow MissingError=CompareToReference(MissingGrid,ExactGrid,
|
||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
||||
const bool MissingRejected=MissingError.Missing>0&&MissingError.Maximum>1.0;
|
||||
FaultControlsPass=FaultControlsPass&&MissingRejected;
|
||||
std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-displayed-surface missing=%llu status=%s\n",
|
||||
@@ -435,8 +640,8 @@ namespace StandaloneScreenSpaceGate
|
||||
const FStandaloneCpuTile* FloatingPointer=&FloatingTile;
|
||||
const std::span<const FStandaloneCpuTile* const> FloatingSpan(&FloatingPointer,1);
|
||||
const FHeightGrid FloatingGrid=Rasterize(FloatingSpan,CoarseTile.Key,VoxelMeters);
|
||||
const FErrorRow FloatingError=CompareToReference(FloatingGrid,ExactGrid,Lod,
|
||||
Budgets[Lod]);
|
||||
const FErrorRow FloatingError=CompareToReference(FloatingGrid,ExactGrid,
|
||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
||||
const bool FloatingRejected=FloatingError.Floating>0||FloatingError.Maximum>1.0;
|
||||
FaultControlsPass=FaultControlsPass&&FloatingRejected;
|
||||
std::printf("[ScreenSpaceFaultInjection] lod=%d case=2m-floating-surface floating=%llu max_px=%.4f status=%s\n",
|
||||
@@ -461,20 +666,24 @@ namespace StandaloneScreenSpaceGate
|
||||
FinePointers.data(),FinePointers.size());
|
||||
const FHeightGrid FineGrid=Rasterize(FineSpan,CoarseTile.Key,VoxelMeters);
|
||||
PopRows[Lod]=ComparePop(CoarseGrid,FineGrid,CoarseTile.Key,VoxelMeters,
|
||||
Budgets[Lod],Assembly);
|
||||
Budgets[Lod],Assembly,View);
|
||||
const FPopRow& Pop=PopRows[Lod];
|
||||
const bool PopPass=Pop.Valid&&Pop.Cracks==0&&Pop.Floating==0&&Pop.Maximum<=1.0;
|
||||
GatePass=GatePass&&PopPass;
|
||||
std::printf("[ScreenSpaceSwap] lod=%d_to_%d samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f cracks=%llu floating=%llu status=%s\n",
|
||||
std::printf("[ScreenSpaceSwap] lod=%d_to_%d samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xyz_m=(%.3f,%.3f,%.3f) max_slope_deg=%.2f cracks=%llu floating=%llu status=%s\n",
|
||||
Lod,Lod-1,static_cast<unsigned long long>(Pop.Samples),
|
||||
Pop.P50,Pop.P99,Pop.Maximum,static_cast<unsigned long long>(Pop.Cracks),
|
||||
Pop.P50,Pop.P99,Pop.Maximum,Pop.VerticalP50,Pop.VerticalP99,Pop.VerticalMaximum,
|
||||
Pop.MaximumNormalDistanceMeters,Pop.MaximumX,Pop.MaximumY,
|
||||
Pop.MaximumDisplayedZ,
|
||||
Pop.MaximumSlopeDegrees,static_cast<unsigned long long>(Pop.Cracks),
|
||||
static_cast<unsigned long long>(Pop.Floating),
|
||||
PopPass?"PASS":"REJECT");
|
||||
PrintSlopeTail("swap",Lod,Pop.SlopeSamples,Pop.SlopeP99,Pop.SlopeMaximum);
|
||||
|
||||
const FHeightGrid MissingFineGrid(CoarseGrid.Side,CoarseGrid.MinimumX,
|
||||
CoarseGrid.MaximumX,CoarseGrid.MinimumY,CoarseGrid.MaximumY);
|
||||
const FPopRow MissingFineError=ComparePop(CoarseGrid,MissingFineGrid,
|
||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly);
|
||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
||||
const bool CrackRejected=MissingFineError.Cracks>0&&MissingFineError.Maximum>1.0;
|
||||
FaultControlsPass=FaultControlsPass&&CrackRejected;
|
||||
GatePass=GatePass&&FaultControlsPass;
|
||||
@@ -490,8 +699,8 @@ namespace StandaloneScreenSpaceGate
|
||||
const FStandaloneCpuTile* BadPointer=&Bad;
|
||||
const std::span<const FStandaloneCpuTile* const> BadSpan(&BadPointer,1);
|
||||
const FHeightGrid BadGrid=Rasterize(BadSpan,Bad.Key,VoxelMeters);
|
||||
const FErrorRow BadError=CompareToReference(BadGrid,ExactGrid,Lod,
|
||||
Budgets[Lod]);
|
||||
const FErrorRow BadError=CompareToReference(BadGrid,ExactGrid,
|
||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
||||
const bool Rejected=BadError.Missing>0||BadError.Floating>0
|
||||
||BadError.Samples<16||BadError.Maximum>1.0;
|
||||
NegativeControlsPass=NegativeControlsPass&&Rejected;
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace
|
||||
bool ForceGenerate=false;
|
||||
bool MeshCoverage=false;
|
||||
bool ScreenSpaceGate=false;
|
||||
bool VisualDetailCutoff=false;
|
||||
bool HasSeedOverride=false;
|
||||
int SeedOverride=0;
|
||||
bool HasRenderDistanceOverride=false;
|
||||
@@ -98,7 +97,6 @@ namespace
|
||||
" --auto-surface-camera (place the 27-tile cube on this seed's surface)\n"
|
||||
" --mesh-coverage (report non-empty tiles and indexed triangles)\n"
|
||||
" --screen-space-gate (measure display error and historical negative controls)\n"
|
||||
" --visual-detail-cutoff (enable the declared far visual octave cutoff)\n"
|
||||
" --render-distance-chunks=N (0 disables the extra far ring)\n"
|
||||
" --tls-probe=N (Linux worker TLS/stack measurement; N=0..12)\n"
|
||||
" --tls-probe-touch=none|surface-world|floating-islands (first-use probe)\n"
|
||||
@@ -114,7 +112,6 @@ namespace
|
||||
else if(arg=="--force-generate")options.ForceGenerate=true;
|
||||
else if(arg=="--mesh-coverage")options.MeshCoverage=true;
|
||||
else if(arg=="--screen-space-gate")options.ScreenSpaceGate=true;
|
||||
else if(arg=="--visual-detail-cutoff")options.VisualDetailCutoff=true;
|
||||
else if(arg=="--auto-surface-camera")options.AutoSurfaceCamera=true;
|
||||
else if(arg.starts_with("--recipe="))
|
||||
{
|
||||
@@ -317,5 +314,5 @@ int main(int argc,char** argv)
|
||||
scheduler,options.StreamHash,false,false,false,false,coldStart,
|
||||
options.ProfileColumnOverlap,options.FarHorizonLOD8InteriorStepVoxels,
|
||||
options.MeshCoverage,options.MeshExportPath,options.CameraYawDegrees,
|
||||
options.CameraPitchDegrees,options.ScreenSpaceGate,options.VisualDetailCutoff);
|
||||
options.CameraPitchDegrees,options.ScreenSpaceGate);
|
||||
}
|
||||
@@ -137,22 +137,17 @@ namespace StandaloneStreamBenchmark
|
||||
std::chrono::steady_clock::time_point ColdStart,bool bProfileColumnOverlap,
|
||||
int32 FarHorizonLOD8InteriorStepVoxels,bool bReportMeshCoverage=false,
|
||||
const std::filesystem::path& MeshExportPath={},float CameraYawDegrees=0.0f,
|
||||
float CameraPitchDegrees=0.0f,bool bScreenSpaceGate=false,
|
||||
bool bApplyVisualDetailCutoff=false)
|
||||
float CameraPitchDegrees=0.0f,bool bScreenSpaceGate=false)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
FStandaloneOwnerWorld& World=*OwnerWorld;
|
||||
const VoxelForgeCore::WorldAssemblyManifest* Assembly=CoreSnapshot.worldAssembly();
|
||||
std::array<double,9> ClosestDisplayDistances{};
|
||||
std::array<std::int64_t,9> PixelBudgetsSubvoxels{};
|
||||
std::array<std::int64_t,9> AppliedPixelBudgetsSubvoxels{};
|
||||
if(bScreenSpaceGate||bApplyVisualDetailCutoff)
|
||||
if(bScreenSpaceGate)
|
||||
{
|
||||
ClosestDisplayDistances=bScreenSpaceGate
|
||||
?StandaloneScreenSpaceGate::ClosestDisplayedDistancesMeters(
|
||||
World,CameraPositionVoxels,World.CoreVoxelSizeMicrons)
|
||||
:StandaloneScreenSpaceGate::ClipmapClosestDisplayedDistancesMeters(
|
||||
World,World.CoreVoxelSizeMicrons);
|
||||
ClosestDisplayDistances=StandaloneScreenSpaceGate::ClosestDisplayedDistancesMeters(
|
||||
World,CameraPositionVoxels,World.CoreVoxelSizeMicrons);
|
||||
const double VoxelMeters=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
|
||||
for(int32 Lod=3;Lod<=World.MaxClipLevel&&Lod<9;++Lod)
|
||||
PixelBudgetsSubvoxels[static_cast<std::size_t>(Lod)]=static_cast<std::int64_t>(std::floor(
|
||||
@@ -160,67 +155,6 @@ namespace StandaloneStreamBenchmark
|
||||
*StandaloneScreenSpaceGate::OnePixelRadians/VoxelMeters
|
||||
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
|
||||
}
|
||||
AppliedPixelBudgetsSubvoxels=PixelBudgetsSubvoxels;
|
||||
if(Assembly&&(bScreenSpaceGate||bApplyVisualDetailCutoff))
|
||||
{
|
||||
for(int32 Lod=3;Lod<=World.MaxClipLevel&&Lod<9;++Lod)
|
||||
{
|
||||
const std::int32_t Cells=FMath::Max(1,World.CoarseTileCells);
|
||||
const std::int32_t Step=FMath::Max(1,(CHUNK_SIZE<<Lod)/Cells);
|
||||
VoxelForgeCore::VisualDetailCutoffV1 Probe;
|
||||
Probe.pixelBudgetSubvoxels=PixelBudgetsSubvoxels[static_cast<std::size_t>(Lod)];
|
||||
Probe.sampleStepSubvoxels=static_cast<std::int64_t>(Step)
|
||||
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
std::uint64_t TermCount=0,EligibleTermCount=0,PotentialSkippedOctaves=0;
|
||||
for(const auto& Plan:Assembly->plan.strates)
|
||||
{
|
||||
const VoxelForgeCore::Heightfield2D Field(Assembly->recipe,Plan);
|
||||
for(const auto& Term:Field.visualDetailContract().terms)
|
||||
{
|
||||
++TermCount;
|
||||
const auto Result=VoxelForgeCore::FindVisualDetailTailCutoffV1(Term,Probe);
|
||||
if(Result.firstSkippedOctave==0)continue;
|
||||
++EligibleTermCount;
|
||||
PotentialSkippedOctaves+=Term.octaveCount-Result.firstSkippedOctave;
|
||||
}
|
||||
}
|
||||
std::printf("[WorldAssemblerVisualDetailCutoffPlan] lod=%d terms=%llu eligible_terms=%llu potential_skipped_octaves=%llu per_call_budget_divisor=%u sample_step_vox=%d status=%s\n",
|
||||
Lod,static_cast<unsigned long long>(TermCount),
|
||||
static_cast<unsigned long long>(EligibleTermCount),
|
||||
static_cast<unsigned long long>(PotentialSkippedOctaves),
|
||||
Probe.callBudgetDivisor,Step,
|
||||
EligibleTermCount==0?"exact-no-eligible-tail":"candidate");
|
||||
if(bApplyVisualDetailCutoff&&EligibleTermCount==0)
|
||||
AppliedPixelBudgetsSubvoxels[static_cast<std::size_t>(Lod)]=0;
|
||||
}
|
||||
}
|
||||
if(bScreenSpaceGate&&Assembly)
|
||||
{
|
||||
for(const auto& Plan:Assembly->plan.strates)
|
||||
{
|
||||
const VoxelForgeCore::Heightfield2D Field(Assembly->recipe,Plan);
|
||||
for(const auto& Term:Field.visualDetailContract().terms)
|
||||
for(std::uint32_t Octave=0;Octave<Term.octaveCount;++Octave)
|
||||
{
|
||||
const std::int64_t Wavelength=Term.baseWavelengthSubvoxels
|
||||
/static_cast<std::int64_t>(1ull<<Octave);
|
||||
const long double Amplitude=static_cast<long double>(Term.maximumDisplacementSubvoxels)
|
||||
*Term.maximumNoiseAbsQ16/65536.0L
|
||||
/static_cast<long double>(1ull<<Octave);
|
||||
std::printf("[WorldAssemblerVisualDetailTerm] stratum=%d id=%llu octave=%u wavelength_vox=%.3f max_abs_surface_vox=%.3f declared=1\n",
|
||||
Plan.strateIndex,static_cast<unsigned long long>(Term.stableId),Octave,
|
||||
static_cast<double>(Wavelength)/256.0,static_cast<double>(Amplitude));
|
||||
}
|
||||
}
|
||||
for(const auto& Island:Assembly->floatingIslands)
|
||||
{
|
||||
const VoxelForgeCore::FloatingIslandFieldV1 Field(
|
||||
Island.manifest.recipe,Island.manifest.plan);
|
||||
const auto Contract=Field.visualDetailContract();
|
||||
std::printf("[WorldAssemblerVisualDetailContract] recipe=floating-islands strate=%d declared_terms=%zu exact_path=%d\n",
|
||||
Island.strateIndex,Contract.terms.size(),Contract.terms.empty()?1:0);
|
||||
}
|
||||
}
|
||||
const bool bRoundTunnels=World.Generator->bStandaloneResearchRoundTunnels;
|
||||
const bool bEnhancedTunnelWalls=World.Generator->bStandaloneResearchEnhancedWalls;
|
||||
VoxelForgeCore::World Shape(CoreSnapshot);
|
||||
@@ -309,12 +243,7 @@ namespace StandaloneStreamBenchmark
|
||||
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
|
||||
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
|
||||
FarHorizonLOD8InteriorStepVoxels);
|
||||
if(bScreenSpaceGate||bApplyVisualDetailCutoff)
|
||||
{
|
||||
Streamer.SetVisualDetailCutoffBudgets(AppliedPixelBudgetsSubvoxels,
|
||||
bApplyVisualDetailCutoff);
|
||||
Streamer.SetVisualDetailProfilingEnabled(bScreenSpaceGate);
|
||||
}
|
||||
if(bScreenSpaceGate)Streamer.SetMesherStageProfilingEnabled(true);
|
||||
const auto StreamStart=Clock::now();
|
||||
const FStandaloneStreamingUpdate Update=Streamer.StartWithDesired(Desired);
|
||||
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
|
||||
@@ -370,14 +299,11 @@ namespace StandaloneStreamBenchmark
|
||||
std::array<bool,9> ColumnCaptureCompleteByLOD{};
|
||||
ColumnCaptureCompleteByLOD.fill(true);
|
||||
std::array<double,9> WorkerSecondsByLOD{};
|
||||
std::array<std::array<std::uint64_t,8>,9> DetailOctaveEvaluationsByLOD{};
|
||||
std::array<std::array<std::uint64_t,8>,9> DetailOctaveEligibleByLOD{};
|
||||
std::array<std::array<std::uint64_t,8>,9> DetailOctaveSkipsByLOD{};
|
||||
std::array<std::array<double,8>,9> DetailOctaveMillisecondsByLOD{};
|
||||
std::array<std::array<double,8>,9> DetailOctaveEligibleMillisecondsByLOD{};
|
||||
std::array<double,9> ColumnEvalMsByLOD{},MarchingExclusiveMsByLOD{};
|
||||
std::array<double,9> GroundRefineExclusiveMsByLOD{},CapRefineExclusiveMsByLOD{};
|
||||
std::array<double,9> MeshAssemblyMsByLOD{};
|
||||
std::array<double,9> SkirtMsByLOD{};
|
||||
std::array<std::uint64_t,9> SkirtTrianglesByLOD{};
|
||||
std::array<std::uint64_t,9> TileCountByLOD{};
|
||||
for(const FVoxelTileKey& Tile:Desired.Tiles)
|
||||
if(Tile.Level>=0&&Tile.Level<static_cast<int32>(ExpectedTilesByLOD.size()))
|
||||
@@ -425,19 +351,8 @@ namespace StandaloneStreamBenchmark
|
||||
GroundRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.groundRefinementExclusiveMilliseconds;
|
||||
CapRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.capRefinementExclusiveMilliseconds;
|
||||
MeshAssemblyMsByLOD[LOD]+=Tile.CoreDiagnostics.meshAssemblyMilliseconds;
|
||||
for(std::size_t Octave=0;Octave<8;++Octave)
|
||||
{
|
||||
DetailOctaveEvaluationsByLOD[LOD][Octave]+=
|
||||
Tile.CoreDiagnostics.visualDetailOctaveEvaluations[Octave];
|
||||
DetailOctaveEligibleByLOD[LOD][Octave]+=
|
||||
Tile.CoreDiagnostics.visualDetailOctaveBudgetEligible[Octave];
|
||||
DetailOctaveSkipsByLOD[LOD][Octave]+=
|
||||
Tile.CoreDiagnostics.visualDetailOctaveSkips[Octave];
|
||||
DetailOctaveMillisecondsByLOD[LOD][Octave]+=
|
||||
Tile.CoreDiagnostics.visualDetailOctaveMilliseconds[Octave];
|
||||
DetailOctaveEligibleMillisecondsByLOD[LOD][Octave]+=
|
||||
Tile.CoreDiagnostics.visualDetailOctaveEligibleMilliseconds[Octave];
|
||||
}
|
||||
SkirtMsByLOD[LOD]+=Tile.CoreDiagnostics.marchingCubesSkirtMilliseconds;
|
||||
SkirtTrianglesByLOD[LOD]+=Tile.SkirtTriangleCount;
|
||||
if(bScreenSpaceGate&&Assembly&&Tile.Key.Level>=3&&Tile.Key.Level<9
|
||||
&&!Tile.Vertices.empty()
|
||||
&&StandaloneScreenSpaceGate::TileHasNativeSurface(Tile.Key,*Assembly,
|
||||
@@ -541,7 +456,8 @@ namespace StandaloneStreamBenchmark
|
||||
if(Assembly)
|
||||
bScreenSpaceGatePassing=StandaloneScreenSpaceGate::Run(GateSampleTiles,
|
||||
Streamer,*Assembly,ClosestDisplayDistances,World.CoreVoxelSizeMicrons,
|
||||
World.MaxClipLevel,PixelBudgetsSubvoxels);
|
||||
World.MaxClipLevel,PixelBudgetsSubvoxels,CameraYawDegrees,
|
||||
CameraPitchDegrees);
|
||||
else
|
||||
{std::fprintf(stderr,"[ScreenSpaceGateResult] no-world-assembly status=REJECT\n");bScreenSpaceGatePassing=false;}
|
||||
}
|
||||
@@ -576,19 +492,12 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
for(std::size_t LOD=0;LOD<TileCountByLOD.size();++LOD)
|
||||
{
|
||||
std::printf("[WorldAssemblerLODWorker] lod=%zu tiles=%llu worker_sum_s=%.6f column_eval_exclusive_ms=%.3f mc_exclusive_ms=%.3f ground_refine_exclusive_ms=%.3f cap_refine_exclusive_ms=%.3f mesh_assembly_ms=%.3f\n",
|
||||
std::printf("[WorldAssemblerLODWorker] lod=%zu tiles=%llu worker_sum_s=%.6f column_eval_exclusive_ms=%.3f mc_exclusive_ms=%.3f ground_refine_exclusive_ms=%.3f cap_refine_exclusive_ms=%.3f skirt_ms=%.3f skirt_triangles=%llu mesh_assembly_ms=%.3f\n",
|
||||
LOD,static_cast<unsigned long long>(TileCountByLOD[LOD]),WorkerSecondsByLOD[LOD],
|
||||
ColumnEvalMsByLOD[LOD],MarchingExclusiveMsByLOD[LOD],
|
||||
GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD],MeshAssemblyMsByLOD[LOD]);
|
||||
if(bScreenSpaceGate)
|
||||
for(std::size_t Octave=0;Octave<8;++Octave)
|
||||
std::printf("[WorldAssemblerLODDetailOctave] lod=%zu octave=%zu evaluated=%llu budget_eligible=%llu skipped=%llu eval_ms=%.6f eligible_ms=%.6f\n",
|
||||
LOD,Octave,
|
||||
static_cast<unsigned long long>(DetailOctaveEvaluationsByLOD[LOD][Octave]),
|
||||
static_cast<unsigned long long>(DetailOctaveEligibleByLOD[LOD][Octave]),
|
||||
static_cast<unsigned long long>(DetailOctaveSkipsByLOD[LOD][Octave]),
|
||||
DetailOctaveMillisecondsByLOD[LOD][Octave],
|
||||
DetailOctaveEligibleMillisecondsByLOD[LOD][Octave]);
|
||||
GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD],
|
||||
SkirtMsByLOD[LOD],static_cast<unsigned long long>(SkirtTrianglesByLOD[LOD]),
|
||||
MeshAssemblyMsByLOD[LOD]);
|
||||
}
|
||||
const double NeighborhoodSeconds=std::chrono::duration<double>(NeighborhoodReady-ColdStart).count();
|
||||
const double NeighborhoodMeshSeconds=std::chrono::duration<double>(NeighborhoodReady-ShapeReady).count();
|
||||
|
||||
@@ -646,16 +646,14 @@ public:
|
||||
int32 GetWorkerCount()const{return WorkerCount;}
|
||||
uint64 GetCacheHits()const{return CacheHitCount.load(std::memory_order_relaxed);}
|
||||
|
||||
void SetVisualDetailCutoffBudgets(const std::array<std::int64_t,9>& Budgets,bool Apply)
|
||||
{VisualDetailBudgetSubvoxels=Budgets;bEnableVisualDetailCutoff=true;bApplyVisualDetailCutoff=Apply;}
|
||||
void SetVisualDetailProfilingEnabled(bool Enabled)
|
||||
{bProfileVisualDetailOctaves=Enabled;}
|
||||
void SetMesherStageProfilingEnabled(bool Enabled)
|
||||
{bProfileMesherStages=Enabled;}
|
||||
FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key,
|
||||
std::uint32_t GlobalOctaveDrop=0,std::uint32_t CellStride=1,
|
||||
bool ForceExactVisualDetail=false)
|
||||
bool ForceGenerate=false)
|
||||
{
|
||||
FStandaloneTileJob Job;Job.Key=Key;Job.StartedAt=FClock::now();Job.QueuedAt=Job.StartedAt;
|
||||
return Generate(Job,GlobalOctaveDrop,CellStride,0,ForceExactVisualDetail);
|
||||
return Generate(Job,GlobalOctaveDrop,CellStride,ForceGenerate);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -775,13 +773,12 @@ private:
|
||||
}
|
||||
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job,
|
||||
std::uint32_t DiagnosticGlobalOctaveDrop=0,std::uint32_t DiagnosticCellStride=1,
|
||||
std::int64_t DiagnosticVisualBudgetSubvoxels=0,
|
||||
bool ForceExactVisualDetail=false)
|
||||
bool ForceGenerate=false)
|
||||
{
|
||||
const auto StartTime=Job.StartedAt;
|
||||
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
|
||||
Out.QueuedAt=Job.QueuedAt;Out.StartedAt=Job.StartedAt;
|
||||
if(MeshCache&&!ForceExactVisualDetail)
|
||||
if(MeshCache&&!ForceGenerate)
|
||||
{
|
||||
if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key))
|
||||
{
|
||||
@@ -820,19 +817,9 @@ private:
|
||||
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
|
||||
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
|
||||
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
|
||||
Options.visualDetailPixelBudgetSubvoxels=DiagnosticVisualBudgetSubvoxels;
|
||||
if(DiagnosticVisualBudgetSubvoxels==0&&bEnableVisualDetailCutoff
|
||||
&&Level>=3&&Level<static_cast<int32>(VisualDetailBudgetSubvoxels.size()))
|
||||
Options.visualDetailPixelBudgetSubvoxels=
|
||||
VisualDetailBudgetSubvoxels[static_cast<std::size_t>(Level)];
|
||||
Options.visualDetailSampleStepVoxels=FMath::Max(1,Extent/Cells);
|
||||
Options.profileVisualDetailOctaves=bProfileVisualDetailOctaves&&!ForceExactVisualDetail;
|
||||
Options.visualDetailCutoffEnabled=bApplyVisualDetailCutoff
|
||||
&&!ForceExactVisualDetail&&Level>=3
|
||||
&&Options.visualDetailPixelBudgetSubvoxels>0;
|
||||
Options.visualDetailContractVersion=Options.visualDetailCutoffEnabled
|
||||
?VoxelForgeCore::CurrentVisualDetailCutoffContractVersion:0;
|
||||
Options.profileMesherStages=bProfileMesherStages&&!ForceGenerate;
|
||||
Options.diagnosticGlobalOctaveDrop=DiagnosticGlobalOctaveDrop;
|
||||
Options.diagnosticCellStride=std::max<std::uint32_t>(1,DiagnosticCellStride);
|
||||
Options.diagnosticColumnInterpolationStride=
|
||||
std::max<std::uint32_t>(1,DiagnosticCellStride);
|
||||
std::string Error;
|
||||
@@ -1007,10 +994,7 @@ private:
|
||||
bool bSkipColors=false;
|
||||
bool bCaptureColumnCoordinates=false;
|
||||
int32 Lod8HorizonInteriorStepVoxels=0;
|
||||
std::array<std::int64_t,9> VisualDetailBudgetSubvoxels{};
|
||||
bool bEnableVisualDetailCutoff=false;
|
||||
bool bApplyVisualDetailCutoff=false;
|
||||
bool bProfileVisualDetailOctaves=false;
|
||||
bool bProfileMesherStages=false;
|
||||
std::atomic<uint64> CacheHitCount{0};
|
||||
mutable std::mutex Mutex;
|
||||
std::condition_variable Wake;
|
||||
|
||||
@@ -1768,6 +1768,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const int32 GroundSurfaceTriangleCount=GroundTris.Num()/3;
|
||||
const int32 CapSurfaceTriangleCount=CapTris.Num()/3;
|
||||
TArray<uint8> GroundSkirtFaceMasks,CapSkirtFaceMasks;
|
||||
const uint64 SkirtStageStartCycles=OutStageTimings?FPlatformTime::Cycles64():0;
|
||||
if (bGenerateSkirts && (GroundTris.Num() + CapTris.Num()) > 0)
|
||||
{
|
||||
const float ExtentCm = (float)(CellsPerAxis * Step) * GeometryVoxelSize;
|
||||
@@ -2007,6 +2008,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
EmitSkirts(GroundTris,GroundSkirtFaceMasks);
|
||||
EmitSkirts(CapTris,CapSkirtFaceMasks);
|
||||
}
|
||||
if(OutStageTimings)
|
||||
OutStageTimings->SkirtMilliseconds=static_cast<double>(
|
||||
FPlatformTime::Cycles64()-SkirtStageStartCycles)
|
||||
*FPlatformTime::GetSecondsPerCycle64()*1000.0;
|
||||
|
||||
// F17 — concatène sol PUIS sky-cap : un run d'indices contigu par polygroup (voir
|
||||
// FVoxelMeshData::NumCeilingTriangles). Mêmes triangles qu'avant, seul l'ordre change.
|
||||
@@ -2059,6 +2064,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
AuditInterpolationCycles,AuditInterpolationSamples,SurfaceCellCalls);
|
||||
OutStageTimings->VertexDedupStreamMilliseconds=ScaleSampledCycles(
|
||||
AuditDedupStreamCycles,AuditDedupStreamSamples,SurfaceCellCalls);
|
||||
OutStageTimings->SkirtMilliseconds=std::max(0.0,
|
||||
OutStageTimings->SkirtMilliseconds);
|
||||
const double TotalMilliseconds=static_cast<double>(
|
||||
FPlatformTime::Cycles64()-StageMesherStartCycles)*CycleToMs;
|
||||
OutStageTimings->OtherMilliseconds=FMath::Max(0.0,TotalMilliseconds
|
||||
@@ -2066,7 +2073,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
-OutStageTimings->CellClassificationMilliseconds
|
||||
-OutStageTimings->GradientNormalsMilliseconds
|
||||
-OutStageTimings->EdgeInterpolationMilliseconds
|
||||
-OutStageTimings->VertexDedupStreamMilliseconds);
|
||||
-OutStageTimings->VertexDedupStreamMilliseconds
|
||||
-OutStageTimings->SkirtMilliseconds);
|
||||
}
|
||||
|
||||
return MeshData;
|
||||
|
||||
@@ -50,6 +50,7 @@ struct FVoxelMarchingCubesStageTimings
|
||||
double GradientNormalsMilliseconds = 0.0;
|
||||
double EdgeInterpolationMilliseconds = 0.0;
|
||||
double VertexDedupStreamMilliseconds = 0.0;
|
||||
double SkirtMilliseconds = 0.0;
|
||||
double OtherMilliseconds = 0.0;
|
||||
uint64 GradientFallbackDensityCalls = 0;
|
||||
uint64 EdgeMidpointDensityCalls = 0;
|
||||
|
||||
Reference in new issue
Block a user