wip(L15, side branch): extended far gate + rejected far-mesh candidates (null result)
Refine x2 at LOD4-8 saves 13.6% but breaks the 1 px rule, shading and swaps, and opens holes; station x2 and ray removal open connector gaps. Not for assembler/world. See Linux15/REPORT.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
1 parent
a176d0743a
commit
a6185b1a3f
17 files changed
+938
-115
No files matched your search
@@ -570,6 +570,15 @@ ApplyModification` (BlueprintCallable) + `EditorCarveSphere/EditorFillSphere` (C
|
|||||||
driven by `EditorBrush*` props.
|
driven by `EditorBrush*` props.
|
||||||
|
|
||||||
### 8.10 Performance invariants (DON'T regress)
|
### 8.10 Performance invariants (DON'T regress)
|
||||||
|
|
||||||
|
- **Far display mesh budget (L15)** (`WorldAssemblyEvaluatorVersion` 7): recipes may explicitly
|
||||||
|
declare `far_display_mesh` for LOD3+ display generation. With no declaration, the L14 mesh path
|
||||||
|
remains byte-for-byte selected. This setting must never alter LOD0–2, exact field queries,
|
||||||
|
collision, edits, or server state. A declared reduction must pass the same-LOD screen gate at the
|
||||||
|
closest displayed distance: at most 1.0 px geometry displacement, at most 1/255 normalized
|
||||||
|
shading/material delta, no worse swap pop, and no new holes, cracks, or floating pieces. Connector
|
||||||
|
wall repairs are safety geometry: retain every station/ray needed to close the existing gaps. Keep
|
||||||
|
the setting in the shared Core path so all recipe routes use the same declaration and evaluator.
|
||||||
- **Streaming** (`UpdateChunksAroundPosition`): rebuild/cull the desired set only when the player
|
- **Streaming** (`UpdateChunksAroundPosition`): rebuild/cull the desired set only when the player
|
||||||
crosses a level-0 chunk boundary, an anchor moves, or a rebuild is forced; use the stamped
|
crosses a level-0 chunk boundary, an anchor moves, or a rebuild is forced; use the stamped
|
||||||
desired map for the cull and idle via `bAllChunksLoaded`. On every rebuild, pending requests whose
|
desired map for the cull and idle via `bAllChunksLoaded`. On every rebuild, pending requests whose
|
||||||
@@ -1840,6 +1849,17 @@ fill and check page tiles against the evaluator they actually sample. This is th
|
|||||||
the Unreal adapter and its asynchronous MPSC/Epoch flow are unchanged. See
|
the Unreal adapter and its asynchronous MPSC/Epoch flow are unchanged. See
|
||||||
[`Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md).
|
[`Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md).
|
||||||
|
|
||||||
|
### 8.37 Screen-gated far display budget — World Assembler M3 L15
|
||||||
|
|
||||||
|
Evaluator version 7 adds the optional, serialized `far_display_mesh` recipe declaration for
|
||||||
|
display-only LOD3+ refinement and connector repair. A missing declaration selects the existing L14
|
||||||
|
path; exact queries, LOD0–2, collision, edits, and route binding stay on their existing shared Core
|
||||||
|
evaluators. The acceptance gate compares against today's same-LOD display mesh, reports geometry and
|
||||||
|
fixed-light/material shading deltas, checks every displayed tile, and measures each adjacent LOD
|
||||||
|
swap before and after. It rejects any new hole, crack, or floating piece, including lost connector
|
||||||
|
panels. No tested L15 candidate met the full budget, so no declaration is enabled by default. See
|
||||||
|
[`Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md).
|
||||||
|
|
||||||
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
||||||
|
|
||||||
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
||||||
|
|||||||
+11
-1
@@ -1132,11 +1132,21 @@ Implementation and evidence: [`Standalone/Research/WorldAssemblerM3/Linux9/REPOR
|
|||||||
|
|
||||||
| Symbol | Canonical file | Role |
|
| Symbol | Canonical file | Role |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `FStandaloneShapeWorld::SetSharedDensitySource`, `SharedDensitySource` | `Standalone/Core/src/ShapeWorldInternal.h`, bound before publication in `Standalone/Core/src/VoxelForgeCore.cpp` | Makes compiled analytic pages delegate field, lattice, detail, classification, and solid queries to the Core world's base shape; `WorldAssemblyEvaluatorVersion` is 6. |
|
| `FStandaloneShapeWorld::SetSharedDensitySource`, `SharedDensitySource` | `Standalone/Core/src/ShapeWorldInternal.h`, bound before publication in `Standalone/Core/src/VoxelForgeCore.cpp` | Makes compiled analytic pages delegate field, lattice, detail, classification, and solid queries to the Core world's base shape; `WorldAssemblyEvaluatorVersion` is 7, including optional far display-mesh recipe settings. |
|
||||||
|
|
||||||
The base manifest field is authoritative for tile generation and exact queries. See ARCHITECTURE
|
The base manifest field is authoritative for tile generation and exact queries. See ARCHITECTURE
|
||||||
§8.36 and [`Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md).
|
§8.36 and [`Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux13/REPORT.md).
|
||||||
|
|
||||||
|
### 3.23 Screen-gated far display mesh — World Assembler M3 L15
|
||||||
|
|
||||||
|
`ApplyFarDisplayMeshDeclaration` in `Standalone/Source/WorldAssemblyPrototype.h` parses the optional
|
||||||
|
`far_display_mesh` recipe block; `WorldAssemblyEvaluatorVersion` 7 serializes and hashes it.
|
||||||
|
`VoxelForgeCore::World::generateTile` consumes its display-only refinement and connector settings.
|
||||||
|
No declaration keeps the L14 output path. `Standalone/Source/LinuxScreenSpaceGate.h` compares every
|
||||||
|
desired LOD3–8 tile against today's same-LOD mesh and audits geometry, shading, swaps, and connector
|
||||||
|
coverage. The measured candidates and null result are in
|
||||||
|
[`Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux15/REPORT.md).
|
||||||
|
|
||||||
## 4. The density pipeline (most-edited hot path)
|
## 4. The density pipeline (most-edited hot path)
|
||||||
|
|
||||||
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
||||||
|
|||||||
@@ -184,6 +184,12 @@ namespace VoxelForgeCore
|
|||||||
std::uint64_t farConnectorColumnEvaluations = 0;
|
std::uint64_t farConnectorColumnEvaluations = 0;
|
||||||
double farStratumColumnComputeMilliseconds = 0.0;
|
double farStratumColumnComputeMilliseconds = 0.0;
|
||||||
double farConnectorColumnComputeMilliseconds = 0.0;
|
double farConnectorColumnComputeMilliseconds = 0.0;
|
||||||
|
// Opt-in quality audit at the L14 route-wall stations and four axial rays.
|
||||||
|
std::uint64_t farConnectorAuditRays = 0;
|
||||||
|
std::uint64_t farConnectorAuditHoles = 0;
|
||||||
|
std::uint64_t farConnectorAuditFloating = 0;
|
||||||
|
std::vector<std::uint64_t> farConnectorAuditHoleIds;
|
||||||
|
std::vector<std::uint64_t> farConnectorAuditFloatingIds;
|
||||||
std::uint64_t farShellTrianglesAdded = 0;
|
std::uint64_t farShellTrianglesAdded = 0;
|
||||||
std::uint64_t horizonGroundTriangles = 0;
|
std::uint64_t horizonGroundTriangles = 0;
|
||||||
std::uint64_t horizonGroundTrianglesRemoved = 0;
|
std::uint64_t horizonGroundTrianglesRemoved = 0;
|
||||||
@@ -250,7 +256,8 @@ namespace VoxelForgeCore
|
|||||||
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
||||||
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 1;
|
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 1;
|
||||||
// Version 6 binds every compiled tile page to the world's base density evaluator.
|
// Version 6 binds every compiled tile page to the world's base density evaluator.
|
||||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 6;
|
// Version 7 adds optional, display-only far-mesh quality declarations.
|
||||||
|
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 7;
|
||||||
|
|
||||||
enum class FieldChannel : std::uint32_t
|
enum class FieldChannel : std::uint32_t
|
||||||
{
|
{
|
||||||
@@ -312,6 +319,17 @@ namespace VoxelForgeCore
|
|||||||
std::int64_t biomeClimateReliefWavelengthSubvoxels = 256 * 3333;
|
std::int64_t biomeClimateReliefWavelengthSubvoxels = 256 * 3333;
|
||||||
std::int64_t biomeClimateMoistureWavelengthSubvoxels = 256 * 2500;
|
std::int64_t biomeClimateMoistureWavelengthSubvoxels = 256 * 2500;
|
||||||
std::int64_t biomeClimateReliefContrastQ16 = 2ll << 16;
|
std::int64_t biomeClimateReliefContrastQ16 = 2ll << 16;
|
||||||
|
// Optional display-only mesh budget. Zero leaves the L14 far-mesh path unchanged.
|
||||||
|
// Budget is in 1/1000 pixel at the closest display distance.
|
||||||
|
std::uint32_t farDisplayGeometryBudgetMillipixels = 0;
|
||||||
|
// Scale/mask change only display refinement density at the selected LODs.
|
||||||
|
// A zero scale or mask leaves the current refinement grids intact.
|
||||||
|
std::uint32_t farRefinementGridScale = 0;
|
||||||
|
std::uint32_t farRefinementLodMask = 0;
|
||||||
|
// Connector scale 0 and ray mask 0 preserve the current station/ray pattern.
|
||||||
|
std::uint32_t farConnectorStationScale = 0;
|
||||||
|
std::uint32_t farConnectorRayMask = 0;
|
||||||
|
bool farConnectorReuseStationRadii = false;
|
||||||
std::vector<WorldAssemblyBiomeProfileV1> biomes;
|
std::vector<WorldAssemblyBiomeProfileV1> biomes;
|
||||||
std::uint64_t contentHash = 0;
|
std::uint64_t contentHash = 0;
|
||||||
};
|
};
|
||||||
@@ -1099,6 +1117,16 @@ namespace VoxelForgeCore
|
|||||||
// Incremente quand le plafond lointain change. Les hashes LOD4-8 incluent cette version;
|
// Incremente quand le plafond lointain change. Les hashes LOD4-8 incluent cette version;
|
||||||
// densite et hashes LOD0-3 restent inchanges.
|
// densite et hashes LOD0-3 restent inchanges.
|
||||||
std::uint32_t farHorizonVersion = 3;
|
std::uint32_t farHorizonVersion = 3;
|
||||||
|
// Research-only override of the recipe declaration; used by same-LOD controls.
|
||||||
|
bool overrideFarDisplayMesh = false;
|
||||||
|
std::uint32_t farDisplayGeometryBudgetMillipixels = 0;
|
||||||
|
std::uint32_t farRefinementGridScale = 0;
|
||||||
|
std::uint32_t farRefinementLodMask = 0;
|
||||||
|
std::uint32_t farConnectorStationScale = 0;
|
||||||
|
std::uint32_t farConnectorRayMask = 0;
|
||||||
|
bool farConnectorReuseStationRadii = false;
|
||||||
|
bool diagnosticRemoveFarConnectorPanels = false;
|
||||||
|
bool diagnosticAuditFarConnectorCoverage = false;
|
||||||
// Linux research controls for reproducing L5 negative controls. Defaults preserve output.
|
// Linux research controls for reproducing L5 negative controls. Defaults preserve output.
|
||||||
std::uint32_t diagnosticGlobalOctaveDrop = 0;
|
std::uint32_t diagnosticGlobalOctaveDrop = 0;
|
||||||
std::uint32_t diagnosticCellStride = 1;
|
std::uint32_t diagnosticCellStride = 1;
|
||||||
|
|||||||
@@ -242,7 +242,8 @@ inline bool FindFarConnectorRadiusNearNominal(const FStandaloneShapeWorld& Shape
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,
|
inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,
|
||||||
int32 Step,int32 Cells,FVoxelMeshData& Mesh)
|
int32 Step,int32 Cells,FVoxelMeshData& Mesh,int32 StationScale=1,
|
||||||
|
uint32 RayMask=0x0fu,bool bReuseStationRadii=false)
|
||||||
{
|
{
|
||||||
if(Key.Level<6||Step<=0)return;
|
if(Key.Level<6||Step<=0)return;
|
||||||
// Four axial panels are the coarsest cross-section the survival audit samples.
|
// Four axial panels are the coarsest cross-section the survival audit samples.
|
||||||
@@ -313,7 +314,8 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
|
|||||||
if(!bIntersects||TMax-TMin<1.0e-6)continue;
|
if(!bIntersects||TMax-TMin<1.0e-6)continue;
|
||||||
// LOD7 and LOD8 share a 256-voxel connector station grid; near LOD keeps 128-voxel checks.
|
// LOD7 and LOD8 share a 256-voxel connector station grid; near LOD keeps 128-voxel checks.
|
||||||
// LOD7 et LOD8 partagent des stations aux 256 voxels; pres du joueur, garder les pas de 128.
|
// LOD7 et LOD8 partagent des stations aux 256 voxels; pres du joueur, garder les pas de 128.
|
||||||
const double ConnectorStationStep=Key.Level>=7?std::max(256.0,static_cast<double>(Step)):128.0;
|
const double ConnectorStationStep=(Key.Level>=7?std::max(256.0,static_cast<double>(Step)):128.0)
|
||||||
|
*std::max(1,StationScale);
|
||||||
const int32 AxialSampleCount=std::max(1,static_cast<int32>(std::ceil(Length/ConnectorStationStep)));
|
const int32 AxialSampleCount=std::max(1,static_cast<int32>(std::ceil(Length/ConnectorStationStep)));
|
||||||
std::vector<uint8> MissingMasks(static_cast<size_t>(AxialSampleCount),0);
|
std::vector<uint8> MissingMasks(static_cast<size_t>(AxialSampleCount),0);
|
||||||
std::vector<std::array<double,4>> SourceRadii(static_cast<size_t>(AxialSampleCount));
|
std::vector<std::array<double,4>> SourceRadii(static_cast<size_t>(AxialSampleCount));
|
||||||
@@ -332,6 +334,7 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
|
|||||||
const double DetectionTolerance=MatchTolerance*0.98;
|
const double DetectionTolerance=MatchTolerance*0.98;
|
||||||
for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex)
|
for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex)
|
||||||
{
|
{
|
||||||
|
if((RayMask&(1u<<DirectionIndex))==0)continue;
|
||||||
const FVector& Direction=RadialDirections[DirectionIndex];
|
const FVector& Direction=RadialDirections[DirectionIndex];
|
||||||
if(!ContainsFarTilePoint(Key,Center+Direction*RayLength))continue;
|
if(!ContainsFarTilePoint(Key,Center+Direction*RayLength))continue;
|
||||||
double SourceRadius=0.0,SourceUncertainty=0.0;
|
double SourceRadius=0.0,SourceUncertainty=0.0;
|
||||||
@@ -388,7 +391,13 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
|
|||||||
{Ring.Radii[DirectionIndex]=Connector.Radius;continue;}
|
{Ring.Radii[DirectionIndex]=Connector.Radius;continue;}
|
||||||
double Radius=Connector.Radius;
|
double Radius=Connector.Radius;
|
||||||
double RadiusUncertainty=0.0;
|
double RadiusUncertainty=0.0;
|
||||||
if(!FindFarConnectorRadiusNearNominal(Shape,Center,
|
const int32 NearestStation=std::clamp(static_cast<int32>(std::lround(
|
||||||
|
T*AxialSampleCount-0.5)),0,AxialSampleCount-1);
|
||||||
|
const double ReusedRadius=bReuseStationRadii
|
||||||
|
?SourceRadii[static_cast<size_t>(NearestStation)][DirectionIndex]:0.0;
|
||||||
|
if(ReusedRadius>0.0)
|
||||||
|
Radius=ReusedRadius;
|
||||||
|
else if(!FindFarConnectorRadiusNearNominal(Shape,Center,
|
||||||
RadialDirections[DirectionIndex],Connector.Radius,Radius,RadiusUncertainty)
|
RadialDirections[DirectionIndex],Connector.Radius,Radius,RadiusUncertainty)
|
||||||
&&!FindFarConnectorSurfaceRadius(Shape,Center,RadialDirections[DirectionIndex],
|
&&!FindFarConnectorSurfaceRadius(Shape,Center,RadialDirections[DirectionIndex],
|
||||||
Connector.Radius,Radius))
|
Connector.Radius,Radius))
|
||||||
@@ -432,7 +441,8 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::vector<FRing> Rings;
|
std::vector<FRing> Rings;
|
||||||
const double MaxRingStep=Key.Level>=7?std::max(256.0,static_cast<double>(Step)):128.0;
|
const double MaxRingStep=(Key.Level>=7?std::max(256.0,static_cast<double>(Step)):128.0)
|
||||||
|
*std::max(1,StationScale);
|
||||||
double CurrentT=RunMin;
|
double CurrentT=RunMin;
|
||||||
Rings.push_back(MakeRing(CurrentT,RunStart,RunEnd,RunRadiusMask));
|
Rings.push_back(MakeRing(CurrentT,RunStart,RunEnd,RunRadiusMask));
|
||||||
while(CurrentT<RunMax-1.0e-7)
|
while(CurrentT<RunMax-1.0e-7)
|
||||||
@@ -814,6 +824,112 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
|
|||||||
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
|
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FFarConnectorCoverageAudit
|
||||||
|
{
|
||||||
|
std::uint64_t Rays=0;
|
||||||
|
std::uint64_t Holes=0;
|
||||||
|
std::uint64_t Floating=0;
|
||||||
|
std::vector<std::uint64_t> HoleIds;
|
||||||
|
std::vector<std::uint64_t> FloatingIds;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::uint64_t FarConnectorAuditRayId(const FVoxelTileKey& Key,uint32 Connector,
|
||||||
|
int32 Station,int32 Direction)
|
||||||
|
{
|
||||||
|
std::uint64_t Hash=14695981039346656037ull;
|
||||||
|
const auto Add=[&](std::uint64_t Value,int Bytes)
|
||||||
|
{
|
||||||
|
for(int Byte=0;Byte<Bytes;++Byte)
|
||||||
|
{Hash^=static_cast<uint8>(Value>>(Byte*8));Hash*=1099511628211ull;}
|
||||||
|
};
|
||||||
|
Add(static_cast<uint32>(Key.Coord.X),4);Add(static_cast<uint32>(Key.Coord.Y),4);
|
||||||
|
Add(static_cast<uint32>(Key.Coord.Z),4);Add(static_cast<uint32>(Key.Level),4);
|
||||||
|
Add(Connector,4);Add(static_cast<uint32>(Station),4);Add(static_cast<uint32>(Direction),4);
|
||||||
|
return Hash?Hash:1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline FFarConnectorCoverageAudit AuditFarConnectorCoverage(const FStandaloneShapeWorld& Shape,
|
||||||
|
const FVoxelTileKey& Key,int32 Step,int32 Cells,const FVoxelMeshData& Mesh)
|
||||||
|
{
|
||||||
|
FFarConnectorCoverageAudit Audit;
|
||||||
|
if(Key.Level<6||Step<=0||Mesh.Triangles.Num()<3)return Audit;
|
||||||
|
const FIntVector TileOrigin=Key.OriginVoxels();
|
||||||
|
const int32 TileExtent=Key.ExtentVoxels();
|
||||||
|
FFarMeshSegmentIndex MeshIndex(Mesh,TileOrigin,TileExtent,Cells,Step);
|
||||||
|
const auto& Connectors=Shape.GetFarConnectorSegments();
|
||||||
|
std::vector<uint32> CandidateIndices;
|
||||||
|
Shape.FindFarConnectorCandidates(
|
||||||
|
FIntVector(TileOrigin.X-64,TileOrigin.Y-64,TileOrigin.Z-64),
|
||||||
|
FIntVector(TileOrigin.X+TileExtent+64,TileOrigin.Y+TileExtent+64,TileOrigin.Z+TileExtent+64),
|
||||||
|
CandidateIndices);
|
||||||
|
for(const uint32 ConnectorIndex:CandidateIndices)
|
||||||
|
{
|
||||||
|
if(ConnectorIndex>=Connectors.size())continue;
|
||||||
|
const auto& Connector=Connectors[ConnectorIndex];
|
||||||
|
const FVector Delta=Connector.End-Connector.Start;
|
||||||
|
const double Length=std::sqrt(Delta.X*Delta.X+Delta.Y*Delta.Y+Delta.Z*Delta.Z);
|
||||||
|
if(Length<=1.0e-6)continue;
|
||||||
|
const FVector Axis=Delta/Length;
|
||||||
|
FVector U=FVector::CrossProduct(Axis,FVector::UpVector);
|
||||||
|
if(U.SizeSquared()<0.01)U=FVector::CrossProduct(Axis,FVector::RightVector);
|
||||||
|
U.Normalize();
|
||||||
|
FVector V=FVector::CrossProduct(Axis,U);V.Normalize();
|
||||||
|
const FVector Directions[4]={U,U*-1.0,V,V*-1.0};
|
||||||
|
const double Deltas[3]={Delta.X,Delta.Y,Delta.Z};
|
||||||
|
const double Starts[3]={Connector.Start.X,Connector.Start.Y,Connector.Start.Z};
|
||||||
|
const double Low[3]={TileOrigin.X-Connector.Radius,TileOrigin.Y-Connector.Radius,
|
||||||
|
TileOrigin.Z-Connector.Radius};
|
||||||
|
const double High[3]={TileOrigin.X+TileExtent+Connector.Radius,
|
||||||
|
TileOrigin.Y+TileExtent+Connector.Radius,TileOrigin.Z+TileExtent+Connector.Radius};
|
||||||
|
double TMin=0.0,TMax=1.0;
|
||||||
|
bool bIntersects=true;
|
||||||
|
for(int32 AxisIndex=0;AxisIndex<3;++AxisIndex)
|
||||||
|
{
|
||||||
|
if(std::abs(Deltas[AxisIndex])<1.0e-9)
|
||||||
|
{if(Starts[AxisIndex]<Low[AxisIndex]||Starts[AxisIndex]>High[AxisIndex])bIntersects=false;continue;}
|
||||||
|
double A=(Low[AxisIndex]-Starts[AxisIndex])/Deltas[AxisIndex];
|
||||||
|
double B=(High[AxisIndex]-Starts[AxisIndex])/Deltas[AxisIndex];
|
||||||
|
if(A>B)std::swap(A,B);
|
||||||
|
TMin=std::max(TMin,A);TMax=std::min(TMax,B);
|
||||||
|
if(TMin>TMax){bIntersects=false;break;}
|
||||||
|
}
|
||||||
|
if(!bIntersects||TMax-TMin<1.0e-6)continue;
|
||||||
|
const double StationStep=Key.Level>=7?std::max(256.0,static_cast<double>(Step)):128.0;
|
||||||
|
const int32 StationCount=std::max(1,static_cast<int32>(std::ceil(Length/StationStep)));
|
||||||
|
const double RayLength=Connector.Radius*2.0+32.0;
|
||||||
|
const double MatchTolerance=std::max(8.0,static_cast<double>(Connector.Radius)*0.35);
|
||||||
|
const double DetectionTolerance=MatchTolerance*0.98;
|
||||||
|
const double SourceRadiusBand=std::max(16.0,static_cast<double>(Connector.Radius)*0.75);
|
||||||
|
for(int32 Station=0;Station<StationCount;++Station)
|
||||||
|
{
|
||||||
|
const FVector Center=Connector.Start+Delta*((static_cast<double>(Station)+0.5)/StationCount);
|
||||||
|
if(!ContainsFarTilePoint(Key,Center))continue;
|
||||||
|
for(int32 DirectionIndex=0;DirectionIndex<4;++DirectionIndex)
|
||||||
|
{
|
||||||
|
if(!ContainsFarTilePoint(Key,Center+Directions[DirectionIndex]*RayLength))continue;
|
||||||
|
double SourceRadius=0.0,Uncertainty=0.0;
|
||||||
|
const bool bSource=FindFarConnectorRadiusNearNominal(Shape,Center,
|
||||||
|
Directions[DirectionIndex],Connector.Radius,SourceRadius,Uncertainty)
|
||||||
|
||FindFarConnectorSurfaceRadius(Shape,Center,Directions[DirectionIndex],
|
||||||
|
Connector.Radius,SourceRadius);
|
||||||
|
const double MeshDifference=MeshIndex.NearestHitDifference(Center,
|
||||||
|
Directions[DirectionIndex],RayLength,bSource?SourceRadius:Connector.Radius);
|
||||||
|
if(bSource&&std::abs(SourceRadius-Connector.Radius)<=SourceRadiusBand)
|
||||||
|
{
|
||||||
|
++Audit.Rays;
|
||||||
|
if(!std::isfinite(MeshDifference)||MeshDifference>DetectionTolerance)
|
||||||
|
Audit.HoleIds.push_back(FarConnectorAuditRayId(Key,ConnectorIndex,Station,DirectionIndex));
|
||||||
|
}
|
||||||
|
else if(std::isfinite(MeshDifference)&&MeshDifference<=DetectionTolerance)
|
||||||
|
Audit.FloatingIds.push_back(FarConnectorAuditRayId(Key,ConnectorIndex,Station,DirectionIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Audit.Holes=Audit.HoleIds.size();
|
||||||
|
Audit.Floating=Audit.FloatingIds.size();
|
||||||
|
return Audit;
|
||||||
|
}
|
||||||
|
|
||||||
struct FFarMeshColumnIndex
|
struct FFarMeshColumnIndex
|
||||||
{
|
{
|
||||||
FFarMeshColumnIndex(const FVoxelMeshData& InMesh,const FIntVector& InOrigin,int32 InExtent,
|
FFarMeshColumnIndex(const FVoxelMeshData& InMesh,const FIntVector& InOrigin,int32 InExtent,
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ namespace VoxelForgeCore
|
|||||||
|
|
||||||
FNativeCapRefinementStats RefineFarNativeGround(const FStandaloneOwnerWorld& Owner,
|
FNativeCapRefinementStats RefineFarNativeGround(const FStandaloneOwnerWorld& Owner,
|
||||||
const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,int32 MeshStepVoxels,
|
const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,int32 MeshStepVoxels,
|
||||||
int32 InteriorStepOverride,FVoxelMeshData& Mesh)
|
int32 InteriorStepOverride,int32 RefinementGridScale,FVoxelMeshData& Mesh)
|
||||||
{
|
{
|
||||||
FNativeCapRefinementStats Stats;
|
FNativeCapRefinementStats Stats;
|
||||||
if(Key.Level<5||!Owner.Generator)return Stats;
|
if(Key.Level<5||!Owner.Generator)return Stats;
|
||||||
@@ -354,11 +354,12 @@ namespace VoxelForgeCore
|
|||||||
const int32 RequestedStep=Key.Level==8
|
const int32 RequestedStep=Key.Level==8
|
||||||
?(InteriorStepOverride>0?InteriorStepOverride:MeshStepVoxels)
|
?(InteriorStepOverride>0?InteriorStepOverride:MeshStepVoxels)
|
||||||
:std::min(BoundaryStepVoxels,1<<Key.Level);
|
:std::min(BoundaryStepVoxels,1<<Key.Level);
|
||||||
|
const int32 ScaledRequestedStep=RequestedStep*std::max(1,RefinementGridScale);
|
||||||
const int32 GridStepVoxels=Key.Level==8
|
const int32 GridStepVoxels=Key.Level==8
|
||||||
?((RequestedStep<BoundaryStepVoxels||RequestedStep%BoundaryStepVoxels!=0
|
?((ScaledRequestedStep<BoundaryStepVoxels||ScaledRequestedStep%BoundaryStepVoxels!=0
|
||||||
||TileExtent<=0||TileExtent%RequestedStep!=0)
|
||TileExtent<=0||TileExtent%ScaledRequestedStep!=0)
|
||||||
?BoundaryStepVoxels:std::max(BoundaryStepVoxels,RequestedStep))
|
?BoundaryStepVoxels:std::max(BoundaryStepVoxels,ScaledRequestedStep))
|
||||||
:RequestedStep;
|
:ScaledRequestedStep;
|
||||||
if(TileExtent<=0||TileExtent%GridStepVoxels!=0)return Stats;
|
if(TileExtent<=0||TileExtent%GridStepVoxels!=0)return Stats;
|
||||||
const std::vector<int32> GridCoordinates=
|
const std::vector<int32> GridCoordinates=
|
||||||
BuildNativeHorizonGridCoordinates(TileExtent,GridStepVoxels);
|
BuildNativeHorizonGridCoordinates(TileExtent,GridStepVoxels);
|
||||||
@@ -644,7 +645,7 @@ namespace VoxelForgeCore
|
|||||||
|
|
||||||
FNativeCapRefinementStats RefineFarNativeCap(const FStandaloneOwnerWorld& Owner,
|
FNativeCapRefinementStats RefineFarNativeCap(const FStandaloneOwnerWorld& Owner,
|
||||||
const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,int32 MeshStepVoxels,
|
const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,int32 MeshStepVoxels,
|
||||||
int32 InteriorStepOverride,FVoxelMeshData& Mesh)
|
int32 InteriorStepOverride,int32 RefinementGridScale,FVoxelMeshData& Mesh)
|
||||||
{
|
{
|
||||||
FNativeCapRefinementStats Stats;
|
FNativeCapRefinementStats Stats;
|
||||||
if(Key.Level<4||!Owner.Generator)return Stats;
|
if(Key.Level<4||!Owner.Generator)return Stats;
|
||||||
@@ -654,11 +655,12 @@ namespace VoxelForgeCore
|
|||||||
:(Key.Level<5?std::min(BoundaryStepVoxels,1<<Key.Level):BoundaryStepVoxels);
|
:(Key.Level<5?std::min(BoundaryStepVoxels,1<<Key.Level):BoundaryStepVoxels);
|
||||||
const FIntVector TileOrigin=Key.OriginVoxels();
|
const FIntVector TileOrigin=Key.OriginVoxels();
|
||||||
const int32 TileExtent=Key.ExtentVoxels();
|
const int32 TileExtent=Key.ExtentVoxels();
|
||||||
|
const int32 ScaledRequestedStep=RequestedStep*std::max(1,RefinementGridScale);
|
||||||
const int32 EffectiveGridStep=Key.Level==8
|
const int32 EffectiveGridStep=Key.Level==8
|
||||||
?((RequestedStep<BoundaryStepVoxels||RequestedStep%BoundaryStepVoxels!=0
|
?((ScaledRequestedStep<BoundaryStepVoxels||ScaledRequestedStep%BoundaryStepVoxels!=0
|
||||||
||TileExtent<=0||TileExtent%RequestedStep!=0)
|
||TileExtent<=0||TileExtent%ScaledRequestedStep!=0)
|
||||||
?BoundaryStepVoxels:std::max(BoundaryStepVoxels,RequestedStep))
|
?BoundaryStepVoxels:std::max(BoundaryStepVoxels,ScaledRequestedStep))
|
||||||
:RequestedStep;
|
:ScaledRequestedStep;
|
||||||
constexpr int64 Scale=WorldAssemblySubvoxelsPerVoxel;
|
constexpr int64 Scale=WorldAssemblySubvoxelsPerVoxel;
|
||||||
if(TileExtent<=0||TileExtent%EffectiveGridStep!=0)return Stats;
|
if(TileExtent<=0||TileExtent%EffectiveGridStep!=0)return Stats;
|
||||||
const std::vector<int32> GridCoordinates=
|
const std::vector<int32> GridCoordinates=
|
||||||
@@ -1606,6 +1608,37 @@ namespace VoxelForgeCore
|
|||||||
options.legacyDirectMappedColumnCache||key.lod<=2);
|
options.legacyDirectMappedColumnCache||key.lod<=2);
|
||||||
if(!impl_||!impl_->owner)
|
if(!impl_||!impl_->owner)
|
||||||
{ error="resolved world snapshot is empty";return std::nullopt; }
|
{ error="resolved world snapshot is empty";return std::nullopt; }
|
||||||
|
const WorldAssemblyRecipeV1* FarRecipe=impl_->worldAssembly?&impl_->worldAssembly->recipe:nullptr;
|
||||||
|
std::uint32_t FarDisplayBudget=FarRecipe?FarRecipe->farDisplayGeometryBudgetMillipixels:0;
|
||||||
|
std::uint32_t FarRefinementScale=FarRecipe?FarRecipe->farRefinementGridScale:0;
|
||||||
|
std::uint32_t FarRefinementLodMask=FarRecipe?FarRecipe->farRefinementLodMask:0;
|
||||||
|
std::uint32_t FarConnectorStationScale=FarRecipe?FarRecipe->farConnectorStationScale:0;
|
||||||
|
std::uint32_t FarConnectorRayMask=FarRecipe?FarRecipe->farConnectorRayMask:0;
|
||||||
|
bool bFarConnectorReuseStationRadii=FarRecipe&&FarRecipe->farConnectorReuseStationRadii;
|
||||||
|
if(options.overrideFarDisplayMesh)
|
||||||
|
{
|
||||||
|
FarDisplayBudget=options.farDisplayGeometryBudgetMillipixels;
|
||||||
|
FarRefinementScale=options.farRefinementGridScale;
|
||||||
|
FarRefinementLodMask=options.farRefinementLodMask;
|
||||||
|
FarConnectorStationScale=options.farConnectorStationScale;
|
||||||
|
FarConnectorRayMask=options.farConnectorRayMask;
|
||||||
|
bFarConnectorReuseStationRadii=options.farConnectorReuseStationRadii;
|
||||||
|
}
|
||||||
|
const auto ValidFarScale=[](std::uint32_t Value)
|
||||||
|
{return Value==0||Value==2||Value==4||Value==8;};
|
||||||
|
if((FarDisplayBudget!=0&&FarDisplayBudget!=1000)
|
||||||
|
||!ValidFarScale(FarRefinementScale)
|
||||||
|
||!ValidFarScale(FarConnectorStationScale)
|
||||||
|
||(FarRefinementLodMask&~0x1f0u)!=0||FarConnectorRayMask>0x0fu
|
||||||
|
||(FarRefinementScale!=0&&FarRefinementLodMask==0))
|
||||||
|
{error="far display mesh override is outside the recipe's supported 1 px settings";return std::nullopt;}
|
||||||
|
const std::uint32_t ActiveFarRefinementScale=FarDisplayBudget==1000
|
||||||
|
&&(FarRefinementLodMask&(1u<<std::clamp(key.lod,0,31)))!=0
|
||||||
|
?FarRefinementScale:0;
|
||||||
|
const int32 ActiveConnectorStationScale=FarDisplayBudget==1000
|
||||||
|
&&FarConnectorStationScale!=0?static_cast<int32>(FarConnectorStationScale):1;
|
||||||
|
const uint32 ActiveConnectorRayMask=FarDisplayBudget==1000
|
||||||
|
&&FarConnectorRayMask!=0?FarConnectorRayMask:0x0fu;
|
||||||
const bool bShapeReady=impl_->shapeReady.load(std::memory_order_acquire);
|
const bool bShapeReady=impl_->shapeReady.load(std::memory_order_acquire);
|
||||||
const bool bNativeOnly=!bShapeReady||options.diagnosticForceNativeOnly;
|
const bool bNativeOnly=!bShapeReady||options.diagnosticForceNativeOnly;
|
||||||
if(!bShapeReady&&!options.diagnosticForceNativeOnly&&!isTileShapeFree(key))
|
if(!bShapeReady&&!options.diagnosticForceNativeOnly&&!isTileShapeFree(key))
|
||||||
@@ -2135,7 +2168,10 @@ namespace VoxelForgeCore
|
|||||||
BeforeConnectorColumnComputeMilliseconds=Columns.columnComputeMilliseconds;
|
BeforeConnectorColumnComputeMilliseconds=Columns.columnComputeMilliseconds;
|
||||||
}
|
}
|
||||||
const auto ConnectorStart=std::chrono::steady_clock::now();
|
const auto ConnectorStart=std::chrono::steady_clock::now();
|
||||||
AppendFarConnectorShells(farShape,nativeKey,step,world.CoarseTileCells,mesh);
|
if(!options.diagnosticRemoveFarConnectorPanels)
|
||||||
|
AppendFarConnectorShells(farShape,nativeKey,step,world.CoarseTileCells,mesh,
|
||||||
|
ActiveConnectorStationScale,ActiveConnectorRayMask,
|
||||||
|
FarDisplayBudget==1000&&bFarConnectorReuseStationRadii);
|
||||||
const auto AfterConnector=FStandaloneShapeWorld::GetThreadClassificationStats();
|
const auto AfterConnector=FStandaloneShapeWorld::GetThreadClassificationStats();
|
||||||
out.diagnostics.farConnectorShellMilliseconds=std::chrono::duration<double,std::milli>(
|
out.diagnostics.farConnectorShellMilliseconds=std::chrono::duration<double,std::milli>(
|
||||||
std::chrono::steady_clock::now()-ConnectorStart).count();
|
std::chrono::steady_clock::now()-ConnectorStart).count();
|
||||||
@@ -2161,6 +2197,16 @@ namespace VoxelForgeCore
|
|||||||
}
|
}
|
||||||
out.diagnostics.farShellTrianglesAdded+=static_cast<std::uint64_t>(
|
out.diagnostics.farShellTrianglesAdded+=static_cast<std::uint64_t>(
|
||||||
std::max(0,mesh.Triangles.Num()/3-BeforeConnectorTriangles));
|
std::max(0,mesh.Triangles.Num()/3-BeforeConnectorTriangles));
|
||||||
|
if(options.diagnosticAuditFarConnectorCoverage)
|
||||||
|
{
|
||||||
|
const auto Audit=AuditFarConnectorCoverage(farShape,nativeKey,step,
|
||||||
|
world.CoarseTileCells,mesh);
|
||||||
|
out.diagnostics.farConnectorAuditRays=Audit.Rays;
|
||||||
|
out.diagnostics.farConnectorAuditHoles=Audit.Holes;
|
||||||
|
out.diagnostics.farConnectorAuditFloating=Audit.Floating;
|
||||||
|
out.diagnostics.farConnectorAuditHoleIds=Audit.HoleIds;
|
||||||
|
out.diagnostics.farConnectorAuditFloatingIds=Audit.FloatingIds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
out.diagnostics.farShellMilliseconds=std::chrono::duration<double,std::milli>(
|
out.diagnostics.farShellMilliseconds=std::chrono::duration<double,std::milli>(
|
||||||
std::chrono::steady_clock::now()-FarShellStart).count();
|
std::chrono::steady_clock::now()-FarShellStart).count();
|
||||||
@@ -2168,12 +2214,14 @@ namespace VoxelForgeCore
|
|||||||
{
|
{
|
||||||
const auto HorizonStart=std::chrono::steady_clock::now();
|
const auto HorizonStart=std::chrono::steady_clock::now();
|
||||||
FNativeCapRefinementStats GroundStats;
|
FNativeCapRefinementStats GroundStats;
|
||||||
|
const int32 ActiveRefinementScale=ActiveFarRefinementScale>1
|
||||||
|
?static_cast<int32>(ActiveFarRefinementScale):1;
|
||||||
const double ColumnMillisecondsBeforeGround=
|
const double ColumnMillisecondsBeforeGround=
|
||||||
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
||||||
const auto GroundStart=std::chrono::steady_clock::now();
|
const auto GroundStart=std::chrono::steady_clock::now();
|
||||||
if(key.lod>=4)
|
if(key.lod>=4)
|
||||||
GroundStats=RefineFarNativeGround(world,*impl_->worldAssembly,nativeKey,step,
|
GroundStats=RefineFarNativeGround(world,*impl_->worldAssembly,nativeKey,step,
|
||||||
options.lod8HorizonInteriorStepVoxels,mesh);
|
options.lod8HorizonInteriorStepVoxels,ActiveRefinementScale,mesh);
|
||||||
const double GroundWallMilliseconds=std::chrono::duration<double,std::milli>(
|
const double GroundWallMilliseconds=std::chrono::duration<double,std::milli>(
|
||||||
std::chrono::steady_clock::now()-GroundStart).count();
|
std::chrono::steady_clock::now()-GroundStart).count();
|
||||||
const double ColumnMillisecondsAfterGround=
|
const double ColumnMillisecondsAfterGround=
|
||||||
@@ -2186,7 +2234,7 @@ namespace VoxelForgeCore
|
|||||||
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
||||||
const auto CapStart=std::chrono::steady_clock::now();
|
const auto CapStart=std::chrono::steady_clock::now();
|
||||||
const auto CapStats=RefineFarNativeCap(world,*impl_->worldAssembly,nativeKey,step,
|
const auto CapStats=RefineFarNativeCap(world,*impl_->worldAssembly,nativeKey,step,
|
||||||
options.lod8HorizonInteriorStepVoxels,mesh);
|
options.lod8HorizonInteriorStepVoxels,ActiveRefinementScale,mesh);
|
||||||
const double CapWallMilliseconds=std::chrono::duration<double,std::milli>(
|
const double CapWallMilliseconds=std::chrono::duration<double,std::milli>(
|
||||||
std::chrono::steady_clock::now()-CapStart).count();
|
std::chrono::steady_clock::now()-CapStart).count();
|
||||||
const double ColumnMillisecondsAfterCap=
|
const double ColumnMillisecondsAfterCap=
|
||||||
|
|||||||
@@ -244,6 +244,24 @@ namespace world_assembly_detail
|
|||||||
|| recipe.biomeBorderBlendSubvoxels < 0
|
|| recipe.biomeBorderBlendSubvoxels < 0
|
||||||
|| recipe.biomeBorderBlendSubvoxels > recipe.biomeCellSizeSubvoxels)
|
|| recipe.biomeBorderBlendSubvoxels > recipe.biomeCellSizeSubvoxels)
|
||||||
{ error = "world assembly biome-map parameters are outside the supported fixed-scale range"; return false; }
|
{ error = "world assembly biome-map parameters are outside the supported fixed-scale range"; return false; }
|
||||||
|
if (recipe.evaluatorVersion >= 7)
|
||||||
|
{
|
||||||
|
constexpr std::uint32_t FarLodMask = 0x1f0u; // LOD4..8.
|
||||||
|
const bool anyFarSetting = recipe.farDisplayGeometryBudgetMillipixels != 0
|
||||||
|
|| recipe.farRefinementGridScale != 0 || recipe.farRefinementLodMask != 0
|
||||||
|
|| recipe.farConnectorStationScale != 0 || recipe.farConnectorRayMask != 0
|
||||||
|
|| recipe.farConnectorReuseStationRadii;
|
||||||
|
const auto validScale = [](std::uint32_t value)
|
||||||
|
{ return value == 0 || value == 2 || value == 4 || value == 8; };
|
||||||
|
if ((anyFarSetting && recipe.farDisplayGeometryBudgetMillipixels != 1000)
|
||||||
|
|| recipe.farDisplayGeometryBudgetMillipixels > 1000
|
||||||
|
|| !validScale(recipe.farRefinementGridScale)
|
||||||
|
|| (recipe.farRefinementLodMask & ~FarLodMask) != 0
|
||||||
|
|| !validScale(recipe.farConnectorStationScale)
|
||||||
|
|| recipe.farConnectorRayMask > 0x0fu
|
||||||
|
|| (recipe.farRefinementGridScale != 0 && recipe.farRefinementLodMask == 0))
|
||||||
|
{ error = "far display mesh declaration must use the 1 px budget and supported LOD, scale, and ray values"; return false; }
|
||||||
|
}
|
||||||
std::vector<std::uint32_t> ids;
|
std::vector<std::uint32_t> ids;
|
||||||
ids.reserve(recipe.biomes.size());
|
ids.reserve(recipe.biomes.size());
|
||||||
for (const auto& biome : recipe.biomes)
|
for (const auto& biome : recipe.biomes)
|
||||||
@@ -431,6 +449,16 @@ namespace world_assembly_detail
|
|||||||
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeClimateMoistureWavelengthSubvoxels), 8);
|
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeClimateMoistureWavelengthSubvoxels), 8);
|
||||||
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeClimateReliefContrastQ16), 8);
|
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeClimateReliefContrastQ16), 8);
|
||||||
}
|
}
|
||||||
|
if (recipe.evaluatorVersion >= 7)
|
||||||
|
{
|
||||||
|
HashWord(hash, 0x4641524449535037ull, 8);
|
||||||
|
HashWord(hash, recipe.farDisplayGeometryBudgetMillipixels, 4);
|
||||||
|
HashWord(hash, recipe.farRefinementGridScale, 4);
|
||||||
|
HashWord(hash, recipe.farRefinementLodMask, 4);
|
||||||
|
HashWord(hash, recipe.farConnectorStationScale, 4);
|
||||||
|
HashWord(hash, recipe.farConnectorRayMask, 4);
|
||||||
|
HashWord(hash, recipe.farConnectorReuseStationRadii ? 1u : 0u, 1);
|
||||||
|
}
|
||||||
HashWord(hash, recipe.biomes.size(), 4);
|
HashWord(hash, recipe.biomes.size(), 4);
|
||||||
for (const auto& biome : recipe.biomes)
|
for (const auto& biome : recipe.biomes)
|
||||||
{
|
{
|
||||||
@@ -641,6 +669,16 @@ namespace world_assembly_detail
|
|||||||
|| !ReadInteger(value.Get("climate_moisture_wavelength_q"), recipe.biomeClimateMoistureWavelengthSubvoxels)
|
|| !ReadInteger(value.Get("climate_moisture_wavelength_q"), recipe.biomeClimateMoistureWavelengthSubvoxels)
|
||||||
|| !ReadInteger(value.Get("climate_relief_contrast_q16"), recipe.biomeClimateReliefContrastQ16)))
|
|| !ReadInteger(value.Get("climate_relief_contrast_q16"), recipe.biomeClimateReliefContrastQ16)))
|
||||||
{ error = "world assembly recipe is missing evaluator-2 climate parameters"; return false; }
|
{ error = "world assembly recipe is missing evaluator-2 climate parameters"; return false; }
|
||||||
|
if (recipe.evaluatorVersion >= 7
|
||||||
|
&& (!ReadU32(value.Get("far_display_budget_millipixels"), recipe.farDisplayGeometryBudgetMillipixels)
|
||||||
|
|| !ReadU32(value.Get("far_refinement_grid_scale"), recipe.farRefinementGridScale)
|
||||||
|
|| !ReadU32(value.Get("far_refinement_lod_mask"), recipe.farRefinementLodMask)
|
||||||
|
|| !ReadU32(value.Get("far_connector_station_scale"), recipe.farConnectorStationScale)
|
||||||
|
|| !ReadU32(value.Get("far_connector_ray_mask"), recipe.farConnectorRayMask)
|
||||||
|
|| !std::holds_alternative<bool>(value.Get("far_connector_reuse_station_radii").Data)))
|
||||||
|
{ error = "world assembly recipe is missing evaluator-7 far display mesh parameters"; return false; }
|
||||||
|
if (recipe.evaluatorVersion >= 7)
|
||||||
|
recipe.farConnectorReuseStationRadii = std::get<bool>(value.Get("far_connector_reuse_station_radii").Data);
|
||||||
for (const auto& item : value.Get("biomes").AsArray())
|
for (const auto& item : value.Get("biomes").AsArray())
|
||||||
{
|
{
|
||||||
WorldAssemblyBiomeProfileV1 biome;
|
WorldAssemblyBiomeProfileV1 biome;
|
||||||
@@ -702,6 +740,14 @@ namespace world_assembly_detail
|
|||||||
out << ",\"climate_relief_wavelength_q\":" << recipe.biomeClimateReliefWavelengthSubvoxels
|
out << ",\"climate_relief_wavelength_q\":" << recipe.biomeClimateReliefWavelengthSubvoxels
|
||||||
<< ",\"climate_moisture_wavelength_q\":" << recipe.biomeClimateMoistureWavelengthSubvoxels
|
<< ",\"climate_moisture_wavelength_q\":" << recipe.biomeClimateMoistureWavelengthSubvoxels
|
||||||
<< ",\"climate_relief_contrast_q16\":" << recipe.biomeClimateReliefContrastQ16;
|
<< ",\"climate_relief_contrast_q16\":" << recipe.biomeClimateReliefContrastQ16;
|
||||||
|
if (recipe.evaluatorVersion >= 7)
|
||||||
|
out << ",\"far_display_budget_millipixels\":" << recipe.farDisplayGeometryBudgetMillipixels
|
||||||
|
<< ",\"far_refinement_grid_scale\":" << recipe.farRefinementGridScale
|
||||||
|
<< ",\"far_refinement_lod_mask\":" << recipe.farRefinementLodMask
|
||||||
|
<< ",\"far_connector_station_scale\":" << recipe.farConnectorStationScale
|
||||||
|
<< ",\"far_connector_ray_mask\":" << recipe.farConnectorRayMask
|
||||||
|
<< ",\"far_connector_reuse_station_radii\":"
|
||||||
|
<< (recipe.farConnectorReuseStationRadii ? "true" : "false");
|
||||||
out << ",\"content_hash\":\"" << HashText(recipe.contentHash) << "\",\"biomes\":[";
|
out << ",\"content_hash\":\"" << HashText(recipe.contentHash) << "\",\"biomes\":[";
|
||||||
for (std::size_t i = 0; i < recipe.biomes.size(); ++i)
|
for (std::size_t i = 0; i < recipe.biomes.size(); ++i)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
# Linux15 report
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Null result. No far-mesh shortcut passed the full 1 px, shading, swap, and topology gates, so no
|
||||||
|
recipe enables one by default. Evaluator version 6→7 adds the optional `far_display_mesh` declaration;
|
||||||
|
omitting it keeps the L14 output path. The controls are in the shared Core layer and do not touch
|
||||||
|
LOD0–2, exact queries, collision, edits, or server state.
|
||||||
|
|
||||||
|
Candidates measured:
|
||||||
|
|
||||||
|
- Refinement grid scale ×2 at LOD4, 5, 6, 7, and 8. The all-tile LOD4 and LOD5 runs reject; the
|
||||||
|
remaining per-LOD probes exceed 1 px or change shading, and the LOD6–8 candidates worsen at
|
||||||
|
least one adjacent swap.
|
||||||
|
- Reusing connector station radii leaves geometry p99 at 1.07 px but reaches 15.23 px maximum;
|
||||||
|
shading delta reaches 0.667 and the normal changes by 73.3°.
|
||||||
|
- Coarsening refinement at LOD4–8 is the largest measured saving, but exceeds the display budget.
|
||||||
|
Halving connector station density also saves time but opens gaps. Removing
|
||||||
|
any one ray direction also opens gaps, and the removed-panel control finds 99 new holes.
|
||||||
|
|
||||||
|
## Screen-space gate
|
||||||
|
|
||||||
|
The gate samples 65×65 surface grids across all 1,145 displayed LOD3–8 tiles (5,208,134 samples;
|
||||||
|
skirts excluded). Geometry is projected normal displacement against today's same-LOD mesh at its
|
||||||
|
closest display distance, using 90° horizontal FOV and 2,560 px (0.000613592 rad/px). Shading delta
|
||||||
|
is the maximum of fixed-light normalized Lambert change and material selector/blend-channel change;
|
||||||
|
the limit is 1/255. Each number below is p50/p99/max. Exact-path baseline values are all zero.
|
||||||
|
The gate rejects geometry max above 1 px, shading max above 1/255, any worse per-sample swap,
|
||||||
|
missing surface, or new hole/crack/floater. Its own validation fails if S13, stride-2, missing
|
||||||
|
surface, floating surface, missing fine-ring, or removed-panel controls are not rejected; the
|
||||||
|
panel control must expose at least one new connector hole.
|
||||||
|
|
||||||
|
| LOD | Tiles | Geometry px: baseline → refine×2 all | Shading delta: baseline → refine×2 all | Refine×2 holes / cracks / floating |
|
||||||
|
|---:|---:|---:|---:|---:|
|
||||||
|
| 3 | 384 | 0/0/0 → 0/0/0 | 0/0/0 → 0/0/0 | 0 / 0 / 0 |
|
||||||
|
| 4 | 320 | 0/0/0 → 0/2.370/606.562 | 0/0/0 → 0/0.023529/1 | 254 / 400 / 254 |
|
||||||
|
| 5 | 144 | 0/0/0 → 0/12.728/95.453 | 0/0/0 → 0/1/1 | 1,183 / 76 / 138 |
|
||||||
|
| 6 | 96 | 0/0/0 → 0.023/13.408/799.596 | 0/0/0 → 0/1/1 | 476 / 0 / 201 |
|
||||||
|
| 7 | 48 | 0/0/0 → 0/13.242/248.905 | 0/0/0 → 0/1/1 | 30 / 0 / 1 |
|
||||||
|
| 8 | 153 | 0/0/0 → 0.158/19.250/187.465 | 0/0/0 → 0.003922/1/1 | 45 / 0 / 946 |
|
||||||
|
|
||||||
|
The selected-patch swap report compares each baseline with refine×2 all. Values are p50/p99/max px.
|
||||||
|
|
||||||
|
| Swap (distance) | Before | After refine×2 all |
|
||||||
|
|---|---:|---:|
|
||||||
|
| 3→2 (96 m) | 0.611/3.767/6.045 | 0.611/3.767/6.045 |
|
||||||
|
| 4→3 (192 m) | 0.752/4.630/7.513 | 0.752/4.630/7.513 |
|
||||||
|
| 5→4 (384 m) | 0/9.580/31.783 | 2.435/32.321/86.615 |
|
||||||
|
| 6→5 (768 m) | 0/9.754/70.340 | 0.343/19.142/78.077 |
|
||||||
|
| 7→6 (1,536 m) | 0/9.040/42.355 | 0.200/15.109/50.994 |
|
||||||
|
| 8→7 (3,072 m) | 0.826/18.030/47.182 | 1.528/24.547/64.449 |
|
||||||
|
|
||||||
|
The station×2 LOD6 screen scan measures 0/2/813.571 px, shading 0/0/1, with 1,349 holes and 1,690
|
||||||
|
floaters. Its connector audit counts missing ray stations by stable tile/ray/station ID: holes 8→221
|
||||||
|
(216 new), floating classifications 464→405 (111 new IDs). In the refine×2 all row above, the
|
||||||
|
screen scan reports those all-tile holes, cracks, and floaters. Station×2 leaves all six selected-patch
|
||||||
|
swap p50/p99/max rows at baseline values, including 6→5 at 0/9.754/70.340 px. All four ray directions are required:
|
||||||
|
the four one-ray-removed
|
||||||
|
controls create 1,008, 1,022, 1,171, and 1,158 new holes. The explicit panel-removal control creates
|
||||||
|
99 new holes. The exact-path audit has zero new holes, cracks, or floating pieces; its raw connector
|
||||||
|
audit counts remain 8 holes and 464 pre-existing floating classifications.
|
||||||
|
|
||||||
|
The LOD4×2 and LOD5×2 candidates also fail in isolation. Reusing station radii misses the LOD6
|
||||||
|
geometry and shading limits. The four one-ray-removed masks add 1,008, 1,022, 1,171, and 1,158
|
||||||
|
connector holes, respectively.
|
||||||
|
|
||||||
|
## Negative controls
|
||||||
|
|
||||||
|
- The S13 global-octave-drop and stride-2 column-interpolation controls are rejected at every LOD3–8.
|
||||||
|
- Missing displayed surface, a floating surface, and missing fine-ring surface controls are rejected.
|
||||||
|
- Removing connector panels is rejected with 99 new holes. The exact connector path has zero new
|
||||||
|
station IDs classified as holes, cracks, or floaters.
|
||||||
|
|
||||||
|
## Speed
|
||||||
|
|
||||||
|
Five interleaved W12 pairs per candidate, hashing off, same 2,553-tile L1 view.
|
||||||
|
`whole_view_ready` is the metric. Both candidates fail the quality gate.
|
||||||
|
|
||||||
|
Station×2 connector stations:
|
||||||
|
|
||||||
|
| Pair | L14 baseline | Station×2 | Change |
|
||||||
|
|---:|---:|---:|---:|
|
||||||
|
| 1 | 1.320 s | 1.304 s | −1.2% |
|
||||||
|
| 2 | 1.363 s | 1.330 s | −2.4% |
|
||||||
|
| 3 | 1.371 s | 1.332 s | −2.8% |
|
||||||
|
| 4 | 1.393 s | 1.341 s | −3.7% |
|
||||||
|
| 5 | 1.391 s | 1.345 s | −3.3% |
|
||||||
|
| Median | 1.371 s | 1.332 s | −2.8% (3.3% median paired gain) |
|
||||||
|
|
||||||
|
Refinement grid scale ×2 at LOD4–8:
|
||||||
|
|
||||||
|
| Pair | L14 baseline | Refine×2 all | Change |
|
||||||
|
|---:|---:|---:|---:|
|
||||||
|
| 1 | 1.305 s | 1.164 s | −10.8% |
|
||||||
|
| 2 | 1.336 s | 1.177 s | −11.9% |
|
||||||
|
| 3 | 1.362 s | 1.169 s | −14.2% |
|
||||||
|
| 4 | 1.378 s | 1.178 s | −14.5% |
|
||||||
|
| 5 | 1.384 s | 1.190 s | −14.0% |
|
||||||
|
| Median | 1.362 s | 1.177 s | −13.6% (14.0% median paired gain) |
|
||||||
|
|
||||||
|
## Hashes and invariants
|
||||||
|
|
||||||
|
The undeclared/default recipe path matches L14. LOD0–2 density/mesh/payload/topology hashes:
|
||||||
|
|
||||||
|
| LOD | Density | Mesh | Payload | Topology |
|
||||||
|
|---:|---|---|---|---|
|
||||||
|
| 0 | `769f2a706d585a72` | `33eeeb39fa443816` | `645621a1d968983f` | `c2457ed905036c9d` |
|
||||||
|
| 1 | `69cc5a5c0a2c60af` | `b48335f5410c57d5` | `d15c5480162ab8ba` | `feca1bd025823604` |
|
||||||
|
| 2 | `85deed5683a77202` | `53469895987aeea2` | `e55f8ae7e1532033` | `d5feb12a0e85c09b` |
|
||||||
|
|
||||||
|
Exact queries (density / unclamped / detail / collision) remain
|
||||||
|
`e4d4a8eb95b35933 / e4d4a8eb95b35933 / efcba20c0c70c415 / e604823a249029bf`. The 24 m crossing's
|
||||||
|
432 moved tiles match L14 at W1/W12 with Clang off and GCC default:
|
||||||
|
`337ca62131576728 / 7612f48e2ddbbaab / 69a67ed4add83b87 / f11ac1ebd0f7ddb8` (D/M/P/T).
|
||||||
|
Its LOD0–2 per-LOD hashes also match the three rows above.
|
||||||
|
|
||||||
|
Determinism passed 28/28 configurations: SurfaceWorld seed 0 and FloatingIslands seed 0, Clang 19
|
||||||
|
FP-contract off and GCC 14 default, W1/W8/W12 and five W12 repeats. All per-LOD and whole-view
|
||||||
|
hashes match L14. L13 route parity passed for SurfaceWorld seeds 0, 1, and 17 and FloatingIslands
|
||||||
|
seed 0; page, native-only, higher-LOD, edit, and history mismatch counts are zero.
|
||||||
|
|
||||||
|
The L3 FP-contract preamble/self-test, L4 `FLazy`, L7 shared cache, L9/L12 proofs, L11 readiness and
|
||||||
|
crash fix, L13 route binding, and CLAUDE.md async rules remain in place. The evaluator and optional
|
||||||
|
recipe declaration are shared by all recipes. Builds and runs used CPUs 0–11, up to W12, normal
|
||||||
|
priority, 6 GiB address-space limits, and the unchanged `powersave` governor. Peak RSS was 904,400 KB
|
||||||
|
(GCC build); peak runtime RSS was 736,432 KB. No run exceeded the time or memory limits.
|
||||||
|
|
||||||
|
## Capture pairs
|
||||||
|
|
||||||
|
JPEGs are 1280×720 and each is under 74 KB. Before is L14; after is the rejected refinement×2
|
||||||
|
candidate applied at LOD4–8.
|
||||||
|
The L1 view uses `(0,-35,-487.94) m`, yaw 0°, pitch −22°. Horizon uses the same location at pitch −2°.
|
||||||
|
The mesa frame aims from the L1 location at the L6 mesa band (`(-472,-1432,-463.151) m` in the
|
||||||
|
gate) with yaw −161° and pitch 0.9°.
|
||||||
|
|
||||||
|
| View | Before | After refine×2 all |
|
||||||
|
|---|---|---|
|
||||||
|
| L1 camera | [l1-camera-before.jpg](captures/l1-camera-before.jpg) (59 KB) | [l1-camera-after-refineall.jpg](captures/l1-camera-after-refineall.jpg) (57 KB) |
|
||||||
|
| Horizon | [horizon-before.jpg](captures/horizon-before.jpg) (63 KB) | [horizon-after-refineall.jpg](captures/horizon-after-refineall.jpg) (60 KB) |
|
||||||
|
| LOD6 mesa rim | [lod6-mesa-rim-before.jpg](captures/lod6-mesa-rim-before.jpg) (74 KB) | [lod6-mesa-rim-after-refineall.jpg](captures/lod6-mesa-rim-after-refineall.jpg) (67 KB) |
|
||||||
|
|
||||||
|
## Failed and retried runs
|
||||||
|
|
||||||
|
- The first Clang build wrapper exited 127 because `/usr/bin/time` is absent; the direct Clang
|
||||||
|
`-ffp-contract=off` build succeeded. GCC 14 default also built successfully.
|
||||||
|
- A later sequential-build monitor was interrupted after Clang completed and before its GCC step
|
||||||
|
launched. Clang and GCC were rebuilt separately with the same compiler settings and 6 GiB cap.
|
||||||
|
- The first hash matrix omitted `--seed=0` and was discarded. A second FloatingIslands matrix used
|
||||||
|
the SurfaceWorld spawn camera; it was rerun with the L14 auto-surface camera. The corrected matrix
|
||||||
|
passed all 28 configurations.
|
||||||
|
- Initial SurfaceWorld route probes used no camera for seed 0 (the 2,370-tile bound), omitted
|
||||||
|
`--region-crossing` for seed 0 (shape compiler safety bound), and used the fixed seed-0 camera for
|
||||||
|
seed 1 (LOD2/exact-query parity mismatch; seed 17 was not run with that setup). They were rerun
|
||||||
|
with L14's camera selection and region-crossing option; all four final route audits passed. The
|
||||||
|
first 24 m crossing also omitted `--region-crossing` and exceeded the shape-build safety bound;
|
||||||
|
all four corrected compiler/worker combinations passed.
|
||||||
|
- The first connector audit compared aggregate counts. It was replaced with stable per-ray station
|
||||||
|
ID comparison before the final gate. The initial L4 selected-tile gate missed a changed tile; the
|
||||||
|
all-desired-tile coverage gate found and rejected it. The full-view gate is used in the table above.
|
||||||
|
- The first JPEG encoding pass did not set CPU affinity or an address-space cap. All six files were
|
||||||
|
re-encoded under CPUs 0–11 and the 6 GiB cap; sizes and pixels stayed the same.
|
||||||
|
- The first five speed pairs included screen-gate color capture on ordinary streaming tiles. That
|
||||||
|
diagnostic copy is now enabled only for gate runs; all five cost pairs were rerun and the table
|
||||||
|
above uses only the final logs. The final exact gate passed and station×2 remained rejected.
|
||||||
|
- No builds, captures, final audits, or cost runs timed out or breached their memory limits. The
|
||||||
|
intentional candidate rejections are listed above, not treated as infrastructure failures.
|
||||||
|
|
||||||
|
Evidence logs are in `Saved/Linux`: the final baseline and station×2 all-tile gates are
|
||||||
|
`l15-final-gatecolors-exact.log` and `l15-final-gatecolors-station2.log`; the best-payoff
|
||||||
|
refinement×2 all-tile gate is `l15-final-gatecolors-refineall.log`. Single-LOD refinement coverage is in
|
||||||
|
`l15-gate-alltiles-refine-l4x2.log` and `l15-gate-alltiles-refine-l5x2.log`. Single-LOD refinement
|
||||||
|
and connector-ray probes are `l15-gate-refine-l{x}x2.log`, `l15-gate-reuse.log`, and
|
||||||
|
`l15-gate-rays{14,13,11,7}.log`. Hash, route, and crossing evidence is in
|
||||||
|
`l15-final-hash-audit.txt`, `l15-final-route-*.log`, and `l15-final-crossing-24m-*.log`; final
|
||||||
|
station×2 speed pairs are `l15-final-speed-pair{1..5}-{before,after}.log`; refinement×2 pairs are
|
||||||
|
`l15-final-speed-refineall-pair{1..5}-{before,after}.log`.
|
||||||
|
|
||||||
|
## Note for the owner
|
||||||
|
|
||||||
|
The default far terrain stays the same because every measured reduction exceeded the 1 px geometry
|
||||||
|
or shading limit, worsened a swap, or opened holes. Refinement×2 across LOD4–8 saves 13.6% by the
|
||||||
|
median whole-view ratio, but creates hundreds-pixel errors and visible gaps, so the captures show it
|
||||||
|
for review only and it is not enabled.
|
||||||
|
|
||||||
|
## Orchestrator review (Claude)
|
||||||
|
|
||||||
|
- I looked at the LOD6 mesa-rim pair. After refine×2, the jagged crests are visibly smoothed, black
|
||||||
|
holes appear in the cap/ceiling, and a dark sliver shows on the left horizon. That confirms the
|
||||||
|
gate's rejection: the instrument is right this time, not the world.
|
||||||
|
- **Not merged into `assembler/world`:** the candidate knobs and the evaluator 6→7 bump add no default
|
||||||
|
output and would invalidate version stamps for nothing. The full L15 code (the extended gate with
|
||||||
|
shading, swap and connector-ID audits, and the candidate controls) is kept on side branch
|
||||||
|
`wip/l15-far-budget`. `assembler/world` stays at L14 code plus this report.
|
||||||
|
- Conclusion of L14+L15: far terrain has no exact or within-budget saving left with today's design. The
|
||||||
|
remaining far cost is the price of the current refinement and connector-repair geometry.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
@@ -42,6 +42,7 @@ namespace StandaloneScreenSpaceGate
|
|||||||
double MaximumSlopeDegrees=0.0;
|
double MaximumSlopeDegrees=0.0;
|
||||||
std::array<std::uint64_t,4> SlopeSamples{};
|
std::array<std::uint64_t,4> SlopeSamples{};
|
||||||
std::array<double,4> SlopeP99{},SlopeMaximum{};
|
std::array<double,4> SlopeP99{},SlopeMaximum{};
|
||||||
|
std::vector<double> SamplePixels;
|
||||||
bool Valid=false;
|
bool Valid=false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,6 +53,14 @@ namespace StandaloneScreenSpaceGate
|
|||||||
double SlopeY=0.0;
|
double SlopeY=0.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct FMeshSurfaceHit
|
||||||
|
{
|
||||||
|
double Z=0.0;
|
||||||
|
std::array<double,3> Normal{};
|
||||||
|
std::array<std::uint8_t,4> Color{};
|
||||||
|
bool Ceiling=false;
|
||||||
|
};
|
||||||
|
|
||||||
struct FViewBasis
|
struct FViewBasis
|
||||||
{
|
{
|
||||||
std::array<double,3> Right{};
|
std::array<double,3> Right{};
|
||||||
@@ -162,7 +171,7 @@ namespace StandaloneScreenSpaceGate
|
|||||||
{
|
{
|
||||||
int Side=65;
|
int Side=65;
|
||||||
double MinimumX=0.0,MaximumX=0.0,MinimumY=0.0,MaximumY=0.0;
|
double MinimumX=0.0,MaximumX=0.0,MinimumY=0.0,MaximumY=0.0;
|
||||||
std::vector<std::vector<double>> Hits;
|
std::vector<std::vector<FMeshSurfaceHit>> Hits;
|
||||||
|
|
||||||
FHeightGrid()=default;
|
FHeightGrid()=default;
|
||||||
FHeightGrid(int InSide,double MinX,double MaxX,double MinY,double MaxY)
|
FHeightGrid(int InSide,double MinX,double MaxX,double MinY,double MaxY)
|
||||||
@@ -175,13 +184,13 @@ namespace StandaloneScreenSpaceGate
|
|||||||
double Y(int Iy) const
|
double Y(int Iy) const
|
||||||
{return MinimumY+(MaximumY-MinimumY)*Iy/(Side-1);}
|
{return MinimumY+(MaximumY-MinimumY)*Iy/(Side-1);}
|
||||||
|
|
||||||
void Add(int Ix,int Iy,double Z)
|
void Add(int Ix,int Iy,const FMeshSurfaceHit& Hit)
|
||||||
{
|
{
|
||||||
auto& Values=Hits[static_cast<std::size_t>(Iy)*Side+Ix];
|
auto& Values=Hits[static_cast<std::size_t>(Iy)*Side+Ix];
|
||||||
Values.push_back(Z);
|
Values.push_back(Hit);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<double>& At(int Ix,int Iy) const
|
const std::vector<FMeshSurfaceHit>& At(int Ix,int Iy) const
|
||||||
{return Hits[static_cast<std::size_t>(Iy)*Side+Ix];}
|
{return Hits[static_cast<std::size_t>(Iy)*Side+Ix];}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -246,6 +255,8 @@ namespace StandaloneScreenSpaceGate
|
|||||||
{
|
{
|
||||||
if(!Tile||Tile->Indices.size()<3)continue;
|
if(!Tile||Tile->Indices.size()<3)continue;
|
||||||
const std::size_t TriangleCount=Tile->Indices.size()/3;
|
const std::size_t TriangleCount=Tile->Indices.size()/3;
|
||||||
|
const std::size_t CeilingStart=TriangleCount
|
||||||
|
-std::min<std::size_t>(TriangleCount,Tile->CeilingTriangleCount);
|
||||||
for(std::size_t Triangle=0;Triangle<TriangleCount;++Triangle)
|
for(std::size_t Triangle=0;Triangle<TriangleCount;++Triangle)
|
||||||
{
|
{
|
||||||
if(Triangle<Tile->TriangleSkirtFaceMasks.size()
|
if(Triangle<Tile->TriangleSkirtFaceMasks.size()
|
||||||
@@ -274,15 +285,33 @@ namespace StandaloneScreenSpaceGate
|
|||||||
const double W1=((C[1]-A[1])*(X-C[0])+(A[0]-C[0])*(Y-C[1]))/Denominator;
|
const double W1=((C[1]-A[1])*(X-C[0])+(A[0]-C[0])*(Y-C[1]))/Denominator;
|
||||||
const double W2=1.0-W0-W1;
|
const double W2=1.0-W0-W1;
|
||||||
if(W0<-1e-6||W1<-1e-6||W2<-1e-6)continue;
|
if(W0<-1e-6||W1<-1e-6||W2<-1e-6)continue;
|
||||||
Grid.Add(Ix,Iy,W0*A[2]+W1*B[2]+W2*C[2]);
|
const auto& VA=Tile->Vertices[I0];
|
||||||
|
const auto& VB=Tile->Vertices[I1];
|
||||||
|
const auto& VC=Tile->Vertices[I2];
|
||||||
|
std::array<double,3> Normal{};
|
||||||
|
for(int Axis=0;Axis<3;++Axis)
|
||||||
|
Normal[Axis]=W0*VA.Normal[Axis]+W1*VB.Normal[Axis]+W2*VC.Normal[Axis];
|
||||||
|
const double NormalLength=std::sqrt(Normal[0]*Normal[0]
|
||||||
|
+Normal[1]*Normal[1]+Normal[2]*Normal[2]);
|
||||||
|
if(NormalLength>1.0e-12)
|
||||||
|
for(double& Component:Normal)Component/=NormalLength;
|
||||||
|
std::array<std::uint8_t,4> Color{};
|
||||||
|
if(Tile->VertexColors.size()==Tile->Vertices.size())
|
||||||
|
for(int Channel=0;Channel<4;++Channel)
|
||||||
|
Color[Channel]=static_cast<std::uint8_t>(std::clamp(std::lround(
|
||||||
|
W0*Tile->VertexColors[I0][Channel]
|
||||||
|
+W1*Tile->VertexColors[I1][Channel]
|
||||||
|
+W2*Tile->VertexColors[I2][Channel]),0l,255l));
|
||||||
|
Grid.Add(Ix,Iy,{W0*A[2]+W1*B[2]+W2*C[2],Normal,Color,Triangle>=CeilingStart});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(auto& Values:Grid.Hits)
|
for(auto& Values:Grid.Hits)
|
||||||
{
|
{
|
||||||
std::sort(Values.begin(),Values.end());
|
std::stable_sort(Values.begin(),Values.end(),[](const FMeshSurfaceHit& A,
|
||||||
Values.erase(std::unique(Values.begin(),Values.end(),[](double A,double B)
|
const FMeshSurfaceHit& B){return A.Z<B.Z;});
|
||||||
{return std::abs(A-B)<0.02;}),Values.end());
|
Values.erase(std::unique(Values.begin(),Values.end(),[](const FMeshSurfaceHit& A,
|
||||||
|
const FMeshSurfaceHit& B){return std::abs(A.Z-B.Z)<0.02;}),Values.end());
|
||||||
}
|
}
|
||||||
return Grid;
|
return Grid;
|
||||||
}
|
}
|
||||||
@@ -335,14 +364,14 @@ namespace StandaloneScreenSpaceGate
|
|||||||
return Roots;
|
return Roots;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool MatchNearest(const std::vector<double>& Hits,const std::vector<bool>& Used,
|
inline bool MatchNearest(const std::vector<FMeshSurfaceHit>& Hits,const std::vector<bool>& Used,
|
||||||
double Root,std::size_t& OutIndex,double& OutError)
|
double Root,std::size_t& OutIndex,double& OutError)
|
||||||
{
|
{
|
||||||
double Best=std::numeric_limits<double>::infinity();std::size_t BestIndex=Hits.size();
|
double Best=std::numeric_limits<double>::infinity();std::size_t BestIndex=Hits.size();
|
||||||
for(std::size_t I=0;I<Hits.size();++I)
|
for(std::size_t I=0;I<Hits.size();++I)
|
||||||
if(I>=Used.size()||!Used[I])
|
if(I>=Used.size()||!Used[I])
|
||||||
{
|
{
|
||||||
const double Error=std::abs(Hits[I]-Root);
|
const double Error=std::abs(Hits[I].Z-Root);
|
||||||
if(Error<Best){Best=Error;BestIndex=I;}
|
if(Error<Best){Best=Error;BestIndex=I;}
|
||||||
}
|
}
|
||||||
if(BestIndex==Hits.size())return false;
|
if(BestIndex==Hits.size())return false;
|
||||||
@@ -371,7 +400,7 @@ namespace StandaloneScreenSpaceGate
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Used[HitIndex]=true;
|
Used[HitIndex]=true;
|
||||||
AddError(Accumulator,MeshGrid.At(Ix,Iy)[HitIndex]-Root.ZMeters,
|
AddError(Accumulator,MeshGrid.At(Ix,Iy)[HitIndex].Z-Root.ZMeters,
|
||||||
Root,PixelBudgetMeters,View,MeshGrid.X(Ix),MeshGrid.Y(Iy));
|
Root,PixelBudgetMeters,View,MeshGrid.X(Ix),MeshGrid.Y(Iy));
|
||||||
++Row.Samples;
|
++Row.Samples;
|
||||||
}
|
}
|
||||||
@@ -399,7 +428,7 @@ namespace StandaloneScreenSpaceGate
|
|||||||
std::vector<bool> UsedReference(Reference.size(),false),UsedMesh(Mesh.size(),false);
|
std::vector<bool> UsedReference(Reference.size(),false),UsedMesh(Mesh.size(),false);
|
||||||
for(std::size_t ReferenceIndex=0;ReferenceIndex<Reference.size();++ReferenceIndex)
|
for(std::size_t ReferenceIndex=0;ReferenceIndex<Reference.size();++ReferenceIndex)
|
||||||
{
|
{
|
||||||
const double ReferenceZ=Reference[ReferenceIndex];
|
const double ReferenceZ=Reference[ReferenceIndex].Z;
|
||||||
UsedReference[ReferenceIndex]=true;
|
UsedReference[ReferenceIndex]=true;
|
||||||
std::size_t MeshIndex=0;double MeshError=0.0;
|
std::size_t MeshIndex=0;double MeshError=0.0;
|
||||||
if(!MatchNearest(Mesh,UsedMesh,ReferenceZ,MeshIndex,MeshError))
|
if(!MatchNearest(Mesh,UsedMesh,ReferenceZ,MeshIndex,MeshError))
|
||||||
@@ -416,7 +445,7 @@ namespace StandaloneScreenSpaceGate
|
|||||||
if(BestRoot!=Roots.end()&&std::abs(BestRoot->ZMeters-ReferenceZ)<1.0)
|
if(BestRoot!=Roots.end()&&std::abs(BestRoot->ZMeters-ReferenceZ)<1.0)
|
||||||
Root=*BestRoot;
|
Root=*BestRoot;
|
||||||
}
|
}
|
||||||
AddError(Accumulator,Mesh[MeshIndex]-Reference[ReferenceIndex],
|
AddError(Accumulator,Mesh[MeshIndex].Z-Reference[ReferenceIndex].Z,
|
||||||
Root,PixelBudgetMeters,View,ReferenceGrid.X(Ix),ReferenceGrid.Y(Iy));
|
Root,PixelBudgetMeters,View,ReferenceGrid.X(Ix),ReferenceGrid.Y(Iy));
|
||||||
}
|
}
|
||||||
for(std::size_t I=0;I<Mesh.size();++I)
|
for(std::size_t I=0;I<Mesh.size();++I)
|
||||||
@@ -426,6 +455,110 @@ namespace StandaloneScreenSpaceGate
|
|||||||
return Row;
|
return Row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FMeshComparisonRow
|
||||||
|
{
|
||||||
|
FErrorRow Geometry;
|
||||||
|
std::uint64_t Holes=0,Cracks=0,Floating=0,MaterialMismatches=0;
|
||||||
|
double ShadeP50=0.0,ShadeP99=0.0,ShadeMaximum=0.0;
|
||||||
|
double NormalDegreesP50=0.0,NormalDegreesP99=0.0,NormalDegreesMaximum=0.0;
|
||||||
|
std::vector<double> GeometryPixels,ShadeValues,NormalAngles;
|
||||||
|
bool Valid=false;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::array<double,3> UnitNormal(const FMeshSurfaceHit& Hit)
|
||||||
|
{
|
||||||
|
std::array<double,3> N=Hit.Normal;
|
||||||
|
const double Length=std::sqrt(N[0]*N[0]+N[1]*N[1]+N[2]*N[2]);
|
||||||
|
if(Length<=1.0e-12)return {0.0,0.0,Hit.Ceiling?-1.0:1.0};
|
||||||
|
for(double& Component:N)Component/=Length;
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline FMeshComparisonRow CompareMeshes(const FHeightGrid& Reference,
|
||||||
|
const FHeightGrid& Candidate,const FVoxelTileKey& Patch,double VoxelMeters,
|
||||||
|
double PixelBudgetMeters,const FViewBasis& View)
|
||||||
|
{
|
||||||
|
FMeshComparisonRow Row;Row.Valid=Reference.Side==Candidate.Side;
|
||||||
|
if(!Row.Valid)return Row;
|
||||||
|
FErrorAccumulator Geometry;
|
||||||
|
std::vector<double> ShadeValues,NormalAngles;
|
||||||
|
const std::size_t CellCount=static_cast<std::size_t>(Reference.Side)*Reference.Side;
|
||||||
|
ShadeValues.reserve(CellCount);NormalAngles.reserve(CellCount);
|
||||||
|
const double LightLength=std::sqrt(0.3*0.3+(-0.4)*(-0.4)+0.85*0.85);
|
||||||
|
const std::array<double,3> Light={0.3/LightLength,-0.4/LightLength,0.85/LightLength};
|
||||||
|
for(int Iy=0;Iy<Reference.Side;++Iy)
|
||||||
|
for(int Ix=0;Ix<Reference.Side;++Ix)
|
||||||
|
{
|
||||||
|
const auto& Before=Reference.At(Ix,Iy);
|
||||||
|
const auto& After=Candidate.At(Ix,Iy);
|
||||||
|
std::vector<bool> Used(After.size(),false);
|
||||||
|
for(std::size_t I=0;I<Before.size();++I)
|
||||||
|
{
|
||||||
|
std::size_t Matched=0;double Difference=0.0;
|
||||||
|
if(!MatchNearest(After,Used,Before[I].Z,Matched,Difference))
|
||||||
|
{
|
||||||
|
++Row.Holes;AddMissing(Geometry);ShadeValues.push_back(1.0);
|
||||||
|
NormalAngles.push_back(180.0);continue;
|
||||||
|
}
|
||||||
|
Used[Matched]=true;
|
||||||
|
const FMeshSurfaceHit& A=Before[I];
|
||||||
|
const FMeshSurfaceHit& B=After[Matched];
|
||||||
|
const auto NA=UnitNormal(A),NB=UnitNormal(B);
|
||||||
|
const double Dot=std::clamp(NA[0]*NB[0]+NA[1]*NB[1]+NA[2]*NB[2],-1.0,1.0);
|
||||||
|
constexpr double Pi=3.14159265358979323846;
|
||||||
|
const double Angle=std::acos(Dot)*180.0/Pi;
|
||||||
|
NormalAngles.push_back(Angle);
|
||||||
|
const double DiffuseA=std::max(0.0,NA[0]*Light[0]+NA[1]*Light[1]+NA[2]*Light[2]);
|
||||||
|
const double DiffuseB=std::max(0.0,NB[0]*Light[0]+NB[1]*Light[1]+NB[2]*Light[2]);
|
||||||
|
const bool bMaterialChanged=A.Color[0]!=B.Color[0]||A.Color[3]!=B.Color[3]
|
||||||
|
||A.Ceiling!=B.Ceiling;
|
||||||
|
if(bMaterialChanged)++Row.MaterialMismatches;
|
||||||
|
const double MaterialDelta=bMaterialChanged?1.0:std::max(
|
||||||
|
std::abs(static_cast<double>(A.Color[1])-B.Color[1]),
|
||||||
|
std::abs(static_cast<double>(A.Color[2])-B.Color[2]))/255.0;
|
||||||
|
ShadeValues.push_back(std::max(std::abs(DiffuseA-DiffuseB),MaterialDelta));
|
||||||
|
const double NX=NA[2]<0.0?NA[0]/std::max(1.0e-12,-NA[2])
|
||||||
|
:-NA[0]/std::max(1.0e-12,NA[2]);
|
||||||
|
const double NY=NA[2]<0.0?NA[1]/std::max(1.0e-12,-NA[2])
|
||||||
|
:-NA[1]/std::max(1.0e-12,NA[2]);
|
||||||
|
AddError(Geometry,B.Z-A.Z,{A.Z,NX,NY},PixelBudgetMeters,View,
|
||||||
|
Reference.X(Ix),Reference.Y(Iy));
|
||||||
|
}
|
||||||
|
for(std::size_t I=0;I<After.size();++I)
|
||||||
|
if(!Used[I])
|
||||||
|
{++Row.Floating;AddMissing(Geometry);ShadeValues.push_back(1.0);NormalAngles.push_back(180.0);}
|
||||||
|
}
|
||||||
|
for(int Iy=0;Iy<Reference.Side;++Iy)
|
||||||
|
for(int Ix=0;Ix<Reference.Side;++Ix)
|
||||||
|
{
|
||||||
|
const std::size_t Index=static_cast<std::size_t>(Iy)*Reference.Side+Ix;
|
||||||
|
const auto CheckEdge=[&](int NX,int NY)
|
||||||
|
{
|
||||||
|
if(NX>=Reference.Side||NY>=Reference.Side)return;
|
||||||
|
const auto& A=Reference.Hits[Index];
|
||||||
|
const auto& B=Reference.At(NX,NY);
|
||||||
|
const auto& CA=Candidate.Hits[Index];
|
||||||
|
const auto& CB=Candidate.At(NX,NY);
|
||||||
|
if(!A.empty()&&!B.empty()&&(CA.empty()!=CB.empty()))++Row.Cracks;
|
||||||
|
};
|
||||||
|
CheckEdge(Ix+1,Iy);CheckEdge(Ix,Iy+1);
|
||||||
|
}
|
||||||
|
Row.Geometry.Lod=Patch.Level;Row.Geometry.Valid=Row.Valid;
|
||||||
|
Row.Geometry.Missing=Row.Holes;Row.Geometry.Floating=Row.Floating;
|
||||||
|
FinalizeError(Row.Geometry,Geometry);
|
||||||
|
Row.ShadeP50=Percentile(ShadeValues,0.50);
|
||||||
|
Row.ShadeP99=Percentile(ShadeValues,0.99);
|
||||||
|
Row.ShadeMaximum=ShadeValues.empty()?0.0:*std::max_element(ShadeValues.begin(),ShadeValues.end());
|
||||||
|
Row.NormalDegreesP50=Percentile(NormalAngles,0.50);
|
||||||
|
Row.NormalDegreesP99=Percentile(NormalAngles,0.99);
|
||||||
|
Row.NormalDegreesMaximum=NormalAngles.empty()?0.0
|
||||||
|
:*std::max_element(NormalAngles.begin(),NormalAngles.end());
|
||||||
|
Row.GeometryPixels=std::move(Geometry.Pixels);
|
||||||
|
Row.ShadeValues=std::move(ShadeValues);
|
||||||
|
Row.NormalAngles=std::move(NormalAngles);
|
||||||
|
return Row;
|
||||||
|
}
|
||||||
|
|
||||||
inline FPopRow ComparePop(const FHeightGrid& Coarse,const FHeightGrid& Fine,
|
inline FPopRow ComparePop(const FHeightGrid& Coarse,const FHeightGrid& Fine,
|
||||||
const FVoxelTileKey& Patch,double VoxelMeters,double PixelBudgetMeters,
|
const FVoxelTileKey& Patch,double VoxelMeters,double PixelBudgetMeters,
|
||||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly,const FViewBasis& View)
|
const VoxelForgeCore::WorldAssemblyManifest& Assembly,const FViewBasis& View)
|
||||||
@@ -452,11 +585,12 @@ namespace StandaloneScreenSpaceGate
|
|||||||
}
|
}
|
||||||
CoarseUsed[CoarseIndex]=true;
|
CoarseUsed[CoarseIndex]=true;
|
||||||
FineUsed[FineIndex]=true;
|
FineUsed[FineIndex]=true;
|
||||||
AddError(Accumulator,CoarseHits[CoarseIndex]-FineHits[FineIndex],
|
AddError(Accumulator,CoarseHits[CoarseIndex].Z-FineHits[FineIndex].Z,
|
||||||
Root,PixelBudgetMeters,View,Coarse.X(Ix),Coarse.Y(Iy));
|
Root,PixelBudgetMeters,View,Coarse.X(Ix),Coarse.Y(Iy));
|
||||||
++Row.Samples;
|
++Row.Samples;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Row.SamplePixels=Accumulator.Pixels;
|
||||||
FErrorRow Stats;Stats.Lod=Patch.Level;Stats.Valid=true;
|
FErrorRow Stats;Stats.Lod=Patch.Level;Stats.Valid=true;
|
||||||
FinalizeError(Stats,Accumulator);
|
FinalizeError(Stats,Accumulator);
|
||||||
Row.Samples=Stats.Samples;Row.P50=Stats.P50;Row.P99=Stats.P99;Row.Maximum=Stats.Maximum;
|
Row.Samples=Stats.Samples;Row.P50=Stats.P50;Row.P99=Stats.P99;Row.Maximum=Stats.Maximum;
|
||||||
@@ -472,6 +606,16 @@ namespace StandaloneScreenSpaceGate
|
|||||||
return Row;
|
return Row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool PopNotWorseAtAnySample(const FPopRow& Before,const FPopRow& After)
|
||||||
|
{
|
||||||
|
if(!Before.Valid||!After.Valid||Before.Samples<16
|
||||||
|
||Before.SamplePixels.size()!=After.SamplePixels.size()
|
||||||
|
||After.Cracks>Before.Cracks||After.Floating>Before.Floating)return false;
|
||||||
|
for(std::size_t I=0;I<Before.SamplePixels.size();++I)
|
||||||
|
if(After.SamplePixels[I]>Before.SamplePixels[I]+1.0e-4)return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
inline std::array<double,9> ClosestDisplayedDistancesMeters(
|
inline std::array<double,9> ClosestDisplayedDistancesMeters(
|
||||||
const FStandaloneOwnerWorld& World,const FVector& CameraVoxels,
|
const FStandaloneOwnerWorld& World,const FVector& CameraVoxels,
|
||||||
std::uint32_t VoxelSizeMicrons)
|
std::uint32_t VoxelSizeMicrons)
|
||||||
@@ -544,6 +688,7 @@ namespace StandaloneScreenSpaceGate
|
|||||||
|
|
||||||
inline bool Run(const std::array<std::optional<FStandaloneCpuTile>,9>& SampleTiles,
|
inline bool Run(const std::array<std::optional<FStandaloneCpuTile>,9>& SampleTiles,
|
||||||
FStandaloneStreamingGenerator& Streamer,
|
FStandaloneStreamingGenerator& Streamer,
|
||||||
|
const std::vector<FVoxelTileKey>& DesiredKeys,
|
||||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly,
|
const VoxelForgeCore::WorldAssemblyManifest& Assembly,
|
||||||
const std::array<double,9>& Distances,std::uint32_t VoxelSizeMicrons,int MaxLod,
|
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,
|
||||||
@@ -552,17 +697,26 @@ namespace StandaloneScreenSpaceGate
|
|||||||
const double VoxelMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
|
const double VoxelMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
|
||||||
const FViewBasis View=MakeViewBasis(CameraYawDegrees,CameraPitchDegrees);
|
const FViewBasis View=MakeViewBasis(CameraYawDegrees,CameraPitchDegrees);
|
||||||
std::array<double,9> Budgets{};
|
std::array<double,9> Budgets{};
|
||||||
std::array<FErrorRow,9> ErrorRows{};
|
VoxelForgeCore::TileGenerationOptions BaselineOptions;
|
||||||
std::array<FPopRow,9> PopRows{};
|
BaselineOptions.overrideFarDisplayMesh=true;
|
||||||
|
VoxelForgeCore::TileGenerationOptions CandidateOptions;
|
||||||
|
CandidateOptions.overrideFarDisplayMesh=true;
|
||||||
|
CandidateOptions.farDisplayGeometryBudgetMillipixels=Assembly.recipe.farDisplayGeometryBudgetMillipixels;
|
||||||
|
CandidateOptions.farRefinementGridScale=Assembly.recipe.farRefinementGridScale;
|
||||||
|
CandidateOptions.farRefinementLodMask=Assembly.recipe.farRefinementLodMask;
|
||||||
|
CandidateOptions.farConnectorStationScale=Assembly.recipe.farConnectorStationScale;
|
||||||
|
CandidateOptions.farConnectorRayMask=Assembly.recipe.farConnectorRayMask;
|
||||||
|
CandidateOptions.farConnectorReuseStationRadii=Assembly.recipe.farConnectorReuseStationRadii;
|
||||||
bool GatePass=true;
|
bool GatePass=true;
|
||||||
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",
|
constexpr double ShadeLimit=1.0/255.0;
|
||||||
|
std::printf("[ScreenSpaceGate] angular_pixel=%.9f rad (horizontal_fov=90deg,width=2560) lod>=3_geometry_limit=1.0px same_lod_reference=today mesh sampling=65x65 skirts=excluded shading=max(lambert-delta,material-selector-or-blend-delta) shade_limit=1/255 normal_probe_vox=1 view_yaw_deg=%.3f view_pitch_deg=%.3f\n",
|
||||||
OnePixelRadians,CameraYawDegrees,CameraPitchDegrees);
|
OnePixelRadians,CameraYawDegrees,CameraPitchDegrees);
|
||||||
for(int Lod=0;Lod<=MaxLod&&Lod<9;++Lod)
|
for(int Lod=0;Lod<=MaxLod&&Lod<9;++Lod)
|
||||||
{
|
{
|
||||||
Budgets[Lod]=Distances[Lod]*OnePixelRadians;
|
Budgets[Lod]=Distances[Lod]*OnePixelRadians;
|
||||||
std::printf("[ScreenSpaceDistance] lod=%d closest_m=%.3f pixel_budget_m=%.6f pixel_budget_cm=%.3f pixel_budget_subvoxels=%lld rule=%s\n",
|
std::printf("[ScreenSpaceDistance] lod=%d closest_m=%.3f pixel_budget_m=%.6f pixel_budget_cm=%.3f pixel_budget_subvoxels=%lld rule=%s\n",
|
||||||
Lod,Distances[Lod],Budgets[Lod],Budgets[Lod]*100.0,
|
Lod,Distances[Lod],Budgets[Lod],Budgets[Lod]*100.0,
|
||||||
static_cast<long long>(PixelBudgetsSubvoxels[Lod]),Lod<3?"exact-2cm":"1px");
|
static_cast<long long>(PixelBudgetsSubvoxels[Lod]),Lod<3?"exact-2cm":"same_lod_1px");
|
||||||
}
|
}
|
||||||
const double LOD3CellMeters=static_cast<double>(CHUNK_SIZE*(1<<3))*VoxelMeters/64.0;
|
const double LOD3CellMeters=static_cast<double>(CHUNK_SIZE*(1<<3))*VoxelMeters/64.0;
|
||||||
PrintKnownFailureControl(LOD3CellMeters,Distances[3],View);
|
PrintKnownFailureControl(LOD3CellMeters,Distances[3],View);
|
||||||
@@ -578,115 +732,117 @@ namespace StandaloneScreenSpaceGate
|
|||||||
const FStandaloneCpuTile* CoarsePtr=&CoarseTile;
|
const FStandaloneCpuTile* CoarsePtr=&CoarseTile;
|
||||||
const std::span<const FStandaloneCpuTile* const> CoarseSpan(&CoarsePtr,1);
|
const std::span<const FStandaloneCpuTile* const> CoarseSpan(&CoarsePtr,1);
|
||||||
const FHeightGrid CoarseGrid=Rasterize(CoarseSpan,CoarseTile.Key,VoxelMeters);
|
const FHeightGrid CoarseGrid=Rasterize(CoarseSpan,CoarseTile.Key,VoxelMeters);
|
||||||
const FStandaloneCpuTile ExactTile=Streamer.GenerateDiagnostic(
|
FStandaloneCpuTile BaselineTile=Streamer.GenerateDiagnostic(
|
||||||
CoarseTile.Key,0,1,true);
|
CoarseTile.Key,0,1,true,&BaselineOptions,true);
|
||||||
const FStandaloneCpuTile* ExactPointer=&ExactTile;
|
const FStandaloneCpuTile* BaselinePointer=&BaselineTile;
|
||||||
const std::span<const FStandaloneCpuTile* const> ExactSpan(&ExactPointer,1);
|
const std::span<const FStandaloneCpuTile* const> BaselineSpan(&BaselinePointer,1);
|
||||||
const FHeightGrid ExactGrid=Rasterize(ExactSpan,ExactTile.Key,VoxelMeters);
|
const FHeightGrid BaselineGrid=Rasterize(BaselineSpan,BaselineTile.Key,VoxelMeters);
|
||||||
ErrorRows[Lod]=CompareToReference(CoarseGrid,ExactGrid,CoarseTile.Key,VoxelMeters,
|
const FMeshComparisonRow MeshDelta=CompareMeshes(BaselineGrid,CoarseGrid,
|
||||||
Budgets[Lod],Assembly,View);
|
CoarseTile.Key,VoxelMeters,Budgets[Lod],View);
|
||||||
const FErrorRow& Error=ErrorRows[Lod];
|
const FErrorRow& Error=MeshDelta.Geometry;
|
||||||
const bool LodPass=Error.Valid&&Error.Missing==0&&Error.Floating==0
|
const bool LodPass=Error.Valid&&Error.Missing==0&&Error.Floating==0
|
||||||
&&Error.Samples>=16&&Error.Maximum<=1.0;
|
&&MeshDelta.Cracks==0&&Error.Samples>=16&&Error.Maximum<=1.0
|
||||||
|
&&MeshDelta.ShadeMaximum<=ShadeLimit;
|
||||||
GatePass=GatePass&&LodPass;
|
GatePass=GatePass&&LodPass;
|
||||||
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",
|
std::printf("[ScreenSpaceGateLOD] lod=%d key=(%d,%d,%d) samples=%llu geometry_px_p50=%.4f p99=%.4f max=%.4f shading_delta_p50=%.6f p99=%.6f max=%.6f normal_deg_p50=%.4f p99=%.4f max=%.4f material_mismatch=%llu holes=%llu cracks=%llu floating=%llu status=%s\n",
|
||||||
Lod,CoarseTile.Key.Coord.X,CoarseTile.Key.Coord.Y,CoarseTile.Key.Coord.Z,
|
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,
|
static_cast<unsigned long long>(Error.Samples),Error.P50,Error.P99,Error.Maximum,
|
||||||
Error.VerticalP50,Error.VerticalP99,Error.VerticalMaximum,
|
MeshDelta.ShadeP50,MeshDelta.ShadeP99,MeshDelta.ShadeMaximum,
|
||||||
Error.MaximumNormalDistanceMeters,Error.MaximumX,Error.MaximumY,
|
MeshDelta.NormalDegreesP50,MeshDelta.NormalDegreesP99,MeshDelta.NormalDegreesMaximum,
|
||||||
Error.MaximumSlopeDegrees,
|
static_cast<unsigned long long>(MeshDelta.MaterialMismatches),
|
||||||
static_cast<unsigned long long>(Error.Missing),static_cast<unsigned long long>(Error.Floating),
|
static_cast<unsigned long long>(MeshDelta.Holes),
|
||||||
|
static_cast<unsigned long long>(MeshDelta.Cracks),
|
||||||
|
static_cast<unsigned long long>(MeshDelta.Floating),
|
||||||
LodPass?"PASS":"REJECT");
|
LodPass?"PASS":"REJECT");
|
||||||
PrintSlopeTail("same_lod",Lod,Error.SlopeSamples,Error.SlopeP99,Error.SlopeMaximum);
|
|
||||||
|
|
||||||
const FErrorRow FieldError=CompareToExact(CoarseGrid,CoarseTile.Key,VoxelMeters,
|
const FErrorRow FieldError=CompareToExact(CoarseGrid,CoarseTile.Key,VoxelMeters,
|
||||||
Budgets[Lod],Assembly,View);
|
Budgets[Lod],Assembly,View);
|
||||||
const bool FieldPass=FieldError.Valid&&FieldError.Missing==0&&FieldError.Floating==0
|
const bool FieldPass=FieldError.Valid&&FieldError.Missing==0&&FieldError.Floating==0
|
||||||
&&FieldError.Samples>=16&&FieldError.Maximum<=1.0;
|
&&FieldError.Samples>=16&&FieldError.Maximum<=1.0;
|
||||||
GatePass=GatePass&&FieldPass;
|
std::printf("[ScreenSpaceGateFieldLOD] lod=%d samples=%llu field_geometry_px_p50=%.4f p99=%.4f max=%.4f missing=%llu floating=%llu status=INFO_ONLY\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,
|
Lod,static_cast<unsigned long long>(FieldError.Samples),FieldError.P50,
|
||||||
FieldError.P99,FieldError.Maximum,FieldError.VerticalP50,FieldError.VerticalP99,
|
FieldError.P99,FieldError.Maximum,
|
||||||
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.Missing),
|
||||||
static_cast<unsigned long long>(FieldError.Floating),FieldPass?"PASS":"REJECT");
|
static_cast<unsigned long long>(FieldError.Floating));
|
||||||
PrintSlopeTail("field",Lod,FieldError.SlopeSamples,FieldError.SlopeP99,
|
|
||||||
FieldError.SlopeMaximum);
|
|
||||||
|
|
||||||
bool FaultControlsPass=true;
|
bool FaultControlsPass=true;
|
||||||
const FHeightGrid MissingGrid(ExactGrid.Side,ExactGrid.MinimumX,ExactGrid.MaximumX,
|
const FHeightGrid MissingGrid(BaselineGrid.Side,BaselineGrid.MinimumX,BaselineGrid.MaximumX,
|
||||||
ExactGrid.MinimumY,ExactGrid.MaximumY);
|
BaselineGrid.MinimumY,BaselineGrid.MaximumY);
|
||||||
const FErrorRow MissingError=CompareToReference(MissingGrid,ExactGrid,
|
const FMeshComparisonRow MissingError=CompareMeshes(BaselineGrid,MissingGrid,
|
||||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
CoarseTile.Key,VoxelMeters,Budgets[Lod],View);
|
||||||
const bool MissingRejected=MissingError.Missing>0&&MissingError.Maximum>1.0;
|
const bool MissingRejected=MissingError.Holes>0&&MissingError.Geometry.Maximum>1.0;
|
||||||
FaultControlsPass=FaultControlsPass&&MissingRejected;
|
FaultControlsPass=FaultControlsPass&&MissingRejected;
|
||||||
std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-displayed-surface missing=%llu status=%s\n",
|
std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-displayed-surface holes=%llu status=%s\n",
|
||||||
Lod,static_cast<unsigned long long>(MissingError.Missing),
|
Lod,static_cast<unsigned long long>(MissingError.Holes),
|
||||||
MissingRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
|
MissingRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
|
||||||
|
|
||||||
FStandaloneCpuTile FloatingTile=CoarseTile;
|
FStandaloneCpuTile FloatingTile=BaselineTile;
|
||||||
const std::uint32_t FloatingVertexBase=static_cast<std::uint32_t>(FloatingTile.Vertices.size());
|
const std::uint32_t FloatingVertexBase=static_cast<std::uint32_t>(FloatingTile.Vertices.size());
|
||||||
for(FStandaloneCpuVertex Vertex:CoarseTile.Vertices)
|
for(FStandaloneCpuVertex Vertex:BaselineTile.Vertices)
|
||||||
{
|
{
|
||||||
Vertex.Position[2]+=2.0f;
|
Vertex.Position[2]+=2.0f;
|
||||||
FloatingTile.Vertices.push_back(Vertex);
|
FloatingTile.Vertices.push_back(Vertex);
|
||||||
}
|
}
|
||||||
for(const std::uint32_t Index:CoarseTile.Indices)
|
FloatingTile.VertexColors.insert(FloatingTile.VertexColors.end(),
|
||||||
|
BaselineTile.VertexColors.begin(),BaselineTile.VertexColors.end());
|
||||||
|
for(const std::uint32_t Index:BaselineTile.Indices)
|
||||||
FloatingTile.Indices.push_back(FloatingVertexBase+Index);
|
FloatingTile.Indices.push_back(FloatingVertexBase+Index);
|
||||||
FloatingTile.TriangleSkirtFaceMasks.insert(FloatingTile.TriangleSkirtFaceMasks.end(),
|
FloatingTile.TriangleSkirtFaceMasks.insert(FloatingTile.TriangleSkirtFaceMasks.end(),
|
||||||
CoarseTile.TriangleSkirtFaceMasks.begin(),CoarseTile.TriangleSkirtFaceMasks.end());
|
BaselineTile.TriangleSkirtFaceMasks.begin(),BaselineTile.TriangleSkirtFaceMasks.end());
|
||||||
const FStandaloneCpuTile* FloatingPointer=&FloatingTile;
|
const FStandaloneCpuTile* FloatingPointer=&FloatingTile;
|
||||||
const std::span<const FStandaloneCpuTile* const> FloatingSpan(&FloatingPointer,1);
|
const std::span<const FStandaloneCpuTile* const> FloatingSpan(&FloatingPointer,1);
|
||||||
const FHeightGrid FloatingGrid=Rasterize(FloatingSpan,CoarseTile.Key,VoxelMeters);
|
const FHeightGrid FloatingGrid=Rasterize(FloatingSpan,BaselineTile.Key,VoxelMeters);
|
||||||
const FErrorRow FloatingError=CompareToReference(FloatingGrid,ExactGrid,
|
const FMeshComparisonRow FloatingError=CompareMeshes(BaselineGrid,FloatingGrid,
|
||||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
CoarseTile.Key,VoxelMeters,Budgets[Lod],View);
|
||||||
const bool FloatingRejected=FloatingError.Floating>0||FloatingError.Maximum>1.0;
|
const bool FloatingRejected=FloatingError.Floating>0||FloatingError.Geometry.Maximum>1.0;
|
||||||
FaultControlsPass=FaultControlsPass&&FloatingRejected;
|
FaultControlsPass=FaultControlsPass&&FloatingRejected;
|
||||||
std::printf("[ScreenSpaceFaultInjection] lod=%d case=2m-floating-surface floating=%llu max_px=%.4f status=%s\n",
|
std::printf("[ScreenSpaceFaultInjection] lod=%d case=2m-floating-surface floating=%llu max_px=%.4f status=%s\n",
|
||||||
Lod,static_cast<unsigned long long>(FloatingError.Floating),FloatingError.Maximum,
|
Lod,static_cast<unsigned long long>(FloatingError.Floating),FloatingError.Geometry.Maximum,
|
||||||
FloatingRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
|
FloatingRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
|
||||||
|
|
||||||
std::vector<FStandaloneCpuTile> FineTiles;
|
std::vector<FStandaloneCpuTile> FineBeforeTiles,FineAfterTiles;
|
||||||
FineTiles.reserve(8);
|
FineBeforeTiles.reserve(8);FineAfterTiles.reserve(8);
|
||||||
for(int Dz=0;Dz<=1;++Dz)
|
for(int Dz=0;Dz<=1;++Dz)
|
||||||
for(int Dy=0;Dy<=1;++Dy)
|
for(int Dy=0;Dy<=1;++Dy)
|
||||||
for(int Dx=0;Dx<=1;++Dx)
|
for(int Dx=0;Dx<=1;++Dx)
|
||||||
{
|
{
|
||||||
const FVoxelTileKey FineKey(FIntVector(2*CoarseTile.Key.Coord.X+Dx,
|
const FVoxelTileKey FineKey(FIntVector(2*CoarseTile.Key.Coord.X+Dx,
|
||||||
2*CoarseTile.Key.Coord.Y+Dy,2*CoarseTile.Key.Coord.Z+Dz),Lod-1);
|
2*CoarseTile.Key.Coord.Y+Dy,2*CoarseTile.Key.Coord.Z+Dz),Lod-1);
|
||||||
FStandaloneCpuTile FineTile=Streamer.GenerateDiagnostic(FineKey);
|
FStandaloneCpuTile FineBefore=Streamer.GenerateDiagnostic(
|
||||||
if(!FineTile.Vertices.empty())FineTiles.push_back(std::move(FineTile));
|
FineKey,0,1,true,&BaselineOptions,true);
|
||||||
|
FStandaloneCpuTile FineAfter=Streamer.GenerateDiagnostic(
|
||||||
|
FineKey,0,1,true,nullptr,true);
|
||||||
|
if(!FineBefore.Vertices.empty())FineBeforeTiles.push_back(std::move(FineBefore));
|
||||||
|
if(!FineAfter.Vertices.empty())FineAfterTiles.push_back(std::move(FineAfter));
|
||||||
}
|
}
|
||||||
std::vector<const FStandaloneCpuTile*> FinePointers;
|
std::vector<const FStandaloneCpuTile*> FineBeforePointers,FineAfterPointers;
|
||||||
FinePointers.reserve(FineTiles.size());
|
FineBeforePointers.reserve(FineBeforeTiles.size());FineAfterPointers.reserve(FineAfterTiles.size());
|
||||||
for(const auto& Tile:FineTiles)FinePointers.push_back(&Tile);
|
for(const auto& Tile:FineBeforeTiles)FineBeforePointers.push_back(&Tile);
|
||||||
const auto FineSpan=std::span<const FStandaloneCpuTile* const>(
|
for(const auto& Tile:FineAfterTiles)FineAfterPointers.push_back(&Tile);
|
||||||
FinePointers.data(),FinePointers.size());
|
const auto FineBeforeSpan=std::span<const FStandaloneCpuTile* const>(
|
||||||
const FHeightGrid FineGrid=Rasterize(FineSpan,CoarseTile.Key,VoxelMeters);
|
FineBeforePointers.data(),FineBeforePointers.size());
|
||||||
PopRows[Lod]=ComparePop(CoarseGrid,FineGrid,CoarseTile.Key,VoxelMeters,
|
const auto FineAfterSpan=std::span<const FStandaloneCpuTile* const>(
|
||||||
|
FineAfterPointers.data(),FineAfterPointers.size());
|
||||||
|
const FHeightGrid FineBeforeGrid=Rasterize(FineBeforeSpan,CoarseTile.Key,VoxelMeters);
|
||||||
|
const FHeightGrid FineAfterGrid=Rasterize(FineAfterSpan,CoarseTile.Key,VoxelMeters);
|
||||||
|
const FPopRow PopBefore=ComparePop(BaselineGrid,FineBeforeGrid,CoarseTile.Key,VoxelMeters,
|
||||||
Budgets[Lod],Assembly,View);
|
Budgets[Lod],Assembly,View);
|
||||||
const FPopRow& Pop=PopRows[Lod];
|
const FPopRow PopAfter=ComparePop(CoarseGrid,FineAfterGrid,CoarseTile.Key,VoxelMeters,
|
||||||
const bool PopPass=Pop.Valid&&Pop.Cracks==0&&Pop.Floating==0&&Pop.Maximum<=1.0;
|
Budgets[Lod],Assembly,View);
|
||||||
|
const bool PopPass=PopNotWorseAtAnySample(PopBefore,PopAfter);
|
||||||
GatePass=GatePass&&PopPass;
|
GatePass=GatePass&&PopPass;
|
||||||
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",
|
std::printf("[ScreenSpaceSwap] swap=%d_to_%d distance_m=%.3f samples_before=%llu pop_before_px_p50=%.4f p99=%.4f max=%.4f pop_after_px_p50=%.4f p99=%.4f max=%.4f missing_root_samples_before=%llu after=%llu status=%s\n",
|
||||||
Lod,Lod-1,static_cast<unsigned long long>(Pop.Samples),
|
Lod,Lod-1,Distances[Lod],static_cast<unsigned long long>(PopBefore.Samples),
|
||||||
Pop.P50,Pop.P99,Pop.Maximum,Pop.VerticalP50,Pop.VerticalP99,Pop.VerticalMaximum,
|
PopBefore.P50,PopBefore.P99,PopBefore.Maximum,PopAfter.P50,PopAfter.P99,PopAfter.Maximum,
|
||||||
Pop.MaximumNormalDistanceMeters,Pop.MaximumX,Pop.MaximumY,
|
static_cast<unsigned long long>(PopBefore.Cracks),static_cast<unsigned long long>(PopAfter.Cracks),
|
||||||
Pop.MaximumDisplayedZ,
|
|
||||||
Pop.MaximumSlopeDegrees,static_cast<unsigned long long>(Pop.Cracks),
|
|
||||||
static_cast<unsigned long long>(Pop.Floating),
|
|
||||||
PopPass?"PASS":"REJECT");
|
PopPass?"PASS":"REJECT");
|
||||||
PrintSlopeTail("swap",Lod,Pop.SlopeSamples,Pop.SlopeP99,Pop.SlopeMaximum);
|
|
||||||
|
|
||||||
const FHeightGrid MissingFineGrid(CoarseGrid.Side,CoarseGrid.MinimumX,
|
const FHeightGrid MissingFineGrid(BaselineGrid.Side,BaselineGrid.MinimumX,
|
||||||
CoarseGrid.MaximumX,CoarseGrid.MinimumY,CoarseGrid.MaximumY);
|
BaselineGrid.MaximumX,BaselineGrid.MinimumY,BaselineGrid.MaximumY);
|
||||||
const FPopRow MissingFineError=ComparePop(CoarseGrid,MissingFineGrid,
|
const FPopRow MissingFineError=ComparePop(BaselineGrid,MissingFineGrid,
|
||||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
||||||
const bool CrackRejected=MissingFineError.Cracks>0&&MissingFineError.Maximum>1.0;
|
const bool CrackRejected=MissingFineError.Cracks>0&&MissingFineError.Maximum>1.0;
|
||||||
FaultControlsPass=FaultControlsPass&&CrackRejected;
|
FaultControlsPass=FaultControlsPass&&CrackRejected;
|
||||||
GatePass=GatePass&&FaultControlsPass;
|
|
||||||
std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-fine-ring-surface cracks=%llu max_px=%.4f status=%s\n",
|
std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-fine-ring-surface cracks=%llu max_px=%.4f status=%s\n",
|
||||||
Lod,static_cast<unsigned long long>(MissingFineError.Cracks),
|
Lod,static_cast<unsigned long long>(MissingFineError.Cracks),
|
||||||
MissingFineError.Maximum,CrackRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
|
MissingFineError.Maximum,CrackRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
|
||||||
@@ -695,25 +851,170 @@ namespace StandaloneScreenSpaceGate
|
|||||||
const auto CheckNegative=[&](const char* Name,std::uint32_t Drop,std::uint32_t Stride)
|
const auto CheckNegative=[&](const char* Name,std::uint32_t Drop,std::uint32_t Stride)
|
||||||
{
|
{
|
||||||
FStandaloneCpuTile Bad=Streamer.GenerateDiagnostic(
|
FStandaloneCpuTile Bad=Streamer.GenerateDiagnostic(
|
||||||
CoarseTile.Key,Drop,Stride,true);
|
CoarseTile.Key,Drop,Stride,true,nullptr,true);
|
||||||
const FStandaloneCpuTile* BadPointer=&Bad;
|
const FStandaloneCpuTile* BadPointer=&Bad;
|
||||||
const std::span<const FStandaloneCpuTile* const> BadSpan(&BadPointer,1);
|
const std::span<const FStandaloneCpuTile* const> BadSpan(&BadPointer,1);
|
||||||
const FHeightGrid BadGrid=Rasterize(BadSpan,Bad.Key,VoxelMeters);
|
const FHeightGrid BadGrid=Rasterize(BadSpan,Bad.Key,VoxelMeters);
|
||||||
const FErrorRow BadError=CompareToReference(BadGrid,ExactGrid,
|
const FMeshComparisonRow BadError=CompareMeshes(BaselineGrid,BadGrid,
|
||||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
CoarseTile.Key,VoxelMeters,Budgets[Lod],View);
|
||||||
const bool Rejected=BadError.Missing>0||BadError.Floating>0
|
const bool Rejected=BadError.Holes>0||BadError.Floating>0||BadError.Cracks>0
|
||||||
||BadError.Samples<16||BadError.Maximum>1.0;
|
||BadError.Geometry.Samples<16||BadError.Geometry.Maximum>1.0
|
||||||
|
||BadError.ShadeMaximum>ShadeLimit;
|
||||||
NegativeControlsPass=NegativeControlsPass&&Rejected;
|
NegativeControlsPass=NegativeControlsPass&&Rejected;
|
||||||
std::printf("[ScreenSpaceNegative] lod=%d case=%s p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n",
|
std::printf("[ScreenSpaceNegative] lod=%d case=%s p50_px=%.4f p99_px=%.4f max_px=%.4f shade_max=%.6f holes=%llu cracks=%llu floating=%llu status=%s\n",
|
||||||
Lod,Name,BadError.P50,BadError.P99,BadError.Maximum,
|
Lod,Name,BadError.Geometry.P50,BadError.Geometry.P99,BadError.Geometry.Maximum,
|
||||||
static_cast<unsigned long long>(BadError.Missing),
|
BadError.ShadeMaximum,static_cast<unsigned long long>(BadError.Holes),
|
||||||
|
static_cast<unsigned long long>(BadError.Cracks),
|
||||||
static_cast<unsigned long long>(BadError.Floating),
|
static_cast<unsigned long long>(BadError.Floating),
|
||||||
Rejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
|
Rejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
|
||||||
};
|
};
|
||||||
CheckNegative("s13-global-octave-drop",1,1);
|
CheckNegative("s13-global-octave-drop",1,1);
|
||||||
CheckNegative("stride-2-column-interpolation",0,2);
|
CheckNegative("stride-2-column-interpolation",0,2);
|
||||||
GatePass=GatePass&&NegativeControlsPass;
|
GatePass=GatePass&&FaultControlsPass&&NegativeControlsPass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FCoverageRow
|
||||||
|
{
|
||||||
|
std::uint64_t Tiles=0,Holes=0,Cracks=0,Floating=0,MaterialMismatches=0;
|
||||||
|
std::vector<double> GeometryPixels,ShadeValues,NormalAngles;
|
||||||
|
};
|
||||||
|
std::array<FCoverageRow,9> Coverage;
|
||||||
|
for(const FVoxelTileKey& Key:DesiredKeys)
|
||||||
|
{
|
||||||
|
const int Lod=Key.Level;
|
||||||
|
if(Lod<3||Lod>MaxLod||Lod>=9)continue;
|
||||||
|
FStandaloneCpuTile BaselineTile=Streamer.GenerateDiagnostic(
|
||||||
|
Key,0,1,true,&BaselineOptions,true);
|
||||||
|
FStandaloneCpuTile CandidateTile=Streamer.GenerateDiagnostic(
|
||||||
|
Key,0,1,true,&CandidateOptions,true);
|
||||||
|
const FStandaloneCpuTile* BaselinePointer=&BaselineTile;
|
||||||
|
const FStandaloneCpuTile* CandidatePointer=&CandidateTile;
|
||||||
|
const std::span<const FStandaloneCpuTile* const> BaselineSpan(&BaselinePointer,1);
|
||||||
|
const std::span<const FStandaloneCpuTile* const> CandidateSpan(&CandidatePointer,1);
|
||||||
|
const FHeightGrid BaselineGrid=Rasterize(BaselineSpan,Key,VoxelMeters);
|
||||||
|
const FHeightGrid CandidateGrid=Rasterize(CandidateSpan,Key,VoxelMeters);
|
||||||
|
const FMeshComparisonRow Delta=CompareMeshes(BaselineGrid,CandidateGrid,
|
||||||
|
Key,VoxelMeters,Budgets[Lod],View);
|
||||||
|
auto& Row=Coverage[Lod];
|
||||||
|
++Row.Tiles;
|
||||||
|
Row.Holes+=Delta.Holes;Row.Cracks+=Delta.Cracks;Row.Floating+=Delta.Floating;
|
||||||
|
Row.MaterialMismatches+=Delta.MaterialMismatches;
|
||||||
|
Row.GeometryPixels.insert(Row.GeometryPixels.end(),
|
||||||
|
Delta.GeometryPixels.begin(),Delta.GeometryPixels.end());
|
||||||
|
Row.ShadeValues.insert(Row.ShadeValues.end(),Delta.ShadeValues.begin(),Delta.ShadeValues.end());
|
||||||
|
Row.NormalAngles.insert(Row.NormalAngles.end(),Delta.NormalAngles.begin(),Delta.NormalAngles.end());
|
||||||
|
}
|
||||||
|
for(int Lod=3;Lod<=MaxLod&&Lod<9;++Lod)
|
||||||
|
{
|
||||||
|
auto& Row=Coverage[Lod];
|
||||||
|
const double GeometryP50=Percentile(Row.GeometryPixels,0.50);
|
||||||
|
const double GeometryP99=Percentile(Row.GeometryPixels,0.99);
|
||||||
|
const double GeometryMaximum=Row.GeometryPixels.empty()?0.0
|
||||||
|
:*std::max_element(Row.GeometryPixels.begin(),Row.GeometryPixels.end());
|
||||||
|
const double ShadeP50=Percentile(Row.ShadeValues,0.50);
|
||||||
|
const double ShadeP99=Percentile(Row.ShadeValues,0.99);
|
||||||
|
const double ShadeMaximum=Row.ShadeValues.empty()?0.0
|
||||||
|
:*std::max_element(Row.ShadeValues.begin(),Row.ShadeValues.end());
|
||||||
|
const double NormalP50=Percentile(Row.NormalAngles,0.50);
|
||||||
|
const double NormalP99=Percentile(Row.NormalAngles,0.99);
|
||||||
|
const double NormalMaximum=Row.NormalAngles.empty()?0.0
|
||||||
|
:*std::max_element(Row.NormalAngles.begin(),Row.NormalAngles.end());
|
||||||
|
const bool LodPass=Row.Tiles>0&&Row.GeometryPixels.size()>=16
|
||||||
|
&&GeometryMaximum<=1.0&&ShadeMaximum<=ShadeLimit
|
||||||
|
&&Row.Holes==0&&Row.Cracks==0&&Row.Floating==0;
|
||||||
|
GatePass=GatePass&&LodPass;
|
||||||
|
std::printf("[ScreenSpaceGateCoverageLOD] lod=%d tiles=%llu geometry_samples=%llu geometry_px_p50=%.4f p99=%.4f max=%.4f shading_delta_p50=%.6f p99=%.6f max=%.6f normal_deg_p50=%.4f p99=%.4f max=%.4f material_mismatch=%llu holes=%llu cracks=%llu floating=%llu status=%s\n",
|
||||||
|
Lod,static_cast<unsigned long long>(Row.Tiles),
|
||||||
|
static_cast<unsigned long long>(Row.GeometryPixels.size()),
|
||||||
|
GeometryP50,GeometryP99,GeometryMaximum,ShadeP50,ShadeP99,ShadeMaximum,
|
||||||
|
NormalP50,NormalP99,NormalMaximum,
|
||||||
|
static_cast<unsigned long long>(Row.MaterialMismatches),
|
||||||
|
static_cast<unsigned long long>(Row.Holes),
|
||||||
|
static_cast<unsigned long long>(Row.Cracks),
|
||||||
|
static_cast<unsigned long long>(Row.Floating),LodPass?"PASS":"REJECT");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t RepairOpportunities=0,BaselineWallHoles=0,BaselineWallFloating=0;
|
||||||
|
std::uint64_t CandidateWallRays=0,CandidateWallHoles=0,CandidateWallFloating=0;
|
||||||
|
std::vector<std::uint64_t> BaselineHoleIds,CandidateHoleIds;
|
||||||
|
std::vector<std::uint64_t> BaselineFloatingIds,CandidateFloatingIds;
|
||||||
|
std::vector<std::uint64_t> CandidatePanelRemovalHoleIds;
|
||||||
|
bool bRanPanelRemovalControl=false;
|
||||||
|
for(const FVoxelTileKey& Key:DesiredKeys)
|
||||||
|
{
|
||||||
|
if(Key.Level<6||Key.Level>8)continue;
|
||||||
|
VoxelForgeCore::TileGenerationOptions NoPanelBaseline=BaselineOptions;
|
||||||
|
NoPanelBaseline.diagnosticRemoveFarConnectorPanels=true;
|
||||||
|
NoPanelBaseline.diagnosticAuditFarConnectorCoverage=true;
|
||||||
|
const FStandaloneCpuTile BaseNoPanels=Streamer.GenerateDiagnostic(Key,0,1,true,&NoPanelBaseline);
|
||||||
|
RepairOpportunities+=BaseNoPanels.CoreDiagnostics.farConnectorAuditHoles;
|
||||||
|
|
||||||
|
VoxelForgeCore::TileGenerationOptions AuditedBaseline=BaselineOptions;
|
||||||
|
AuditedBaseline.diagnosticAuditFarConnectorCoverage=true;
|
||||||
|
const FStandaloneCpuTile BaseRepaired=Streamer.GenerateDiagnostic(Key,0,1,true,&AuditedBaseline);
|
||||||
|
BaselineWallHoles+=BaseRepaired.CoreDiagnostics.farConnectorAuditHoles;
|
||||||
|
BaselineWallFloating+=BaseRepaired.CoreDiagnostics.farConnectorAuditFloating;
|
||||||
|
BaselineHoleIds.insert(BaselineHoleIds.end(),
|
||||||
|
BaseRepaired.CoreDiagnostics.farConnectorAuditHoleIds.begin(),
|
||||||
|
BaseRepaired.CoreDiagnostics.farConnectorAuditHoleIds.end());
|
||||||
|
BaselineFloatingIds.insert(BaselineFloatingIds.end(),
|
||||||
|
BaseRepaired.CoreDiagnostics.farConnectorAuditFloatingIds.begin(),
|
||||||
|
BaseRepaired.CoreDiagnostics.farConnectorAuditFloatingIds.end());
|
||||||
|
|
||||||
|
VoxelForgeCore::TileGenerationOptions AuditedCandidate=CandidateOptions;
|
||||||
|
AuditedCandidate.diagnosticAuditFarConnectorCoverage=true;
|
||||||
|
const FStandaloneCpuTile CandidateRepaired=Streamer.GenerateDiagnostic(Key,0,1,true,&AuditedCandidate);
|
||||||
|
CandidateWallRays+=CandidateRepaired.CoreDiagnostics.farConnectorAuditRays;
|
||||||
|
CandidateWallHoles+=CandidateRepaired.CoreDiagnostics.farConnectorAuditHoles;
|
||||||
|
CandidateWallFloating+=CandidateRepaired.CoreDiagnostics.farConnectorAuditFloating;
|
||||||
|
CandidateHoleIds.insert(CandidateHoleIds.end(),
|
||||||
|
CandidateRepaired.CoreDiagnostics.farConnectorAuditHoleIds.begin(),
|
||||||
|
CandidateRepaired.CoreDiagnostics.farConnectorAuditHoleIds.end());
|
||||||
|
CandidateFloatingIds.insert(CandidateFloatingIds.end(),
|
||||||
|
CandidateRepaired.CoreDiagnostics.farConnectorAuditFloatingIds.begin(),
|
||||||
|
CandidateRepaired.CoreDiagnostics.farConnectorAuditFloatingIds.end());
|
||||||
|
|
||||||
|
if(!bRanPanelRemovalControl&&BaseNoPanels.CoreDiagnostics.farConnectorAuditHoles>0)
|
||||||
|
{
|
||||||
|
AuditedCandidate.diagnosticRemoveFarConnectorPanels=true;
|
||||||
|
const FStandaloneCpuTile CandidateNoPanels=Streamer.GenerateDiagnostic(
|
||||||
|
Key,0,1,true,&AuditedCandidate);
|
||||||
|
CandidatePanelRemovalHoleIds=CandidateNoPanels.CoreDiagnostics.farConnectorAuditHoleIds;
|
||||||
|
bRanPanelRemovalControl=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::sort(BaselineHoleIds.begin(),BaselineHoleIds.end());
|
||||||
|
std::sort(CandidateHoleIds.begin(),CandidateHoleIds.end());
|
||||||
|
std::sort(BaselineFloatingIds.begin(),BaselineFloatingIds.end());
|
||||||
|
std::sort(CandidateFloatingIds.begin(),CandidateFloatingIds.end());
|
||||||
|
const auto IsSubset=[](const std::vector<std::uint64_t>& A,const std::vector<std::uint64_t>& B)
|
||||||
|
{return std::includes(B.begin(),B.end(),A.begin(),A.end());};
|
||||||
|
std::sort(CandidatePanelRemovalHoleIds.begin(),CandidatePanelRemovalHoleIds.end());
|
||||||
|
const bool bNoNewHoles=IsSubset(CandidateHoleIds,BaselineHoleIds);
|
||||||
|
const bool bNoNewFloating=IsSubset(CandidateFloatingIds,BaselineFloatingIds);
|
||||||
|
const bool bPanelRemovalDetected=bRanPanelRemovalControl
|
||||||
|
&&!IsSubset(CandidatePanelRemovalHoleIds,CandidateHoleIds);
|
||||||
|
std::vector<std::uint64_t> NewHoleIds,NewFloatingIds,PanelRemovalNewHoleIds;
|
||||||
|
std::set_difference(CandidateHoleIds.begin(),CandidateHoleIds.end(),
|
||||||
|
BaselineHoleIds.begin(),BaselineHoleIds.end(),std::back_inserter(NewHoleIds));
|
||||||
|
std::set_difference(CandidateFloatingIds.begin(),CandidateFloatingIds.end(),
|
||||||
|
BaselineFloatingIds.begin(),BaselineFloatingIds.end(),std::back_inserter(NewFloatingIds));
|
||||||
|
std::set_difference(CandidatePanelRemovalHoleIds.begin(),CandidatePanelRemovalHoleIds.end(),
|
||||||
|
CandidateHoleIds.begin(),CandidateHoleIds.end(),std::back_inserter(PanelRemovalNewHoleIds));
|
||||||
|
const bool bRepairRowsPass=RepairOpportunities>0&&bNoNewHoles&&bNoNewFloating
|
||||||
|
&&bPanelRemovalDetected;
|
||||||
|
GatePass=GatePass&&bRepairRowsPass;
|
||||||
|
std::printf("[ScreenSpaceConnectorGate] lods=6..8 baseline_missing_ray_stations=%llu candidate_audited_rays=%llu baseline_holes=%llu candidate_holes=%llu new_holes=%llu baseline_floating=%llu candidate_floating=%llu new_floating=%llu panel_removal_new_holes=%llu status=%s\n",
|
||||||
|
static_cast<unsigned long long>(RepairOpportunities),
|
||||||
|
static_cast<unsigned long long>(CandidateWallRays),
|
||||||
|
static_cast<unsigned long long>(BaselineWallHoles),
|
||||||
|
static_cast<unsigned long long>(CandidateWallHoles),
|
||||||
|
static_cast<unsigned long long>(NewHoleIds.size()),
|
||||||
|
static_cast<unsigned long long>(BaselineWallFloating),
|
||||||
|
static_cast<unsigned long long>(CandidateWallFloating),
|
||||||
|
static_cast<unsigned long long>(NewFloatingIds.size()),
|
||||||
|
static_cast<unsigned long long>(PanelRemovalNewHoleIds.size()),
|
||||||
|
bRepairRowsPass?"PASS":"REJECT");
|
||||||
std::printf("[ScreenSpaceGateResult] status=%s\n",GatePass?"PASS":"REJECT");
|
std::printf("[ScreenSpaceGateResult] status=%s\n",GatePass?"PASS":"REJECT");
|
||||||
return GatePass;
|
return GatePass;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -412,6 +412,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
|
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
|
||||||
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
|
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
|
||||||
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache);
|
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache);
|
||||||
|
Streamer.SetDisplayAttributeCaptureEnabled(bScreenSpaceGate);
|
||||||
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
|
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
|
||||||
if(bScreenSpaceGate||bReportLodWorkProfile)Streamer.SetMesherStageProfilingEnabled(true);
|
if(bScreenSpaceGate||bReportLodWorkProfile)Streamer.SetMesherStageProfilingEnabled(true);
|
||||||
const auto StreamStart=Clock::now();
|
const auto StreamStart=Clock::now();
|
||||||
@@ -1480,7 +1481,7 @@ namespace StandaloneStreamBenchmark
|
|||||||
{
|
{
|
||||||
if(Assembly)
|
if(Assembly)
|
||||||
bScreenSpaceGatePassing=StandaloneScreenSpaceGate::Run(GateSampleTiles,
|
bScreenSpaceGatePassing=StandaloneScreenSpaceGate::Run(GateSampleTiles,
|
||||||
Streamer,*Assembly,ClosestDisplayDistances,World.CoreVoxelSizeMicrons,
|
Streamer,Desired.Tiles,*Assembly,ClosestDisplayDistances,World.CoreVoxelSizeMicrons,
|
||||||
World.MaxClipLevel,PixelBudgetsSubvoxels,CameraYawDegrees,
|
World.MaxClipLevel,PixelBudgetsSubvoxels,CameraYawDegrees,
|
||||||
CameraPitchDegrees);
|
CameraPitchDegrees);
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -43,9 +43,11 @@ struct FStandaloneCpuTile
|
|||||||
FVoxelTileKey Key;
|
FVoxelTileKey Key;
|
||||||
uint64 RequestedCrossing=0;
|
uint64 RequestedCrossing=0;
|
||||||
std::vector<FStandaloneCpuVertex> Vertices;
|
std::vector<FStandaloneCpuVertex> Vertices;
|
||||||
|
std::vector<std::array<uint8,4>> VertexColors;
|
||||||
std::vector<uint32> Indices;
|
std::vector<uint32> Indices;
|
||||||
std::vector<uint8> TriangleSkirtFaceMasks;
|
std::vector<uint8> TriangleSkirtFaceMasks;
|
||||||
uint32 SurfaceVertexCount=0;
|
uint32 SurfaceVertexCount=0;
|
||||||
|
uint32 CeilingTriangleCount=0;
|
||||||
uint32 SkirtTriangleCount=0;
|
uint32 SkirtTriangleCount=0;
|
||||||
uint64 DensitySamples=0;
|
uint64 DensitySamples=0;
|
||||||
double WorkerSeconds=0.0;
|
double WorkerSeconds=0.0;
|
||||||
@@ -653,12 +655,17 @@ public:
|
|||||||
{bProfileMesherStages=Enabled;}
|
{bProfileMesherStages=Enabled;}
|
||||||
void SetUniformBlockProofAuditEnabled(bool Enabled)
|
void SetUniformBlockProofAuditEnabled(bool Enabled)
|
||||||
{bAuditUniformBlockProofs=Enabled;}
|
{bAuditUniformBlockProofs=Enabled;}
|
||||||
|
void SetDisplayAttributeCaptureEnabled(bool Enabled)
|
||||||
|
{bCaptureDisplayAttributes=Enabled;}
|
||||||
FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key,
|
FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key,
|
||||||
std::uint32_t GlobalOctaveDrop=0,std::uint32_t CellStride=1,
|
std::uint32_t GlobalOctaveDrop=0,std::uint32_t CellStride=1,
|
||||||
bool ForceGenerate=false)
|
bool ForceGenerate=false,
|
||||||
|
const VoxelForgeCore::TileGenerationOptions* FarDisplayOverride=nullptr,
|
||||||
|
bool CaptureDisplayAttributes=false)
|
||||||
{
|
{
|
||||||
FStandaloneTileJob Job;Job.Key=Key;Job.StartedAt=FClock::now();Job.QueuedAt=Job.StartedAt;
|
FStandaloneTileJob Job;Job.Key=Key;Job.StartedAt=FClock::now();Job.QueuedAt=Job.StartedAt;
|
||||||
return Generate(Job,GlobalOctaveDrop,CellStride,ForceGenerate);
|
return Generate(Job,GlobalOctaveDrop,CellStride,ForceGenerate,FarDisplayOverride,
|
||||||
|
CaptureDisplayAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -778,7 +785,9 @@ private:
|
|||||||
}
|
}
|
||||||
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job,
|
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job,
|
||||||
std::uint32_t DiagnosticGlobalOctaveDrop=0,std::uint32_t DiagnosticCellStride=1,
|
std::uint32_t DiagnosticGlobalOctaveDrop=0,std::uint32_t DiagnosticCellStride=1,
|
||||||
bool ForceGenerate=false)
|
bool ForceGenerate=false,
|
||||||
|
const VoxelForgeCore::TileGenerationOptions* FarDisplayOverride=nullptr,
|
||||||
|
bool CaptureDisplayAttributes=false)
|
||||||
{
|
{
|
||||||
const auto StartTime=Job.StartedAt;
|
const auto StartTime=Job.StartedAt;
|
||||||
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
|
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
|
||||||
@@ -829,6 +838,18 @@ private:
|
|||||||
Options.diagnosticCellStride=std::max<std::uint32_t>(1,DiagnosticCellStride);
|
Options.diagnosticCellStride=std::max<std::uint32_t>(1,DiagnosticCellStride);
|
||||||
Options.diagnosticColumnInterpolationStride=
|
Options.diagnosticColumnInterpolationStride=
|
||||||
std::max<std::uint32_t>(1,DiagnosticCellStride);
|
std::max<std::uint32_t>(1,DiagnosticCellStride);
|
||||||
|
if(FarDisplayOverride)
|
||||||
|
{
|
||||||
|
Options.overrideFarDisplayMesh=FarDisplayOverride->overrideFarDisplayMesh;
|
||||||
|
Options.farDisplayGeometryBudgetMillipixels=FarDisplayOverride->farDisplayGeometryBudgetMillipixels;
|
||||||
|
Options.farRefinementGridScale=FarDisplayOverride->farRefinementGridScale;
|
||||||
|
Options.farRefinementLodMask=FarDisplayOverride->farRefinementLodMask;
|
||||||
|
Options.farConnectorStationScale=FarDisplayOverride->farConnectorStationScale;
|
||||||
|
Options.farConnectorRayMask=FarDisplayOverride->farConnectorRayMask;
|
||||||
|
Options.farConnectorReuseStationRadii=FarDisplayOverride->farConnectorReuseStationRadii;
|
||||||
|
Options.diagnosticRemoveFarConnectorPanels=FarDisplayOverride->diagnosticRemoveFarConnectorPanels;
|
||||||
|
Options.diagnosticAuditFarConnectorCoverage=FarDisplayOverride->diagnosticAuditFarConnectorCoverage;
|
||||||
|
}
|
||||||
std::string Error;
|
std::string Error;
|
||||||
auto Result=CoreWorld->generateTile(
|
auto Result=CoreWorld->generateTile(
|
||||||
{{Job.Key.Coord.X,Job.Key.Coord.Y,Job.Key.Coord.Z},Job.Key.Level},Options,Error);
|
{{Job.Key.Coord.X,Job.Key.Coord.Y,Job.Key.Coord.Z},Job.Key.Level},Options,Error);
|
||||||
@@ -842,6 +863,7 @@ private:
|
|||||||
Out.DensitySamples=Result->densitySampleCount;
|
Out.DensitySamples=Result->densitySampleCount;
|
||||||
Out.CoreDiagnostics=Result->diagnostics;
|
Out.CoreDiagnostics=Result->diagnostics;
|
||||||
Out.SurfaceVertexCount=Result->groups.surfaceVertexCount;
|
Out.SurfaceVertexCount=Result->groups.surfaceVertexCount;
|
||||||
|
Out.CeilingTriangleCount=Result->groups.ceilingTriangleCount;
|
||||||
Out.SkirtTriangleCount=Result->groups.skirtTriangleCount;
|
Out.SkirtTriangleCount=Result->groups.skirtTriangleCount;
|
||||||
Out.MeshPayloadAllocatedBytes=Result->meshPayloadAllocatedBytes;
|
Out.MeshPayloadAllocatedBytes=Result->meshPayloadAllocatedBytes;
|
||||||
Out.DensityHash=Result->densityHash;Out.MeshHash=Result->meshHash;
|
Out.DensityHash=Result->densityHash;Out.MeshHash=Result->meshHash;
|
||||||
@@ -849,12 +871,18 @@ private:
|
|||||||
Out.bHasDensityHash=Result->hasDensityHash;Out.bHasMeshHash=Result->hasMeshHash;
|
Out.bHasDensityHash=Result->hasDensityHash;Out.bHasMeshHash=Result->hasMeshHash;
|
||||||
Out.bHasPayloadHash=Result->hasPayloadHash;Out.bHasTopologyHash=Result->hasTopologyHash;
|
Out.bHasPayloadHash=Result->hasPayloadHash;Out.bHasTopologyHash=Result->hasTopologyHash;
|
||||||
Out.Vertices.reserve(Result->vertices.size());
|
Out.Vertices.reserve(Result->vertices.size());
|
||||||
|
const bool bCaptureAttributes=CaptureDisplayAttributes||bCaptureDisplayAttributes;
|
||||||
|
if(bCaptureAttributes)Out.VertexColors.reserve(Result->vertices.size());
|
||||||
for(const auto& Vertex:Result->vertices)
|
for(const auto& Vertex:Result->vertices)
|
||||||
|
{
|
||||||
Out.Vertices.push_back({
|
Out.Vertices.push_back({
|
||||||
{static_cast<float>(Vertex.position.x*0.01),static_cast<float>(Vertex.position.y*0.01),
|
{static_cast<float>(Vertex.position.x*0.01),static_cast<float>(Vertex.position.y*0.01),
|
||||||
static_cast<float>(Vertex.position.z*0.01)},
|
static_cast<float>(Vertex.position.z*0.01)},
|
||||||
{static_cast<float>(Vertex.normal.x),static_cast<float>(Vertex.normal.y),
|
{static_cast<float>(Vertex.normal.x),static_cast<float>(Vertex.normal.y),
|
||||||
static_cast<float>(Vertex.normal.z)}});
|
static_cast<float>(Vertex.normal.z)}});
|
||||||
|
if(bCaptureAttributes)
|
||||||
|
Out.VertexColors.push_back({Vertex.color.r,Vertex.color.g,Vertex.color.b,Vertex.color.a});
|
||||||
|
}
|
||||||
Out.Indices=std::move(Result->indices);
|
Out.Indices=std::move(Result->indices);
|
||||||
Out.TriangleSkirtFaceMasks=std::move(Result->skirtFaceMasks);
|
Out.TriangleSkirtFaceMasks=std::move(Result->skirtFaceMasks);
|
||||||
if(bProfile)
|
if(bProfile)
|
||||||
@@ -1001,6 +1029,7 @@ private:
|
|||||||
bool bSkipUV=false;
|
bool bSkipUV=false;
|
||||||
bool bSkipColors=false;
|
bool bSkipColors=false;
|
||||||
bool bCaptureColumnCoordinates=false;
|
bool bCaptureColumnCoordinates=false;
|
||||||
|
bool bCaptureDisplayAttributes=false;
|
||||||
bool bSharedColumnCache=true;
|
bool bSharedColumnCache=true;
|
||||||
int32 Lod8HorizonInteriorStepVoxels=0;
|
int32 Lod8HorizonInteriorStepVoxels=0;
|
||||||
bool bProfileMesherStages=false;
|
bool bProfileMesherStages=false;
|
||||||
|
|||||||
@@ -30,6 +30,34 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
return std::max<std::int64_t>(1,WorldAssemblyFixed(wavelength));
|
return std::max<std::int64_t>(1,WorldAssemblyFixed(wavelength));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool ApplyFarDisplayMeshDeclaration(const VFJson::Value& root,
|
||||||
|
VoxelForgeCore::WorldAssemblyRecipeV1& recipe,std::string& error)
|
||||||
|
{
|
||||||
|
const VFJson::Value& declaration=root.Get("far_display_mesh");
|
||||||
|
if(std::holds_alternative<std::nullptr_t>(declaration.Data))return true;
|
||||||
|
if(!declaration.IsObject())
|
||||||
|
{error="far_display_mesh must be an object when declared";return false;}
|
||||||
|
const auto ReadUnsigned=[&](const char* key,std::uint32_t& output)
|
||||||
|
{
|
||||||
|
const VFJson::Value& value=declaration.Get(key);
|
||||||
|
if(!value.IsNumber()||!std::isfinite(value.Number())||value.Number()<0.0
|
||||||
|
||value.Number()>static_cast<double>(UINT32_MAX)
|
||||||
|
||std::floor(value.Number())!=value.Number())return false;
|
||||||
|
output=static_cast<std::uint32_t>(value.Number());return true;
|
||||||
|
};
|
||||||
|
if(!ReadUnsigned("geometry_budget_millipixels",recipe.farDisplayGeometryBudgetMillipixels)
|
||||||
|
||!ReadUnsigned("refinement_grid_scale",recipe.farRefinementGridScale)
|
||||||
|
||!ReadUnsigned("refinement_lod_mask",recipe.farRefinementLodMask)
|
||||||
|
||!ReadUnsigned("connector_station_scale",recipe.farConnectorStationScale)
|
||||||
|
||!ReadUnsigned("connector_ray_mask",recipe.farConnectorRayMask))
|
||||||
|
{error="far_display_mesh declaration has a missing or non-integer parameter";return false;}
|
||||||
|
const VFJson::Value& reuse=declaration.Get("connector_reuse_station_radii");
|
||||||
|
if(!std::holds_alternative<bool>(reuse.Data))
|
||||||
|
{error="far_display_mesh connector_reuse_station_radii must be boolean";return false;}
|
||||||
|
recipe.farConnectorReuseStationRadii=std::get<bool>(reuse.Data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
inline uint64 WorldAssemblyFeatureId(uint32 seed,int64 x,int64 y,uint32 kind)
|
inline uint64 WorldAssemblyFeatureId(uint32 seed,int64 x,int64 y,uint32 kind)
|
||||||
{
|
{
|
||||||
uint64 hash=14695981039346656037ull;
|
uint64 hash=14695981039346656037ull;
|
||||||
@@ -223,6 +251,7 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
recipe.biomeClimateReliefWavelengthSubvoxels=WorldAssemblyWavelength(map.ReliefFrequency,3333.0);
|
recipe.biomeClimateReliefWavelengthSubvoxels=WorldAssemblyWavelength(map.ReliefFrequency,3333.0);
|
||||||
recipe.biomeClimateMoistureWavelengthSubvoxels=WorldAssemblyWavelength(map.MoistureFrequency,2500.0);
|
recipe.biomeClimateMoistureWavelengthSubvoxels=WorldAssemblyWavelength(map.MoistureFrequency,2500.0);
|
||||||
recipe.biomeClimateReliefContrastQ16=WorldAssemblyQ16(map.ReliefContrast);
|
recipe.biomeClimateReliefContrastQ16=WorldAssemblyQ16(map.ReliefContrast);
|
||||||
|
if(!ApplyFarDisplayMeshDeclaration(root,recipe,error))return false;
|
||||||
const auto makeProfile=[&](uint32 stableId,const FSurfaceGenerationParams& params,
|
const auto makeProfile=[&](uint32 stableId,const FSurfaceGenerationParams& params,
|
||||||
const FBiomeResolved* climate)
|
const FBiomeResolved* climate)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user