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
+474
-615
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);
|
||||
Heightfield2D::setThreadColumnOctaveDrop(options.diagnosticGlobalOctaveDrop);
|
||||
Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(
|
||||
options.diagnosticColumnInterpolationStride>1&&level>=3
|
||||
?static_cast<std::int64_t>(MeshStep)*WorldAssemblySubvoxelsPerVoxel
|
||||
*options.diagnosticColumnInterpolationStride:0);
|
||||
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),
|
||||
|
||||
Reference in new issue
Block a user