wip(assembler): round L22 -- one 8 m layout chunk for every strate; real ceiling -> content -> bedrock
StrateLayoutChunkSizeMeters (world setting, default 8 m; owner decision) is resolved once in the director/adapter and standalone loader; every generator reads the resolved width (no CHUNK_SIZE / Core-tile inference). Every strate gets solid ceiling and bedrock bands from its BoundarySealThickness in the shared composition path (diggable by default); faces come from the shared boundary path. Owner world: FloatingIslands +8..-120 m (128 m, now with a bedrock floor), SurfaceWorld -120..-632 m (unchanged; full-detail and exact-query hashes equal L21); the 60 m gap is gone. The strict layout validator passes on the real world, with 0 endpoint dispatch disagreements. Spawn/benchmark camera derived from route.front. Ledges under the islands gone (reviewer capture). Evaluator 7, recipe schema 2, layout snapshot 2, FI geometry 4. Open: SW cap profiles exceed the ceiling band (owner call); transitions still Hard; Unreal setting/manager/adapter edits uncompiled here. 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
8753f6dbe4
commit
55c26a05a7
28 files changed
+1423
-570
No files matched your search
+26
-1
@@ -1869,7 +1869,7 @@ query, stratum position, scale, or asynchronous route changes. The strict LOD0
|
||||
audit and the matched before/after captures are in
|
||||
[`Standalone/Research/WorldAssemblerM3/Linux19/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux19/REPORT.md).
|
||||
|
||||
### 8.39 Resolved layout snapshots and nominal strate bands — World Assembler L21
|
||||
### 8.39 Resolved layout snapshots and nominal strate bands — World Assembler L21 (historical)
|
||||
|
||||
`ResolvedLayoutSnapshotV1` is a separate Core-owned record built from the resolved manager slots and
|
||||
the native SurfaceWorld/FloatingIslands plans. It has a canonical JSON representation, schema version,
|
||||
@@ -1899,6 +1899,31 @@ parameter blend at either recipe boundary. The FI v3 upper seal remains a ceilin
|
||||
content range, and its missing lower bedrock is recorded without changing evaluation. See
|
||||
[`Standalone/Research/WorldAssemblerM3/Linux21/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux21/REPORT.md).
|
||||
|
||||
### 8.40 Fixed-metre strate layout units and shared boundary volumes — World Assembler L22
|
||||
|
||||
`StrateLayoutChunkSizeMeters` is one world-level setting, defaulting to **8 m**, independent of the
|
||||
legacy 32-voxel chunk and Core's 32³ storage tile. The director resolves the setting once to physical
|
||||
micrometres and Core subvoxels; every generator plan and dispatch reads that snapshot. At a 25 cm Core
|
||||
voxel, an 8 m layout chunk is 32 Core voxels (8192 1/256-voxel units); at 20 cm it is 40 Core voxels
|
||||
(10240 units). The Core tile remains 32³ voxels and is not a layout chunk.
|
||||
|
||||
The owner's FI/SW intervals are contiguous: FloatingIslands occupies [−120,+8) m and SurfaceWorld
|
||||
occupies [−632,−120) m. Each strate is ceiling → content → bedrock. Both solid, diggable boundary
|
||||
bands use its generator's existing `BoundarySealThickness` and `BaseDensity`; `InterStrateGapChunks`
|
||||
controls only spacing, and `EdgeSealThickness` remains horizontal. No new thickness parameter or hidden
|
||||
band height was added. The shared composition layer emits both volumes for every generator and the
|
||||
shared mesh path closes their visible ceiling and floor faces.
|
||||
|
||||
The snapshot is schema V2 and is consumed by bounds planning, native and legacy dispatch, solid-band
|
||||
composition, and validation. Strict validation accepts the real owner layouts, and the boundary probe
|
||||
audit agrees at all 27 sampled endpoints in both recipe modes. The owner authored Gradient transition
|
||||
is recorded as an explicit Hard fallback with zero interpenetration; Gradient and Interleaved mixing
|
||||
remain unimplemented and must not be inferred from the declared blend width. Seven validator negative
|
||||
controls continue to reject malformed bands.
|
||||
|
||||
Evidence and owner caveats are in
|
||||
[`Standalone/Research/WorldAssemblerM3/Linux22/REPORT.md`](Standalone/Research/WorldAssemblerM3/Linux22/REPORT.md).
|
||||
|
||||
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
||||
|
||||
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
||||
|
||||
@@ -26,9 +26,12 @@ try to run anything.
|
||||
- **Density sign: negative = solid, positive = air** (MC convention at the mesher).
|
||||
`FVoxelModification::Strength` negative = carve. *The #1 source of confusion.*
|
||||
- **Density functions take VOXEL coords, not cm.** The legacy plugin field uses 12.5 cm voxels;
|
||||
the opt-in Core field uses 25 cm voxels and its own tile keys. One legacy 32³ chunk is 4 m.
|
||||
Conversions live in `VoxelTypes.h` and the Core bridge; do not mix scales in queries, anchors, edits,
|
||||
or surface lookups.
|
||||
the opt-in Core field uses 25 cm voxels and its own 32³-voxel storage tile. The legacy 32³ chunk
|
||||
spans 4 m at 12.5 cm. Strate placement uses the separate world-level `StrateLayoutChunkSizeMeters`
|
||||
setting (8 m by default), resolved once and shared by every generator. At 25 cm that layout unit is
|
||||
32 Core voxels; at 20 cm it is 40. Never infer the layout unit from `CHUNK_SIZE` or the Core tile.
|
||||
Conversions live in `VoxelTypes.h`, the director, and the Core bridge; do not mix scales in queries,
|
||||
anchors, edits, or surface lookups.
|
||||
- **Determinism:** all randomness = hash of (coord, seed, strateIndex). No RNG state — same seed ⇒
|
||||
same world. Player edits (diff layer) are the only non-deterministic overlay.
|
||||
- **Core world invariants:** Core density keeps the same sign (negative solid, positive air), but Core
|
||||
|
||||
+11
-7
@@ -29,7 +29,9 @@ A **density-field voxel terrain** plugin for Unreal Engine, built around undergr
|
||||
### Core terminology
|
||||
| Term | Meaning |
|
||||
|------|---------|
|
||||
| Chunk | 32×32×32 voxels (`CHUNK_SIZE`). Voxel = 12.5 cm (`VOXEL_SIZE`), so a chunk is 4 m. |
|
||||
| Legacy chunk | 32×32×32 voxels (`CHUNK_SIZE`). At the legacy 12.5 cm voxel size it spans 4 m; it is not the strate-layout unit or the Core tile. |
|
||||
| Strate-layout chunk | World-level `StrateLayoutChunkSizeMeters`, default 8 m, shared by all generators and independent of voxel size. The director resolves it once and Core reads the resolved snapshot. At 25 cm it is 32 Core voxels; at 20 cm it is 40 Core voxels. |
|
||||
| Core tile | Internal 32×32×32 Core voxel tile. Its metre size follows the Core voxel size and stays separate from the configurable layout chunk. |
|
||||
| Density | Scalar field. `< 0` solid, `> 0` air, `0` = surface (`IsoLevel`). |
|
||||
| Strate | Vertical layer of the world, stacked downward. Has its own generator + params. |
|
||||
| Generator type | Archetype per strate: `TunnelNetwork`, `FlatPlain`, `CrystalChamber`, `Maze`, `SurfaceWorld`, `VerticalShafts`, `FloatingIslands`, `Underwater`. See §8. |
|
||||
@@ -78,10 +80,12 @@ what generator type, and is there a passage/elevator SDF near here?"
|
||||
Paths relative to `Source/VoxelForge/`. `Public/` = headers, `Private/` = impl.
|
||||
|
||||
### 3.0 Human-scale generation contract (2026-10-01)
|
||||
`VOXEL_SIZE` is **12.5 cm**; 32³ voxels make each chunk **4 m**. The gameplay capsule remains
|
||||
**0.34 m radius / 1.76 m tall** (**2.72 / 14.08 voxels**). The default strate envelope is
|
||||
**16 chunks / 512 voxels / 64 m**; serialized owner strate assets that still say 8 chunks must be
|
||||
changed to 16. Research-field dimensions are authored in metres and converted at runtime. The live
|
||||
`VOXEL_SIZE` is **12.5 cm** for the legacy generator. Its 32³-voxel chunks span **4 m**. The
|
||||
world-level strate-layout unit is a separate setting with an **8 m default**; a 16-chunk strate is
|
||||
therefore **128 m**, and a 64-chunk strate is **512 m**. Core still stores work in 32³-voxel tiles;
|
||||
at 25 cm those tiles happen to span 8 m, but they are not the layout unit. The gameplay capsule remains
|
||||
**0.34 m radius / 1.76 m tall** (**2.72 / 14.08 legacy voxels**). Research-field dimensions are authored
|
||||
in metres and converted at runtime. The live
|
||||
strate generator continues to read voxel-valued parameters from its assets, so those authored
|
||||
dimensions become half as large in metres unless their asset values are doubled.
|
||||
|
||||
@@ -163,7 +167,7 @@ remains `0`, and lateral regions remain gated off.
|
||||
| `../../Standalone/Core/src/CoreNamespacePreamble.h` | First include of every Core TU. Forces FP contraction off per compiler (Clang/MSVC/GCC pragmas; unknown compiler = `#error`) and defines `vfcore::fp_contract::selfTest`, which `World::compile` runs before building: a fused multiply-add (e.g. Clang `-ffp-contract=fast`) refuses world generation. The world depends on contraction (Linux2/Linux3 reports), so never remove either. |
|
||||
| `../../VoxelForgeCore/Private/ModuleEntry/` | Unreal module entry and an isolated `CoreMinimal.h` shim for the actual engine header. |
|
||||
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` | Standard-library-only public Core API. `VOXELFORGECORE_API` exports the API from the Unreal module and is empty for standalone builds. |
|
||||
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV1`: versioned micrometre intervals, owned ceiling/content/bedrock bands, transition and dispatch provenance, recipe anchors, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built from resolved plans; no evaluator call sites consume it. §8.39. |
|
||||
| `../../Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `../../Standalone/Core/src/ResolvedLayoutInternal.h` | `ResolvedLayoutSnapshotV2`: physical layout-chunk width, versioned micrometre intervals, owned ceiling/content/bedrock bands, transition and dispatch provenance, recipe anchors, canonical JSON/FNV identity, compatibility/strict validators, and negative-control audit. Built once from the director-resolved plan and consumed by bounds planning, Core dispatch, composition, and validation. §8.40. |
|
||||
| `../../Standalone/Core/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. |
|
||||
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
|
||||
| Step-3 scale and game validation | `step3-scale` measures the same 519³ and 259³ six-worker lattices at 12.5 cm. The cached medians are 7.347496 s and 1.169567 s respectively; determinism and brick sound-proof audits pass. `step3-game` routes in-game generation through the brick evaluator and records tile timings, spawn fill, moving frames, and profile-switch rebuilds. The visible D3D12 run measured tile generation p50/p95/max 3.326/31.045/169.867 ms, full-detail L0 fill 12.369 s for 2,765 tiles, and moving-frame p50/p95/p99/max 4.856/8.922/13.090/80.575 ms. Switching profiles rebuilt 5,815 loaded tiles. Both high-resolution screenshot requests were logged, but the harness found only one fresh PNG and it is a blank pale viewport; no lit comparison capture is accepted. Raw output: `Saved/TestHarness/step3_game_visible_final`; scale output: `Saved/Research/step3/scale/step3_scale_cacheperf_v1`. |
|
||||
@@ -182,7 +186,7 @@ remains `0`, and lateral regions remain gated off.
|
||||
| Symbol | Line | Notes |
|
||||
|--------|------|-------|
|
||||
| `CHUNK_SIZE` (32) | 211 | Chunk dimensions. 64³ would cut draw calls but makes streaming too bursty; fps is fixed render-side instead. |
|
||||
| `VOXEL_SIZE` (12.5f cm) | 213 | World scale; one 32³ chunk is 4 m. |
|
||||
| `VOXEL_SIZE` (12.5f cm) | 213 | Legacy generator scale; `CHUNK_SIZE` 32 spans 4 m here. Keep separate from the 8 m default layout chunk and 32³ Core tile. |
|
||||
| `EVoxelFace` enum | 266 | 6 cube faces. |
|
||||
| `WorldToChunkCoord` | 287 | World cm → chunk coord (handles negatives via floor). |
|
||||
| `VoxelMath::IsFiniteFast` / `IsFinite` | 24 / 45 | Exact IEEE bit test (`voxel.FastIsFinite` A/B switch). |
|
||||
|
||||
@@ -32,6 +32,38 @@ namespace
|
||||
using FCoreJsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
|
||||
constexpr double OwnerVoxelSizeCm = VOXEL_SIZE;
|
||||
|
||||
float StrateBoundarySealThickness(const UVoxelStrateDefinition& Definition)
|
||||
{
|
||||
switch(Definition.GeneratorType)
|
||||
{
|
||||
case ECaveGeneratorType::SurfaceWorld:return Definition.SurfaceParams.BoundarySealThickness;
|
||||
case ECaveGeneratorType::FloatingIslands:return Definition.FloatingIslandParams.BoundarySealThickness;
|
||||
case ECaveGeneratorType::Maze:return Definition.MazeParams.BoundarySealThickness;
|
||||
case ECaveGeneratorType::VerticalShafts:return Definition.VerticalShaftParams.BoundarySealThickness;
|
||||
case ECaveGeneratorType::TunnelNetwork:
|
||||
case ECaveGeneratorType::FlatPlain:
|
||||
case ECaveGeneratorType::CrystalChamber:
|
||||
case ECaveGeneratorType::Underwater:return Definition.GenerationParams.BoundarySealThickness;
|
||||
}
|
||||
return Definition.GenerationParams.BoundarySealThickness;
|
||||
}
|
||||
|
||||
float StrateBoundarySolidDensity(const UVoxelStrateDefinition& Definition)
|
||||
{
|
||||
switch(Definition.GeneratorType)
|
||||
{
|
||||
case ECaveGeneratorType::SurfaceWorld:return Definition.SurfaceParams.BaseDensity;
|
||||
case ECaveGeneratorType::FloatingIslands:return Definition.FloatingIslandParams.BaseDensity;
|
||||
case ECaveGeneratorType::Maze:return Definition.MazeParams.BaseDensity;
|
||||
case ECaveGeneratorType::VerticalShafts:return Definition.VerticalShaftParams.BaseDensity;
|
||||
case ECaveGeneratorType::TunnelNetwork:
|
||||
case ECaveGeneratorType::FlatPlain:
|
||||
case ECaveGeneratorType::CrystalChamber:
|
||||
case ECaveGeneratorType::Underwater:return Definition.GenerationParams.BaseDensity;
|
||||
}
|
||||
return Definition.GenerationParams.BaseDensity;
|
||||
}
|
||||
|
||||
std::int64_t WorldAssemblyFixed(double Voxels)
|
||||
{
|
||||
return static_cast<std::int64_t>(std::llround(
|
||||
@@ -287,8 +319,10 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
|
||||
const FSurfaceGenerationParams Surface = StrateManager->GetSurfaceParamsForChunk(Chunk);
|
||||
const FBiomeContext BiomeContext = StrateManager->GetBiomeContextForChunk(Chunk);
|
||||
const UVoxelStrateDefinition* Definition = Slot.Definition;
|
||||
const double BottomVoxel = static_cast<double>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const double HeightVoxels = static_cast<double>(Slot.TopChunkZ - Slot.BottomChunkZ + 1) * CHUNK_SIZE;
|
||||
const double LayoutChunkVoxels = static_cast<double>(Recipe.layoutChunkWidthSubvoxels)
|
||||
/ WorldAssemblySubvoxelsPerVoxel;
|
||||
const double BottomVoxel = static_cast<double>(Slot.BottomChunkZ) * LayoutChunkVoxels;
|
||||
const double HeightVoxels = static_cast<double>(Slot.TopChunkZ - Slot.BottomChunkZ + 1) * LayoutChunkVoxels;
|
||||
const FBiomeMapParams& Map = BiomeContext.Map;
|
||||
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
|
||||
Recipe.biomeCellSizeSubvoxels = WorldAssemblyFixed(Map.CellSize);
|
||||
@@ -422,7 +456,7 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
|
||||
Shelf.minimumSubvoxels = {WorldAssemblyFixed(MinX), WorldAssemblyFixed(MinY),
|
||||
std::max(WorldAssemblyFixed(BottomVoxel), LowestGround - WorldAssemblyFixed(4.0))};
|
||||
Shelf.maximumSubvoxels = {WorldAssemblyFixed(MaxX), WorldAssemblyFixed(MaxY),
|
||||
std::min(WorldAssemblyFixed((Slot.TopChunkZ + 1) * CHUNK_SIZE),
|
||||
std::min(WorldAssemblyFixed((Slot.TopChunkZ + 1) * LayoutChunkVoxels),
|
||||
HighestGround + WorldAssemblyFixed(ShelfHeight + 4.0))};
|
||||
Shelf.parametersSubvoxels = {WorldAssemblyFixed(DirX), WorldAssemblyFixed(DirY),
|
||||
WorldAssemblyFixed(ShelfHeight), WorldAssemblyFixed(Reach)};
|
||||
@@ -440,7 +474,7 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
|
||||
|
||||
bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
|
||||
const FStrateSlot& Slot, VoxelForgeCore::WorldAssemblyManifest& Manifest,
|
||||
double CoreVoxelSizeCm, FString& OutError)
|
||||
double CoreVoxelSizeCm, std::int64_t LayoutChunkWidthSubvoxels, FString& OutError)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
const FIntVector Chunk(0, 0, Slot.TopChunkZ);
|
||||
@@ -450,7 +484,8 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
|
||||
IslandPlan.topChunkZ = Slot.TopChunkZ;
|
||||
IslandPlan.bottomChunkZ = Slot.BottomChunkZ;
|
||||
auto& Recipe = IslandPlan.manifest.recipe;
|
||||
Recipe.geometryVersion = 3;
|
||||
Recipe.geometryVersion = 4;
|
||||
Recipe.layoutChunkWidthSubvoxels = LayoutChunkWidthSubvoxels;
|
||||
Recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
|
||||
Recipe.ownerVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
|
||||
Recipe.coreVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(CoreVoxelSizeCm * 10000.0));
|
||||
@@ -463,12 +498,10 @@ bool BuildFloatingIslandStratumPlan(const UVoxelStrateManager* StrateManager,
|
||||
}
|
||||
Recipe.ownerToCoreScaleNumerator = Recipe.ownerVoxelSizeMicrons / ScaleGcd;
|
||||
Recipe.ownerToCoreScaleDenominator = Recipe.coreVoxelSizeMicrons / ScaleGcd;
|
||||
const double CoreVoxelsPerOwnerChunk = static_cast<double>(CHUNK_SIZE)
|
||||
* Recipe.ownerToCoreScaleNumerator / Recipe.ownerToCoreScaleDenominator;
|
||||
Recipe.layoutMinimumZSubvoxels = WorldAssemblyFixed(
|
||||
static_cast<double>(Slot.BottomChunkZ) * CoreVoxelsPerOwnerChunk);
|
||||
Recipe.layoutMaximumZSubvoxels = WorldAssemblyFixed(
|
||||
static_cast<double>(Slot.TopChunkZ + 1) * CoreVoxelsPerOwnerChunk);
|
||||
Recipe.layoutMinimumZSubvoxels = static_cast<std::int64_t>(Slot.BottomChunkZ)
|
||||
* LayoutChunkWidthSubvoxels;
|
||||
Recipe.layoutMaximumZSubvoxels = static_cast<std::int64_t>(Slot.TopChunkZ + 1)
|
||||
* LayoutChunkWidthSubvoxels;
|
||||
Recipe.layoutCenterTopZSubvoxels = (Recipe.layoutMinimumZSubvoxels
|
||||
+ Recipe.layoutMaximumZSubvoxels) / 2;
|
||||
Recipe.islandDensityQ16 = WorldAssemblyQ16(FMath::Clamp(Params.IslandDensity, 0.0f, 1.0f));
|
||||
@@ -758,6 +791,12 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
const TSharedRef<FJsonObject>& Root,
|
||||
FString& OutError)
|
||||
{
|
||||
const std::uint32_t LayoutChunkSizeMicrometres=StrateManager->GetStrateLayoutChunkSizeMicrometres();
|
||||
const std::uint32_t CoreVoxelMicrometres=static_cast<std::uint32_t>(std::llround(CoreVoxelSizeCm*10000.0));
|
||||
const std::int64_t LayoutChunkWidthSubvoxels=static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(LayoutChunkSizeMicrometres)*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
|
||||
/std::max<std::uint32_t>(1,CoreVoxelMicrometres)));
|
||||
StrateManager->SetHardTransitionFallbackForAssembly(true);
|
||||
FString ManifestJson;
|
||||
FString ManifestHash;
|
||||
if (!Settings->GetWorldAssemblyManifestJson(ManifestJson, ManifestHash, OutError))
|
||||
@@ -783,6 +822,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
{
|
||||
VoxelForgeCore::WorldAssemblyManifest Generated;
|
||||
Generated.recipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
|
||||
Generated.recipe.layoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels;
|
||||
int32 SurfaceSlotCount = 0;
|
||||
int32 IslandSlotCount = 0;
|
||||
for (const FStrateSlot& Slot : StrateManager->GetLayout())
|
||||
@@ -815,6 +855,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
{
|
||||
VoxelForgeCore::WorldAssemblyRecipeV1 SlotRecipe;
|
||||
SlotRecipe.seed = static_cast<std::uint32_t>(StrateManager->GetWorldSeed());
|
||||
SlotRecipe.layoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels;
|
||||
VoxelForgeCore::WorldAssemblyStratumPlanV1 Stratum;
|
||||
if (!BuildSurfaceStratumPlan(StrateManager, Slot,
|
||||
SlotRecipe, Stratum, OutError))
|
||||
@@ -829,7 +870,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
else if (Type == ECaveGeneratorType::FloatingIslands)
|
||||
{
|
||||
if (!BuildFloatingIslandStratumPlan(StrateManager, Slot, Generated,
|
||||
CoreVoxelSizeCm, OutError))
|
||||
CoreVoxelSizeCm, LayoutChunkWidthSubvoxels, OutError))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -873,7 +914,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
OutError = TEXT("floating-island override uses a different Core voxel size; regenerate the plan.");
|
||||
return false;
|
||||
}
|
||||
if(Island.manifest.recipe.layoutChunkWidthSubvoxels!=LayoutChunkWidthSubvoxels)
|
||||
{OutError=TEXT("floating-island override uses a different strate-layout chunk width; regenerate the plan.");return false;}
|
||||
}
|
||||
if(Manifest->recipe.layoutChunkWidthSubvoxels!=LayoutChunkWidthSubvoxels)
|
||||
{OutError=TEXT("world assembly manifest uses a different strate-layout chunk width; regenerate the plan.");return false;}
|
||||
|
||||
int32 SurfaceSlotCount = 0;
|
||||
int32 FloatingIslandSlotCount = 0;
|
||||
@@ -946,11 +991,13 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
|
||||
// Keep the inspectable physical-layout export separate from the manifest identity: it is
|
||||
// assembled from the same resolved slots/plans, but adding it must not salt evaluation hashes.
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV1 Layout;
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV2 Layout;
|
||||
Layout.worldIdentity = std::string("owner-world/seed/")
|
||||
+ std::to_string(StrateManager->GetWorldSeed());
|
||||
Layout.ownerVoxelSizeMicrometres = static_cast<std::uint32_t>(std::llround(OwnerVoxelSizeCm * 10000.0));
|
||||
Layout.coreVoxelSizeMicrometres = ExpectedCoreVoxelMicrons;
|
||||
Layout.strateLayoutChunkSizeMicrometres=LayoutChunkSizeMicrometres;
|
||||
Layout.strateLayoutChunkWidthSubvoxels=LayoutChunkWidthSubvoxels;
|
||||
const auto generatorName = [](ECaveGeneratorType Type) -> const char*
|
||||
{
|
||||
switch (Type)
|
||||
@@ -987,7 +1034,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
if (Slot.Definition == nullptr) continue;
|
||||
const ECaveGeneratorType Type = StrateManager->GetGeneratorTypeForChunk(
|
||||
FIntVector(0, 0, Slot.TopChunkZ));
|
||||
VoxelForgeCore::ResolvedLayoutStratumV1 Item;
|
||||
VoxelForgeCore::ResolvedLayoutStratumV2 Item;
|
||||
Item.strateIndex = Slot.StrateIndex;
|
||||
Item.generatorKind = generatorName(Type);
|
||||
Item.stableId = Layout.worldIdentity + "/strate/" + std::to_string(Slot.StrateIndex)
|
||||
@@ -998,12 +1045,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
Item.recipeVersion = Type == ECaveGeneratorType::SurfaceWorld ? Manifest->recipe.schemaVersion : 0;
|
||||
Item.voxelSizeMicrometres = ExpectedCoreVoxelMicrons;
|
||||
Item.interiorBottomMicrometres = static_cast<std::int64_t>(Slot.BottomChunkZ)
|
||||
* CHUNK_SIZE * ExpectedCoreVoxelMicrons;
|
||||
* LayoutChunkSizeMicrometres;
|
||||
Item.interiorTopMicrometres = static_cast<std::int64_t>(Slot.TopChunkZ + 1)
|
||||
* CHUNK_SIZE * ExpectedCoreVoxelMicrons;
|
||||
Item.managerChunkWidthMicrometres = CHUNK_SIZE * OwnerVoxelMicrons;
|
||||
Item.dispatchChunkWidthMicrometres = Type == ECaveGeneratorType::FloatingIslands
|
||||
? CHUNK_SIZE * OwnerVoxelMicrons : CHUNK_SIZE * ExpectedCoreVoxelMicrons;
|
||||
* LayoutChunkSizeMicrometres;
|
||||
Item.managerChunkWidthMicrometres = LayoutChunkSizeMicrometres;
|
||||
Item.dispatchChunkWidthMicrometres = LayoutChunkSizeMicrometres;
|
||||
Item.transitionMode = transitionName(Slot.Definition->TransitionType);
|
||||
Item.transitionBlendChunks = Slot.Definition->TransitionBlendChunks;
|
||||
Item.transitionWidthMicrometres = static_cast<std::int64_t>(Item.transitionBlendChunks)
|
||||
@@ -1011,13 +1057,16 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
Item.transitionWarpAmplitudeMicrometres = Item.transitionMode == "Interleaved"
|
||||
? 2 * Item.managerChunkWidthMicrometres : 0;
|
||||
Item.transitionInterpenetrationMicrometres = 0;
|
||||
Item.transitionHonouredInCore = false;
|
||||
Item.transitionDispatchPath = "upper strate owns lower interface; manager GetGenerationParams can blend legacy vectors, but Core's compiled field selects per-strate/native recipe fields without transition blending";
|
||||
Item.transitionHonouredInCore = Slot.Definition->TransitionType == EVoxelStrateTransition::Hard;
|
||||
Item.transitionFallsBackToHard = Slot.Definition->TransitionType != EVoxelStrateTransition::Hard;
|
||||
Item.transitionDispatchPath = Item.transitionFallsBackToHard
|
||||
? "declared Gradient/Interleaved recorded; Core uses an explicit Hard transition fallback with zero interpenetration"
|
||||
: "declared Hard transition is applied at this interface";
|
||||
Item.managerLookup = "UVoxelStrateManager::GetGeneratorTypeForChunk; transition is controlled by upper strate";
|
||||
if (Type == ECaveGeneratorType::SurfaceWorld)
|
||||
{
|
||||
Item.boundDispatchPath = "BuildSurfaceStratumPlan uses slot chunkZ * 32 Core voxels";
|
||||
Item.queryDispatchPath = "NativeSurfaceDensityAt uses floor(CoreZ / 32), then GetGeneratorTypeForChunk/GetStrateIndex";
|
||||
Item.boundDispatchPath = "BuildSurfaceStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV2";
|
||||
Item.queryDispatchPath = "NativeSurfaceDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV2";
|
||||
const auto Plan = std::find_if(Manifest->plan.strates.begin(), Manifest->plan.strates.end(),
|
||||
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
|
||||
if (Plan != Manifest->plan.strates.end())
|
||||
@@ -1025,7 +1074,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
for (const auto& Profile : Manifest->recipe.biomes)
|
||||
if (Profile.hasCapProfile)
|
||||
{
|
||||
VoxelForgeCore::ResolvedLayoutAnchorV1 Anchor;
|
||||
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
|
||||
Anchor.kind = "cap_profile";
|
||||
Anchor.stableId = "biome/" + std::to_string(Profile.stableId);
|
||||
Anchor.units = "1/256 Core voxel";
|
||||
@@ -1037,7 +1086,7 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
for (std::size_t RouteIndex = 0; RouteIndex < Plan->route.size(); ++RouteIndex)
|
||||
{
|
||||
const auto& Point = Plan->route[RouteIndex];
|
||||
VoxelForgeCore::ResolvedLayoutAnchorV1 Anchor;
|
||||
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
|
||||
Anchor.kind = "route_point"; Anchor.stableId = "route/" + std::to_string(RouteIndex);
|
||||
Anchor.units = "1/256 Core voxel";
|
||||
Anchor.values = { Point.positionSubvoxels[0], Point.positionSubvoxels[1],
|
||||
@@ -1045,17 +1094,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
Item.anchors.push_back(std::move(Anchor));
|
||||
}
|
||||
}
|
||||
Item.ceiling = { "ceiling", Item.interiorTopMicrometres, Item.interiorTopMicrometres,
|
||||
false, true, false, "no separate volumetric boundary band; cap profile remains content",
|
||||
"none", "Heightfield2D cap + AppendStratumBoundaryCeilings fallback mesh" };
|
||||
Item.bedrock = { "bedrock", Item.interiorBottomMicrometres,
|
||||
Item.interiorBottomMicrometres, false, true, false,
|
||||
"no lower bedrock band", "none", "none" };
|
||||
}
|
||||
else if (Type == ECaveGeneratorType::FloatingIslands)
|
||||
{
|
||||
Item.boundDispatchPath = "BuildFloatingIslandStratumPlan uses slot chunkZ * 32 * owner/Core voxel ratio";
|
||||
Item.queryDispatchPath = "NativeFloatingIslandDensityAt uses floor(CoreZ / (32 * owner/Core ratio)), then manager lookup";
|
||||
Item.boundDispatchPath = "BuildFloatingIslandStratumPlan uses resolved layout chunk width from ResolvedLayoutSnapshotV2";
|
||||
Item.queryDispatchPath = "NativeFloatingIslandDensityAt selects floor(CoreZ / resolved layout chunk width) from ResolvedLayoutSnapshotV2";
|
||||
const auto Plan = std::find_if(Manifest->floatingIslands.begin(), Manifest->floatingIslands.end(),
|
||||
[&Slot](const auto& Entry) { return Entry.strateIndex == Slot.StrateIndex; });
|
||||
std::int64_t SealMicrometres = 0;
|
||||
@@ -1063,12 +1106,11 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
{
|
||||
Item.interiorBottomMicrometres = FixedToMicrons(Plan->manifest.recipe.layoutMinimumZSubvoxels);
|
||||
Item.interiorTopMicrometres = FixedToMicrons(Plan->manifest.recipe.layoutMaximumZSubvoxels);
|
||||
SealMicrometres = FixedToMicrons(Plan->manifest.recipe.boundarySealSubvoxels);
|
||||
Item.recipeVersion = Plan->manifest.recipe.geometryVersion;
|
||||
for (std::size_t IslandIndex = 0; IslandIndex < Plan->manifest.plan.islands.size(); ++IslandIndex)
|
||||
{
|
||||
const auto& Island = Plan->manifest.plan.islands[IslandIndex];
|
||||
VoxelForgeCore::ResolvedLayoutAnchorV1 Anchor;
|
||||
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
|
||||
Anchor.kind = "island_anchor"; Anchor.stableId = "island/" + std::to_string(IslandIndex);
|
||||
Anchor.units = "1/256 Core voxel";
|
||||
Anchor.values = { Island.centerSubvoxels[0], Island.centerSubvoxels[1],
|
||||
@@ -1076,38 +1118,35 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
Item.anchors.push_back(std::move(Anchor));
|
||||
}
|
||||
}
|
||||
Item.ceiling = { "ceiling", Item.interiorTopMicrometres - SealMicrometres,
|
||||
Item.interiorTopMicrometres, SealMicrometres > 0, true, false,
|
||||
"v3 shared upper boundary seal; one-sided and inset from upper edge",
|
||||
"upper/interior side", "VF_ApplyUpperBoundarySeal" };
|
||||
Item.bedrock = { "bedrock", Item.interiorBottomMicrometres,
|
||||
Item.interiorBottomMicrometres, false, true, false,
|
||||
"no lower seal in FloatingIsland geometryVersion 3", "none", "none" };
|
||||
}
|
||||
else
|
||||
{
|
||||
Item.boundDispatchPath = "resolved manager slot scaled by Core chunk width";
|
||||
Item.queryDispatchPath = "legacy Core route; manager chunk selected by floor(CoreZ / 32)";
|
||||
const std::int64_t SealMicrometres = static_cast<std::int64_t>(std::llround(
|
||||
std::max(0.0f, Slot.Definition->GenerationParams.BoundarySealThickness)
|
||||
* OwnerVoxelMicrons));
|
||||
Item.ceiling = { "ceiling", Item.interiorTopMicrometres,
|
||||
Item.interiorTopMicrometres + SealMicrometres, false, true, false,
|
||||
"manager configures a two-sided legacy seal, but Core's compiled analytic field does not apply this density band",
|
||||
"configured upper side", "legacy VF_ApplyBoundarySeal; absent from current Core density path" };
|
||||
Item.bedrock = { "bedrock", Item.interiorBottomMicrometres - SealMicrometres,
|
||||
Item.interiorBottomMicrometres, false, true, false,
|
||||
"manager configures a two-sided legacy seal, but Core's compiled analytic field does not apply this density band; breakable by default",
|
||||
"configured lower side", "legacy VF_ApplyBoundarySeal; absent from current Core density path" };
|
||||
Item.boundDispatchPath = "resolved manager slot scaled by ResolvedLayoutSnapshotV2 chunk width";
|
||||
Item.queryDispatchPath = "legacy Core route selects manager chunk by floor(CoreZ / resolved layout chunk width)";
|
||||
}
|
||||
const std::int64_t SealMicrometres=static_cast<std::int64_t>(std::llround(
|
||||
std::max(0.0f,StrateBoundarySealThickness(*Slot.Definition))*OwnerVoxelMicrons));
|
||||
if(SealMicrometres<=0)
|
||||
{OutError=FString::Printf(TEXT("Strate %d has no positive BoundarySealThickness for shared ceiling and bedrock bands."),Slot.StrateIndex);return false;}
|
||||
const std::int64_t SolidDensityQ16=std::max<std::int64_t>(1,WorldAssemblyQ16(
|
||||
std::max(0.05f,StrateBoundarySolidDensity(*Slot.Definition))));
|
||||
Item.ceiling={"ceiling",Item.interiorTopMicrometres-SealMicrometres,
|
||||
Item.interiorTopMicrometres,true,true,false,"solid ceiling from strate BoundarySealThickness; breakable by default",
|
||||
"interior-facing lower surface","shared resolved-layout density band",SolidDensityQ16};
|
||||
Item.bedrock={"bedrock",Item.interiorBottomMicrometres,
|
||||
Item.interiorBottomMicrometres+SealMicrometres,true,true,false,
|
||||
"solid bedrock floor from strate BoundarySealThickness; breakable by default",
|
||||
"interior-facing upper surface","shared resolved-layout density band",SolidDensityQ16};
|
||||
Item.content = { "content", Item.interiorBottomMicrometres,
|
||||
Item.interiorTopMicrometres, true, true, false, "resolved generator interior",
|
||||
"none", "none" };
|
||||
Item.content.bottomMicrometres+=SealMicrometres;
|
||||
Item.content.topMicrometres-=SealMicrometres;
|
||||
for (std::size_t PassageIndex = 0; PassageIndex < StrateManager->GetPassages().Num(); ++PassageIndex)
|
||||
{
|
||||
const FVoxelPassage& Passage = StrateManager->GetPassages()[static_cast<int32>(PassageIndex)];
|
||||
if (Passage.UpperStrateIndex != Slot.StrateIndex && Passage.LowerStrateIndex != Slot.StrateIndex) continue;
|
||||
VoxelForgeCore::ResolvedLayoutAnchorV1 Anchor;
|
||||
VoxelForgeCore::ResolvedLayoutAnchorV2 Anchor;
|
||||
Anchor.kind = "passage"; Anchor.stableId = "passage/" + std::to_string(PassageIndex);
|
||||
Anchor.units = "milli owner voxels";
|
||||
Anchor.values = { static_cast<std::int64_t>(std::llround(Passage.UpperPoint.X * 1000.0)),
|
||||
@@ -1127,15 +1166,23 @@ bool AttachValidatedWorldAssembly(const UVoxelSettings* Settings,
|
||||
return false;
|
||||
}
|
||||
const std::string LayoutJson = Layout.ToJson();
|
||||
const FString LayoutText = UTF8_TO_TCHAR(LayoutJson.c_str());
|
||||
TSharedPtr<FJsonObject> LayoutObject;
|
||||
const TSharedRef<TJsonReader<>> LayoutReader = TJsonReaderFactory<>::Create(LayoutText);
|
||||
if (!FJsonSerializer::Deserialize(LayoutReader, LayoutObject) || !LayoutObject.IsValid())
|
||||
Root->SetStringField(TEXT("resolved_layout_json"),UTF8_TO_TCHAR(LayoutJson.c_str()));
|
||||
|
||||
const auto SurfaceSpawnPlan=std::find_if(Manifest->plan.strates.begin(),Manifest->plan.strates.end(),
|
||||
[](const auto& Plan){return !Plan.route.empty();});
|
||||
if(SurfaceSpawnPlan!=Manifest->plan.strates.end())
|
||||
{
|
||||
OutError = TEXT("Core resolved-layout canonical JSON could not be embedded in the resolved snapshot.");
|
||||
return false;
|
||||
const auto& Spawn=SurfaceSpawnPlan->route.front();
|
||||
TSharedRef<FJsonObject> ViewerStart=MakeShared<FJsonObject>();
|
||||
ViewerStart->SetBoolField(TEXT("found_open_space"),true);
|
||||
ViewerStart->SetNumberField(TEXT("x"),static_cast<double>(Spawn.positionSubvoxels[0])
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
ViewerStart->SetNumberField(TEXT("y"),static_cast<double>(Spawn.positionSubvoxels[1])
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
ViewerStart->SetNumberField(TEXT("z"),static_cast<double>(Spawn.positionSubvoxels[2])
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
Root->SetObjectField(TEXT("viewer_start"),ViewerStart);
|
||||
}
|
||||
Root->SetObjectField(TEXT("resolved_layout"), LayoutObject.ToSharedRef());
|
||||
|
||||
const std::string CanonicalJson = Manifest->ToJson();
|
||||
if (CanonicalJson.empty())
|
||||
@@ -1206,6 +1253,8 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
|
||||
// Override only the temporary schema copy. Clipmap tuning on the owner's asset is preserved.
|
||||
SettingsValue->AsObject()->SetNumberField(TEXT("ClipRadius"), CoreClipRadius);
|
||||
SettingsValue->AsObject()->SetNumberField(TEXT("MaxClipLevel"), CoreMaxClipLevel);
|
||||
SettingsValue->AsObject()->SetNumberField(TEXT("StrateLayoutChunkSizeMeters"),
|
||||
Settings->StrateLayoutChunkSizeMeters);
|
||||
|
||||
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
|
||||
Root->SetNumberField(TEXT("schema_version"), 1);
|
||||
@@ -1225,6 +1274,8 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
|
||||
Root->SetNumberField(TEXT("edge_seal_thickness"), Settings->EdgeSealThickness);
|
||||
Root->SetNumberField(TEXT("total_strates"), StrateManager->GetNumStrates());
|
||||
Root->SetNumberField(TEXT("inter_strate_gap_chunks"), Settings->InterStrateGapChunks);
|
||||
Root->SetNumberField(TEXT("strate_layout_chunk_size_um"),
|
||||
std::llround(Settings->StrateLayoutChunkSizeMeters*1000000.0));
|
||||
Root->SetNumberField(TEXT("clip_radius"), CoreClipRadius);
|
||||
Root->SetNumberField(TEXT("max_clip_level"), CoreMaxClipLevel);
|
||||
Root->SetNumberField(TEXT("full_res_clip_levels"), Settings->FullResClipLevels);
|
||||
@@ -1292,12 +1343,15 @@ bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
|
||||
return false;
|
||||
}
|
||||
Root->SetArrayField(TEXT("density_samples"), {});
|
||||
if(!Root->HasField(TEXT("viewer_start")))
|
||||
{
|
||||
TSharedRef<FJsonObject> ViewerStart = MakeShared<FJsonObject>();
|
||||
ViewerStart->SetBoolField(TEXT("found_open_space"), false);
|
||||
ViewerStart->SetNumberField(TEXT("x"), 0.0);
|
||||
ViewerStart->SetNumberField(TEXT("y"), 0.0);
|
||||
ViewerStart->SetNumberField(TEXT("z"), 0.0);
|
||||
Root->SetObjectField(TEXT("viewer_start"), ViewerStart);
|
||||
}
|
||||
|
||||
FString WriterBuffer;
|
||||
TSharedRef<FCoreJsonWriter> Writer = TJsonWriterFactory<TCHAR,
|
||||
|
||||
@@ -321,7 +321,7 @@ namespace
|
||||
const FVector ChunkCenter(
|
||||
(ChunkCoord.X + 0.5f) * (float)CHUNK_SIZE,
|
||||
(ChunkCoord.Y + 0.5f) * (float)CHUNK_SIZE,
|
||||
(ChunkCoord.Z + 0.5f) * (float)CHUNK_SIZE);
|
||||
(ChunkCoord.Z + 0.5f) * Manager->GetStrateLayoutChunkWidthVoxels());
|
||||
const float ChunkRadius = (float)CHUNK_SIZE * 0.8660254f + 3.0f;
|
||||
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
|
||||
Cache.FloorProjections.SetNum(Passages.Num());
|
||||
@@ -1103,7 +1103,7 @@ static bool VF_FindWalkableTunnelAir(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(Position.X / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(Position.Y / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(Position.Z / Manager->GetStrateLayoutChunkWidthVoxels()));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord);
|
||||
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
|
||||
const FVoxelPassage* PassageData = Passages.GetData();
|
||||
@@ -1138,7 +1138,7 @@ static bool VF_IsAnyPassageFloorAt(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(Position.X / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(Position.Y / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(Position.Z / Manager->GetStrateLayoutChunkWidthVoxels()));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord);
|
||||
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
|
||||
const FVoxelPassage* PassageData = Passages.GetData();
|
||||
@@ -1169,6 +1169,17 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
|
||||
UE_LOG(LogTemp, Error, TEXT("[StrateManager] No settings provided!"));
|
||||
return false;
|
||||
}
|
||||
if (!FMath::IsFinite(Settings->StrateLayoutChunkSizeMeters)
|
||||
|| Settings->StrateLayoutChunkSizeMeters < 0.25f
|
||||
|| Settings->StrateLayoutChunkSizeMeters > 64.0f)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("[StrateManager] StrateLayoutChunkSizeMeters must be in [0.25, 64] m."));
|
||||
return false;
|
||||
}
|
||||
StrateLayoutChunkSizeMicrometres = static_cast<uint32>(std::llround(
|
||||
static_cast<double>(Settings->StrateLayoutChunkSizeMeters) * 1000000.0));
|
||||
StrateLayoutChunkWidthVoxels = static_cast<float>(
|
||||
static_cast<double>(StrateLayoutChunkSizeMicrometres) / (10000.0 * VOXEL_SIZE));
|
||||
|
||||
FVoxelSeasonManifest SeasonManifest;
|
||||
bool bUseSeason = false;
|
||||
@@ -1342,10 +1353,11 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
|
||||
Slot.Definition->FloatingIslandParams = SeasonStrate.Params.FloatingIslandParams;
|
||||
|
||||
Slot.HeightInChunks = SeasonStrate.HeightInChunks;
|
||||
Slot.TopChunkZ = SeasonStrate.TopWorldZ / CHUNK_SIZE - 1;
|
||||
Slot.BottomChunkZ = SeasonStrate.BottomWorldZ / CHUNK_SIZE;
|
||||
if (SeasonStrate.TopWorldZ % CHUNK_SIZE != 0
|
||||
|| SeasonStrate.BottomWorldZ % CHUNK_SIZE != 0
|
||||
const int32 LayoutChunkVoxels=FMath::RoundToInt(StrateLayoutChunkWidthVoxels);
|
||||
Slot.TopChunkZ = SeasonStrate.TopWorldZ / LayoutChunkVoxels - 1;
|
||||
Slot.BottomChunkZ = SeasonStrate.BottomWorldZ / LayoutChunkVoxels;
|
||||
if (SeasonStrate.TopWorldZ % LayoutChunkVoxels != 0
|
||||
|| SeasonStrate.BottomWorldZ % LayoutChunkVoxels != 0
|
||||
|| Slot.TopChunkZ != CurrentTopZ
|
||||
|| Slot.BottomChunkZ != CurrentTopZ - (Slot.HeightInChunks - 1))
|
||||
{
|
||||
@@ -1525,8 +1537,8 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
|
||||
}
|
||||
VF_SetComposerRuntimeBounds(
|
||||
Override->Params,
|
||||
(float)(TargetSlot->TopChunkZ + 1) * CHUNK_SIZE,
|
||||
(float)TargetSlot->BottomChunkZ * CHUNK_SIZE);
|
||||
(float)(TargetSlot->TopChunkZ + 1) * StrateLayoutChunkWidthVoxels,
|
||||
(float)TargetSlot->BottomChunkZ * StrateLayoutChunkWidthVoxels);
|
||||
Override->bUseRecipe = bUseRecipe;
|
||||
if (Recipe != nullptr)
|
||||
{
|
||||
@@ -1545,8 +1557,8 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
|
||||
Override->bUseRegions = true;
|
||||
Override->Regions = *InRegions;
|
||||
VF_RekeyStrateRegionManifest(Override->Regions, CandidateSeed, StrateIndex);
|
||||
Override->Regions.StrateTopWorldZ = (float)(TargetSlot->TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Override->Regions.StrateBottomWorldZ = (float)TargetSlot->BottomChunkZ * CHUNK_SIZE;
|
||||
Override->Regions.StrateTopWorldZ = (float)(TargetSlot->TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Override->Regions.StrateBottomWorldZ = (float)TargetSlot->BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
VF_SetStrateArchetypeRuntimeBounds(Override->Params,
|
||||
Override->Regions.StrateTopWorldZ,
|
||||
Override->Regions.StrateBottomWorldZ);
|
||||
@@ -1796,11 +1808,11 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
const FStratePassageConfig& Cfg = UpperDef->PassageConfig;
|
||||
|
||||
// Upper strate floor and lower strate ceiling (differ when there's a bedrock gap).
|
||||
const float UpperTopZ = (float)(Upper.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
const float UpperBottomZ = (float)(Upper.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float LowerTopZ = (float)(Lower.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
const float UpperMax = (float)Upper.HeightInChunks * CHUNK_SIZE * 0.9f;
|
||||
const float LowerMax = (float)Lower.HeightInChunks * CHUNK_SIZE * 0.9f;
|
||||
const float UpperTopZ = (float)(Upper.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
const float UpperBottomZ = (float)(Upper.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float LowerTopZ = (float)(Lower.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
const float UpperMax = (float)Upper.HeightInChunks * StrateLayoutChunkWidthVoxels * 0.9f;
|
||||
const float LowerMax = (float)Lower.HeightInChunks * StrateLayoutChunkWidthVoxels * 0.9f;
|
||||
|
||||
const float DistLo = FMath::Min(Cfg.DistanceMin, Cfg.DistanceMax);
|
||||
const float DistHi = FMath::Max(Cfg.DistanceMin, Cfg.DistanceMax);
|
||||
@@ -1930,7 +1942,7 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
LowerDef->FloatingIslandParams,
|
||||
LowerQuerySeed,
|
||||
LowerTopZ,
|
||||
(float)(Lower.BottomChunkZ) * CHUNK_SIZE,
|
||||
(float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels,
|
||||
PX,
|
||||
PY,
|
||||
MaxLateralSnap,
|
||||
@@ -1990,7 +2002,7 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
if (bAimedLowerAtPlayerFitPoint)
|
||||
{
|
||||
const float LowerSealThickness = BoundarySealThicknessFor(*LowerDef);
|
||||
const float LowerBottomZ = (float)(Lower.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float LowerBottomZ = (float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float InnerBottomZ = LowerBottomZ + LowerSealThickness;
|
||||
const float InnerTopZ = LowerTopZ - LowerSealThickness;
|
||||
if (VoxelMath::IsFinite(SuggestedLowerPoint.X)
|
||||
@@ -2158,7 +2170,7 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
Cfg.MouthRadius,
|
||||
LowerDoorDirection,
|
||||
LowerTopZ,
|
||||
(float)(Lower.BottomChunkZ) * CHUNK_SIZE,
|
||||
(float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels,
|
||||
LowerDef ? BoundarySealThicknessFor(*LowerDef) : 0.0f,
|
||||
bAimedLowerAtPlayerFitPoint);
|
||||
Passage.UpperPoint = Passage.UpperLanding.StandingPoint;
|
||||
@@ -2583,8 +2595,8 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
if (bOpenSurfaceEntry && OriginSpineRadius > 0.0f && StrateLayout.Num() > 0)
|
||||
{
|
||||
const FStrateSlot& Top = StrateLayout[0];
|
||||
const float TopZ = (float)(Top.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
const float BottomZ = (float)Top.BottomChunkZ * CHUNK_SIZE;
|
||||
const float TopZ = (float)(Top.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = (float)Top.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
const float TopSeal = Top.Definition
|
||||
? BoundarySealThicknessFor(*Top.Definition) : 0.0f;
|
||||
const VoxelPassageGeometry::FOriginLandingGeometry OriginLanding =
|
||||
@@ -2607,7 +2619,7 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
// the top seal into the finite landing room; lower strate seals remain untouched.
|
||||
Entry.Radius = FMath::Max(OriginSpineRadius,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.25f));
|
||||
Entry.UpperPoint = FVector(0.0f, 0.0f, TopZ + CHUNK_SIZE);
|
||||
Entry.UpperPoint = FVector(0.0f, 0.0f, TopZ + StrateLayoutChunkWidthVoxels);
|
||||
// The capsule's lower tangent meets the room ceiling; it never bores through the
|
||||
// room's floor or creates an origin column in strate 0.
|
||||
Entry.LowerPoint = FVector(
|
||||
@@ -2671,7 +2683,7 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
|
||||
// The shortlist is rebuilt once for a (manager, version, chunk) and is shared by the tube,
|
||||
// landing, and floor paths. No source-fit stencil or topology search is allowed below it.
|
||||
@@ -2817,7 +2829,7 @@ void UVoxelStrateManager::ApplyPassageModifier(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
|
||||
const FVoxelPassage* PassageData = Passages.GetData();
|
||||
const FVector Position(WorldX, WorldY, WorldZ);
|
||||
@@ -2910,7 +2922,7 @@ void UVoxelStrateManager::ApplyPassageLandingAir(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
|
||||
if (Nearby.Num() == 0) return;
|
||||
|
||||
@@ -3021,7 +3033,7 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
|
||||
const FVoxelPassage* PassageData = Passages.GetData();
|
||||
const FVector Position(WorldX, WorldY, WorldZ);
|
||||
@@ -3117,7 +3129,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
|
||||
if (Nearby.Num() == 0) return;
|
||||
|
||||
@@ -3244,7 +3256,7 @@ void UVoxelStrateManager::ApplyPassageNativeFloorMC(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
|
||||
if (Nearby.Num() == 0
|
||||
|| VoxelPassageGeometry::VerticalShaftConnectorAirMarker())
|
||||
@@ -3435,7 +3447,7 @@ void UVoxelStrateManager::ApplyPassageLandingRoomFloorMC(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
|
||||
const FVoxelPassage* PassageData = Passages.GetData();
|
||||
const FVector Position(WorldX, WorldY, WorldZ);
|
||||
@@ -5909,8 +5921,8 @@ bool UVoxelStrateManager::AnyLandingFloorAtLattice(
|
||||
for (const FStrateSlot& Slot : StrateLayout)
|
||||
{
|
||||
if (Slot.Definition == nullptr) continue;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
|
||||
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
|
||||
VoxelPassageGeometry::BuildOriginLandingGeometry(
|
||||
@@ -5952,8 +5964,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearBox(
|
||||
for (const FStrateSlot& Slot : StrateLayout)
|
||||
{
|
||||
if (Slot.Definition == nullptr) continue;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
if (VoxelPassageGeometry::OriginLandingRoomTouchesBox(
|
||||
VoxelBox, TopZ, BottomZ,
|
||||
VF_BoundarySealThicknessForDefinition(*Slot.Definition),
|
||||
@@ -5973,8 +5985,8 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearBox(
|
||||
for (const FStrateSlot& Slot : StrateLayout)
|
||||
{
|
||||
if (Slot.Definition == nullptr) continue;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
if (VoxelPassageGeometry::OriginLandingFloorTouchesBox(
|
||||
VoxelBox, TopZ, BottomZ,
|
||||
VF_BoundarySealThicknessForDefinition(*Slot.Definition),
|
||||
@@ -5998,8 +6010,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearLattice(
|
||||
for (const FStrateSlot& Slot : StrateLayout)
|
||||
{
|
||||
if (Slot.Definition == nullptr) continue;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
|
||||
if (VoxelPassageGeometry::OriginLandingRoomTouchesLattice(
|
||||
VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius,
|
||||
@@ -6036,8 +6048,8 @@ bool UVoxelStrateManager::AnyOriginLandingAirNearLattice(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
|
||||
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
|
||||
VoxelPassageGeometry::BuildOriginLandingGeometry(
|
||||
@@ -6113,8 +6125,8 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearLattice(
|
||||
for (const FStrateSlot& Slot : StrateLayout)
|
||||
{
|
||||
if (Slot.Definition == nullptr) continue;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
|
||||
if (VoxelPassageGeometry::OriginLandingFloorTouchesLattice(
|
||||
VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius))
|
||||
@@ -6148,7 +6160,7 @@ int32 UVoxelStrateManager::FindSlotIndexForChunkZ(int32 ChunkZ) const
|
||||
UVoxelStrateDefinition* UVoxelStrateManager::GetStrateAt(float WorldZ) const
|
||||
{
|
||||
// Convert world Z to chunk Z coordinate
|
||||
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / CHUNK_SIZE);
|
||||
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels);
|
||||
int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
|
||||
if (SlotIdx >= 0)
|
||||
{
|
||||
@@ -6159,7 +6171,7 @@ UVoxelStrateDefinition* UVoxelStrateManager::GetStrateAt(float WorldZ) const
|
||||
|
||||
int32 UVoxelStrateManager::GetStrateIndex(float WorldZ) const
|
||||
{
|
||||
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / CHUNK_SIZE);
|
||||
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels);
|
||||
int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
|
||||
if (SlotIdx >= 0)
|
||||
{
|
||||
@@ -6318,9 +6330,9 @@ FSlabGenerationParams UVoxelStrateManager::GetSlabParamsForChunk(const FIntVecto
|
||||
|
||||
// Fill in the runtime Z bounds (voxel coordinates, same convention as
|
||||
// FStrateGenerationParams::StrateTopWorldZ / StrateBottomWorldZ).
|
||||
// TopChunkZ+1 because the top chunk's CEILING is at (TopChunkZ+1)*CHUNK_SIZE.
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
// TopChunkZ+1 because the top chunk's CEILING is at (TopChunkZ+1)*StrateLayoutChunkWidthVoxels.
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
|
||||
return Result;
|
||||
}
|
||||
@@ -6353,8 +6365,8 @@ FMazeGenerationParams UVoxelStrateManager::GetMazeParamsForChunk(const FIntVecto
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -6380,8 +6392,8 @@ FSurfaceGenerationParams UVoxelStrateManager::GetSurfaceParamsForChunk(const FIn
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -6407,8 +6419,8 @@ FVerticalShaftParams UVoxelStrateManager::GetVerticalShaftParamsForChunk(const F
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -6434,8 +6446,8 @@ FFloatingIslandParams UVoxelStrateManager::GetFloatingIslandParamsForChunk(const
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -6469,14 +6481,14 @@ FBiomeContext UVoxelStrateManager::GetBiomeContextForChunk(const FIntVector& Chu
|
||||
|
||||
bool UVoxelStrateManager::GetStrateUnrealZRange(float WorldZ, float& OutTopZ, float& OutBottomZ) const
|
||||
{
|
||||
const int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / CHUNK_SIZE);
|
||||
const int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels);
|
||||
const int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
|
||||
if (SlotIdx < 0) return false;
|
||||
|
||||
const FStrateSlot& Slot = StrateLayout[SlotIdx];
|
||||
// Voxel-space Z bounds → Unreal units. Ceiling = top chunk's upper edge.
|
||||
OutTopZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE * VOXEL_SIZE;
|
||||
OutBottomZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE * VOXEL_SIZE;
|
||||
OutTopZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels * VOXEL_SIZE;
|
||||
OutBottomZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels * VOXEL_SIZE;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6489,8 +6501,8 @@ FStrateDisturbanceParams UVoxelStrateManager::GetDisturbanceParamsForChunk(const
|
||||
}
|
||||
const FStrateSlot& Slot = StrateLayout[SlotIdx];
|
||||
FStrateDisturbanceParams Result = Slot.Definition->Disturbances;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -6522,8 +6534,8 @@ float UVoxelStrateManager::GetWaterLevelWorldZForChunk(const FIntVector& ChunkCo
|
||||
}
|
||||
if (Rel <= 0.0f) return -FLT_MAX;
|
||||
|
||||
const float BottomZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
const float BottomZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float TopZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
return FMath::Lerp(BottomZ, TopZ, FMath::Clamp(Rel, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
@@ -6567,8 +6579,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
FindComposerOverride(Slot.StrateIndex))
|
||||
{
|
||||
FStrateGenerationParams Result = Override->Params.TunnelNetworkParams;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
// A candidate is a hard replacement of this slot. Do not blend its rolled vector with an
|
||||
// authored neighbour at a boundary; this is also how the offline fixture measures it.
|
||||
return ResolveFinalParams(Result);
|
||||
@@ -6578,8 +6590,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
if (SeasonStrates.IsValidIndex(SlotIdx))
|
||||
{
|
||||
FStrateGenerationParams Result = SeasonStrates[SlotIdx].Params.TunnelNetworkParams;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
// Season vectors are already final, measured slot records. Vertical structural posts own
|
||||
// their boundary; blending them with another selected recipe would describe neither one.
|
||||
return ResolveFinalParams(Result);
|
||||
@@ -6596,8 +6608,17 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
//
|
||||
// TopChunkZ=0, CHUNK_SIZE=32: top of the strate = chunk 0's top edge = voxel Z=32
|
||||
// BottomChunkZ=-3: bottom of the strate = chunk -3's bottom edge = voxel Z=-3*32 = -96
|
||||
BaseParams.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
BaseParams.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
BaseParams.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
BaseParams.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
|
||||
// Core currently dispatches one generator field per resolved strate. Gradient and
|
||||
// interleaved metadata therefore use the explicitly declared Hard fallback for every
|
||||
// legacy parameter lookup as well; mixing parameter vectors here would disagree with the
|
||||
// shared ceiling/content/bedrock composition layer.
|
||||
if (bHardTransitionFallbackForAssembly)
|
||||
{
|
||||
return ResolveFinalParams(BaseParams);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// BOUNDARY BLENDING (transition-type aware)
|
||||
@@ -6670,8 +6691,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
if (EffectiveBlend > 0 && DistFromBottom < EffectiveBlend)
|
||||
{
|
||||
FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition);
|
||||
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * CHUNK_SIZE;
|
||||
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
|
||||
// Linear alpha: 0 at EffectiveBlend chunks away, 1 at the boundary
|
||||
float Alpha = 1.0f - ((float)DistFromBottom / (float)EffectiveBlend);
|
||||
@@ -6708,8 +6729,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
if (DistFromBottom < CheckRange)
|
||||
{
|
||||
FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition);
|
||||
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * CHUNK_SIZE;
|
||||
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
|
||||
// Sample 3D Perlin noise to warp the boundary position.
|
||||
// XY frequency 0.15 gives medium-scale variation (~6-7 chunks per cycle).
|
||||
@@ -6769,8 +6790,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
if (EffectiveBlend > 0 && DistFromTop < EffectiveBlend)
|
||||
{
|
||||
FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition);
|
||||
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * CHUNK_SIZE;
|
||||
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
|
||||
float Alpha = 1.0f - ((float)DistFromTop / (float)EffectiveBlend);
|
||||
Alpha = FMath::Clamp(Alpha, 0.0f, 1.0f);
|
||||
@@ -6791,8 +6812,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
if (DistFromTop < CheckRange)
|
||||
{
|
||||
FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition);
|
||||
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * CHUNK_SIZE;
|
||||
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
|
||||
// Same noise function but with a different seed offset to avoid
|
||||
// symmetry between top and bottom boundaries of adjacent strates
|
||||
|
||||
@@ -419,6 +419,13 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates", meta = (ClampMin = "0", ClampMax = "16"))
|
||||
int32 InterStrateGapChunks = 0;
|
||||
|
||||
// Physical width of one vertical strate-layout chunk. This is independent of the
|
||||
// Core tile width (32^3 voxels) and of the source generator's voxel resolution.
|
||||
// 8 m preserves the owner's current SurfaceWorld extent at 25 cm Core voxels.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates|World Assembly",
|
||||
meta = (ClampMin = "0.25", ClampMax = "64.0", Units = "m"))
|
||||
float StrateLayoutChunkSizeMeters = 8.0f;
|
||||
|
||||
//=========================================================================
|
||||
// SPINE & INTER-STRATE CONNECTIONS (the (0,0) descent axis)
|
||||
//=========================================================================
|
||||
|
||||
@@ -244,6 +244,13 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "Strate")
|
||||
int32 GetStrateIndex(float WorldZ) const;
|
||||
|
||||
// Director-resolved physical strate chunk width expressed in owner voxel units.
|
||||
// This is independent of Core's 32^3 tile width.
|
||||
float GetStrateLayoutChunkWidthVoxels() const { return StrateLayoutChunkWidthVoxels; }
|
||||
uint32 GetStrateLayoutChunkSizeMicrometres() const { return StrateLayoutChunkSizeMicrometres; }
|
||||
void SetHardTransitionFallbackForAssembly(bool bEnabled)
|
||||
{ bHardTransitionFallbackForAssembly = bEnabled; }
|
||||
|
||||
/** Layout/passage generation counter (bumped by every Initialize → GeneratePassages).
|
||||
* Hot-path callers key thread_local memos on this so an editor rebuild (RebuildStrates /
|
||||
* live edit) can never serve a stale strate index or passage shortlist. */
|
||||
@@ -525,6 +532,11 @@ protected:
|
||||
// VoxelSettings::InterStrateGapChunks during Initialize().
|
||||
int32 InterStrateGapChunks = 0;
|
||||
|
||||
// Resolved once from VoxelSettings::StrateLayoutChunkSizeMeters during Initialize().
|
||||
float StrateLayoutChunkWidthVoxels = 32.0f;
|
||||
uint32 StrateLayoutChunkSizeMicrometres = 8000000;
|
||||
bool bHardTransitionFallbackForAssembly = false;
|
||||
|
||||
// Production keeps the legacy writer available only as a fail-closed fallback for a
|
||||
// non-native/malformed passage; native D-floors bypass it. This switch never changes the
|
||||
// passage carve or air contract.
|
||||
|
||||
@@ -248,9 +248,9 @@ namespace VoxelForgeCore
|
||||
// This is independent of tile size and is the fixed input scale for Core field blocks.
|
||||
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
|
||||
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
|
||||
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 1;
|
||||
// Version 6 binds every compiled tile page to the world's base density evaluator.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 6;
|
||||
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 2;
|
||||
// Version 7 binds plans to the physical strate-layout chunk width and shared boundary bands.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 7;
|
||||
|
||||
enum class FieldChannel : std::uint32_t
|
||||
{
|
||||
@@ -302,6 +302,8 @@ namespace VoxelForgeCore
|
||||
std::uint32_t schemaVersion = WorldAssemblySchemaVersion;
|
||||
std::uint32_t evaluatorVersion = WorldAssemblyEvaluatorVersion;
|
||||
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
|
||||
// Resolved physical strate chunk width in 1/256 Core voxel units; tileVoxelCount remains 32.
|
||||
std::int64_t layoutChunkWidthSubvoxels = 8192;
|
||||
std::uint32_t seed = 0;
|
||||
std::int64_t biomeCellSizeSubvoxels = 256 * 800;
|
||||
std::int64_t biomeWarpSubvoxels = 256 * 250;
|
||||
@@ -393,8 +395,9 @@ namespace VoxelForgeCore
|
||||
// Profile 1 preserves the original shallow pads. Profile 2 adds a thick crust,
|
||||
// bounded lobed roots and arched spans without changing the feature-family schema.
|
||||
// Version 3 keeps the version-2 island shapes, removes the all-XY lower boundary seal,
|
||||
// and routes its upper seal through the shared strate-boundary primitive.
|
||||
std::uint32_t geometryVersion = 3;
|
||||
// and routes its upper seal through the shared strate-boundary primitive. Version 4
|
||||
// records the director-resolved strate layout width in the recipe.
|
||||
std::uint32_t geometryVersion = 4;
|
||||
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
|
||||
// Import scale is explicit in microns; the reduced ratio maps owner-authored lengths to Core voxels.
|
||||
// L'echelle d'import est explicite en microns; le rapport reduit convertit les longueurs owner.
|
||||
@@ -408,6 +411,9 @@ namespace VoxelForgeCore
|
||||
std::int64_t layoutCenterTopZSubvoxels = -64 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t layoutMinimumZSubvoxels = -128 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t layoutMaximumZSubvoxels = 0;
|
||||
// Director-resolved physical strate layout chunk in 1/256 Core voxel units.
|
||||
// This is independent of the internal 32-voxel Core tile.
|
||||
std::int64_t layoutChunkWidthSubvoxels = 8192;
|
||||
std::uint32_t primaryIslandCount = 3;
|
||||
std::int64_t islandDensityQ16 = 1ll << 15;
|
||||
// Legacy owner lengths use ownerVoxelSizeMicrons; the scale ratio converts them to Core voxels.
|
||||
@@ -531,7 +537,8 @@ namespace VoxelForgeCore
|
||||
std::uint64_t contentHash = 0;
|
||||
|
||||
static std::optional<FloatingIslandManifestV1> FromJson(
|
||||
std::string_view json, std::string& error);
|
||||
std::string_view json, std::string& error,
|
||||
std::int64_t resolvedLayoutChunkWidthSubvoxels = 0);
|
||||
bool finalize(std::string& error);
|
||||
bool validate(std::string& error) const;
|
||||
std::string ToJson() const;
|
||||
@@ -566,10 +573,10 @@ namespace VoxelForgeCore
|
||||
// Read-only, versioned account of the physical layout resolved by the director and the
|
||||
// Core adapter. Coordinates are half-open micrometre intervals; recipe anchors retain their
|
||||
// source fixed-point units so this record never rounds the recipe itself.
|
||||
inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV1 = 1;
|
||||
enum class ResolvedLayoutValidationModeV1 : std::uint8_t { compatibility, strict };
|
||||
inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV2 = 2;
|
||||
enum class ResolvedLayoutValidationModeV2 : std::uint8_t { compatibility, strict };
|
||||
|
||||
struct ResolvedLayoutAnchorV1
|
||||
struct ResolvedLayoutAnchorV2
|
||||
{
|
||||
std::string kind;
|
||||
std::string stableId;
|
||||
@@ -577,7 +584,7 @@ namespace VoxelForgeCore
|
||||
std::vector<std::int64_t> values;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutBandV1
|
||||
struct ResolvedLayoutBandV2
|
||||
{
|
||||
std::string kind; // ceiling, content, bedrock
|
||||
std::int64_t bottomMicrometres = 0;
|
||||
@@ -588,9 +595,10 @@ namespace VoxelForgeCore
|
||||
std::string behaviour;
|
||||
std::string densitySide;
|
||||
std::string capPath;
|
||||
std::int64_t solidDensityQ16 = 1ll << 16;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutStratumV1
|
||||
struct ResolvedLayoutStratumV2
|
||||
{
|
||||
std::string stableId;
|
||||
std::int32_t order = 0;
|
||||
@@ -602,56 +610,59 @@ namespace VoxelForgeCore
|
||||
std::int64_t interiorTopMicrometres = 0;
|
||||
std::uint32_t voxelSizeMicrometres = 0;
|
||||
std::array<std::int64_t, 3> latticeOriginMicrometres{};
|
||||
ResolvedLayoutBandV1 ceiling;
|
||||
ResolvedLayoutBandV1 content;
|
||||
ResolvedLayoutBandV1 bedrock;
|
||||
ResolvedLayoutBandV2 ceiling;
|
||||
ResolvedLayoutBandV2 content;
|
||||
ResolvedLayoutBandV2 bedrock;
|
||||
std::string transitionMode; // mode at this strate's lower interface
|
||||
std::int32_t transitionBlendChunks = 0;
|
||||
std::int64_t transitionWidthMicrometres = 0;
|
||||
std::int64_t transitionWarpAmplitudeMicrometres = 0;
|
||||
std::int64_t transitionInterpenetrationMicrometres = 0;
|
||||
bool transitionHonouredInCore = false;
|
||||
bool transitionFallsBackToHard = false;
|
||||
std::string transitionDispatchPath;
|
||||
std::string boundDispatchPath;
|
||||
std::string queryDispatchPath;
|
||||
std::string managerLookup;
|
||||
std::int64_t managerChunkWidthMicrometres = 0;
|
||||
std::int64_t dispatchChunkWidthMicrometres = 0;
|
||||
std::vector<ResolvedLayoutAnchorV1> anchors;
|
||||
std::vector<ResolvedLayoutAnchorV2> anchors;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutIssueV1
|
||||
struct ResolvedLayoutIssueV2
|
||||
{
|
||||
std::string code;
|
||||
std::string detail;
|
||||
};
|
||||
|
||||
struct VFCORE_API ResolvedLayoutSnapshotV1
|
||||
struct VFCORE_API ResolvedLayoutSnapshotV2
|
||||
{
|
||||
std::uint32_t schemaVersion = ResolvedLayoutSchemaVersionV1;
|
||||
std::uint32_t schemaVersion = ResolvedLayoutSchemaVersionV2;
|
||||
std::string worldIdentity;
|
||||
std::uint32_t ownerVoxelSizeMicrometres = 0;
|
||||
std::uint32_t coreVoxelSizeMicrometres = 0;
|
||||
std::vector<ResolvedLayoutStratumV1> strates;
|
||||
std::uint32_t strateLayoutChunkSizeMicrometres = 8000000;
|
||||
std::int64_t strateLayoutChunkWidthSubvoxels = 8192;
|
||||
std::vector<ResolvedLayoutStratumV2> strates;
|
||||
std::string stableIdentity;
|
||||
|
||||
bool finalize(std::string& error);
|
||||
bool validate(ResolvedLayoutValidationModeV1 mode,
|
||||
std::vector<ResolvedLayoutIssueV1>& issues,
|
||||
bool validate(ResolvedLayoutValidationModeV2 mode,
|
||||
std::vector<ResolvedLayoutIssueV2>& issues,
|
||||
std::string& error) const;
|
||||
std::string ToJson() const;
|
||||
static std::optional<ResolvedLayoutSnapshotV1> FromJson(
|
||||
static std::optional<ResolvedLayoutSnapshotV2> FromJson(
|
||||
std::string_view json, std::string& error);
|
||||
};
|
||||
|
||||
struct ResolvedLayoutValidatorAuditV1
|
||||
struct ResolvedLayoutValidatorAuditV2
|
||||
{
|
||||
std::uint32_t controlsRun = 0;
|
||||
std::uint32_t controlsRejectedAsExpected = 0;
|
||||
bool passed = false;
|
||||
std::vector<ResolvedLayoutIssueV1> failures;
|
||||
std::vector<ResolvedLayoutIssueV2> failures;
|
||||
};
|
||||
VFCORE_API ResolvedLayoutValidatorAuditV1 AuditResolvedLayoutValidatorV1();
|
||||
VFCORE_API ResolvedLayoutValidatorAuditV2 AuditResolvedLayoutValidatorV2();
|
||||
|
||||
struct FloatingIslandBoundsAuditResult
|
||||
{
|
||||
@@ -930,6 +941,7 @@ namespace VoxelForgeCore
|
||||
explicit operator bool() const noexcept { return static_cast<bool>(data_); }
|
||||
const WorldConfiguration& configuration() const;
|
||||
const WorldAssemblyManifest* worldAssembly() const noexcept;
|
||||
const ResolvedLayoutSnapshotV2* resolvedLayout() const noexcept;
|
||||
|
||||
private:
|
||||
struct Data;
|
||||
|
||||
@@ -817,8 +817,9 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
|
||||
struct FFarMeshColumnIndex
|
||||
{
|
||||
FFarMeshColumnIndex(const FVoxelMeshData& InMesh,const FIntVector& InOrigin,int32 InExtent,
|
||||
int32 InCells,double InStep)
|
||||
int32 InCells,double InStep,double InVoxelSizeCm)
|
||||
:Mesh(InMesh),Origin(InOrigin),Extent(InExtent),Cells(InCells),Step(InStep),
|
||||
VoxelSizeCm(InVoxelSizeCm),
|
||||
ByColumn(static_cast<size_t>(InCells)*InCells)
|
||||
{
|
||||
for(int32 Triangle=0;Triangle+2<Mesh.Triangles.Num();Triangle+=3)
|
||||
@@ -908,16 +909,54 @@ struct FFarMeshColumnIndex
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasUpwardHit(double X,double Y,double Z,double Tolerance) const
|
||||
{
|
||||
const int32 CX=std::clamp(static_cast<int32>(std::floor((X-Origin.X)/Step)),0,Cells-1);
|
||||
const int32 CY=std::clamp(static_cast<int32>(std::floor((Y-Origin.Y)/Step)),0,Cells-1);
|
||||
const auto& Candidates=ByColumn[static_cast<size_t>(CY)*Cells+CX];
|
||||
for(const int32 Triangle:Candidates)
|
||||
{
|
||||
const int32 IA=Mesh.Triangles[Triangle*3],IB=Mesh.Triangles[Triangle*3+1],
|
||||
IC=Mesh.Triangles[Triangle*3+2];
|
||||
if(IA<0||IB<0||IC<0||IA>=Mesh.Normals.Num()||IB>=Mesh.Normals.Num()
|
||||
||IC>=Mesh.Normals.Num())continue;
|
||||
if(Mesh.Normals[IA].Z+Mesh.Normals[IB].Z+Mesh.Normals[IC].Z<=1.5)continue;
|
||||
const double AX=PositionAxis(IA,0),AY=PositionAxis(IA,1),AZ=PositionAxis(IA,2);
|
||||
const double BX=PositionAxis(IB,0),BY=PositionAxis(IB,1),BZ=PositionAxis(IB,2);
|
||||
const double CXv=PositionAxis(IC,0),CYv=PositionAxis(IC,1),CZ=PositionAxis(IC,2);
|
||||
const double Den=(BX-AX)*(CYv-AY)-(BY-AY)*(CXv-AX);
|
||||
if(std::abs(Den)<1.0e-10)continue;
|
||||
const double U=((X-AX)*(CYv-AY)-(Y-AY)*(CXv-AX))/Den;
|
||||
const double V=((BX-AX)*(Y-AY)-(BY-AY)*(X-AX))/Den;
|
||||
const double W=1.0-U-V;
|
||||
if(U<-1.0e-7||V<-1.0e-7||W<-1.0e-7)continue;
|
||||
const double Hit=AZ+U*(BZ-AZ)+V*(CZ-AZ);
|
||||
if(std::abs(Hit-Z)<=Tolerance)return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasUpwardCellCoverage(double X0,double Y0,double X1,double Y1,
|
||||
double Z,double Tolerance) const
|
||||
{
|
||||
constexpr double Samples[5]={0.0,0.25,0.5,0.75,1.0};
|
||||
for(const double V:Samples)for(const double U:Samples)
|
||||
if(!HasUpwardHit(X0+(X1-X0)*U,Y0+(Y1-Y0)*V,Z,Tolerance))return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
double PositionAxis(int32 Index,int32 Axis) const
|
||||
{
|
||||
const FVector& P=Mesh.Vertices[Index];
|
||||
return (Axis==0?P.X:(Axis==1?P.Y:P.Z))*0.04;
|
||||
const double WorldCentimetres=Axis==0?P.X:(Axis==1?P.Y:P.Z);
|
||||
return WorldCentimetres/VoxelSizeCm;
|
||||
}
|
||||
const FVoxelMeshData& Mesh;
|
||||
FIntVector Origin;
|
||||
int32 Extent=0,Cells=0;
|
||||
double Step=1.0;
|
||||
double VoxelSizeCm=25.0;
|
||||
std::vector<std::vector<int32>> ByColumn;
|
||||
};
|
||||
|
||||
@@ -1040,7 +1079,8 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
|
||||
const bool bHasColors=Mesh.Colors.Num()==OldVertexCount;
|
||||
const FColor Color=bHasColors&&OldVertexCount>0?Mesh.Colors[0]:FColor(160,160,160,255);
|
||||
const int32 Cells=std::max(1,World.CoarseTileCells);
|
||||
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step);
|
||||
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step,
|
||||
static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0);
|
||||
std::vector<FVector> NewVertices,NewNormals;
|
||||
std::vector<FVector2f> NewUVs;std::vector<FColor> NewColors;
|
||||
std::vector<int32> NewTriangles;
|
||||
@@ -1188,40 +1228,43 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
|
||||
const int32 GridSide=Extent/GridStep+1;
|
||||
struct FCandidate
|
||||
{
|
||||
FStandaloneShapeWorld::FResolvedStratumBounds Bounds;
|
||||
std::vector<uint8> SolidBelow;
|
||||
double FaceZ=0.0;
|
||||
double Thickness=0.0;
|
||||
std::vector<uint8> SolidAbove;
|
||||
};
|
||||
std::vector<FCandidate> Candidates;
|
||||
const double CoreMicrons=std::max<std::uint32_t>(1,World.CoreVoxelSizeMicrons);
|
||||
for(const FStrateSlot& Slot:World.Manager->GetLayout())
|
||||
{
|
||||
const auto* Stratum=Shape.FindResolvedLayoutStratum(Slot.StrateIndex);
|
||||
if(!Stratum||!Stratum->ceiling.presentInCurrentField
|
||||
||Stratum->ceiling.topMicrometres<=Stratum->ceiling.bottomMicrometres)continue;
|
||||
FCandidate Candidate;
|
||||
if(!Shape.GetResolvedStratumBounds(Slot,Candidate.Bounds)
|
||||
||Candidate.Bounds.SealThickness<=0.0
|
||||
||Candidate.Bounds.TopZ<Origin.Z-1.0e-6
|
||||
||Candidate.Bounds.TopZ>Origin.Z+Extent+1.0e-6)continue;
|
||||
const double Epsilon=std::min(0.5,Candidate.Bounds.SealThickness*0.25);
|
||||
Candidate.FaceZ=static_cast<double>(Stratum->ceiling.bottomMicrometres)/CoreMicrons;
|
||||
Candidate.Thickness=static_cast<double>(Stratum->ceiling.topMicrometres
|
||||
-Stratum->ceiling.bottomMicrometres)/CoreMicrons;
|
||||
if(Candidate.FaceZ<Origin.Z-1.0e-6||Candidate.FaceZ>Origin.Z+Extent+1.0e-6)continue;
|
||||
const double Epsilon=std::min(0.5,Candidate.Thickness*0.25);
|
||||
if(Epsilon<=0.0)continue;
|
||||
Candidate.SolidBelow.resize(static_cast<std::size_t>(GridSide)*GridSide,0);
|
||||
bool AnySolidBelow=false;
|
||||
Candidate.SolidAbove.resize(static_cast<std::size_t>(GridSide)*GridSide,0);
|
||||
bool AnySolidAbove=false;
|
||||
for(int32 Y=0;Y<GridSide;++Y)for(int32 X=0;X<GridSide;++X)
|
||||
{
|
||||
const double WorldX=Origin.X+X*GridStep;
|
||||
const double WorldY=Origin.Y+Y*GridStep;
|
||||
const float Below=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY),
|
||||
static_cast<float>(Candidate.Bounds.TopZ-Epsilon));
|
||||
// The shared upper boundary closes the stratum even where its raw field stays
|
||||
// solid above the configured top. A zero-crossing test leaves holes there.
|
||||
// Le plafond partage ferme la strate meme si le champ reste solide au-dessus.
|
||||
const bool bSolidBelow=std::isfinite(Below)&&Below<0.0f;
|
||||
Candidate.SolidBelow[static_cast<std::size_t>(Y*GridSide+X)]=bSolidBelow?1:0;
|
||||
AnySolidBelow=AnySolidBelow||bSolidBelow;
|
||||
const float Above=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY),
|
||||
static_cast<float>(Candidate.FaceZ+Epsilon));
|
||||
const bool bSolidAbove=std::isfinite(Above)&&Above<0.0f;
|
||||
Candidate.SolidAbove[static_cast<std::size_t>(Y*GridSide+X)]=bSolidAbove?1:0;
|
||||
AnySolidAbove=AnySolidAbove||bSolidAbove;
|
||||
}
|
||||
if(AnySolidBelow)Candidates.push_back(std::move(Candidate));
|
||||
if(AnySolidAbove)Candidates.push_back(std::move(Candidate));
|
||||
}
|
||||
if(Candidates.empty())return;
|
||||
|
||||
const int32 Cells=std::max(1,World.CoarseTileCells);
|
||||
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step);
|
||||
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step,
|
||||
static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0);
|
||||
const int32 OldVertexCount=Mesh.Vertices.Num();
|
||||
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
|
||||
const bool bHasUVs=Mesh.UVs.Num()==OldVertexCount;
|
||||
@@ -1235,7 +1278,7 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
|
||||
for(const FCandidate& Candidate:Candidates)
|
||||
{
|
||||
std::vector<int32> VertexMap(static_cast<std::size_t>(GridSide)*GridSide,INDEX_NONE);
|
||||
const double CeilingZ=Candidate.Bounds.TopZ;
|
||||
const double CeilingZ=Candidate.FaceZ;
|
||||
const auto AddVertex=[&](int32 X,int32 Y)
|
||||
{
|
||||
const std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X);
|
||||
@@ -1253,8 +1296,8 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
|
||||
{
|
||||
const std::size_t I00=static_cast<std::size_t>(Y*GridSide+X);
|
||||
const std::size_t I10=I00+1,I01=I00+GridSide,I11=I01+1;
|
||||
if(!Candidate.SolidBelow[I00]||!Candidate.SolidBelow[I10]
|
||||
||!Candidate.SolidBelow[I01]||!Candidate.SolidBelow[I11])continue;
|
||||
if(!Candidate.SolidAbove[I00]||!Candidate.SolidAbove[I10]
|
||||
||!Candidate.SolidAbove[I01]||!Candidate.SolidAbove[I11])continue;
|
||||
const double CellX0=Origin.X+X*GridStep,CellY0=Origin.Y+Y*GridStep;
|
||||
const double CellX1=CellX0+GridStep,CellY1=CellY0+GridStep;
|
||||
// Suppress a coincident plane only when it covers the full cell. A hit at just the
|
||||
@@ -1303,3 +1346,121 @@ inline void AppendStratumBoundaryCeilings(const FStandaloneOwnerWorld& World,
|
||||
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
|
||||
Mesh.NumCeilingTriangles+=static_cast<int32>(NewTriangles.size()/3);
|
||||
}
|
||||
|
||||
// Add the interior-facing top of each bedrock band. The density composition already
|
||||
// makes this band solid and editable; this planar closure keeps its visible floor
|
||||
// continuous when a coarse marching-cubes step would otherwise skip a sub-voxel band.
|
||||
inline void AppendStratumBoundaryFloors(const FStandaloneOwnerWorld& World,
|
||||
const FStandaloneShapeWorld& Shape,const FVoxelTileKey& Key,int32 Step,FVoxelMeshData& Mesh)
|
||||
{
|
||||
if(Key.Level<0||Step<=0||!World.Manager||!Shape.ResolvedLayout)return;
|
||||
const FIntVector Origin=Key.OriginVoxels();
|
||||
const int32 Extent=Key.ExtentVoxels();
|
||||
if(Extent<=0||Extent%Step!=0)return;
|
||||
const int32 GridSide=Extent/Step+1;
|
||||
struct FCandidate { double FloorZ=0.0; double Thickness=0.0; };
|
||||
std::vector<FCandidate> Candidates;
|
||||
const double CoreMicrons=std::max<std::uint32_t>(1,World.CoreVoxelSizeMicrons);
|
||||
for(const FStrateSlot& Slot:World.Manager->GetLayout())
|
||||
{
|
||||
const auto* Stratum=Shape.FindResolvedLayoutStratum(Slot.StrateIndex);
|
||||
if(!Stratum||!Stratum->bedrock.presentInCurrentField
|
||||
||Stratum->bedrock.topMicrometres<=Stratum->bedrock.bottomMicrometres)continue;
|
||||
const double FloorZ=static_cast<double>(Stratum->bedrock.topMicrometres)/CoreMicrons;
|
||||
if(FloorZ<Origin.Z-1.0e-6||FloorZ>Origin.Z+Extent+1.0e-6)continue;
|
||||
Candidates.push_back({FloorZ,static_cast<double>(Stratum->bedrock.topMicrometres
|
||||
-Stratum->bedrock.bottomMicrometres)/CoreMicrons});
|
||||
}
|
||||
if(Candidates.empty())return;
|
||||
|
||||
const int32 Cells=std::max(1,World.CoarseTileCells);
|
||||
FFarMeshColumnIndex MeshIndex(Mesh,Origin,Extent,Cells,Step,
|
||||
static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0);
|
||||
const int32 OldVertexCount=Mesh.Vertices.Num();
|
||||
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,OldVertexCount);
|
||||
const bool bHasUVs=Mesh.UVs.Num()==OldVertexCount;
|
||||
const bool bHasColors=Mesh.Colors.Num()==OldVertexCount;
|
||||
const FColor Color=bHasColors&&OldVertexCount>0?Mesh.Colors[0]:FColor(160,160,160,255);
|
||||
const double VoxelSizeCm=static_cast<double>(World.CoreVoxelSizeMicrons)/10000.0;
|
||||
std::vector<FVector> NewVertices,NewNormals;
|
||||
std::vector<FVector2f> NewUVs;
|
||||
std::vector<FColor> NewColors;
|
||||
std::vector<int32> NewTriangles;
|
||||
for(const FCandidate& Candidate:Candidates)
|
||||
{
|
||||
const double Epsilon=std::min(0.5,Candidate.Thickness*0.25);
|
||||
if(Epsilon<=0.0)continue;
|
||||
std::vector<uint8> SolidBelow(static_cast<std::size_t>(GridSide)*GridSide,0);
|
||||
for(int32 Y=0;Y<GridSide;++Y)for(int32 X=0;X<GridSide;++X)
|
||||
{
|
||||
const double WorldX=Origin.X+X*Step,WorldY=Origin.Y+Y*Step;
|
||||
const float Density=Shape.DensityAt(static_cast<float>(WorldX),static_cast<float>(WorldY),
|
||||
static_cast<float>(Candidate.FloorZ-Epsilon));
|
||||
SolidBelow[static_cast<std::size_t>(Y*GridSide+X)]=
|
||||
std::isfinite(Density)&&Density<0.0f?1:0;
|
||||
}
|
||||
std::vector<int32> VertexMap(static_cast<std::size_t>(GridSide)*GridSide,INDEX_NONE);
|
||||
const auto AddVertex=[&](int32 X,int32 Y)
|
||||
{
|
||||
const std::size_t GridIndex=static_cast<std::size_t>(Y*GridSide+X);
|
||||
int32& Mapped=VertexMap[GridIndex];
|
||||
if(Mapped!=INDEX_NONE)return Mapped;
|
||||
Mapped=static_cast<int32>(NewVertices.size());
|
||||
const double WorldX=Origin.X+X*Step,WorldY=Origin.Y+Y*Step;
|
||||
NewVertices.emplace_back(WorldX*VoxelSizeCm,WorldY*VoxelSizeCm,
|
||||
Candidate.FloorZ*VoxelSizeCm);
|
||||
NewNormals.emplace_back(0.0,0.0,1.0);
|
||||
if(bHasUVs)NewUVs.emplace_back(static_cast<float>(WorldX),static_cast<float>(WorldY));
|
||||
if(bHasColors)NewColors.push_back(Color);
|
||||
return Mapped;
|
||||
};
|
||||
for(int32 Y=0;Y+1<GridSide;++Y)for(int32 X=0;X+1<GridSide;++X)
|
||||
{
|
||||
const std::size_t I00=static_cast<std::size_t>(Y*GridSide+X);
|
||||
const std::size_t I10=I00+1,I01=I00+GridSide,I11=I01+1;
|
||||
if(!SolidBelow[I00]||!SolidBelow[I10]||!SolidBelow[I01]||!SolidBelow[I11])continue;
|
||||
const double X0=Origin.X+X*Step,Y0=Origin.Y+Y*Step;
|
||||
const double X1=X0+Step,Y1=Y0+Step;
|
||||
if(MeshIndex.HasUpwardCellCoverage(X0,Y0,X1,Y1,Candidate.FloorZ,0.001))continue;
|
||||
const int32 A=AddVertex(X,Y),B=AddVertex(X+1,Y);
|
||||
const int32 C=AddVertex(X+1,Y+1),D=AddVertex(X,Y+1);
|
||||
NewTriangles.insert(NewTriangles.end(),{A,B,C,A,C,D});
|
||||
}
|
||||
}
|
||||
if(NewTriangles.empty())return;
|
||||
|
||||
const int32 AddedVertices=static_cast<int32>(NewVertices.size());
|
||||
auto InsertVertices=[&](auto& Existing,const auto& Added)
|
||||
{
|
||||
using TArrayType=std::remove_reference_t<decltype(Existing)>;
|
||||
TArrayType Combined;Combined.Reserve(Existing.Num()+static_cast<int32>(Added.size()));
|
||||
for(int32 I=0;I<OldSurfaceVertices;++I)Combined.Add(Existing[I]);
|
||||
for(const auto& Value:Added)Combined.Add(Value);
|
||||
for(int32 I=OldSurfaceVertices;I<Existing.Num();++I)Combined.Add(Existing[I]);
|
||||
Existing=std::move(Combined);
|
||||
};
|
||||
InsertVertices(Mesh.Vertices,NewVertices);InsertVertices(Mesh.Normals,NewNormals);
|
||||
if(bHasUVs)InsertVertices(Mesh.UVs,NewUVs);
|
||||
if(bHasColors)InsertVertices(Mesh.Colors,NewColors);
|
||||
for(int32& Index:Mesh.Triangles)if(Index>=OldSurfaceVertices)Index+=AddedVertices;
|
||||
const int32 CeilingStart=std::max(0,Mesh.Triangles.Num()-Mesh.NumCeilingTriangles*3);
|
||||
TArray<int32> CombinedTriangles;CombinedTriangles.Reserve(
|
||||
Mesh.Triangles.Num()+static_cast<int32>(NewTriangles.size()));
|
||||
for(int32 I=0;I<CeilingStart;++I)CombinedTriangles.Add(Mesh.Triangles[I]);
|
||||
for(const int32 Index:NewTriangles)CombinedTriangles.Add(OldSurfaceVertices+Index);
|
||||
for(int32 I=CeilingStart;I<Mesh.Triangles.Num();++I)CombinedTriangles.Add(Mesh.Triangles[I]);
|
||||
const int32 OldTriangleCount=Mesh.Triangles.Num()/3;
|
||||
TArray<uint8> ExistingFaceMasks=Mesh.TriangleSkirtFaceMasks;
|
||||
ExistingFaceMasks.SetNum(OldTriangleCount);
|
||||
TArray<uint8> CombinedFaceMasks;CombinedFaceMasks.Reserve(
|
||||
OldTriangleCount+static_cast<int32>(NewTriangles.size()/3));
|
||||
for(int32 Triangle=0;Triangle<CeilingStart/3;++Triangle)
|
||||
CombinedFaceMasks.Add(ExistingFaceMasks[Triangle]);
|
||||
for(std::size_t Triangle=0;Triangle<NewTriangles.size()/3;++Triangle)
|
||||
CombinedFaceMasks.Add(0);
|
||||
for(int32 Triangle=CeilingStart/3;Triangle<OldTriangleCount;++Triangle)
|
||||
CombinedFaceMasks.Add(ExistingFaceMasks[Triangle]);
|
||||
Mesh.Triangles=std::move(CombinedTriangles);
|
||||
Mesh.TriangleSkirtFaceMasks=std::move(CombinedFaceMasks);
|
||||
Mesh.NumSurfaceVertices=OldSurfaceVertices+AddedVertices;
|
||||
}
|
||||
@@ -170,6 +170,8 @@ namespace floating_island_detail
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutCenterTopZSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutMinimumZSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutMaximumZSubvoxels), 8);
|
||||
if(recipe.geometryVersion>=4)
|
||||
HashWord(hash,static_cast<std::uint64_t>(recipe.layoutChunkWidthSubvoxels),8);
|
||||
HashWord(hash, recipe.primaryIslandCount, 4);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.islandDensityQ16), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.spacingSubvoxels), 8);
|
||||
@@ -245,10 +247,10 @@ namespace floating_island_detail
|
||||
|
||||
inline bool ValidateRecipe(const FloatingIslandRecipeV1& recipe, std::string& error)
|
||||
{
|
||||
const std::array<std::int64_t, 25> values = {
|
||||
const std::array<std::int64_t, 26> values = {
|
||||
recipe.layoutOriginXSubvoxels, recipe.layoutOriginYSubvoxels,
|
||||
recipe.layoutCenterTopZSubvoxels, recipe.layoutMinimumZSubvoxels,
|
||||
recipe.layoutMaximumZSubvoxels, recipe.islandDensityQ16,
|
||||
recipe.layoutMaximumZSubvoxels, recipe.layoutChunkWidthSubvoxels, recipe.islandDensityQ16,
|
||||
recipe.spacingSubvoxels, recipe.minimumRadiusSubvoxels,
|
||||
recipe.maximumRadiusSubvoxels, recipe.thicknessRatioQ16,
|
||||
recipe.verticalJitterQ16, recipe.topFlattenQ16,
|
||||
@@ -266,7 +268,7 @@ namespace floating_island_detail
|
||||
? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0;
|
||||
const std::uint32_t ExpectedDenominator = ScaleGcd > 0
|
||||
? recipe.coreVoxelSizeMicrons / ScaleGcd : 0;
|
||||
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 3
|
||||
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 4
|
||||
|| (recipe.geometryVersion == 1
|
||||
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|
||||
|| recipe.unitsPerVoxel != Q
|
||||
@@ -275,6 +277,11 @@ namespace floating_island_detail
|
||||
|| recipe.ownerToCoreScaleNumerator != ExpectedNumerator
|
||||
|| recipe.ownerToCoreScaleDenominator != ExpectedDenominator
|
||||
|| recipe.primaryIslandCount < 3 || recipe.primaryIslandCount > 9
|
||||
|| recipe.layoutChunkWidthSubvoxels <= 0
|
||||
|| static_cast<long double>(recipe.layoutChunkWidthSubvoxels)*recipe.coreVoxelSizeMicrons
|
||||
< 250000.0L*Q
|
||||
|| static_cast<long double>(recipe.layoutChunkWidthSubvoxels)*recipe.coreVoxelSizeMicrons
|
||||
> 64000000.0L*Q
|
||||
|| recipe.layoutMaximumZSubvoxels <= recipe.layoutMinimumZSubvoxels
|
||||
|| recipe.layoutMaximumZSubvoxels - recipe.layoutMinimumZSubvoxels > 2048 * Q
|
||||
|| std::any_of(values.begin(), values.end(), [](auto value) { return !ValidScalar(value); }))
|
||||
@@ -1196,7 +1203,8 @@ namespace floating_island_detail
|
||||
}
|
||||
|
||||
std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
|
||||
std::string_view json, std::string& error)
|
||||
std::string_view json, std::string& error,
|
||||
std::int64_t resolvedLayoutChunkWidthSubvoxels)
|
||||
{
|
||||
VFJson::Value root;
|
||||
if (!VFJson::Parser(std::string(json)).Parse(root, error)) return std::nullopt;
|
||||
@@ -1222,6 +1230,15 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
|
||||
manifest.recipe.crustDepthBonusQ16 = 0;
|
||||
manifest.recipe.rootDepthRatioQ16 = 0;
|
||||
}
|
||||
if(manifest.recipe.geometryVersion>=4)
|
||||
{
|
||||
if(!floating_island_detail::ReadInteger(recipeValue.Get("layout_chunk_width_q"),
|
||||
manifest.recipe.layoutChunkWidthSubvoxels))
|
||||
{error="floating-island recipe is missing its resolved layout chunk width";return std::nullopt;}
|
||||
if(resolvedLayoutChunkWidthSubvoxels>0
|
||||
&& manifest.recipe.layoutChunkWidthSubvoxels!=resolvedLayoutChunkWidthSubvoxels)
|
||||
{error="floating-island recipe layout width disagrees with the resolved world layout";return std::nullopt;}
|
||||
}
|
||||
if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema)
|
||||
|| !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash)
|
||||
|| !floating_island_detail::ReadU32(recipeValue.Get("schema_version"), manifest.recipe.schemaVersion)
|
||||
@@ -1257,6 +1274,10 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
|
||||
|| !floating_island_detail::ReadHash(planValue.Get("content_hash"), planHash)
|
||||
|| schema != 1)
|
||||
{ error = "floating-island manifest is missing a required versioned recipe or plan field"; return std::nullopt; }
|
||||
if(manifest.recipe.geometryVersion<4 && resolvedLayoutChunkWidthSubvoxels>0)
|
||||
manifest.recipe.layoutChunkWidthSubvoxels=resolvedLayoutChunkWidthSubvoxels;
|
||||
else if(manifest.recipe.geometryVersion<4)
|
||||
{ error="legacy floating-island manifest requires the world's resolved layout chunk width"; return std::nullopt; }
|
||||
manifest.recipe.contentHash = recipeHash;
|
||||
manifest.contentHash = manifestHash;
|
||||
manifest.plan.contentHash = planHash;
|
||||
@@ -1368,6 +1389,8 @@ std::string FloatingIslandManifestV1::ToJson() const
|
||||
out << ",\"geometry_version\":" << recipe.geometryVersion
|
||||
<< ",\"crust_depth_bonus_q16\":" << recipe.crustDepthBonusQ16
|
||||
<< ",\"root_depth_ratio_q16\":" << recipe.rootDepthRatioQ16;
|
||||
if(recipe.geometryVersion>=4)
|
||||
out << ",\"layout_chunk_width_q\":" << recipe.layoutChunkWidthSubvoxels;
|
||||
out << ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels
|
||||
<< ",\"contour_warp_q\":" << recipe.contourWarpSubvoxels
|
||||
<< ",\"roughness_q\":" << recipe.roughnessSubvoxels
|
||||
|
||||
@@ -39,37 +39,42 @@ namespace
|
||||
}
|
||||
std::string LayoutHex(std::uint64_t value)
|
||||
{std::ostringstream out;out<<std::hex<<std::setfill('0')<<std::setw(16)<<value;return out.str();}
|
||||
std::int64_t LayoutTransitionLimit(const ResolvedLayoutStratumV1& stratum)
|
||||
std::int64_t LayoutTransitionLimit(const ResolvedLayoutStratumV2& stratum)
|
||||
{
|
||||
if(stratum.transitionMode=="Hard")return 0;
|
||||
if(stratum.transitionMode=="Hard"||stratum.transitionFallsBackToHard)return 0;
|
||||
return stratum.transitionWidthMicrometres
|
||||
+(stratum.transitionMode=="Interleaved"?stratum.transitionWarpAmplitudeMicrometres:0);
|
||||
}
|
||||
std::string LayoutBandJson(const ResolvedLayoutBandV1& band)
|
||||
std::string LayoutBandJson(const ResolvedLayoutBandV2& band)
|
||||
{
|
||||
std::ostringstream out;out<<"{\"kind\":"<<LayoutEscape(band.kind)
|
||||
<<",\"bottom_um\":"<<band.bottomMicrometres<<",\"top_um\":"<<band.topMicrometres
|
||||
<<",\"present\":"<<(band.presentInCurrentField?"true":"false")
|
||||
<<",\"diggable\":"<<(band.diggable?"true":"false")
|
||||
<<",\"protected\":"<<(band.protectedBand?"true":"false")
|
||||
<<",\"solid_density_q16\":"<<band.solidDensityQ16
|
||||
<<",\"behaviour\":"<<LayoutEscape(band.behaviour)
|
||||
<<",\"density_side\":"<<LayoutEscape(band.densitySide)
|
||||
<<",\"cap_path\":"<<LayoutEscape(band.capPath)<<'}';return out.str();
|
||||
}
|
||||
ResolvedLayoutBandV1 ParseLayoutBand(const VFJson::Value& value)
|
||||
ResolvedLayoutBandV2 ParseLayoutBand(const VFJson::Value& value)
|
||||
{
|
||||
ResolvedLayoutBandV1 b;b.kind=value.Get("kind").String();
|
||||
ResolvedLayoutBandV2 b;b.kind=value.Get("kind").String();
|
||||
b.bottomMicrometres=value.Get("bottom_um").Integer();b.topMicrometres=value.Get("top_um").Integer();
|
||||
b.presentInCurrentField=value.Get("present").Boolean();b.diggable=value.Get("diggable").Boolean(true);
|
||||
b.protectedBand=value.Get("protected").Boolean();b.behaviour=value.Get("behaviour").String();
|
||||
b.protectedBand=value.Get("protected").Boolean();b.solidDensityQ16=value.Get("solid_density_q16").Integer(65536);
|
||||
b.behaviour=value.Get("behaviour").String();
|
||||
b.densitySide=value.Get("density_side").String();b.capPath=value.Get("cap_path").String();return b;
|
||||
}
|
||||
std::string LayoutBodyJson(const ResolvedLayoutSnapshotV1& s)
|
||||
std::string LayoutBodyJson(const ResolvedLayoutSnapshotV2& s)
|
||||
{
|
||||
std::ostringstream out;out<<"{\"schema_version\":"<<s.schemaVersion
|
||||
<<",\"world_identity\":"<<LayoutEscape(s.worldIdentity)
|
||||
<<",\"owner_voxel_size_um\":"<<s.ownerVoxelSizeMicrometres
|
||||
<<",\"core_voxel_size_um\":"<<s.coreVoxelSizeMicrometres<<",\"strates\":[";
|
||||
<<",\"core_voxel_size_um\":"<<s.coreVoxelSizeMicrometres
|
||||
<<",\"strate_layout_chunk_size_um\":"<<s.strateLayoutChunkSizeMicrometres
|
||||
<<",\"strate_layout_chunk_width_subvoxels\":"<<s.strateLayoutChunkWidthSubvoxels
|
||||
<<",\"strates\":[";
|
||||
for(std::size_t i=0;i<s.strates.size();++i)
|
||||
{
|
||||
if(i)out<<',';const auto& x=s.strates[i];
|
||||
@@ -90,6 +95,7 @@ namespace
|
||||
<<",\"transition_warp_amplitude_um\":"<<x.transitionWarpAmplitudeMicrometres
|
||||
<<",\"transition_interpenetration_um\":"<<x.transitionInterpenetrationMicrometres
|
||||
<<",\"transition_honoured_in_core\":"<<(x.transitionHonouredInCore?"true":"false")
|
||||
<<",\"transition_falls_back_to_hard\":"<<(x.transitionFallsBackToHard?"true":"false")
|
||||
<<",\"transition_dispatch_path\":"<<LayoutEscape(x.transitionDispatchPath)
|
||||
<<",\"bound_dispatch_path\":"<<LayoutEscape(x.boundDispatchPath)
|
||||
<<",\"query_dispatch_path\":"<<LayoutEscape(x.queryDispatchPath)
|
||||
@@ -117,17 +123,18 @@ namespace
|
||||
{return obj.Get(key).Boolean(fallback);}
|
||||
}
|
||||
|
||||
std::string ResolvedLayoutSnapshotV1::ToJson() const
|
||||
std::string ResolvedLayoutSnapshotV2::ToJson() const
|
||||
{
|
||||
const std::string body=LayoutBodyJson(*this);
|
||||
return body.substr(0,body.size()-1)+",\"stable_identity\":"
|
||||
+LayoutEscape(stableIdentity.empty()?"resolved-layout-v1-fnv64:"+LayoutHex(LayoutHash(body)):stableIdentity)+"}";
|
||||
+LayoutEscape(stableIdentity.empty()?"resolved-layout-v2-fnv64:"+LayoutHex(LayoutHash(body)):stableIdentity)+"}";
|
||||
}
|
||||
|
||||
bool ResolvedLayoutSnapshotV1::finalize(std::string& error)
|
||||
bool ResolvedLayoutSnapshotV2::finalize(std::string& error)
|
||||
{
|
||||
error.clear();
|
||||
if(schemaVersion!=ResolvedLayoutSchemaVersionV1||worldIdentity.empty()||strates.empty())
|
||||
if(schemaVersion!=ResolvedLayoutSchemaVersionV2||worldIdentity.empty()||strates.empty()
|
||||
||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0)
|
||||
{error="resolved layout has an unsupported version, empty world identity, or no strates";return false;}
|
||||
std::stable_sort(strates.begin(),strates.end(),[](const auto& a,const auto& b)
|
||||
{return a.interiorTopMicrometres!=b.interiorTopMicrometres
|
||||
@@ -143,20 +150,21 @@ bool ResolvedLayoutSnapshotV1::finalize(std::string& error)
|
||||
{error="resolved layout contains an invalid interior interval or voxel size";return false;}
|
||||
}
|
||||
stableIdentity.clear();const std::string body=LayoutBodyJson(*this);
|
||||
stableIdentity="resolved-layout-v1-fnv64:"+LayoutHex(LayoutHash(body));
|
||||
std::vector<ResolvedLayoutIssueV1> issues;
|
||||
return validate(ResolvedLayoutValidationModeV1::compatibility,issues,error);
|
||||
stableIdentity="resolved-layout-v2-fnv64:"+LayoutHex(LayoutHash(body));
|
||||
std::vector<ResolvedLayoutIssueV2> issues;
|
||||
return validate(ResolvedLayoutValidationModeV2::compatibility,issues,error);
|
||||
}
|
||||
|
||||
bool ResolvedLayoutSnapshotV1::validate(ResolvedLayoutValidationModeV1 mode,
|
||||
std::vector<ResolvedLayoutIssueV1>& issues,std::string& error) const
|
||||
bool ResolvedLayoutSnapshotV2::validate(ResolvedLayoutValidationModeV2 mode,
|
||||
std::vector<ResolvedLayoutIssueV2>& issues,std::string& error) const
|
||||
{
|
||||
issues.clear();error.clear();
|
||||
if(schemaVersion!=ResolvedLayoutSchemaVersionV1||worldIdentity.empty()||strates.empty())
|
||||
if(schemaVersion!=ResolvedLayoutSchemaVersionV2||worldIdentity.empty()||strates.empty()
|
||||
||strateLayoutChunkSizeMicrometres==0||strateLayoutChunkWidthSubvoxels<=0)
|
||||
{error="resolved layout schema/world/strata header is invalid";return false;}
|
||||
const auto add=[&](std::string code,std::string detail)
|
||||
{issues.push_back({std::move(code),std::move(detail)});};
|
||||
const bool strict=mode==ResolvedLayoutValidationModeV1::strict;
|
||||
const bool strict=mode==ResolvedLayoutValidationModeV2::strict;
|
||||
for(std::size_t i=0;i<strates.size();++i)
|
||||
{
|
||||
const auto& s=strates[i];
|
||||
@@ -169,7 +177,8 @@ bool ResolvedLayoutSnapshotV1::validate(ResolvedLayoutValidationModeV1 mode,
|
||||
if(s.managerChunkWidthMicrometres>0&&s.dispatchChunkWidthMicrometres>0
|
||||
&&s.managerChunkWidthMicrometres!=s.dispatchChunkWidthMicrometres)
|
||||
add("ambiguous_dispatch_units",s.stableId+" manager chunk width differs from Core dispatch chunk width");
|
||||
if(i+1<strates.size()&&s.transitionMode!="Hard"&&!s.transitionHonouredInCore)
|
||||
if(i+1<strates.size()&&s.transitionMode!="Hard"&&!s.transitionHonouredInCore
|
||||
&&!s.transitionFallsBackToHard)
|
||||
add("transition_not_honoured",s.stableId+" declares a blend/interleave that Core does not apply on this interface");
|
||||
if(!s.ceiling.presentInCurrentField)add("missing_ceiling",s.stableId+" has no explicit ceiling band in today's field");
|
||||
if(!s.bedrock.presentInCurrentField)add("missing_bedrock",s.stableId+" has no explicit bedrock floor in today's field");
|
||||
@@ -180,12 +189,14 @@ bool ResolvedLayoutSnapshotV1::validate(ResolvedLayoutValidationModeV1 mode,
|
||||
add(!s.ceiling.presentInCurrentField?"missing_ceiling":"missing_bedrock",s.stableId+" lacks a required boundary band");
|
||||
if(!s.content.presentInCurrentField)
|
||||
add("missing_content",s.stableId+" has no content generator band");
|
||||
const auto bandShape=[&](const ResolvedLayoutBandV1& b,std::string_view expected)
|
||||
const auto bandShape=[&](const ResolvedLayoutBandV2& b,std::string_view expected)
|
||||
{
|
||||
if(b.kind!=expected||b.topMicrometres<=b.bottomMicrometres)
|
||||
add("wrong_order",s.stableId+" has an invalid "+std::string(expected)+" band");
|
||||
if(b.protectedBand&&b.diggable)
|
||||
add("invalid_protection",s.stableId+" band is both protected and diggable");
|
||||
if(b.solidDensityQ16<=0)
|
||||
add("invalid_solid_density",s.stableId+" has a non-positive solid band strength");
|
||||
};
|
||||
bandShape(s.ceiling,"ceiling");bandShape(s.content,"content");bandShape(s.bedrock,"bedrock");
|
||||
if(s.ceiling.bottomMicrometres!=s.content.topMicrometres
|
||||
@@ -255,18 +266,20 @@ bool ResolvedLayoutSnapshotV1::validate(ResolvedLayoutValidationModeV1 mode,
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<ResolvedLayoutSnapshotV1> ResolvedLayoutSnapshotV1::FromJson(
|
||||
std::optional<ResolvedLayoutSnapshotV2> ResolvedLayoutSnapshotV2::FromJson(
|
||||
std::string_view json,std::string& error)
|
||||
{
|
||||
VFJson::Value root;if(!VFJson::Parser(std::string(json)).Parse(root,error))return std::nullopt;
|
||||
if(!root.IsObject()){error="resolved layout JSON root must be an object";return std::nullopt;}
|
||||
ResolvedLayoutSnapshotV1 s;s.schemaVersion=static_cast<std::uint32_t>(LayoutInt(root,"schema_version"));
|
||||
ResolvedLayoutSnapshotV2 s;s.schemaVersion=static_cast<std::uint32_t>(LayoutInt(root,"schema_version"));
|
||||
s.worldIdentity=LayoutString(root,"world_identity");
|
||||
s.ownerVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"owner_voxel_size_um"));
|
||||
s.coreVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"core_voxel_size_um"));
|
||||
s.strateLayoutChunkSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"strate_layout_chunk_size_um"));
|
||||
s.strateLayoutChunkWidthSubvoxels=LayoutInt(root,"strate_layout_chunk_width_subvoxels");
|
||||
for(const auto& v:root.Get("strates").AsArray())
|
||||
{
|
||||
ResolvedLayoutStratumV1 x;x.stableId=LayoutString(v,"stable_id");
|
||||
ResolvedLayoutStratumV2 x;x.stableId=LayoutString(v,"stable_id");
|
||||
x.order=static_cast<std::int32_t>(LayoutInt(v,"order"));x.strateIndex=static_cast<std::int32_t>(LayoutInt(v,"strate_index"));
|
||||
x.generatorKind=LayoutString(v,"generator_kind");x.recipeId=LayoutString(v,"recipe_id");
|
||||
x.recipeVersion=static_cast<std::uint32_t>(LayoutInt(v,"recipe_version"));
|
||||
@@ -278,12 +291,14 @@ std::optional<ResolvedLayoutSnapshotV1> ResolvedLayoutSnapshotV1::FromJson(
|
||||
x.transitionWidthMicrometres=LayoutInt(v,"transition_width_um");
|
||||
x.transitionWarpAmplitudeMicrometres=LayoutInt(v,"transition_warp_amplitude_um");
|
||||
x.transitionInterpenetrationMicrometres=LayoutInt(v,"transition_interpenetration_um");
|
||||
x.transitionHonouredInCore=LayoutBool(v,"transition_honoured_in_core");x.transitionDispatchPath=LayoutString(v,"transition_dispatch_path");
|
||||
x.transitionHonouredInCore=LayoutBool(v,"transition_honoured_in_core");
|
||||
x.transitionFallsBackToHard=LayoutBool(v,"transition_falls_back_to_hard");
|
||||
x.transitionDispatchPath=LayoutString(v,"transition_dispatch_path");
|
||||
x.boundDispatchPath=LayoutString(v,"bound_dispatch_path");x.queryDispatchPath=LayoutString(v,"query_dispatch_path");
|
||||
x.managerLookup=LayoutString(v,"manager_lookup");x.managerChunkWidthMicrometres=LayoutInt(v,"manager_chunk_width_um");
|
||||
x.dispatchChunkWidthMicrometres=LayoutInt(v,"dispatch_chunk_width_um");
|
||||
for(const auto& a:v.Get("anchors").AsArray())
|
||||
{ResolvedLayoutAnchorV1 p;p.kind=LayoutString(a,"kind");p.stableId=LayoutString(a,"stable_id");p.units=LayoutString(a,"units");
|
||||
{ResolvedLayoutAnchorV2 p;p.kind=LayoutString(a,"kind");p.stableId=LayoutString(a,"stable_id");p.units=LayoutString(a,"units");
|
||||
for(const auto& value:a.Get("values").AsArray())p.values.push_back(value.Integer());x.anchors.push_back(std::move(p));}
|
||||
s.strates.push_back(std::move(x));
|
||||
}
|
||||
@@ -293,12 +308,12 @@ std::optional<ResolvedLayoutSnapshotV1> ResolvedLayoutSnapshotV1::FromJson(
|
||||
return s;
|
||||
}
|
||||
|
||||
ResolvedLayoutValidatorAuditV1 AuditResolvedLayoutValidatorV1()
|
||||
ResolvedLayoutValidatorAuditV2 AuditResolvedLayoutValidatorV2()
|
||||
{
|
||||
ResolvedLayoutValidatorAuditV1 result;
|
||||
ResolvedLayoutValidatorAuditV2 result;
|
||||
auto make=[](std::string id,std::int32_t order,std::int64_t top,std::int64_t bottom)
|
||||
{
|
||||
ResolvedLayoutStratumV1 s;s.stableId=std::move(id);s.order=order;s.strateIndex=order;s.generatorKind="synthetic";
|
||||
ResolvedLayoutStratumV2 s;s.stableId=std::move(id);s.order=order;s.strateIndex=order;s.generatorKind="synthetic";
|
||||
s.interiorTopMicrometres=top;s.interiorBottomMicrometres=bottom;s.voxelSizeMicrometres=250000;
|
||||
s.transitionMode="Gradient";s.transitionWidthMicrometres=4000000;
|
||||
s.transitionHonouredInCore=true;
|
||||
@@ -307,15 +322,15 @@ ResolvedLayoutValidatorAuditV1 AuditResolvedLayoutValidatorV1()
|
||||
s.bedrock={"bedrock",bottom-1000000,bottom,true,true,false,"breakable bedrock","lower face","none"};
|
||||
return s;
|
||||
};
|
||||
const auto expectReject=[&](std::string name,ResolvedLayoutSnapshotV1 candidate)
|
||||
const auto expectReject=[&](std::string name,ResolvedLayoutSnapshotV2 candidate)
|
||||
{
|
||||
++result.controlsRun;std::vector<ResolvedLayoutIssueV1> issues;std::string error;
|
||||
if(!candidate.validate(ResolvedLayoutValidationModeV1::strict,issues,error))++result.controlsRejectedAsExpected;
|
||||
++result.controlsRun;std::vector<ResolvedLayoutIssueV2> issues;std::string error;
|
||||
if(!candidate.validate(ResolvedLayoutValidationModeV2::strict,issues,error))++result.controlsRejectedAsExpected;
|
||||
else result.failures.push_back({"negative_control_accepted",std::move(name)+" was accepted by strict validator"});
|
||||
};
|
||||
auto base=ResolvedLayoutSnapshotV1{};base.worldIdentity="synthetic";base.strates={make("a",0,10000000,0),make("b",1,-2000000,-12000000)};
|
||||
{std::vector<ResolvedLayoutIssueV1> issues;std::string error;
|
||||
if(!base.validate(ResolvedLayoutValidationModeV1::strict,issues,error))
|
||||
auto base=ResolvedLayoutSnapshotV2{};base.worldIdentity="synthetic";base.strates={make("a",0,10000000,0),make("b",1,-2000000,-12000000)};
|
||||
{std::vector<ResolvedLayoutIssueV2> issues;std::string error;
|
||||
if(!base.validate(ResolvedLayoutValidationModeV2::strict,issues,error))
|
||||
result.failures.push_back({"positive_control_rejected","valid ceiling/content/bedrock layout was rejected"});}
|
||||
auto boundaryGap=base;boundaryGap.strates[1].ceiling.topMicrometres-=1;
|
||||
expectReject("bedrock/next-ceiling gap",boundaryGap);
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
class FStandaloneShapeWorld
|
||||
{
|
||||
public:
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV2* ResolvedLayout=nullptr;
|
||||
struct FResolvedStratumBounds
|
||||
{
|
||||
double BottomZ=0.0;
|
||||
@@ -47,12 +48,53 @@ public:
|
||||
: static_cast<double>(Owner->CoreVoxelSizeMicrons) / 10000.0;
|
||||
}
|
||||
|
||||
double GetCoreVoxelsPerOwnerChunk() const
|
||||
double GetResolvedLayoutChunkWidthVoxels() const
|
||||
{
|
||||
const double OwnerVoxelSizeCm = Owner == nullptr || Owner->OwnerVoxelSizeMicrons == 0
|
||||
? 0.5 * static_cast<double>(VOXEL_SIZE)
|
||||
: static_cast<double>(Owner->OwnerVoxelSizeMicrons) / 10000.0;
|
||||
return static_cast<double>(CHUNK_SIZE) * OwnerVoxelSizeCm / GetCoreVoxelSizeCm();
|
||||
return ResolvedLayout&&ResolvedLayout->strateLayoutChunkWidthSubvoxels>0
|
||||
?static_cast<double>(ResolvedLayout->strateLayoutChunkWidthSubvoxels)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
|
||||
:Owner?static_cast<double>(Owner->StrateLayoutChunkWidthSubvoxels)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel:0.0;
|
||||
}
|
||||
|
||||
const VoxelForgeCore::ResolvedLayoutStratumV2* FindResolvedLayoutStratum(int32 StrateIndex) const
|
||||
{
|
||||
if(!ResolvedLayout)return nullptr;
|
||||
const auto it=std::find_if(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(),
|
||||
[StrateIndex](const auto& item){return item.strateIndex==StrateIndex;});
|
||||
return it==ResolvedLayout->strates.end()?nullptr:&*it;
|
||||
}
|
||||
|
||||
bool IsInsideResolvedStrateBoundaryBand(float Z) const
|
||||
{
|
||||
if(!ResolvedLayout||!Owner||!Owner->Manager||!std::isfinite(Z))return false;
|
||||
const double worldZCentimetres=static_cast<double>(Z)*GetCoreVoxelSizeCm();
|
||||
const int32 strateIndex=Owner->Manager->GetStrateIndex(static_cast<float>(worldZCentimetres));
|
||||
const auto* stratum=FindResolvedLayoutStratum(strateIndex);
|
||||
if(!stratum)return false;
|
||||
const double zMicrometres=static_cast<double>(Z)*Owner->CoreVoxelSizeMicrons;
|
||||
const auto solidAt=[&](const VoxelForgeCore::ResolvedLayoutBandV2& band)
|
||||
{return band.presentInCurrentField&&zMicrometres>=band.bottomMicrometres
|
||||
&&zMicrometres<band.topMicrometres;};
|
||||
return solidAt(stratum->ceiling)||solidAt(stratum->bedrock);
|
||||
}
|
||||
|
||||
float ApplyResolvedStrateBoundaryBand(float density,float X,float Y,float Z) const
|
||||
{
|
||||
(void)X;(void)Y;
|
||||
if(!ResolvedLayout||!Owner||!Owner->Manager||!std::isfinite(Z))return density;
|
||||
const double worldZCentimetres=static_cast<double>(Z)*GetCoreVoxelSizeCm();
|
||||
const int32 strateIndex=Owner->Manager->GetStrateIndex(static_cast<float>(worldZCentimetres));
|
||||
const auto* stratum=FindResolvedLayoutStratum(strateIndex);
|
||||
if(!stratum)return density;
|
||||
const double zMicrometres=static_cast<double>(Z)*Owner->CoreVoxelSizeMicrons;
|
||||
const auto solidAt=[&](const VoxelForgeCore::ResolvedLayoutBandV2& band)
|
||||
{return band.presentInCurrentField&&zMicrometres>=band.bottomMicrometres
|
||||
&&zMicrometres<band.topMicrometres;};
|
||||
const auto* band=solidAt(stratum->ceiling)?&stratum->ceiling
|
||||
:solidAt(stratum->bedrock)?&stratum->bedrock:nullptr;
|
||||
if(!band)return density;
|
||||
return -std::max(0.05f,static_cast<float>(band->solidDensityQ16)/65536.0f);
|
||||
}
|
||||
|
||||
static bool MakeResolvedStratumBounds(double BottomZ,double TopZ,double SealThickness,
|
||||
@@ -67,6 +109,17 @@ public:
|
||||
bool GetResolvedStratumBounds(const FStrateSlot& Slot,FResolvedStratumBounds& Out) const
|
||||
{
|
||||
if(!Owner||!Owner->Manager)return false;
|
||||
if(const auto* resolved=FindResolvedLayoutStratum(Slot.StrateIndex))
|
||||
{
|
||||
const double coreMicrons=std::max<std::uint32_t>(1,Owner->CoreVoxelSizeMicrons);
|
||||
const double bottom=static_cast<double>(resolved->interiorBottomMicrometres)/coreMicrons;
|
||||
const double top=static_cast<double>(resolved->interiorTopMicrometres)/coreMicrons;
|
||||
const double ceiling=static_cast<double>(resolved->ceiling.topMicrometres
|
||||
-resolved->ceiling.bottomMicrometres)/coreMicrons;
|
||||
const double bedrock=static_cast<double>(resolved->bedrock.topMicrometres
|
||||
-resolved->bedrock.bottomMicrometres)/coreMicrons;
|
||||
return MakeResolvedStratumBounds(bottom,top,std::max(ceiling,bedrock),Out);
|
||||
}
|
||||
const int32 MidChunkZ=(Slot.TopChunkZ+Slot.BottomChunkZ)/2;
|
||||
if(bUseNativeManifestFields&&WorldAssembly
|
||||
&&Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,MidChunkZ))
|
||||
@@ -85,8 +138,9 @@ public:
|
||||
}
|
||||
const FStrateGenerationParams Params=Owner->Manager->GetGenerationParams(
|
||||
FIntVector(0,0,MidChunkZ));
|
||||
return MakeResolvedStratumBounds(static_cast<double>(Slot.BottomChunkZ)*CHUNK_SIZE,
|
||||
static_cast<double>(Slot.TopChunkZ+1)*CHUNK_SIZE,
|
||||
const double chunkWidth=GetResolvedLayoutChunkWidthVoxels();
|
||||
return MakeResolvedStratumBounds(static_cast<double>(Slot.BottomChunkZ)*chunkWidth,
|
||||
static_cast<double>(Slot.TopChunkZ+1)*chunkWidth,
|
||||
static_cast<double>(Params.BoundarySealThickness),Out);
|
||||
}
|
||||
|
||||
@@ -355,11 +409,12 @@ public:
|
||||
bool bValidatePassageRoutes=true,
|
||||
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InGpuVisualDetailTiles={},
|
||||
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InCpuVisualDetailTiles={},
|
||||
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly=nullptr)
|
||||
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly=nullptr,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout=nullptr)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
const auto TotalStart=Clock::now();
|
||||
Owner=&World;WorldAssembly=InWorldAssembly;
|
||||
Owner=&World;WorldAssembly=InWorldAssembly;ResolvedLayout=InResolvedLayout;
|
||||
WorkerCount=std::clamp(Workers,1,16);DetailVariant=Variant;
|
||||
Scheduler=InScheduler;RoundTunnelsEnabled=bInRoundTunnels;
|
||||
bUseNativeManifestFields=true;
|
||||
@@ -516,10 +571,11 @@ public:
|
||||
const FIntVector& ClipMaxVoxels,EDetailVariant Variant,
|
||||
bool bInRoundTunnels,
|
||||
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout,
|
||||
std::string& Error)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
Owner=&World;WorldAssembly=InWorldAssembly;
|
||||
Owner=&World;WorldAssembly=InWorldAssembly;ResolvedLayout=InResolvedLayout;
|
||||
WorkerCount=1;DetailVariant=Variant;Scheduler=nullptr;
|
||||
// Analytic pages retain their bounded owner bins for route bookkeeping. Once published,
|
||||
// the Core world binds each page to its base shape as the single density evaluator.
|
||||
@@ -555,18 +611,20 @@ public:
|
||||
|
||||
void InitializeNativeSurfaceOnly(FStandaloneOwnerWorld& World,
|
||||
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout,
|
||||
const FIntVector& InMinimum,const FIntVector& InMaximum)
|
||||
{
|
||||
Owner=&World;WorldAssembly=InWorldAssembly;
|
||||
Owner=&World;WorldAssembly=InWorldAssembly;ResolvedLayout=InResolvedLayout;
|
||||
NativeOnlyMinimum=InMinimum;NativeOnlyMaximum=InMaximum;
|
||||
bUseNativeManifestFields=true;bNativeOnly=true;bAnalyticOnly=true;Built=true;
|
||||
NativeOnlyViolations.store(0,std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool BuildShapeDependencyProof(FStandaloneOwnerWorld& World,
|
||||
const FIntVector& MinVoxels,const FIntVector& MaxVoxels,bool bInRoundTunnels)
|
||||
const FIntVector& MinVoxels,const FIntVector& MaxVoxels,bool bInRoundTunnels,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV2* InResolvedLayout=nullptr)
|
||||
{
|
||||
Owner=&World;WorkerCount=1;RoundTunnelsEnabled=bInRoundTunnels;
|
||||
Owner=&World;ResolvedLayout=InResolvedLayout;WorkerCount=1;RoundTunnelsEnabled=bInRoundTunnels;
|
||||
bAnalyticOnly=true;bNativeOnly=false;Stats={};
|
||||
for(int32 Axis=0;Axis<3;++Axis)
|
||||
{
|
||||
@@ -1071,19 +1129,19 @@ public:
|
||||
float NativeDensity=0.0f;
|
||||
if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||
{RecordNativeOnlyViolation();return 0.0f;}
|
||||
return NativeDensity;
|
||||
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
|
||||
}
|
||||
float IslandDensity=0.0f;
|
||||
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
||||
{
|
||||
++ThreadClassificationStats().NativeFloatFieldCalls;
|
||||
return ApplyEditBrushes(IslandDensity,X,Y,Z);
|
||||
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(IslandDensity,X,Y,Z),X,Y,Z);
|
||||
}
|
||||
float NativeDensity=0.0f;
|
||||
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||
{
|
||||
++ThreadClassificationStats().NativeFloatFieldCalls;
|
||||
return ApplyEditBrushes(NativeDensity,X,Y,Z);
|
||||
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
|
||||
}
|
||||
if(UsesLegacySurfaceField(X,Y,Z))
|
||||
return LegacySurfaceDensityAt(X,Y,Z);
|
||||
@@ -1104,7 +1162,7 @@ public:
|
||||
if(UsesLegacySurfaceField(X,Y,Z))return "SurfaceWorld-legacy";
|
||||
if(Owner&&Owner->Manager)
|
||||
{
|
||||
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CHUNK_SIZE));
|
||||
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(GetResolvedLayoutChunkWidthVoxels()));
|
||||
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ));
|
||||
switch(Type)
|
||||
{
|
||||
@@ -1133,21 +1191,21 @@ public:
|
||||
if(!NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
||||
static_cast<float>(Z),NativeDensity))
|
||||
{RecordNativeOnlyViolation();return 0.0f;}
|
||||
return NativeDensity;
|
||||
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
|
||||
}
|
||||
float IslandDensity=0.0f;
|
||||
if(NativeFloatingIslandDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
||||
static_cast<float>(Z),IslandDensity))
|
||||
{
|
||||
++ThreadClassificationStats().NativeLatticeFieldCalls;
|
||||
return ApplyEditBrushes(IslandDensity,X,Y,Z);
|
||||
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(IslandDensity,X,Y,Z),X,Y,Z);
|
||||
}
|
||||
float NativeDensity=0.0f;
|
||||
if(NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
||||
static_cast<float>(Z),NativeDensity))
|
||||
{
|
||||
++ThreadClassificationStats().NativeLatticeFieldCalls;
|
||||
return ApplyEditBrushes(NativeDensity,X,Y,Z);
|
||||
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
|
||||
}
|
||||
if(UsesLegacySurfaceField(static_cast<float>(X),static_cast<float>(Y),
|
||||
static_cast<float>(Z)))
|
||||
@@ -1171,19 +1229,19 @@ public:
|
||||
float NativeDensity=0.0f;
|
||||
if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||
{RecordNativeOnlyViolation();return 0.0f;}
|
||||
return NativeDensity;
|
||||
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
|
||||
}
|
||||
float IslandDensity=0.0f;
|
||||
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
||||
{
|
||||
++ThreadClassificationStats().NativeUnclampedFieldCalls;
|
||||
return ApplyEditBrushes(IslandDensity,X,Y,Z);
|
||||
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(IslandDensity,X,Y,Z),X,Y,Z);
|
||||
}
|
||||
float NativeDensity=0.0f;
|
||||
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||
{
|
||||
++ThreadClassificationStats().NativeUnclampedFieldCalls;
|
||||
return ApplyEditBrushes(NativeDensity,X,Y,Z);
|
||||
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z),X,Y,Z);
|
||||
}
|
||||
if(UsesLegacySurfaceField(X,Y,Z))
|
||||
return LegacySurfaceDensityAt(X,Y,Z);
|
||||
@@ -1224,6 +1282,12 @@ public:
|
||||
OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z);
|
||||
return;
|
||||
}
|
||||
if(IsInsideResolvedStrateBoundaryBand(static_cast<float>(Z)))
|
||||
{
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z);
|
||||
return;
|
||||
}
|
||||
bool bHasLegacySurfaceSample=false;
|
||||
bool bHasNativeIslandSample=false;
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
@@ -1307,13 +1371,14 @@ private:
|
||||
float Density=static_cast<float>(Proxy)*(1.0f/256.0f);
|
||||
if((Density<0.0f)!=bRock)
|
||||
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/256.0f);
|
||||
return ApplyEditBrushes(Density,FX,FY,FZ);
|
||||
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(Density,FX,FY,FZ),FX,FY,FZ);
|
||||
}
|
||||
// Match the fixed fallback to the edge of the stored proxy band. The
|
||||
// displacement is tapered to zero there, so this avoids a magnitude
|
||||
// jump along the candidate bit-mask boundary.
|
||||
constexpr float ProxyBandEdge=DetailSupremum+0.125f;
|
||||
return ApplyEditBrushes(bRock?-ProxyBandEdge:ProxyBandEdge,FX,FY,FZ);
|
||||
return ApplyEditBrushes(ApplyResolvedStrateBoundaryBand(
|
||||
bRock?-ProxyBandEdge:ProxyBandEdge,FX,FY,FZ),FX,FY,FZ);
|
||||
}
|
||||
|
||||
float AnalyticDensityAt(float X,float Y,float Z,bool bClampNearZero=true,
|
||||
@@ -1329,14 +1394,21 @@ private:
|
||||
if(OutFloorInfluence)*OutFloorInfluence=0.0f;
|
||||
float IslandDensity=0.0f;
|
||||
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
||||
return bApplyEdits?ApplyEditBrushes(IslandDensity,X,Y,Z):IslandDensity;
|
||||
{
|
||||
const float composed=ApplyResolvedStrateBoundaryBand(IslandDensity,X,Y,Z);
|
||||
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
||||
}
|
||||
if(UsesLegacySurfaceField(X,Y,Z))
|
||||
{
|
||||
float NativeDensity=0.0f;
|
||||
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||
{
|
||||
NativeDensity=ApplyResolvedStrateBoundaryBand(NativeDensity,X,Y,Z);
|
||||
return bApplyEdits?ApplyEditBrushes(NativeDensity,X,Y,Z):NativeDensity;
|
||||
}
|
||||
const float Density=Owner->Generator->GetDensityAt(X,Y,Z);
|
||||
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
|
||||
const float composed=ApplyResolvedStrateBoundaryBand(Density,X,Y,Z);
|
||||
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
||||
}
|
||||
const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]
|
||||
&&Z>=Min[2]&&Z<=Max[2];
|
||||
@@ -1350,7 +1422,8 @@ private:
|
||||
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
|
||||
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
|
||||
bClampNearZero?0.05f:1.0e-7f);
|
||||
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
|
||||
const float composed=ApplyResolvedStrateBoundaryBand(Density,X,Y,Z);
|
||||
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
|
||||
}
|
||||
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
|
||||
bool bFloorSurface=false,bCriticalAir=false;float CaveInfluence=0.0f,FloorInfluence=0.0f;
|
||||
@@ -1379,6 +1452,7 @@ private:
|
||||
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
|
||||
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
|
||||
bClampNearZero?0.05f:1.0e-7f);
|
||||
Density=ApplyResolvedStrateBoundaryBand(Density,X,Y,Z);
|
||||
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
|
||||
}
|
||||
|
||||
@@ -1388,10 +1462,11 @@ public:
|
||||
{
|
||||
if(SharedDensitySource)
|
||||
return SharedDensitySource->GetNativeSurfaceHeightsAt(X,Y,ChunkZ,OutGroundZ,OutCeilingZ);
|
||||
if(!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
|
||||
if(!WorldAssembly||!ResolvedLayout||!Owner||!Owner->Manager||!Owner->Generator
|
||||
||!std::isfinite(X)||!std::isfinite(Y))return false;
|
||||
const std::int64_t chunkMinimum=static_cast<std::int64_t>(ChunkZ)*CHUNK_SIZE;
|
||||
const std::int64_t chunkMaximum=chunkMinimum+CHUNK_SIZE;
|
||||
const double ChunkWidth=GetResolvedLayoutChunkWidthVoxels();
|
||||
const std::int64_t chunkMinimum=static_cast<std::int64_t>(std::llround(ChunkZ*ChunkWidth));
|
||||
const std::int64_t chunkMaximum=static_cast<std::int64_t>(std::llround((ChunkZ+1)*ChunkWidth));
|
||||
if(bNativeOnly&&(!NativeOnlyContainsColumn(X,Y)
|
||||
||chunkMaximum<NativeOnlyMinimum.Z||chunkMinimum>NativeOnlyMaximum.Z
|
||||
||Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
|
||||
@@ -1400,7 +1475,8 @@ public:
|
||||
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
|
||||
!=ECaveGeneratorType::SurfaceWorld)return false;
|
||||
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
|
||||
static_cast<float>((static_cast<double>(ChunkZ)+0.5)
|
||||
*ResolvedLayout->strateLayoutChunkSizeMicrometres/10000.0));
|
||||
const auto* Plan=FindWorldAssemblyStratum(StrateIndex,ChunkZ);
|
||||
if(!Plan)return false;
|
||||
constexpr double Scale=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
@@ -1413,7 +1489,7 @@ public:
|
||||
const VoxelForgeCore::Heightfield2D Block(WorldAssembly->recipe,*Plan);
|
||||
if(!Block.isValid())return false;
|
||||
const auto Column=Block.point(ToFixed(X),ToFixed(Y),
|
||||
static_cast<int64>(ChunkZ)*CHUNK_SIZE*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
static_cast<int64>(ChunkZ)*ResolvedLayout->strateLayoutChunkWidthSubvoxels);
|
||||
OutGroundZ=static_cast<float>(Column.groundHeightSubvoxels/Scale);
|
||||
OutCeilingZ=static_cast<float>(Column.capHeightSubvoxels/Scale);
|
||||
return std::isfinite(OutGroundZ)&&std::isfinite(OutCeilingZ);
|
||||
@@ -1429,10 +1505,9 @@ public:
|
||||
const int32 Pad=std::max(Step,1);
|
||||
const int32 BMin=Origin.Z-Pad;
|
||||
const int32 BMax=Origin.Z+(Cells+1)*Step;
|
||||
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin)
|
||||
/static_cast<float>(CHUNK_SIZE));
|
||||
const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax)
|
||||
/static_cast<float>(CHUNK_SIZE));
|
||||
const float LayoutWidth=static_cast<float>(GetResolvedLayoutChunkWidthVoxels());
|
||||
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin)/LayoutWidth);
|
||||
const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax)/LayoutWidth);
|
||||
const VoxelForgeCore::WorldAssemblyStratumPlanV1* Plan=nullptr;
|
||||
for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ)
|
||||
{
|
||||
@@ -1474,53 +1549,32 @@ public:
|
||||
const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* IslandPlan=nullptr;
|
||||
bool bSurfaceOverlap=false,bIslandOverlap=false,bOtherStrateOverlap=false;
|
||||
bool bIslandOtherStrateOverlap=false,bBadPlan=false;
|
||||
if(Owner&&Owner->Manager)
|
||||
if(Owner&&Owner->Manager&&ResolvedLayout)
|
||||
{
|
||||
// Keep the delivered SurfaceWorld chunk mapping unchanged.
|
||||
// Conserver le mapping de chunks SurfaceWorld deja livre.
|
||||
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin[2])
|
||||
/static_cast<float>(CHUNK_SIZE));
|
||||
const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax[2])
|
||||
/static_cast<float>(CHUNK_SIZE));
|
||||
const float LayoutWidth=static_cast<float>(GetResolvedLayoutChunkWidthVoxels());
|
||||
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin[2])/LayoutWidth);
|
||||
const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax[2])/LayoutWidth);
|
||||
for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ)
|
||||
{
|
||||
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(
|
||||
FIntVector(0,0,ChunkZ));
|
||||
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ));
|
||||
const int32 StrateIndex=Owner->Manager->GetStrateIndex(static_cast<float>(
|
||||
(static_cast<double>(ChunkZ)+0.5)*ResolvedLayout->strateLayoutChunkSizeMicrometres/10000.0));
|
||||
if(Type==ECaveGeneratorType::SurfaceWorld)
|
||||
{
|
||||
bSurfaceOverlap=true;
|
||||
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
|
||||
bSurfaceOverlap=true;bIslandOtherStrateOverlap=true;
|
||||
const auto* Candidate=FindWorldAssemblyStratum(StrateIndex,ChunkZ);
|
||||
if(!Candidate||(SurfacePlan&&SurfacePlan!=Candidate))
|
||||
bBadPlan=true;
|
||||
if(!Candidate||(SurfacePlan&&SurfacePlan!=Candidate))bBadPlan=true;
|
||||
SurfacePlan=Candidate;
|
||||
}
|
||||
else if(Type!=ECaveGeneratorType::FloatingIslands)
|
||||
bOtherStrateOverlap=true;
|
||||
}
|
||||
|
||||
// Island strata preserve owner-authored physical bounds through the resolved scale ratio.
|
||||
// Les strates d'iles conservent les bornes physiques owner via le rapport de resolution.
|
||||
const double CoreVoxelsPerIslandChunk=GetCoreVoxelsPerOwnerChunk();
|
||||
const int32 FirstIslandChunk=FMath::FloorToInt(static_cast<float>(BMin[2])
|
||||
/static_cast<float>(CoreVoxelsPerIslandChunk));
|
||||
const int32 LastIslandChunk=FMath::FloorToInt(static_cast<float>(BMax[2])
|
||||
/static_cast<float>(CoreVoxelsPerIslandChunk));
|
||||
for(int32 ChunkZ=FirstIslandChunk;ChunkZ<=LastIslandChunk;++ChunkZ)
|
||||
else if(Type==ECaveGeneratorType::FloatingIslands)
|
||||
{
|
||||
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(
|
||||
FIntVector(0,0,ChunkZ));
|
||||
if(Type==ECaveGeneratorType::FloatingIslands)
|
||||
{
|
||||
bIslandOverlap=true;
|
||||
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
|
||||
bIslandOverlap=true;bOtherStrateOverlap=true;
|
||||
const auto* Candidate=FindWorldAssemblyFloatingIslandStratum(StrateIndex,ChunkZ);
|
||||
if(!Candidate||(IslandPlan&&IslandPlan!=Candidate))bBadPlan=true;
|
||||
IslandPlan=Candidate;
|
||||
}
|
||||
else bIslandOtherStrateOverlap=true;
|
||||
else
|
||||
{bOtherStrateOverlap=true;bIslandOtherStrateOverlap=true;}
|
||||
}
|
||||
}
|
||||
const auto Finish=[&](EVoxelTileClass Verdict,bool bUnknown=false)
|
||||
@@ -1536,6 +1590,25 @@ public:
|
||||
else ++Counts.MixedBoxes;
|
||||
return Result;
|
||||
};
|
||||
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
|
||||
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
|
||||
&&EditBoundsMin.Z<=BMax[2]&&EditBoundsMax.Z>=BMin[2])
|
||||
return Finish(EVoxelTileClass::Mixed,true);
|
||||
if(ResolvedLayout&&Owner)
|
||||
{
|
||||
const double ZMinMicrometres=static_cast<double>(BMin[2])*Owner->CoreVoxelSizeMicrons;
|
||||
const double ZMaxMicrometres=static_cast<double>(BMax[2])*Owner->CoreVoxelSizeMicrons;
|
||||
for(const auto& Stratum:ResolvedLayout->strates)
|
||||
for(const auto* Band:{&Stratum.ceiling,&Stratum.bedrock})
|
||||
if(Band->presentInCurrentField&&ZMaxMicrometres>=Band->bottomMicrometres
|
||||
&&ZMinMicrometres<Band->topMicrometres)
|
||||
{
|
||||
if(ZMinMicrometres>=Band->bottomMicrometres
|
||||
&&ZMaxMicrometres<Band->topMicrometres)
|
||||
return Finish(EVoxelTileClass::AllSolid);
|
||||
return Finish(EVoxelTileClass::Mixed);
|
||||
}
|
||||
}
|
||||
if(bNativeOnly)
|
||||
{
|
||||
const bool bInsideProofBox=BMin[0]>=NativeOnlyMinimum.X
|
||||
@@ -1555,10 +1628,6 @@ public:
|
||||
||(bIslandOverlap&&(bIslandOtherStrateOverlap
|
||||
||(!bSurfaceOverlap&&bOtherStrateOverlap))))
|
||||
return Finish(EVoxelTileClass::Mixed,true);
|
||||
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
|
||||
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
|
||||
&&EditBoundsMin.Z<=BMax[2]&&EditBoundsMax.Z>=BMin[2])
|
||||
return Finish(EVoxelTileClass::Mixed,true);
|
||||
// The native interval path below is the exact field selected first by DensityAtLattice
|
||||
// for SurfaceWorld and FloatingIslands. Legacy analytic primitives/layer bounds cannot
|
||||
// modify that field; only the explicit edit transaction above can. Keep their guards on
|
||||
@@ -2151,13 +2220,16 @@ private:
|
||||
|
||||
bool NativeSurfaceDensityAt(float X,float Y,float Z,float& OutDensity) const
|
||||
{
|
||||
if(!bUseNativeManifestFields||!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
|
||||
if(!bUseNativeManifestFields||!WorldAssembly||!ResolvedLayout||!Owner||!Owner->Manager||!Owner->Generator
|
||||
||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))return false;
|
||||
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CHUNK_SIZE));
|
||||
const float LayoutWidth=static_cast<float>(GetResolvedLayoutChunkWidthVoxels());
|
||||
if(!(LayoutWidth>0.0f))return false;
|
||||
const int32 ChunkZ=FMath::FloorToInt(Z/LayoutWidth);
|
||||
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
|
||||
!=ECaveGeneratorType::SurfaceWorld)return false;
|
||||
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
|
||||
static_cast<float>((static_cast<double>(ChunkZ)+0.5)
|
||||
*ResolvedLayout->strateLayoutChunkSizeMicrometres/10000.0));
|
||||
const auto* Plan=FindWorldAssemblyStratum(StrateIndex,ChunkZ);
|
||||
if(!Plan)return false;
|
||||
constexpr double Scale=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
@@ -2177,16 +2249,18 @@ private:
|
||||
bool NativeFloatingIslandDensityAt(float X,float Y,float Z,float& OutDensity) const
|
||||
{
|
||||
if(!bUseNativeManifestFields||!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
|
||||
||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))
|
||||
||!ResolvedLayout||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))
|
||||
return false;
|
||||
// Convert Core query Z back to the owner's legacy chunk coordinate.
|
||||
// Convertir Z du Core vers la coordonnee de chunk legacy du proprietaire.
|
||||
const double CoreVoxelsPerLegacyChunk=GetCoreVoxelsPerOwnerChunk();
|
||||
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CoreVoxelsPerLegacyChunk));
|
||||
const double LayoutChunkWidth=GetResolvedLayoutChunkWidthVoxels();
|
||||
if(!(LayoutChunkWidth>0.0))return false;
|
||||
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(LayoutChunkWidth));
|
||||
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
|
||||
!=ECaveGeneratorType::FloatingIslands)return false;
|
||||
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
|
||||
static_cast<float>((static_cast<double>(ChunkZ)+0.5)
|
||||
*ResolvedLayout->strateLayoutChunkSizeMicrometres/10000.0));
|
||||
const auto* Plan=FindWorldAssemblyFloatingIslandStratum(StrateIndex,ChunkZ);
|
||||
if(!Plan)return false;
|
||||
constexpr double Scale=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
@@ -2212,7 +2286,7 @@ private:
|
||||
Density=std::min(Density,-std::max<std::int64_t>(1,Top-Zq));
|
||||
OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
|
||||
}
|
||||
else
|
||||
else if(Plan->manifest.recipe.geometryVersion<4)
|
||||
{
|
||||
OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
|
||||
FResolvedStratumBounds Bounds;
|
||||
@@ -2226,15 +2300,16 @@ private:
|
||||
static_cast<float>(Bounds.SealThickness),Params.BaseDensity);
|
||||
OutDensity=-InternalDensity;
|
||||
}
|
||||
else OutDensity=static_cast<float>(static_cast<double>(Density)/Scale);
|
||||
return std::isfinite(OutDensity);
|
||||
}
|
||||
|
||||
bool UsesLegacySurfaceField(float X,float Y,float Z) const
|
||||
{
|
||||
if(!Owner||!Owner->Manager||!Owner->Generator)return false;
|
||||
const FIntVector ChunkCoord(FMath::FloorToInt(X/static_cast<float>(CHUNK_SIZE)),
|
||||
FMath::FloorToInt(Y/static_cast<float>(CHUNK_SIZE)),
|
||||
FMath::FloorToInt(Z/static_cast<float>(CHUNK_SIZE)));
|
||||
const double LayoutChunkWidth=GetResolvedLayoutChunkWidthVoxels();
|
||||
if(!(LayoutChunkWidth>0.0))return false;
|
||||
const FIntVector ChunkCoord(0,0,FMath::FloorToInt(Z/static_cast<float>(LayoutChunkWidth)));
|
||||
return Owner->Manager->GetGeneratorTypeForChunk(ChunkCoord)
|
||||
==ECaveGeneratorType::SurfaceWorld;
|
||||
}
|
||||
@@ -2244,7 +2319,8 @@ private:
|
||||
// SurfaceWorld consumes biome overrides, terrain ops, water level and its sky cap in
|
||||
// the legacy stack. Reuse that immutable evaluator as an exact temporary bridge.
|
||||
// SurfaceWorld lit biomes, ops de terrain, niveau d'eau et plafond; reutiliser ce champ.
|
||||
return ApplyEditBrushes(Owner->Generator->GetDensityAt(X,Y,Z),X,Y,Z);
|
||||
const float density=ApplyResolvedStrateBoundaryBand(Owner->Generator->GetDensityAt(X,Y,Z),X,Y,Z);
|
||||
return ApplyEditBrushes(density,X,Y,Z);
|
||||
}
|
||||
|
||||
float ApplyEditBrushes(float Density,double X,double Y,double Z) const
|
||||
@@ -3571,8 +3647,9 @@ private:
|
||||
auto Unit=[&](uint32 H){return static_cast<double>(Hash(H)>>8)*(1.0/16777216.0);};
|
||||
for(const FStrateSlot& Slot:Layout)
|
||||
{
|
||||
const double ZBottom=static_cast<double>(Slot.BottomChunkZ)*CHUNK_SIZE;
|
||||
const double ZTop=static_cast<double>(Slot.TopChunkZ+1)*CHUNK_SIZE;
|
||||
const double LayoutChunkWidth=GetResolvedLayoutChunkWidthVoxels();
|
||||
const double ZBottom=static_cast<double>(Slot.BottomChunkZ)*LayoutChunkWidth;
|
||||
const double ZTop=static_cast<double>(Slot.TopChunkZ+1)*LayoutChunkWidth;
|
||||
for(int32 GY=GY0;GY<=GY1;++GY)for(int32 GX=GX0;GX<=GX1;++GX)
|
||||
{
|
||||
uint32 H=Hash(Seed^static_cast<uint32>(GX)*0x9E3779B9u
|
||||
@@ -4953,6 +5030,16 @@ public:
|
||||
OutDensity[Lane]=DensityAtLattice(X,Y,ZStart+Lane*ZStep);
|
||||
return;
|
||||
}
|
||||
bool bHasResolvedBoundaryBand=false;
|
||||
for(int32 Lane=0;Lane<Count&&!bHasResolvedBoundaryBand;++Lane)
|
||||
bHasResolvedBoundaryBand=IsInsideResolvedStrateBoundaryBand(
|
||||
static_cast<float>(ZStart+Lane*ZStep));
|
||||
if(bHasResolvedBoundaryBand)
|
||||
{
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
OutDensity[Lane]=DensityAtLattice(X,Y,ZStart+Lane*ZStep);
|
||||
return;
|
||||
}
|
||||
bool bHasLegacySurfaceSample=false;
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
bHasLegacySurfaceSample|=UsesLegacySurfaceField(static_cast<float>(X),
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace VoxelForgeCore
|
||||
{
|
||||
std::shared_ptr<FStandaloneOwnerWorld> owner;
|
||||
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
|
||||
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
|
||||
WorldConfiguration configuration;
|
||||
};
|
||||
|
||||
@@ -390,8 +391,8 @@ namespace VoxelForgeCore
|
||||
{
|
||||
const VoxelForgeCore::Heightfield2D Block(Manifest.recipe,Plan);
|
||||
if(!Block.isValid())continue;
|
||||
const int64 StratumMinZ=static_cast<int64>(Plan.bottomChunkZ)*CHUNK_SIZE*Scale;
|
||||
const int64 StratumMaxZ=(static_cast<int64>(Plan.topChunkZ)+1)*CHUNK_SIZE*Scale;
|
||||
const int64 StratumMinZ=static_cast<int64>(Plan.bottomChunkZ)*Manifest.recipe.layoutChunkWidthSubvoxels*Scale;
|
||||
const int64 StratumMaxZ=(static_cast<int64>(Plan.topChunkZ)+1)*Manifest.recipe.layoutChunkWidthSubvoxels*Scale;
|
||||
if(TileMaxZ<StratumMinZ||TileMinZ>StratumMaxZ)continue;
|
||||
struct FGridSample
|
||||
{
|
||||
@@ -439,8 +440,10 @@ namespace VoxelForgeCore
|
||||
|
||||
FGroundProfile Profile;
|
||||
Profile.GridStep=GridStepVoxels;Profile.Coordinates=GridCoordinates;
|
||||
Profile.MinimumZ=static_cast<double>(std::min(Plan.bottomChunkZ,Plan.topChunkZ))*CHUNK_SIZE;
|
||||
Profile.MaximumZ=static_cast<double>(std::max(Plan.bottomChunkZ,Plan.topChunkZ)+1)*CHUNK_SIZE;
|
||||
Profile.MinimumZ=static_cast<double>(std::min(Plan.bottomChunkZ,Plan.topChunkZ))
|
||||
*Manifest.recipe.layoutChunkWidthSubvoxels/Scale;
|
||||
Profile.MaximumZ=static_cast<double>(std::max(Plan.bottomChunkZ,Plan.topChunkZ)+1)
|
||||
*Manifest.recipe.layoutChunkWidthSubvoxels/Scale;
|
||||
Profile.Ground.reserve(Grid.size());Profile.Cap.reserve(Grid.size());
|
||||
for(const auto& Sample:Grid)
|
||||
{Profile.Ground.push_back(Sample.Ground/Scale);Profile.Cap.push_back(Sample.Cap/Scale);}
|
||||
@@ -865,13 +868,15 @@ namespace VoxelForgeCore
|
||||
struct detail_StandaloneSnapshotAdapter
|
||||
{
|
||||
static ResolvedWorldSnapshot FromOwner(std::shared_ptr<FStandaloneOwnerWorld> owner,
|
||||
std::shared_ptr<const WorldAssemblyManifest> worldAssembly = {})
|
||||
std::shared_ptr<const WorldAssemblyManifest> worldAssembly = {},
|
||||
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout = {})
|
||||
{
|
||||
if(!owner||!owner->Settings||!owner->Manager||!owner->Generator)return {};
|
||||
auto data=std::make_shared<ResolvedWorldSnapshot::Data>();
|
||||
data->configuration=MakeConfiguration(*owner);
|
||||
data->owner=std::move(owner);
|
||||
data->worldAssembly=std::move(worldAssembly);
|
||||
data->resolvedLayout=std::move(resolvedLayout);
|
||||
return ResolvedWorldSnapshot(std::move(data));
|
||||
}
|
||||
};
|
||||
@@ -889,6 +894,16 @@ namespace VoxelForgeCore
|
||||
if(!parsed)return {};
|
||||
worldAssembly=std::make_shared<WorldAssemblyManifest>(std::move(*parsed));
|
||||
}
|
||||
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
|
||||
if(jsonRoot.Get("resolved_layout_json").IsString())
|
||||
{
|
||||
auto parsed=ResolvedLayoutSnapshotV2::FromJson(jsonRoot.Get("resolved_layout_json").String(),error);
|
||||
if(!parsed)return {};
|
||||
std::vector<ResolvedLayoutIssueV2> issues;std::string strictError;
|
||||
if(!parsed->validate(ResolvedLayoutValidationModeV2::strict,issues,strictError))
|
||||
{error="strict resolved-layout validation failed: "+strictError;return {};}
|
||||
resolvedLayout=std::make_shared<ResolvedLayoutSnapshotV2>(std::move(*parsed));
|
||||
}
|
||||
|
||||
const bool seedChanged=owner->Seed!=seed;
|
||||
owner->Settings->Seed=seed;
|
||||
@@ -911,7 +926,7 @@ namespace VoxelForgeCore
|
||||
owner->MaxClipLevel=maxLodOverride;
|
||||
owner->Settings->MaxClipLevel=maxLodOverride;
|
||||
}
|
||||
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner),std::move(worldAssembly));
|
||||
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner),std::move(worldAssembly),std::move(resolvedLayout));
|
||||
}
|
||||
|
||||
std::optional<ResolvedWorldSnapshot> ResolvedWorldSnapshot::FromJson(
|
||||
@@ -928,11 +943,23 @@ namespace VoxelForgeCore
|
||||
if(!parsed)return std::nullopt;
|
||||
worldAssembly=std::make_shared<WorldAssemblyManifest>(std::move(*parsed));
|
||||
}
|
||||
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner),std::move(worldAssembly));
|
||||
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
|
||||
if(root.Get("resolved_layout_json").IsString())
|
||||
{
|
||||
auto parsed=ResolvedLayoutSnapshotV2::FromJson(root.Get("resolved_layout_json").String(),error);
|
||||
if(!parsed)return std::nullopt;
|
||||
std::vector<ResolvedLayoutIssueV2> issues;std::string strictError;
|
||||
if(!parsed->validate(ResolvedLayoutValidationModeV2::strict,issues,strictError))
|
||||
{error="strict resolved-layout validation failed: "+strictError;return std::nullopt;}
|
||||
resolvedLayout=std::make_shared<ResolvedLayoutSnapshotV2>(std::move(*parsed));
|
||||
}
|
||||
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner),std::move(worldAssembly),std::move(resolvedLayout));
|
||||
}
|
||||
|
||||
const WorldAssemblyManifest* ResolvedWorldSnapshot::worldAssembly() const noexcept
|
||||
{ return data_?data_->worldAssembly.get():nullptr; }
|
||||
const ResolvedLayoutSnapshotV2* ResolvedWorldSnapshot::resolvedLayout() const noexcept
|
||||
{ return data_?data_->resolvedLayout.get():nullptr; }
|
||||
|
||||
const WorldConfiguration& ResolvedWorldSnapshot::configuration() const
|
||||
{
|
||||
@@ -944,6 +971,7 @@ namespace VoxelForgeCore
|
||||
{
|
||||
std::shared_ptr<FStandaloneOwnerWorld> owner;
|
||||
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
|
||||
std::shared_ptr<const ResolvedLayoutSnapshotV2> resolvedLayout;
|
||||
WorldConfiguration configuration;
|
||||
std::unique_ptr<FStandaloneShapeWorld> shape;
|
||||
std::atomic<bool> shapeReady{false};
|
||||
@@ -984,6 +1012,7 @@ namespace VoxelForgeCore
|
||||
{
|
||||
impl_->owner=snapshot.data_->owner;
|
||||
impl_->worldAssembly=snapshot.data_->worldAssembly;
|
||||
impl_->resolvedLayout=snapshot.data_->resolvedLayout;
|
||||
impl_->configuration=snapshot.data_->configuration;
|
||||
if(impl_->worldAssembly)
|
||||
{
|
||||
@@ -1141,7 +1170,7 @@ namespace VoxelForgeCore
|
||||
static_cast<int32>(std::clamp<std::uint32_t>(request.workerLimit,1,16)),ToNative(request.detail),
|
||||
request.includeFarWorld,error,&scheduler,request.roundTunnels,request.profileDetailBand,
|
||||
request.validatePassageRoutes,request.gpuVisualDetailTiles,
|
||||
request.cpuVisualDetailTiles,impl_->worldAssembly.get());
|
||||
request.cpuVisualDetailTiles,impl_->worldAssembly.get(),impl_->resolvedLayout.get());
|
||||
if(ok)
|
||||
{
|
||||
impl_->shape=std::move(shape);
|
||||
@@ -1156,23 +1185,25 @@ namespace VoxelForgeCore
|
||||
|
||||
bool World::isTileShapeFree(const TileKey& key) const
|
||||
{
|
||||
if(!impl_||!impl_->owner||!impl_->worldAssembly||!impl_->owner->Manager
|
||||
if(!impl_||!impl_->owner||!impl_->worldAssembly||!impl_->resolvedLayout||!impl_->owner->Manager
|
||||
||!impl_->owner->Generator||!impl_->editLog.empty())return false;
|
||||
FIntVector boundsMin,boundsMax;
|
||||
if(!NativeOnlyBoundsForTile(*impl_,key,boundsMin,boundsMax))return false;
|
||||
|
||||
const std::int64_t tileMinimum[3]={boundsMin.X,boundsMin.Y,boundsMin.Z};
|
||||
const std::int64_t tileMaximum[3]={boundsMax.X,boundsMax.Y,boundsMax.Z};
|
||||
const int32 firstSurfaceChunk=FloorDiv(boundsMin.Z,CHUNK_SIZE);
|
||||
const int32 lastSurfaceChunk=FloorDiv(boundsMax.Z,CHUNK_SIZE);
|
||||
const int32 LayoutChunkWidth=static_cast<int32>(std::max<std::int64_t>(1,
|
||||
impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel));
|
||||
const int32 firstSurfaceChunk=FloorDiv(boundsMin.Z,LayoutChunkWidth);
|
||||
const int32 lastSurfaceChunk=FloorDiv(boundsMax.Z,LayoutChunkWidth);
|
||||
const WorldAssemblyStratumPlanV1* selectedPlan=nullptr;
|
||||
for(int32 chunkZ=firstSurfaceChunk;chunkZ<=lastSurfaceChunk;++chunkZ)
|
||||
{
|
||||
if(impl_->owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,chunkZ))
|
||||
!=ECaveGeneratorType::SurfaceWorld)return false;
|
||||
const int32 strateIndex=impl_->owner->Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(chunkZ)*CHUNK_SIZE
|
||||
+CHUNK_SIZE*0.5)*VOXEL_SIZE));
|
||||
static_cast<float>((static_cast<double>(chunkZ)+0.5)
|
||||
*impl_->resolvedLayout->strateLayoutChunkSizeMicrometres/10000.0));
|
||||
const auto found=std::find_if(impl_->worldAssembly->plan.strates.begin(),
|
||||
impl_->worldAssembly->plan.strates.end(),[strateIndex,chunkZ](const auto& plan)
|
||||
{return plan.strateIndex==strateIndex&&chunkZ<=plan.topChunkZ
|
||||
@@ -1184,9 +1215,8 @@ namespace VoxelForgeCore
|
||||
if(!selectedPlan||!Heightfield2D(impl_->worldAssembly->recipe,*selectedPlan).isValid())
|
||||
return false;
|
||||
|
||||
const double coreVoxelsPerOwnerChunk=static_cast<double>(CHUNK_SIZE)
|
||||
*static_cast<double>(impl_->owner->OwnerVoxelSizeMicrons)
|
||||
/static_cast<double>(std::max(1u,impl_->owner->CoreVoxelSizeMicrons));
|
||||
const double coreVoxelsPerOwnerChunk=static_cast<double>(
|
||||
impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels)/WorldAssemblySubvoxelsPerVoxel;
|
||||
const int32 firstIslandChunk=FMath::FloorToInt(
|
||||
static_cast<double>(boundsMin.Z)/coreVoxelsPerOwnerChunk);
|
||||
const int32 lastIslandChunk=FMath::FloorToInt(
|
||||
@@ -1207,7 +1237,7 @@ namespace VoxelForgeCore
|
||||
if(OwnerPassageTouchesBox(*impl_->owner,boundsMin,boundsMax))return false;
|
||||
FStandaloneShapeWorld proofGeometry;
|
||||
if(!proofGeometry.BuildShapeDependencyProof(*impl_->owner,boundsMin,boundsMax,
|
||||
impl_->owner->Generator->bStandaloneResearchRoundTunnels))return false;
|
||||
impl_->owner->Generator->bStandaloneResearchRoundTunnels,impl_->resolvedLayout.get()))return false;
|
||||
const FIntVector tileOrigin=ToNative(key).OriginVoxels();
|
||||
if(proofGeometry.HasShapeDependencyInBox(tileOrigin,1,CHUNK_SIZE))return false;
|
||||
return true;
|
||||
@@ -1321,6 +1351,7 @@ namespace VoxelForgeCore
|
||||
auto region=std::make_shared<FStandaloneShapeWorld>();
|
||||
if(!region->BuildAnalyticRegion(*impl_->owner,fineMin,fineMax,regionMin,regionMax,
|
||||
ToNative(base.detail),base.roundTunnels,impl_->worldAssembly.get(),
|
||||
impl_->resolvedLayout.get(),
|
||||
regionErrors[index]))return;
|
||||
compiledRegions[index]=std::move(region);
|
||||
};
|
||||
@@ -1371,7 +1402,8 @@ namespace VoxelForgeCore
|
||||
auto region=std::make_shared<FStandaloneShapeWorld>();
|
||||
if(!region->Build(*impl_->owner,fineMin,fineMax,regionMin,regionMax,
|
||||
static_cast<int32>(clampedWorkers),ToNative(base.detail),false,error,&scheduler,
|
||||
base.roundTunnels,base.profileDetailBand,false,{}, {},impl_->worldAssembly.get()))return false;
|
||||
base.roundTunnels,base.profileDetailBand,false,{}, {},impl_->worldAssembly.get(),
|
||||
impl_->resolvedLayout.get()))return false;
|
||||
if(!impl_->editLog.empty())region->SetEditTransaction(impl_->editLog);
|
||||
const auto& build=region->GetStats();
|
||||
++stats.regionsBuilt;
|
||||
@@ -1631,6 +1663,7 @@ namespace VoxelForgeCore
|
||||
if(!options.diagnosticForceNativeOnly&&!isTileShapeFree(key))
|
||||
{error="shape-free guard: tile failed the native-only readiness proof";return std::nullopt;}
|
||||
nativeOnlyShape.InitializeNativeSurfaceOnly(*impl_->owner,impl_->worldAssembly.get(),
|
||||
impl_->resolvedLayout.get(),
|
||||
nativeOnlyMinimum,nativeOnlyMaximum);
|
||||
}
|
||||
else if(impl_->tileRegionCount.load(std::memory_order_acquire)!=0)
|
||||
@@ -1787,15 +1820,19 @@ namespace VoxelForgeCore
|
||||
const int64 TileMaximumZ=static_cast<int64>(origin.Z)+extent;
|
||||
for(const auto& Plan:impl_->worldAssembly->plan.strates)
|
||||
{
|
||||
const int64 PlanMinimumZ=static_cast<int64>(std::min(Plan.bottomChunkZ,Plan.topChunkZ))*CHUNK_SIZE;
|
||||
const int64 PlanMaximumZ=(static_cast<int64>(std::max(Plan.bottomChunkZ,Plan.topChunkZ))+1)*CHUNK_SIZE;
|
||||
const int64 PlanMinimumZ=static_cast<int64>(std::min(Plan.bottomChunkZ,Plan.topChunkZ))
|
||||
*impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel;
|
||||
const int64 PlanMaximumZ=(static_cast<int64>(std::max(Plan.bottomChunkZ,Plan.topChunkZ)+1))
|
||||
*impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel;
|
||||
bHasNativeSurfaceHorizonPlan=bHasNativeSurfaceHorizonPlan
|
||||
||(TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ);
|
||||
}
|
||||
for(const auto& Entry:impl_->worldAssembly->floatingIslands)
|
||||
{
|
||||
const int64 PlanMinimumZ=static_cast<int64>(std::min(Entry.bottomChunkZ,Entry.topChunkZ))*CHUNK_SIZE;
|
||||
const int64 PlanMaximumZ=(static_cast<int64>(std::max(Entry.bottomChunkZ,Entry.topChunkZ))+1)*CHUNK_SIZE;
|
||||
const int64 PlanMinimumZ=static_cast<int64>(std::min(Entry.bottomChunkZ,Entry.topChunkZ))
|
||||
*impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel;
|
||||
const int64 PlanMaximumZ=(static_cast<int64>(std::max(Entry.bottomChunkZ,Entry.topChunkZ)+1))
|
||||
*impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels/WorldAssemblySubvoxelsPerVoxel;
|
||||
bHasIslandPlanInTile=bHasIslandPlanInTile
|
||||
||(TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ);
|
||||
}
|
||||
@@ -1896,7 +1933,11 @@ namespace VoxelForgeCore
|
||||
const double CoreVoxelSizeCm=static_cast<double>(impl_->configuration.voxelSizeMicrons)/10000.0;
|
||||
const double OwnerVoxelSizeCm=impl_->owner!=nullptr&&impl_->owner->OwnerVoxelSizeMicrons>0
|
||||
?static_cast<double>(impl_->owner->OwnerVoxelSizeMicrons)/10000.0:0.5*static_cast<double>(VOXEL_SIZE);
|
||||
const double CoreVoxelsPerOwnerChunk=static_cast<double>(CHUNK_SIZE)*OwnerVoxelSizeCm/CoreVoxelSizeCm;
|
||||
const double CoreVoxelsPerOwnerChunk=impl_->resolvedLayout
|
||||
?static_cast<double>(impl_->resolvedLayout->strateLayoutChunkWidthSubvoxels)
|
||||
/WorldAssemblySubvoxelsPerVoxel
|
||||
:static_cast<double>(impl_->owner->StrateLayoutChunkWidthSubvoxels)
|
||||
/WorldAssemblySubvoxelsPerVoxel;
|
||||
const int32 CenterChunkZ=FMath::FloorToInt(TileCenterZ/CoreVoxelsPerOwnerChunk);
|
||||
const int32 StrateIndex=world.Manager->GetStrateIndex(
|
||||
static_cast<float>(TileCenterZ*CoreVoxelSizeCm));
|
||||
@@ -2210,7 +2251,10 @@ namespace VoxelForgeCore
|
||||
out.diagnostics.horizonCapTriangles=CapStats.Triangles;
|
||||
}
|
||||
if(impl_->worldAssembly&&!bOverlapsPlayerEdit)
|
||||
{
|
||||
AppendStratumBoundaryFloors(world,farShape,nativeKey,step,mesh);
|
||||
AppendStratumBoundaryCeilings(world,farShape,nativeKey,step,mesh);
|
||||
}
|
||||
|
||||
out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
|
||||
if(bHasWorldAssembly)
|
||||
|
||||
@@ -235,6 +235,9 @@ namespace world_assembly_detail
|
||||
|| recipe.evaluatorVersion > WorldAssemblyEvaluatorVersion
|
||||
|| recipe.unitsPerVoxel != WorldAssemblySubvoxelsPerVoxel)
|
||||
{ error = "world assembly recipe version, evaluator or fixed scale is unsupported"; return false; }
|
||||
if (recipe.layoutChunkWidthSubvoxels <= 0
|
||||
|| recipe.layoutChunkWidthSubvoxels > 256ll * 512ll)
|
||||
{ error = "resolved strate-layout chunk width is outside the supported physical range"; return false; }
|
||||
if (recipe.biomes.empty() || recipe.biomes.size() > MaxBiomeCount)
|
||||
{ error = "world assembly recipe must contain one to eight biome profiles"; return false; }
|
||||
if (recipe.biomeCellSizeSubvoxels <= 0 || recipe.biomeCellSizeSubvoxels > 100000000ll
|
||||
@@ -296,7 +299,8 @@ namespace world_assembly_detail
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ValidateStratum(const WorldAssemblyStratumPlanV1& stratum, std::string& error)
|
||||
inline bool ValidateStratum(const WorldAssemblyStratumPlanV1& stratum,
|
||||
std::int64_t layoutChunkWidthSubvoxels, std::string& error)
|
||||
{
|
||||
if (stratum.strateIndex < 0 || stratum.topChunkZ < stratum.bottomChunkZ
|
||||
|| stratum.capWavelengthSubvoxels <= 0 || stratum.capUndulationSubvoxels < 0
|
||||
@@ -330,9 +334,9 @@ namespace world_assembly_detail
|
||||
if (feature.type == 2 || feature.type == 4)
|
||||
{
|
||||
const std::int64_t stratumMinZ = static_cast<std::int64_t>(stratum.bottomChunkZ)
|
||||
* 32 * WorldAssemblySubvoxelsPerVoxel;
|
||||
* layoutChunkWidthSubvoxels;
|
||||
const std::int64_t stratumMaxZ = (static_cast<std::int64_t>(stratum.topChunkZ) + 1)
|
||||
* 32 * WorldAssemblySubvoxelsPerVoxel;
|
||||
* layoutChunkWidthSubvoxels;
|
||||
constexpr std::int64_t maximumExceptionExtent = 1024
|
||||
* WorldAssemblySubvoxelsPerVoxel;
|
||||
for (std::size_t axis = 0; axis < 3; ++axis)
|
||||
@@ -420,6 +424,7 @@ namespace world_assembly_detail
|
||||
std::uint64_t hash = StartHash();
|
||||
HashWord(hash, recipe.schemaVersion, 4); HashWord(hash, recipe.evaluatorVersion, 4);
|
||||
HashWord(hash, recipe.unitsPerVoxel, 4); HashWord(hash, recipe.seed, 4);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.layoutChunkWidthSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeCellSizeSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeWarpSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeWarpWavelengthSubvoxels), 8);
|
||||
@@ -628,6 +633,7 @@ namespace world_assembly_detail
|
||||
if (!value.IsObject() || !ReadU32(value.Get("schema_version"), recipe.schemaVersion)
|
||||
|| !ReadU32(value.Get("evaluator_version"), recipe.evaluatorVersion)
|
||||
|| !ReadU32(value.Get("units_per_voxel"), recipe.unitsPerVoxel)
|
||||
|| !ReadInteger(value.Get("layout_chunk_width_q"), recipe.layoutChunkWidthSubvoxels)
|
||||
|| !ReadU32(value.Get("seed"), recipe.seed)
|
||||
|| !ReadInteger(value.Get("biome_cell_size_q"), recipe.biomeCellSizeSubvoxels)
|
||||
|| !ReadInteger(value.Get("biome_warp_q"), recipe.biomeWarpSubvoxels)
|
||||
@@ -693,6 +699,7 @@ namespace world_assembly_detail
|
||||
out << "{\"schema_version\":" << recipe.schemaVersion
|
||||
<< ",\"evaluator_version\":" << recipe.evaluatorVersion
|
||||
<< ",\"units_per_voxel\":" << recipe.unitsPerVoxel
|
||||
<< ",\"layout_chunk_width_q\":" << recipe.layoutChunkWidthSubvoxels
|
||||
<< ",\"seed\":" << recipe.seed
|
||||
<< ",\"biome_cell_size_q\":" << recipe.biomeCellSizeSubvoxels
|
||||
<< ",\"biome_warp_q\":" << recipe.biomeWarpSubvoxels
|
||||
@@ -861,7 +868,8 @@ namespace world_assembly_detail
|
||||
|| !ReadI32(islandValue.Get("bottom_chunk_z"), entry.bottomChunkZ))
|
||||
{ error = "world assembly floating-island slot is missing its resolved strate bounds"; return std::nullopt; }
|
||||
const std::string nestedJson = JsonText(islandValue.Get("manifest"));
|
||||
auto parsedIsland = FloatingIslandManifestV1::FromJson(nestedJson, error);
|
||||
auto parsedIsland = FloatingIslandManifestV1::FromJson(
|
||||
nestedJson, error, manifest.recipe.layoutChunkWidthSubvoxels);
|
||||
if (!parsedIsland) return std::nullopt;
|
||||
entry.manifest = std::move(*parsedIsland);
|
||||
manifest.floatingIslands.push_back(std::move(entry));
|
||||
@@ -1751,15 +1759,15 @@ bool WorldAssemblyManifest::finalize(std::string& error)
|
||||
{ error = "SurfaceWorld stratum recipe seed does not match the world assembly season seed"; return false; }
|
||||
stratum.recipeOverride.contentHash = world_assembly_detail::HashRecipe(stratum.recipeOverride);
|
||||
}
|
||||
if (!world_assembly_detail::ValidateStratum(stratum, error)) return false;
|
||||
if (!world_assembly_detail::ValidateStratum(stratum, recipe.layoutChunkWidthSubvoxels, error)) return false;
|
||||
if (recipe.evaluatorVersion >= 4)
|
||||
{
|
||||
if (stratum.routeConstraints.version != 1 || stratum.route.size() < 2)
|
||||
{ error = "world assembly evaluator 4 requires a versioned entrance-to-exit route"; return false; }
|
||||
const std::int64_t bottomZ = static_cast<std::int64_t>(stratum.bottomChunkZ)
|
||||
* 32 * WorldAssemblySubvoxelsPerVoxel;
|
||||
* recipe.layoutChunkWidthSubvoxels;
|
||||
const std::int64_t topZ = (static_cast<std::int64_t>(stratum.topChunkZ) + 1)
|
||||
* 32 * WorldAssemblySubvoxelsPerVoxel;
|
||||
* recipe.layoutChunkWidthSubvoxels;
|
||||
const std::int64_t halfHeight = stratum.routeConstraints.capsuleHeightSubvoxels / 2;
|
||||
for (const auto& point : stratum.route)
|
||||
{
|
||||
@@ -1789,14 +1797,12 @@ bool WorldAssemblyManifest::finalize(std::string& error)
|
||||
const auto& Scale = islands.manifest.recipe;
|
||||
if (Scale.ownerToCoreScaleNumerator == 0 || Scale.ownerToCoreScaleDenominator == 0)
|
||||
{ error = "floating-island owner-to-Core scale is invalid"; return false; }
|
||||
const double CoreVoxelsPerOwnerChunk = static_cast<double>(CoreTileVoxelCount)
|
||||
* Scale.ownerToCoreScaleNumerator / Scale.ownerToCoreScaleDenominator;
|
||||
const std::int64_t bottomQ = static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(islands.bottomChunkZ) * CoreVoxelsPerOwnerChunk
|
||||
* WorldAssemblySubvoxelsPerVoxel));
|
||||
const std::int64_t topQ = static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(static_cast<std::int64_t>(islands.topChunkZ) + 1)
|
||||
* CoreVoxelsPerOwnerChunk * WorldAssemblySubvoxelsPerVoxel));
|
||||
if(Scale.layoutChunkWidthSubvoxels<=0)
|
||||
{error="floating-island strate has no resolved layout chunk width";return false;}
|
||||
const std::int64_t bottomQ = static_cast<std::int64_t>(islands.bottomChunkZ)
|
||||
*Scale.layoutChunkWidthSubvoxels;
|
||||
const std::int64_t topQ = (static_cast<std::int64_t>(islands.topChunkZ)+1)
|
||||
*Scale.layoutChunkWidthSubvoxels;
|
||||
if (islands.manifest.recipe.layoutMinimumZSubvoxels < bottomQ
|
||||
|| islands.manifest.recipe.layoutMaximumZSubvoxels > topQ)
|
||||
{ error = "floating-island recipe layout escapes the resolved owner strate bounds"; return false; }
|
||||
@@ -2000,7 +2006,7 @@ std::array<std::int64_t,2> Heightfield2D::capHeightBounds() const noexcept
|
||||
if(recipe_->evaluatorVersion>=5)
|
||||
{
|
||||
const long double TopSubvoxels=(static_cast<long double>(plan_->topChunkZ)+1.0L)
|
||||
*CoreTileVoxelCount*WorldAssemblySubvoxelsPerVoxel;
|
||||
*recipe_->layoutChunkWidthSubvoxels;
|
||||
Minimum=std::min(Minimum,TopSubvoxels);
|
||||
Maximum=std::min(Maximum,TopSubvoxels);
|
||||
}
|
||||
@@ -2019,7 +2025,7 @@ std::int64_t Heightfield2D::farSurfaceCapHeightSubvoxels(std::int64_t x,
|
||||
// Clip only the far render surface. Keep the near density evaluator bit-identical.
|
||||
// Borner uniquement le plafond lointain. L'evaluateur de densite proche reste identique.
|
||||
const std::int64_t ownedUpperBoundary=(static_cast<std::int64_t>(plan_->topChunkZ)+1)
|
||||
*CoreTileVoxelCount*WorldAssemblySubvoxelsPerVoxel;
|
||||
*recipe_->layoutChunkWidthSubvoxels;
|
||||
return std::min(column.capHeightSubvoxels,ownedUpperBoundary);
|
||||
}
|
||||
|
||||
@@ -2298,8 +2304,8 @@ FieldInfluenceBounds Heightfield2D::influenceBounds() const noexcept
|
||||
{
|
||||
FieldInfluenceBounds out;
|
||||
if (!isValid()) return out;
|
||||
out.minimumZSubvoxels = static_cast<std::int64_t>(plan_->bottomChunkZ) * 32 * WorldAssemblySubvoxelsPerVoxel;
|
||||
out.maximumZSubvoxels = (static_cast<std::int64_t>(plan_->topChunkZ) + 1) * 32 * WorldAssemblySubvoxelsPerVoxel;
|
||||
out.minimumZSubvoxels = static_cast<std::int64_t>(plan_->bottomChunkZ) * recipe_->layoutChunkWidthSubvoxels;
|
||||
out.maximumZSubvoxels = (static_cast<std::int64_t>(plan_->topChunkZ) + 1) * recipe_->layoutChunkWidthSubvoxels;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
# Linux22 — one strate-layout unit and the closed stack
|
||||
|
||||
The owner setting is now **8 m per layout chunk** for every generator. FloatingIslands spans **+8 to −120 m** (128 m) and SurfaceWorld spans **−120 to −632 m** (512 m). Their shared boundary is −120 m, so the old 60 m gap is gone. SurfaceWorld keeps the same physical interval and content field as L21.
|
||||
|
||||
## Stack and bands
|
||||
|
||||
| Strate | Full interval | Ceiling | Content | Bedrock |
|
||||
|---|---:|---:|---:|---:|
|
||||
| FloatingIslands | +8…−120 m | +7.5…+8 m (0.5 m) | +7.5…−119.5 m (127 m) | −119.5…−120 m (0.5 m) |
|
||||
| SurfaceWorld | −120…−632 m | −120…−126.25 m (6.25 m) | −126.25…−625.75 m (499.5 m) | −625.75…−632 m (6.25 m) |
|
||||
|
||||
```text
|
||||
z=+8.00 m FloatingIslands ceiling outer face
|
||||
z=+7.50 m FloatingIslands content begins
|
||||
Islands: bottoms −63.468…−60.912 m; tops −57.419…−52.357 m
|
||||
z=−119.50 m FloatingIslands bedrock inner face
|
||||
z=−120.00 m FI/SW boundary: FI bedrock meets SW ceiling; no gap
|
||||
z=−126.25 m SurfaceWorld content begins; cap profiles rise above here
|
||||
z=−625.75 m SurfaceWorld bedrock inner face
|
||||
z=−632.00 m SurfaceWorld bedrock outer face
|
||||
```
|
||||
|
||||
The alternate owner SurfaceWorld recipe also exercises TunnelNetwork above it: TunnelNetwork uses the same +8…−120 m interval, with a 1 m ceiling, 126 m content band, and 1 m bedrock band. Every tested strate type receives both solid bands in the shared composition path. Existing `BoundarySealThickness` supplies each band’s thickness: TunnelNetwork 8 owner voxels = 1 m, FloatingIslands 4 = 0.5 m, SurfaceWorld 50 = 6.25 m. `BaseDensity` supplies solid density. Bands are diggable and unprotected by default. `InterStrateGapChunks=0` controls spacing, not band thickness; `EdgeSealThickness` remains horizontal. No new thickness parameter was needed.
|
||||
|
||||
Visible ceilings use the shared L18/L19 boundary path, extended to floors. The authored transitions are Gradient (blend widths 2 chunks for the upper strate and 1 for SurfaceWorld), but Core still uses the explicit **Hard fallback with zero interpenetration**. The snapshot records this deviation; Gradient and Interleaved blending are not claimed as implemented.
|
||||
|
||||
## Unit resolution and validation
|
||||
|
||||
`StrateLayoutChunkSizeMeters` is one world setting with an 8.0 m default. The director/adapter and standalone loader resolve it to micrometres and Core fixed-point width. At 25 cm Core voxels the width is 8,192 subvoxels = 32 Core voxels; at the S1 20 cm check it is 10,240 subvoxels = 40 Core voxels. The internal 32³ Core tile remains a separate concept.
|
||||
|
||||
Changed conversion and consumer sites:
|
||||
|
||||
- `UVoxelStrateManager` resolves the world setting; `VoxelForgeCoreAdapter::AttachValidatedWorldAssembly` passes one fixed-point width into recipes and `ResolvedLayoutSnapshotV2`. `OwnerWorldLoader::Resolve` mirrors that resolution in the standalone path.
|
||||
- `BuildSurfaceStratumPlan` now scales chunk bounds by the resolved width instead of `CHUNK_SIZE` (32 Core voxels). `BuildFloatingIslandStratumPlan` writes the same width into geometry version 4.
|
||||
- `NativeSurfaceDensityAt` and native SurfaceWorld strata classification use snapshot width for `floor(CoreZ / width)` and bounds. FloatingIslands dispatch uses its validated snapshot recipe width. Legacy TunnelNetwork chunk lookup uses the same snapshot width.
|
||||
- Shared ceiling and floor composition, far-shell clipping, cap bounds, endpoint probes, and strict layout validation use the resolved width. `FloatingIslandManifestV1::FromJson` no longer reconstructs legacy width from Core tile size or owner/Core voxel ratio; world assembly supplies and validates the resolved value.
|
||||
|
||||
Current owner snapshots:
|
||||
|
||||
- SurfaceWorld recipe: `resolved-layout-v2-fnv64:96d6a8b64f73c2d8`.
|
||||
- FloatingIslands recipe: `resolved-layout-v2-fnv64:2b343bc155f181d5`.
|
||||
- 20 cm S1 snapshot: `resolved-layout-v2-fnv64:0a56caef7dd44cac`.
|
||||
|
||||
Strict validation passed on the generated owner world for both recipes: zero issues, 7/7 negative controls rejected, and 27 boundary probes per recipe with **zero dispatch/snapshot disagreements**. Both stacks have zero unowned gap and zero undeclared band overlap. The 20 cm snapshot also passed strict validation.
|
||||
|
||||
## SurfaceWorld clearance and spawn
|
||||
|
||||
The clearance scan samples a 4,096 × 4,096 m area at 1 m spacing around the derived route spawn (16,785,409 samples). Clearance is ceiling inner face minus the higher of ground and cap. Negative values mean a cap pokes through the ceiling at −126.25 m.
|
||||
|
||||
| Biome slot / ID | Max ground | Max cap/surface | Minimum sampled clearance |
|
||||
|---|---:|---:|---:|
|
||||
| 0 / 4097 | −442.333 m | −44.496 m | **−81.754 m** |
|
||||
| 1 / 4098 | −313.660 m | −30.881 m | **−95.369 m** |
|
||||
| 2 / 4099 | −419.399 m | −0.425 m | **−125.825 m** |
|
||||
| 3 / 4100 | −361.088 m | −109.922 m | **−16.328 m** |
|
||||
|
||||
Ground/mountain surfaces stay below −313.660 m in this scan. The collisions come from cap profiles: authored `CeilingUndulation` is 1000 or 900 Core voxels (250 m or 225 m). They were not clamped. The owner can raise the strate or reduce those cap profiles. Since SurfaceWorld’s interval and content are unchanged from L21, this clearance issue is unchanged too.
|
||||
|
||||
The SurfaceWorld plan produces 33 route points. The L1 benchmark and spawn camera now derive from `route.front`: **(9.250, 41.250, −502.848) m**, yaw 0°, pitch −22°. Route planning passed (648 candidates, 366 grade rejects, zero sweep rejects); walkability passed 5,642/5,642. The highest route point is −501.714 m, giving 375.464 m clearance to the ceiling face. FloatingIslands walkability passed 4,000/4,000.
|
||||
|
||||
| Island | Bottom…top | Bedrock margin | Ceiling margin |
|
||||
|---:|---:|---:|---:|
|
||||
| 0 | −62.700…−57.419 m | 56.800 m | 64.919 m |
|
||||
| 1 | −61.410…−56.000 m | 58.090 m | 63.500 m |
|
||||
| 2 | −63.468…−55.878 m | 56.032 m | 63.378 m |
|
||||
| 3 | −60.912…−52.357 m | 58.588 m | 59.857 m |
|
||||
|
||||
## Captures
|
||||
|
||||
All six L22 captures are 1600×900 JPEGs under 50 KB (limit 300 KB). SurfaceWorld images compare with L19’s same-named views. The FI review angle is the owner-style low view at (−22, −19, −61) m; separate views look up at the ceiling and down at the new bedrock floor.
|
||||
|
||||
| View | L22 | L19 equivalent |
|
||||
|---|---|---|
|
||||
| SurfaceWorld, route-derived spawn | [L22](captures/surface-world-l1-spawn-after.jpg) | [L19](../Linux19/captures/surface-world-l1-spawn-after.jpg) |
|
||||
| SurfaceWorld horizon | [L22](captures/surface-world-horizon-after.jpg) | [L19](../Linux19/captures/surface-world-horizon-after.jpg) |
|
||||
| SurfaceWorld rings from above | [L22](captures/surface-world-rings-above-after.jpg) | [L19](../Linux19/captures/surface-world-rings-above-after.jpg) |
|
||||
| FloatingIslands low owner angle | [L22](captures/floating-islands-owner-angle-after.jpg) | [L19](../Linux19/captures/floating-islands-owner-angle-after.jpg) |
|
||||
| FloatingIslands ceiling | [L22](captures/floating-islands-ceiling-up-after.jpg) | [L19](../Linux19/captures/floating-islands-ceiling-up-after.jpg) |
|
||||
| FloatingIslands bedrock floor | [L22](captures/floating-islands-bedrock-down-after.jpg) | — |
|
||||
|
||||
A whole-stack side render was not readable from the surface-only mesh export. The vertical stack diagram above gives the exact interfaces and bands.
|
||||
|
||||
## L19 ring tables rerun
|
||||
|
||||
RingProbe ran on L19 and L22 meshes at the same historical cameras: SurfaceWorld (0, −35, −487.94) m and FloatingIslands (−22, −19, −22.8) m. Values are median / p95 / maximum absolute edge-height difference in centimetres, followed by no-hit edge count. These are LOD mesh-edge gaps, not gaps in the validated strate stack.
|
||||
|
||||
### SurfaceWorld rings
|
||||
|
||||
| LOD | L19 edges/samples | L19 cm (gaps) | L22 edges/samples | L22 cm (gaps) |
|
||||
|---|---:|---:|---:|---:|
|
||||
| 0→1 | 46/288 | 0.178 / 0.594 / 1.171 (10) | 46/288 | 0.178 / 0.594 / 1.171 (10) |
|
||||
| 1→2 | 32/288 | 1.419 / 4.990 / 10.438 (0) | 32/288 | 1.419 / 4.990 / 10.438 (0) |
|
||||
| 2→3 | 34/256 | 2.179 / 12.648 / 35.355 (2) | 66/544 | 0 / 8.260 / 35.355 (2) |
|
||||
| 3→4 | 40/265 | 5.099 / 35.999 / 1,190.974 (5) | 72/537 | 0 / 22.456 / 1,190.974 (6) |
|
||||
| 4→5 | 58/270 | 0.235 / 3.694 / 491.570 (14) | 104/835 | 0 / 77.070 / 5,751.081 (6) |
|
||||
| 5→6 | 65/266 | 12.234 / 127.224 / 536.573 (33) | 96/576 | 0 / 0 / 6,935.708 (32) |
|
||||
| 6→7 | 41/269 | 28.238 / 291.861 / 945.477 (7) | 64/288 | 0 / 2,991.225 / 9,240.525 (32) |
|
||||
| 7→8 | 32/275 | 83.843 / 1,225.416 / 3,755.407 (0) | 32/288 | 0 / 10,444.014 / 10,839.630 (0) |
|
||||
|
||||
The 0→1 and 1→2 rows are unchanged. Higher rings changed as the complete strata bands and far boundaries entered the view; the largest far-ring discrepancies increased. The full LOD-stable interface surface remains future work.
|
||||
|
||||
### FloatingIslands / SurfaceWorld interface rings
|
||||
|
||||
| LOD | L19 edges/samples | L19 cm (gaps) | L22 edges/samples | L22 cm (gaps) |
|
||||
|---|---:|---:|---:|---:|
|
||||
| 0→1 | 0/0 | — (0) | 63/185 | 1.328 / 6.795 / 10.623 (17) |
|
||||
| 1→2 | 33/231 | 147.141 / 148.403 / 149.862 (0) | 0/0 | — (0) |
|
||||
| 2→3 | 49/308 | 128.603 / 200.789 / 773.318 (8) | 32/288 | 0 / 0 / 0 (0) |
|
||||
| 3→4 | 64/277 | 3.269 / 47.774 / 188.531 (27) | 32/288 | 0 / 0 / 0 (0) |
|
||||
| 4→5 | 72/544 | 29.992 / 124.373 / 443.482 (6) | 72/547 | 0 / 74.140 / 552.186 (6) |
|
||||
| 5→6 | 64/266 | 12.234 / 127.224 / 536.573 (32) | 64/288 | 0 / 0 / 0 (32) |
|
||||
| 6→7 | 32/264 | 29.013 / 301.752 / 42,624.479 (0) | 32/288 | 0 / 0 / 0 (0) |
|
||||
| 7→8 | 64/275 | 83.843 / 1,225.416 / 3,755.407 (32) | 64/288 | 0 / 0 / 0 (32) |
|
||||
|
||||
The FI table changed substantially. The new bedrock floor is visible at −119.5 m and supplies flat matched samples in several coarser rings; the old L1→2 interface edge is absent in this camera’s L22 mesh. This does not implement the later full LOD-stable interface design.
|
||||
|
||||
## Hashes, determinism, invariants, and cost
|
||||
|
||||
Versions: evaluator 7, recipe schema 2, layout snapshot schema 2, FloatingIslands geometry 4. New W12 baseline rows are density / mesh / payload / topology:
|
||||
|
||||
| Recipe/view | Full detail | Whole view |
|
||||
|---|---|---|
|
||||
| SurfaceWorld, derived spawn | `e7e0b0668a491c46 / ca35fe7a91099574 / 19f29f621cfdbbc7 / e37b3191a7200c67` | `4932f16f7d8dd5ab / 98e68419a13b76db / 56124d542206790c / 75ef1148afa20ca8` |
|
||||
| FloatingIslands, auto island camera | `647bcb4038627e16 / 87324ad688a49bfa / a0c5b9a73288e393 / f9c316a79dd42152` | `2b5b06283399ad9d / 5c66f389b526f6bb / f2e7ff68d9dc2262 / 121abcf0607f0cf1` |
|
||||
|
||||
For the same historical SurfaceWorld camera, L21 and L22 exact-query density/unclamped hashes match at `e4d4a8eb95b35933`; detail is `efcba20c0c70c415`, collision is `e604823a249029bf`. Full-detail hashes also match exactly: `0ee0579bd15cbbe4 / 32e5d4964d610fee / 1932b96ee4b1d910 / 69743c6bad3447dc`. Whole-view hashes differ because shared bands and complete outer strata now contribute to the mesh.
|
||||
|
||||
Determinism passed 28/28 runs (both recipes, Clang 19 with FP contraction off and GCC 14 default; W1, W8, W12×5). The 24 m SurfaceWorld crossing passed 14/14. Resolved snapshots were byte-identical across W1/W8/W12. Final W12 smoke hashes on both post-guard binaries still match their baselines. SurfaceWorld route parity passed with 782,026 exact-query samples and zero mismatches; FloatingIslands also passed with 782,026 samples and zero mismatches. SurfaceWorld’s proof audit checked 25,927 blocks / 34,508,837 samples with zero violations. FI had zero checked blocks and 24,576 conservative unknown/fallback blocks, zero violations; it did not claim positive proof coverage. Shape readiness, base/page equivalence, and walkability audits passed.
|
||||
|
||||
The W12 hashing-off five-pair median cost against the L21 binary was 1.613 s → 1.411 s for SurfaceWorld (−12.52%) and 11.087 s → 18.840 s for FloatingIslands (+69.93%), reflecting FI’s added 64 m of volume. Largest measured process RSS was 897,840 KiB; largest process-tree RSS was 4,603,848 KiB. Builds and runs stayed under the 6 GiB address-space limit, CPUs 0–11, at most 12 workers, and the 15/30 minute limits. The governor was unchanged.
|
||||
|
||||
The L3 FP-contract preamble/self-test and L4 `FLazy`, L7 shared cache, L9/L12 proofs, L11 readiness/crash handling, L13 route binding, L18/L19 shared ceiling, L21 snapshot/validator, `ARCHITECTURE.md` §8.10, and CLAUDE.md async rules remain in place. The Linux22 architecture note is §8.40.
|
||||
|
||||
## Unreal edits and Windows checklist
|
||||
|
||||
Unreal edits are limited to:
|
||||
|
||||
- `Source/VoxelForge/Public/VoxelSettings.h` — world setting `StrateLayoutChunkSizeMeters`, default 8.0.
|
||||
- `Source/VoxelForge/Public/VoxelStrateManager.h` and `Private/VoxelStrateManager.cpp` — resolve and use one layout width for manager strate bounds/lookups.
|
||||
- `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp` — pass that width to plans and the V2 snapshot; compose the same parameter-driven ceiling and bedrock bands for each generator.
|
||||
|
||||
The Unreal module cannot be compiled on this Linux machine. Windows checklist:
|
||||
|
||||
1. Rebuild the VoxelForge module and confirm the setting defaults to 8.0 m.
|
||||
2. Load the owner world with FloatingIslands and SurfaceWorld; verify +8…−120 m and −120…−632 m, with no gap at −120 m.
|
||||
3. Verify ceiling and bedrock are visible and breakable, then check the route-derived SurfaceWorld spawn and cap/ceiling collisions against the captures.
|
||||
4. Run the boundary validator and compare the generated snapshot with the two Linux22 identities above.
|
||||
|
||||
## Failed and retried runs
|
||||
|
||||
- Three early Clang builds failed while implementation files were being synchronized: the standalone loader referenced settings/API not yet copied; the next compile found three undeclared copied-manager width references; a later attempt found a missing closing brace in the clearance audit. These were corrected; the final Clang and GCC builds passed.
|
||||
- One final smoke attempt used a nonexistent `linux22-clang-off` binary suffix and exited 127 before running a workload. It was rerun with the correct `linux22-clang` and `linux22-gcc` paths; all four W12 hash checks passed.
|
||||
- A zero-byte `build-clang-off-floor.log` has no recorded compiler output and was superseded by the successful measured final builds.
|
||||
- The clearance scan completed successfully and reported the cap collisions above; this is a world-fit finding, not a failed run. No build or run timed out or exceeded its limit.
|
||||
|
||||
Nothing was committed, pushed, or stashed.
|
||||
|
||||
## Orchestrator review (Claude)
|
||||
|
||||
- Merged. The strict validator passes on the real owner world; SW full-detail and exact-query hashes are
|
||||
unchanged (content did not move); FI is +8…−120 m with a bedrock floor.
|
||||
- Captures checked by me: the FI owner-angle view shows islands above a clean, flat bedrock floor with a
|
||||
closed ceiling, and the ledges are gone. The SW rings-from-above view shows continuous terrain with no
|
||||
visible ring steps, so the large far-ring values in the RingProbe table (up to ~108 m) are most likely the
|
||||
probe pairing different surfaces now that bands are in view, not real breaks. Treat that table with care
|
||||
until the probe filters by surface owner. The SW horizon is from the new route-derived spawn, so it is not
|
||||
the same angle as L19.
|
||||
- Owner decision needed: SW cap profiles (`CeilingUndulation` 225–250 m) exceed the new ceiling band by up
|
||||
to ~126 m; reported, not clamped.
|
||||
- Cost: FI whole view +70% (its strate doubled in volume); SW −12.5%.
|
||||
- Unreal edits (VoxelSettings `StrateLayoutChunkSizeMeters` = 8.0, StrateManager, CoreAdapter) are
|
||||
UNCOMPILED here; Windows checklist above. CLAUDE.md's scale rule was updated to match.
|
||||
- Transitions are still Hard (recorded deviation).
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
@@ -43,6 +43,8 @@ struct FStandaloneOwnerWorld
|
||||
int32 LODOctaveDrop = 0;
|
||||
std::uint32_t OwnerVoxelSizeMicrons = 125000;
|
||||
std::uint32_t CoreVoxelSizeMicrons = 250000;
|
||||
std::uint32_t StrateLayoutChunkSizeMicrometres = 8000000;
|
||||
std::int64_t StrateLayoutChunkWidthSubvoxels = 8192;
|
||||
};
|
||||
|
||||
namespace StandaloneOwnerLoader
|
||||
@@ -378,6 +380,21 @@ namespace StandaloneOwnerLoader
|
||||
ReadField(Root,"strate_content_cut_min_level",Out.Settings->StrateContentCutMinLevel);
|
||||
ReadField(SettingsJson,"TotalStrates",Out.Settings->TotalStrates);
|
||||
ReadField(SettingsJson,"InterStrateGapChunks",Out.Settings->InterStrateGapChunks);
|
||||
ReadField(SettingsJson,"StrateLayoutChunkSizeMeters",Out.Settings->StrateLayoutChunkSizeMeters);
|
||||
const VFJson::Value& LayoutChunkMicrometres=Root.Get("strate_layout_chunk_size_um");
|
||||
const double LayoutChunkSizeMicrometres=LayoutChunkMicrometres.IsNumber()
|
||||
?LayoutChunkMicrometres.Number()
|
||||
:static_cast<double>(Out.Settings->StrateLayoutChunkSizeMeters)*1000000.0;
|
||||
if(!std::isfinite(LayoutChunkSizeMicrometres)||LayoutChunkSizeMicrometres<250000.0
|
||||
||LayoutChunkSizeMicrometres>64000000.0
|
||||
||std::abs(LayoutChunkSizeMicrometres-std::round(LayoutChunkSizeMicrometres))>0.001)
|
||||
{Error="strate layout chunk size must be an integral micrometre value between 0.25 m and 64 m";return false;}
|
||||
Out.StrateLayoutChunkSizeMicrometres=static_cast<std::uint32_t>(std::llround(LayoutChunkSizeMicrometres));
|
||||
Out.Settings->StrateLayoutChunkSizeMeters=static_cast<float>(LayoutChunkSizeMicrometres/1000000.0);
|
||||
Out.StrateLayoutChunkWidthSubvoxels=static_cast<std::int64_t>(std::llround(
|
||||
LayoutChunkSizeMicrometres*256.0/CoreMicrons));
|
||||
if(Out.StrateLayoutChunkWidthSubvoxels<=0)
|
||||
{Error="strate layout chunk size resolves to no Core subvoxels";return false;}
|
||||
Out.Settings->StratePool.Reset();
|
||||
Out.Settings->FixedStrates.Empty();
|
||||
const auto& Strates = Root.Get("strates").AsArray();
|
||||
@@ -398,6 +415,7 @@ namespace StandaloneOwnerLoader
|
||||
Error = "copied strate manager rejected the owner settings snapshot";
|
||||
return false;
|
||||
}
|
||||
Out.Manager->SetHardTransitionFallbackForAssembly(true);
|
||||
Out.Generator = std::make_unique<UVoxelGenerator>();
|
||||
Out.Generator->InitializeSettings(Out.Settings.get());
|
||||
Out.Generator->SetStrateManager(Out.Manager.get());
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -66,6 +67,7 @@ namespace
|
||||
std::filesystem::path ResolvedLayoutOutputPath;
|
||||
bool ResolvedLayoutOnly=false;
|
||||
bool AuditResolvedLayoutBoundaries=false;
|
||||
bool AuditSurfaceClearance=false;
|
||||
};
|
||||
|
||||
bool ParseInteger(std::string_view text,int& output)
|
||||
@@ -123,6 +125,7 @@ namespace
|
||||
" --resolved-layout-output=FILE (canonical plan-time layout snapshot)\n"
|
||||
" --resolved-layout-only (exit after plan and snapshot validation)\n"
|
||||
" --audit-resolved-layout-boundaries (query Core at each recorded endpoint)\n"
|
||||
" --audit-surface-clearance (sample biome terrain/caps against the resolved ceiling)\n"
|
||||
" --auto-surface-camera (place the 27-tile cube on this seed's surface)\n"
|
||||
" --mesh-coverage (report non-empty tiles and indexed triangles)\n"
|
||||
" --screen-space-gate (measure display error and historical negative controls)\n"
|
||||
@@ -142,6 +145,7 @@ namespace
|
||||
if(arg=="--world-assembler-prototype")options.WorldAssemblerPrototype=true;
|
||||
else if(arg=="--resolved-layout-only")options.ResolvedLayoutOnly=true;
|
||||
else if(arg=="--audit-resolved-layout-boundaries")options.AuditResolvedLayoutBoundaries=true;
|
||||
else if(arg=="--audit-surface-clearance")options.AuditSurfaceClearance=true;
|
||||
else if(arg=="--stream-benchmark")options.StreamBenchmark=true;
|
||||
else if(arg=="--stream-pipeline")options.StreamPipeline=true;
|
||||
else if(arg=="--stream-hash")options.StreamHash=true;
|
||||
@@ -313,7 +317,11 @@ int main(int argc,char** argv)
|
||||
|
||||
FVector cameraVoxels=ownerWorld->ViewerStartVoxels;
|
||||
if(options.HasCamera)
|
||||
cameraVoxels=FVector(options.CameraMeters.X*4.0f,options.CameraMeters.Y*4.0f,options.CameraMeters.Z*4.0f);
|
||||
{
|
||||
const float VoxelsPerMetre=1000000.0f/std::max<std::uint32_t>(1,ownerWorld->CoreVoxelSizeMicrons);
|
||||
cameraVoxels=FVector(options.CameraMeters.X*VoxelsPerMetre,
|
||||
options.CameraMeters.Y*VoxelsPerMetre,options.CameraMeters.Z*VoxelsPerMetre);
|
||||
}
|
||||
|
||||
const auto planStart=Clock::now();
|
||||
VoxelForgeCore::WorldAssemblyManifest worldAssembly;
|
||||
@@ -324,22 +332,19 @@ int main(int argc,char** argv)
|
||||
&&!StandaloneWorldAssemblyPrototype::AttachFloatingIslandWorldAssembly(
|
||||
*ownerWorld,ownerJsonRoot,worldAssembly,error))
|
||||
{std::fprintf(stderr,"Floating-island assembly setup failed: %s\n",error.c_str());return 2;}
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV1 resolvedLayout;
|
||||
const bool buildResolvedLayout=!options.ResolvedLayoutOutputPath.empty()
|
||||
||options.AuditResolvedLayoutBoundaries||options.ResolvedLayoutOnly;
|
||||
if(buildResolvedLayout)
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV2 resolvedLayout;
|
||||
{
|
||||
const std::string_view variant=options.FloatingIslandsRecipe?"floating-islands":"surface-world";
|
||||
if(!StandaloneWorldAssemblyPrototype::BuildResolvedLayoutSnapshot(
|
||||
*ownerWorld,worldAssembly,variant,resolvedLayout,error))
|
||||
{std::fprintf(stderr,"Resolved-layout snapshot failed: %s\n",error.c_str());return 2;}
|
||||
const std::string layoutJson=resolvedLayout.ToJson();
|
||||
if(!options.ResolvedLayoutOutputPath.empty())
|
||||
{
|
||||
const auto roundTrip=VoxelForgeCore::ResolvedLayoutSnapshotV1::FromJson(layoutJson,error);
|
||||
const auto roundTrip=VoxelForgeCore::ResolvedLayoutSnapshotV2::FromJson(layoutJson,error);
|
||||
if(!roundTrip||roundTrip->ToJson()!=layoutJson||roundTrip->stableIdentity!=resolvedLayout.stableIdentity)
|
||||
{if(error.empty())error="resolved-layout canonical serialization did not round-trip";
|
||||
std::fprintf(stderr,"Resolved-layout round trip failed: %s\n",error.c_str());return 2;}
|
||||
if(!options.ResolvedLayoutOutputPath.empty())
|
||||
{
|
||||
if(!options.ResolvedLayoutOutputPath.parent_path().empty())
|
||||
std::filesystem::create_directories(options.ResolvedLayoutOutputPath.parent_path());
|
||||
std::ofstream output(options.ResolvedLayoutOutputPath,std::ios::binary);
|
||||
@@ -347,12 +352,16 @@ int main(int argc,char** argv)
|
||||
output.write(layoutJson.data(),static_cast<std::streamsize>(layoutJson.size()));
|
||||
if(!output){std::fprintf(stderr,"Resolved-layout snapshot write failed: %s\n",options.ResolvedLayoutOutputPath.c_str());return 2;}
|
||||
}
|
||||
const auto controls=VoxelForgeCore::AuditResolvedLayoutValidatorV1();
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutIssueV1> compatibilityIssues;
|
||||
const auto controls=VoxelForgeCore::AuditResolvedLayoutValidatorV2();
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutIssueV2> compatibilityIssues;
|
||||
std::string compatibilityError;
|
||||
const bool compatibilityAccepted=resolvedLayout.validate(
|
||||
VoxelForgeCore::ResolvedLayoutValidationModeV1::compatibility,
|
||||
VoxelForgeCore::ResolvedLayoutValidationModeV2::compatibility,
|
||||
compatibilityIssues,compatibilityError);
|
||||
std::vector<VoxelForgeCore::ResolvedLayoutIssueV2> strictIssues;
|
||||
std::string strictError;
|
||||
const bool strictAccepted=resolvedLayout.validate(
|
||||
VoxelForgeCore::ResolvedLayoutValidationModeV2::strict,strictIssues,strictError);
|
||||
std::printf("[ResolvedLayoutSnapshot] schema=%u stable_identity=%s strata=%zu bytes=%zu round_trip=PASS\n",
|
||||
resolvedLayout.schemaVersion,resolvedLayout.stableIdentity.c_str(),resolvedLayout.strates.size(),layoutJson.size());
|
||||
std::printf("[ResolvedLayoutCompatibility] accepted=%s issues=%zu error=%s\n",
|
||||
@@ -362,12 +371,124 @@ int main(int argc,char** argv)
|
||||
issue.code.c_str(),issue.detail.c_str());
|
||||
if(!compatibilityAccepted)
|
||||
{std::fprintf(stderr,"Resolved-layout compatibility validation failed: %s\n",compatibilityError.c_str());return 2;}
|
||||
std::printf("[ResolvedLayoutStrict] accepted=%s issues=%zu error=%s\n",
|
||||
strictAccepted?"true":"false",strictIssues.size(),strictError.c_str());
|
||||
for(const auto& issue:strictIssues)
|
||||
std::printf("[ResolvedLayoutStrictIssue] code=%s detail=%s\n",issue.code.c_str(),issue.detail.c_str());
|
||||
if(!strictAccepted)
|
||||
{std::fprintf(stderr,"Resolved-layout strict validation failed: %s\n",strictError.c_str());return 2;}
|
||||
std::printf("[ResolvedLayoutValidatorControls] rejected=%u/%u status=%s\n",
|
||||
controls.controlsRejectedAsExpected,controls.controlsRun,controls.passed?"PASS":"FAIL");
|
||||
if(!controls.passed)
|
||||
{for(const auto& failure:controls.failures)std::printf("[ResolvedLayoutValidatorControlFailure] %s %s\n",failure.code.c_str(),failure.detail.c_str());return 2;}
|
||||
if(!options.ResolvedLayoutOutputPath.empty())
|
||||
std::printf("[ResolvedLayoutSnapshotOutput] path=%s\n",options.ResolvedLayoutOutputPath.c_str());
|
||||
if(auto* root=std::get_if<VFJson::Value::Object>(&ownerJsonRoot.Data))
|
||||
{VFJson::Value layoutText;layoutText.Data=layoutJson;(*root)["resolved_layout_json"]=std::move(layoutText);}
|
||||
else
|
||||
{std::fprintf(stderr,"Owner snapshot root cannot carry its resolved layout.\n");return 2;}
|
||||
if(options.AuditSurfaceClearance)
|
||||
{
|
||||
if(options.FloatingIslandsRecipe)
|
||||
{std::fprintf(stderr,"--audit-surface-clearance requires --recipe=surface-world\n");return 2;}
|
||||
const auto surface=std::find_if(resolvedLayout.strates.begin(),resolvedLayout.strates.end(),
|
||||
[](const auto& Stratum){return Stratum.generatorKind=="SurfaceWorld";});
|
||||
const auto plan=surface==resolvedLayout.strates.end()?worldAssembly.plan.strates.end()
|
||||
:std::find_if(worldAssembly.plan.strates.begin(),worldAssembly.plan.strates.end(),
|
||||
[&](const auto& Stratum){return Stratum.strateIndex==surface->strateIndex;});
|
||||
if(surface==resolvedLayout.strates.end()||plan==worldAssembly.plan.strates.end()
|
||||
||!surface->ceiling.presentInCurrentField)
|
||||
{std::fprintf(stderr,"SurfaceWorld clearance audit could not resolve its plan/ceiling\n");return 2;}
|
||||
const VoxelForgeCore::Heightfield2D Field(worldAssembly.recipe,*plan);
|
||||
if(!Field.isValid())
|
||||
{std::fprintf(stderr,"SurfaceWorld clearance audit field is invalid\n");return 2;}
|
||||
constexpr std::int64_t HalfExtentMetres=2048,StepMetres=1;
|
||||
const double CoreMicrometres=std::max<std::uint32_t>(1,ownerWorld->CoreVoxelSizeMicrons);
|
||||
const double MetresPerSubvoxel=CoreMicrometres
|
||||
/(1000000.0*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
double CenterX=0.0,CenterY=0.0;
|
||||
if(!plan->route.empty())
|
||||
{CenterX=plan->route.front().positionSubvoxels[0]*MetresPerSubvoxel;
|
||||
CenterY=plan->route.front().positionSubvoxels[1]*MetresPerSubvoxel;}
|
||||
const std::int64_t CenterXQ=static_cast<std::int64_t>(std::llround(
|
||||
CenterX/MetresPerSubvoxel));
|
||||
const std::int64_t CenterYQ=static_cast<std::int64_t>(std::llround(
|
||||
CenterY/MetresPerSubvoxel));
|
||||
const std::int64_t StepQ=static_cast<std::int64_t>(std::llround(
|
||||
StepMetres/MetresPerSubvoxel));
|
||||
struct FBiomeClearance
|
||||
{
|
||||
std::uint64_t Samples=0;
|
||||
double MaximumGround=-std::numeric_limits<double>::infinity();
|
||||
double MaximumCap=-std::numeric_limits<double>::infinity();
|
||||
double MaximumSurface=-std::numeric_limits<double>::infinity();
|
||||
double MinimumClearance=std::numeric_limits<double>::infinity();
|
||||
double AtX=0.0,AtY=0.0,AtHeight=0.0;
|
||||
std::int64_t MaximumCapQ=std::numeric_limits<std::int64_t>::min();
|
||||
};
|
||||
std::map<std::uint32_t,FBiomeClearance> ByBiome;
|
||||
const double CeilingFace=static_cast<double>(surface->ceiling.bottomMicrometres)/1000000.0;
|
||||
for(std::int64_t Y=-HalfExtentMetres;Y<=HalfExtentMetres;Y+=StepMetres)
|
||||
for(std::int64_t X=-HalfExtentMetres;X<=HalfExtentMetres;X+=StepMetres)
|
||||
{
|
||||
const auto Sample=Field.point(CenterXQ+(X/StepMetres)*StepQ,
|
||||
CenterYQ+(Y/StepMetres)*StepQ,0);
|
||||
const double Ground=Sample.groundHeightSubvoxels*MetresPerSubvoxel;
|
||||
const double Cap=Sample.capHeightSubvoxels*MetresPerSubvoxel;
|
||||
const double SurfaceHeight=std::max(Ground,Cap);
|
||||
auto& Stats=ByBiome[Sample.biomeId];
|
||||
++Stats.Samples;
|
||||
Stats.MaximumGround=std::max(Stats.MaximumGround,Ground);
|
||||
Stats.MaximumCap=std::max(Stats.MaximumCap,Cap);
|
||||
Stats.MaximumCapQ=std::max(Stats.MaximumCapQ,Sample.capHeightSubvoxels);
|
||||
Stats.MaximumSurface=std::max(Stats.MaximumSurface,SurfaceHeight);
|
||||
const double Clearance=CeilingFace-SurfaceHeight;
|
||||
if(Clearance<Stats.MinimumClearance)
|
||||
{Stats.MinimumClearance=Clearance;Stats.AtX=CenterX+X;Stats.AtY=CenterY+Y;
|
||||
Stats.AtHeight=SurfaceHeight;}
|
||||
}
|
||||
double MaximumRouteZ=-std::numeric_limits<double>::infinity();
|
||||
for(const auto& Point:plan->route)
|
||||
MaximumRouteZ=std::max(MaximumRouteZ,Point.positionSubvoxels[2]*MetresPerSubvoxel);
|
||||
const auto CapBounds=Field.capHeightBounds();
|
||||
const auto RouteSample=plan->route.empty()?VoxelForgeCore::HeightfieldPointSample{}
|
||||
:Field.point(plan->route.front().positionSubvoxels[0],
|
||||
plan->route.front().positionSubvoxels[1],0);
|
||||
std::printf("[SurfaceClearancePlan] has_recipe_override=%s layout_chunk_q=%lld plan_cap_q=%lld cap_bounds_q=(%lld,%lld)\n",
|
||||
plan->hasRecipeOverride?"true":"false",
|
||||
static_cast<long long>(worldAssembly.recipe.layoutChunkWidthSubvoxels),
|
||||
static_cast<long long>(plan->capHeightSubvoxels),
|
||||
static_cast<long long>(CapBounds[0]),static_cast<long long>(CapBounds[1]));
|
||||
for(const auto& Profile:worldAssembly.recipe.biomes)
|
||||
std::printf("[SurfaceClearanceProfile] biome_id=%u has_cap=%s cap_q=%lld undulation_q=%lld detail_q=%lld ridge_q=%lld\n",
|
||||
Profile.stableId,Profile.hasCapProfile?"true":"false",
|
||||
static_cast<long long>(Profile.capHeightSubvoxels),
|
||||
static_cast<long long>(Profile.capUndulationSubvoxels),
|
||||
static_cast<long long>(Profile.capDetailAmplitudeSubvoxels),
|
||||
static_cast<long long>(Profile.capRidgeAmplitudeSubvoxels));
|
||||
std::printf("[SurfaceClearanceAudit] box_m=4096x4096 step_m=%lld samples=%lld ceiling_inner_face_m=%.3f unmodified_field=true\n",
|
||||
static_cast<long long>(StepMetres),
|
||||
static_cast<long long>((2*HalfExtentMetres/StepMetres+1)*(2*HalfExtentMetres/StepMetres+1)),
|
||||
CeilingFace);
|
||||
std::printf("[SurfaceClearanceCapRange] plan_min_m=%.3f plan_max_m=%.3f spawn_cap_m=%.3f spawn_ground_m=%.3f\n",
|
||||
CapBounds[0]*MetresPerSubvoxel,CapBounds[1]*MetresPerSubvoxel,
|
||||
RouteSample.capHeightSubvoxels*MetresPerSubvoxel,
|
||||
RouteSample.groundHeightSubvoxels*MetresPerSubvoxel);
|
||||
for(const auto& Feature:plan->features)
|
||||
std::printf("[SurfaceClearanceFeature] type=%u params_q=(%lld,%lld,%lld,%lld)\n",
|
||||
Feature.type,static_cast<long long>(Feature.parametersSubvoxels[0]),
|
||||
static_cast<long long>(Feature.parametersSubvoxels[1]),
|
||||
static_cast<long long>(Feature.parametersSubvoxels[2]),
|
||||
static_cast<long long>(Feature.parametersSubvoxels[3]));
|
||||
for(const auto& [BiomeId,Stats]:ByBiome)
|
||||
std::printf("[SurfaceClearanceBiome] biome_id=%u samples=%llu max_ground_m=%.3f max_cap_m=%.3f max_cap_q=%lld max_surface_m=%.3f min_clearance_m=%.3f at_m=(%.1f,%.1f,%.3f)\n",
|
||||
BiomeId,static_cast<unsigned long long>(Stats.Samples),Stats.MaximumGround,
|
||||
Stats.MaximumCap,static_cast<long long>(Stats.MaximumCapQ),Stats.MaximumSurface,Stats.MinimumClearance,
|
||||
Stats.AtX,Stats.AtY,Stats.AtHeight);
|
||||
std::printf("[SurfaceClearanceRoute] route_points=%zu max_route_z_m=%.3f clearance_m=%.3f status=%s\n",
|
||||
plan->route.size(),MaximumRouteZ,CeilingFace-MaximumRouteZ,
|
||||
MaximumRouteZ<CeilingFace?"PASS":"COLLISION");
|
||||
}
|
||||
if(options.ResolvedLayoutOnly)return 0;
|
||||
}
|
||||
if(options.AutoSurfaceCamera)
|
||||
@@ -391,25 +512,17 @@ int main(int argc,char** argv)
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto xSubvoxels=static_cast<std::int64_t>(std::llround(cameraVoxels.X*UnitsPerVoxel));
|
||||
const auto ySubvoxels=static_cast<std::int64_t>(std::llround(cameraVoxels.Y*UnitsPerVoxel));
|
||||
const auto currentZSubvoxels=static_cast<std::int64_t>(std::llround(cameraVoxels.Z*UnitsPerVoxel));
|
||||
std::int64_t selectedGround=0,bestDistance=std::numeric_limits<std::int64_t>::max();
|
||||
for(const auto& stratum:worldAssembly.plan.strates)
|
||||
{
|
||||
VoxelForgeCore::Heightfield2D field(worldAssembly.recipe,stratum);
|
||||
if(!field.isValid())continue;
|
||||
const auto column=field.point(xSubvoxels,ySubvoxels,0);
|
||||
const std::int64_t distance=std::abs(column.groundHeightSubvoxels-currentZSubvoxels);
|
||||
if(distance<bestDistance)
|
||||
{bestDistance=distance;selectedGround=column.groundHeightSubvoxels;}
|
||||
}
|
||||
if(bestDistance==std::numeric_limits<std::int64_t>::max())
|
||||
{std::fprintf(stderr,"[SeedSurfacePlacement] FAIL: no valid SurfaceWorld heightfield\n");return 2;}
|
||||
cameraVoxels.Z=static_cast<float>(static_cast<double>(selectedGround)/UnitsPerVoxel);
|
||||
std::printf("[SeedSurfacePlacement] recipe=surface-world seed=%d ground_voxel=%.6f camera_voxel=(%.6f,%.6f,%.6f) status=PASS\n",
|
||||
seed,static_cast<double>(selectedGround)/UnitsPerVoxel,
|
||||
cameraVoxels.X,cameraVoxels.Y,cameraVoxels.Z);
|
||||
if(worldAssembly.plan.strates.empty()||worldAssembly.plan.strates.front().route.empty())
|
||||
{std::fprintf(stderr,"[SeedSurfacePlacement] FAIL: route-derived spawn is missing\n");return 2;}
|
||||
const auto& spawn=worldAssembly.plan.strates.front().route.front();
|
||||
cameraVoxels={static_cast<float>(static_cast<double>(spawn.positionSubvoxels[0])/UnitsPerVoxel),
|
||||
static_cast<float>(static_cast<double>(spawn.positionSubvoxels[1])/UnitsPerVoxel),
|
||||
static_cast<float>(static_cast<double>(spawn.positionSubvoxels[2])/UnitsPerVoxel)};
|
||||
std::printf("[SeedSurfacePlacement] recipe=surface-world seed=%d source=route.front position_voxel=(%.6f,%.6f,%.6f) position_m=(%.3f,%.3f,%.3f) status=PASS\n",
|
||||
seed,cameraVoxels.X,cameraVoxels.Y,cameraVoxels.Z,
|
||||
cameraVoxels.X*ownerWorld->CoreVoxelSizeMicrons/1000000.0f,
|
||||
cameraVoxels.Y*ownerWorld->CoreVoxelSizeMicrons/1000000.0f,
|
||||
cameraVoxels.Z*ownerWorld->CoreVoxelSizeMicrons/1000000.0f);
|
||||
}
|
||||
}
|
||||
std::printf("[WorldAssemblerPlanTiming] total_ms=%.3f status=PASS\n",
|
||||
@@ -422,8 +535,9 @@ int main(int argc,char** argv)
|
||||
|
||||
FStandaloneCoreScheduler scheduler;
|
||||
const FVector cameraStepVoxels=options.HasCameraStep
|
||||
?FVector(options.CameraStepMeters.X*4.0f,options.CameraStepMeters.Y*4.0f,
|
||||
options.CameraStepMeters.Z*4.0f):FVector::ZeroVector;
|
||||
?FVector(options.CameraStepMeters.X*1000000.0f/std::max<std::uint32_t>(1,ownerWorld->CoreVoxelSizeMicrons),
|
||||
options.CameraStepMeters.Y*1000000.0f/std::max<std::uint32_t>(1,ownerWorld->CoreVoxelSizeMicrons),
|
||||
options.CameraStepMeters.Z*1000000.0f/std::max<std::uint32_t>(1,ownerWorld->CoreVoxelSizeMicrons)):FVector::ZeroVector;
|
||||
return StandaloneStreamBenchmark::RunPipelinedStreamingBenchmark(ownerWorld,snapshot,
|
||||
cameraVoxels,options.WorkerCount,options.MaxLevel,VoxelForgeCore::DetailVariant::coherent,
|
||||
scheduler,options.StreamHash,false,false,false,false,coldStart,
|
||||
@@ -434,5 +548,5 @@ int main(int argc,char** argv)
|
||||
options.RegionCrossing,options.AuditTileShapeReadiness,
|
||||
options.DiagnosticSlowShapeCompileMs,options.AuditBasePageEquivalence,
|
||||
options.AuditRouteParity,options.AuditResolvedLayoutBoundaries,
|
||||
buildResolvedLayout?&resolvedLayout:nullptr);
|
||||
&resolvedLayout);
|
||||
}
|
||||
@@ -255,7 +255,7 @@ namespace StandaloneStreamBenchmark
|
||||
bool bAuditTileShapeReadiness=false,int DiagnosticSlowShapeCompileMs=0,
|
||||
bool bAuditBasePageEquivalence=false,bool bAuditRouteParity=false,
|
||||
bool bAuditResolvedLayoutBoundaries=false,
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV1* ResolvedLayout=nullptr)
|
||||
const VoxelForgeCore::ResolvedLayoutSnapshotV2* ResolvedLayout=nullptr)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
FStandaloneOwnerWorld& World=*OwnerWorld;
|
||||
@@ -470,7 +470,7 @@ namespace StandaloneStreamBenchmark
|
||||
std::vector<std::string> candidates;
|
||||
for(const auto& Stratum:ResolvedLayout->strates)
|
||||
{
|
||||
const auto contains=[zMicrometres](const VoxelForgeCore::ResolvedLayoutBandV1& Band)
|
||||
const auto contains=[zMicrometres](const VoxelForgeCore::ResolvedLayoutBandV2& Band)
|
||||
{return Band.presentInCurrentField&&zMicrometres>=Band.bottomMicrometres
|
||||
&&zMicrometres<Band.topMicrometres;};
|
||||
if(contains(Stratum.ceiling))candidates.push_back(Stratum.generatorKind+"/ceiling");
|
||||
@@ -482,6 +482,14 @@ namespace StandaloneStreamBenchmark
|
||||
const std::array<std::int64_t,3> offsets{{
|
||||
-static_cast<std::int64_t>(World.CoreVoxelSizeMicrons),0,
|
||||
static_cast<std::int64_t>(World.CoreVoxelSizeMicrons)}};
|
||||
const std::int64_t stackMinimum=std::min_element(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[](const auto& A,const auto& B)
|
||||
{return A.bedrock.bottomMicrometres<B.bedrock.bottomMicrometres;})
|
||||
->bedrock.bottomMicrometres;
|
||||
const std::int64_t stackMaximum=std::max_element(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[](const auto& A,const auto& B)
|
||||
{return A.ceiling.topMicrometres<B.ceiling.topMicrometres;})
|
||||
->ceiling.topMicrometres;
|
||||
for(const std::int64_t Endpoint:endpoints)
|
||||
for(const std::int64_t Offset:offsets)
|
||||
{
|
||||
@@ -501,16 +509,17 @@ namespace StandaloneStreamBenchmark
|
||||
if(std::find(expectedGenerators.begin(),expectedGenerators.end(),Kind)==expectedGenerators.end())
|
||||
expectedGenerators.push_back(Kind);
|
||||
}
|
||||
if(expected.empty())expectedLabel="uncovered";
|
||||
const float density=Shape.densityAt(position);
|
||||
const bool solid=density<0.0f;
|
||||
const bool outsideStack=zMicrometres<stackMinimum||zMicrometres>=stackMaximum;
|
||||
if(expected.empty())expectedLabel=outsideStack?"outside-stack/air":"uncovered";
|
||||
std::string actualGenerator;
|
||||
if(actual=="SurfaceWorld-native"||actual=="SurfaceWorld-legacy")actualGenerator="SurfaceWorld";
|
||||
else if(actual=="FloatingIslands-native")actualGenerator="FloatingIslands";
|
||||
else if(actual.rfind("compiled-analytic/",0)==0)actualGenerator=actual.substr(18);
|
||||
const bool mismatch=expected.empty()
|
||||
?actual!="compiled-analytic/fallback"
|
||||
?(!outsideStack||solid)
|
||||
:std::find(expectedGenerators.begin(),expectedGenerators.end(),actualGenerator)==expectedGenerators.end();
|
||||
const float density=Shape.densityAt(position);
|
||||
const bool solid=density<0.0f;
|
||||
++probes;disagreements+=mismatch?1u:0u;
|
||||
std::printf("[ResolvedLayoutProbe] endpoint_um=%lld offset_um=%lld z_m=%.3f expected=%s actual=%s density=%.7g occupancy=%s disagreement=%s\n",
|
||||
static_cast<long long>(Endpoint),static_cast<long long>(Offset),
|
||||
|
||||
@@ -322,7 +322,7 @@ namespace
|
||||
const FVector ChunkCenter(
|
||||
(ChunkCoord.X + 0.5f) * (float)CHUNK_SIZE,
|
||||
(ChunkCoord.Y + 0.5f) * (float)CHUNK_SIZE,
|
||||
(ChunkCoord.Z + 0.5f) * (float)CHUNK_SIZE);
|
||||
(ChunkCoord.Z + 0.5f) * Manager->GetStrateLayoutChunkWidthVoxels());
|
||||
const float ChunkRadius = (float)CHUNK_SIZE * 0.8660254f + 3.0f;
|
||||
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
|
||||
Cache.FloorProjections.SetNum(Passages.Num());
|
||||
@@ -1104,7 +1104,7 @@ static bool VF_FindWalkableTunnelAir(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(Position.X / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(Position.Y / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(Position.Z / Manager->GetStrateLayoutChunkWidthVoxels()));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord);
|
||||
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
|
||||
const FVoxelPassage* PassageData = Passages.GetData();
|
||||
@@ -1139,7 +1139,7 @@ static bool VF_IsAnyPassageFloorAt(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(Position.X / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(Position.Y / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(Position.Z / Manager->GetStrateLayoutChunkWidthVoxels()));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord);
|
||||
const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
|
||||
const FVoxelPassage* PassageData = Passages.GetData();
|
||||
@@ -1170,6 +1170,17 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
|
||||
UE_LOG(LogTemp, Error, TEXT("[StrateManager] No settings provided!"));
|
||||
return false;
|
||||
}
|
||||
if (!FMath::IsFinite(Settings->StrateLayoutChunkSizeMeters)
|
||||
|| Settings->StrateLayoutChunkSizeMeters < 0.25f
|
||||
|| Settings->StrateLayoutChunkSizeMeters > 64.0f)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("[StrateManager] StrateLayoutChunkSizeMeters must be in [0.25, 64] m."));
|
||||
return false;
|
||||
}
|
||||
StrateLayoutChunkSizeMicrometres = static_cast<uint32>(std::llround(
|
||||
static_cast<double>(Settings->StrateLayoutChunkSizeMeters) * 1000000.0));
|
||||
StrateLayoutChunkWidthVoxels = static_cast<float>(
|
||||
static_cast<double>(StrateLayoutChunkSizeMicrometres) / (10000.0 * VOXEL_SIZE));
|
||||
|
||||
FVoxelSeasonManifest SeasonManifest;
|
||||
bool bUseSeason = false;
|
||||
@@ -1343,10 +1354,10 @@ bool UVoxelStrateManager::Initialize(UVoxelSettings* Settings, int32 WorldSeed)
|
||||
Slot.Definition->FloatingIslandParams = SeasonStrate.Params.FloatingIslandParams;
|
||||
|
||||
Slot.HeightInChunks = SeasonStrate.HeightInChunks;
|
||||
Slot.TopChunkZ = SeasonStrate.TopWorldZ / CHUNK_SIZE - 1;
|
||||
Slot.BottomChunkZ = SeasonStrate.BottomWorldZ / CHUNK_SIZE;
|
||||
if (SeasonStrate.TopWorldZ % CHUNK_SIZE != 0
|
||||
|| SeasonStrate.BottomWorldZ % CHUNK_SIZE != 0
|
||||
Slot.TopChunkZ = SeasonStrate.TopWorldZ / FMath::RoundToInt(StrateLayoutChunkWidthVoxels) - 1;
|
||||
Slot.BottomChunkZ = SeasonStrate.BottomWorldZ / FMath::RoundToInt(StrateLayoutChunkWidthVoxels);
|
||||
if (SeasonStrate.TopWorldZ % FMath::RoundToInt(StrateLayoutChunkWidthVoxels) != 0
|
||||
|| SeasonStrate.BottomWorldZ % FMath::RoundToInt(StrateLayoutChunkWidthVoxels) != 0
|
||||
|| Slot.TopChunkZ != CurrentTopZ
|
||||
|| Slot.BottomChunkZ != CurrentTopZ - (Slot.HeightInChunks - 1))
|
||||
{
|
||||
@@ -1526,8 +1537,8 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
|
||||
}
|
||||
VF_SetComposerRuntimeBounds(
|
||||
Override->Params,
|
||||
(float)(TargetSlot->TopChunkZ + 1) * CHUNK_SIZE,
|
||||
(float)TargetSlot->BottomChunkZ * CHUNK_SIZE);
|
||||
(float)(TargetSlot->TopChunkZ + 1) * StrateLayoutChunkWidthVoxels,
|
||||
(float)TargetSlot->BottomChunkZ * StrateLayoutChunkWidthVoxels);
|
||||
Override->bUseRecipe = bUseRecipe;
|
||||
if (Recipe != nullptr)
|
||||
{
|
||||
@@ -1546,8 +1557,8 @@ bool UVoxelStrateManager::SetComposerOverrideForStrate(
|
||||
Override->bUseRegions = true;
|
||||
Override->Regions = *InRegions;
|
||||
VF_RekeyStrateRegionManifest(Override->Regions, CandidateSeed, StrateIndex);
|
||||
Override->Regions.StrateTopWorldZ = (float)(TargetSlot->TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Override->Regions.StrateBottomWorldZ = (float)TargetSlot->BottomChunkZ * CHUNK_SIZE;
|
||||
Override->Regions.StrateTopWorldZ = (float)(TargetSlot->TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Override->Regions.StrateBottomWorldZ = (float)TargetSlot->BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
VF_SetStrateArchetypeRuntimeBounds(Override->Params,
|
||||
Override->Regions.StrateTopWorldZ,
|
||||
Override->Regions.StrateBottomWorldZ);
|
||||
@@ -1797,11 +1808,11 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
const FStratePassageConfig& Cfg = UpperDef->PassageConfig;
|
||||
|
||||
// Upper strate floor and lower strate ceiling (differ when there's a bedrock gap).
|
||||
const float UpperTopZ = (float)(Upper.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
const float UpperBottomZ = (float)(Upper.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float LowerTopZ = (float)(Lower.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
const float UpperMax = (float)Upper.HeightInChunks * CHUNK_SIZE * 0.9f;
|
||||
const float LowerMax = (float)Lower.HeightInChunks * CHUNK_SIZE * 0.9f;
|
||||
const float UpperTopZ = (float)(Upper.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
const float UpperBottomZ = (float)(Upper.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float LowerTopZ = (float)(Lower.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
const float UpperMax = (float)Upper.HeightInChunks * StrateLayoutChunkWidthVoxels * 0.9f;
|
||||
const float LowerMax = (float)Lower.HeightInChunks * StrateLayoutChunkWidthVoxels * 0.9f;
|
||||
|
||||
const float DistLo = FMath::Min(Cfg.DistanceMin, Cfg.DistanceMax);
|
||||
const float DistHi = FMath::Max(Cfg.DistanceMin, Cfg.DistanceMax);
|
||||
@@ -1931,7 +1942,7 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
LowerDef->FloatingIslandParams,
|
||||
LowerQuerySeed,
|
||||
LowerTopZ,
|
||||
(float)(Lower.BottomChunkZ) * CHUNK_SIZE,
|
||||
(float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels,
|
||||
PX,
|
||||
PY,
|
||||
MaxLateralSnap,
|
||||
@@ -1991,7 +2002,7 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
if (bAimedLowerAtPlayerFitPoint)
|
||||
{
|
||||
const float LowerSealThickness = BoundarySealThicknessFor(*LowerDef);
|
||||
const float LowerBottomZ = (float)(Lower.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float LowerBottomZ = (float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float InnerBottomZ = LowerBottomZ + LowerSealThickness;
|
||||
const float InnerTopZ = LowerTopZ - LowerSealThickness;
|
||||
if (VoxelMath::IsFinite(SuggestedLowerPoint.X)
|
||||
@@ -2159,7 +2170,7 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
Cfg.MouthRadius,
|
||||
LowerDoorDirection,
|
||||
LowerTopZ,
|
||||
(float)(Lower.BottomChunkZ) * CHUNK_SIZE,
|
||||
(float)(Lower.BottomChunkZ) * StrateLayoutChunkWidthVoxels,
|
||||
LowerDef ? BoundarySealThicknessFor(*LowerDef) : 0.0f,
|
||||
bAimedLowerAtPlayerFitPoint);
|
||||
Passage.UpperPoint = Passage.UpperLanding.StandingPoint;
|
||||
@@ -2584,8 +2595,8 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
if (bOpenSurfaceEntry && OriginSpineRadius > 0.0f && StrateLayout.Num() > 0)
|
||||
{
|
||||
const FStrateSlot& Top = StrateLayout[0];
|
||||
const float TopZ = (float)(Top.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
const float BottomZ = (float)Top.BottomChunkZ * CHUNK_SIZE;
|
||||
const float TopZ = (float)(Top.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = (float)Top.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
const float TopSeal = Top.Definition
|
||||
? BoundarySealThicknessFor(*Top.Definition) : 0.0f;
|
||||
const VoxelPassageGeometry::FOriginLandingGeometry OriginLanding =
|
||||
@@ -2608,7 +2619,7 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
// the top seal into the finite landing room; lower strate seals remain untouched.
|
||||
Entry.Radius = FMath::Max(OriginSpineRadius,
|
||||
VoxelPassageGeometry::MetersToVoxels(0.25f));
|
||||
Entry.UpperPoint = FVector(0.0f, 0.0f, TopZ + CHUNK_SIZE);
|
||||
Entry.UpperPoint = FVector(0.0f, 0.0f, TopZ + StrateLayoutChunkWidthVoxels);
|
||||
// The capsule's lower tangent meets the room ceiling; it never bores through the
|
||||
// room's floor or creates an origin column in strate 0.
|
||||
Entry.LowerPoint = FVector(
|
||||
@@ -2672,7 +2683,7 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
|
||||
// The shortlist is rebuilt once for a (manager, version, chunk) and is shared by the tube,
|
||||
// landing, and floor paths. No source-fit stencil or topology search is allowed below it.
|
||||
@@ -2818,7 +2829,7 @@ void UVoxelStrateManager::ApplyPassageModifier(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
|
||||
const FVoxelPassage* PassageData = Passages.GetData();
|
||||
const FVector Position(WorldX, WorldY, WorldZ);
|
||||
@@ -2911,7 +2922,7 @@ void UVoxelStrateManager::ApplyPassageLandingAir(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
|
||||
if (Nearby.Num() == 0) return;
|
||||
|
||||
@@ -3022,7 +3033,7 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
|
||||
const FVoxelPassage* PassageData = Passages.GetData();
|
||||
const FVector Position(WorldX, WorldY, WorldZ);
|
||||
@@ -3118,7 +3129,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
|
||||
if (Nearby.Num() == 0) return;
|
||||
|
||||
@@ -3245,7 +3256,7 @@ void UVoxelStrateManager::ApplyPassageNativeFloorMC(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
|
||||
if (Nearby.Num() == 0
|
||||
|| VoxelPassageGeometry::VerticalShaftConnectorAirMarker())
|
||||
@@ -3436,7 +3447,7 @@ void UVoxelStrateManager::ApplyPassageLandingRoomFloorMC(
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
|
||||
FMath::FloorToInt(WorldZ / StrateLayoutChunkWidthVoxels));
|
||||
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
|
||||
const FVoxelPassage* PassageData = Passages.GetData();
|
||||
const FVector Position(WorldX, WorldY, WorldZ);
|
||||
@@ -5910,8 +5921,8 @@ bool UVoxelStrateManager::AnyLandingFloorAtLattice(
|
||||
for (const FStrateSlot& Slot : StrateLayout)
|
||||
{
|
||||
if (Slot.Definition == nullptr) continue;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
|
||||
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
|
||||
VoxelPassageGeometry::BuildOriginLandingGeometry(
|
||||
@@ -5953,8 +5964,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearBox(
|
||||
for (const FStrateSlot& Slot : StrateLayout)
|
||||
{
|
||||
if (Slot.Definition == nullptr) continue;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
if (VoxelPassageGeometry::OriginLandingRoomTouchesBox(
|
||||
VoxelBox, TopZ, BottomZ,
|
||||
VF_BoundarySealThicknessForDefinition(*Slot.Definition),
|
||||
@@ -5974,8 +5985,8 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearBox(
|
||||
for (const FStrateSlot& Slot : StrateLayout)
|
||||
{
|
||||
if (Slot.Definition == nullptr) continue;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
if (VoxelPassageGeometry::OriginLandingFloorTouchesBox(
|
||||
VoxelBox, TopZ, BottomZ,
|
||||
VF_BoundarySealThicknessForDefinition(*Slot.Definition),
|
||||
@@ -5999,8 +6010,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearLattice(
|
||||
for (const FStrateSlot& Slot : StrateLayout)
|
||||
{
|
||||
if (Slot.Definition == nullptr) continue;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
|
||||
if (VoxelPassageGeometry::OriginLandingRoomTouchesLattice(
|
||||
VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius,
|
||||
@@ -6037,8 +6048,8 @@ bool UVoxelStrateManager::AnyOriginLandingAirNearLattice(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
|
||||
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
|
||||
VoxelPassageGeometry::BuildOriginLandingGeometry(
|
||||
@@ -6114,8 +6125,8 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearLattice(
|
||||
for (const FStrateSlot& Slot : StrateLayout)
|
||||
{
|
||||
if (Slot.Definition == nullptr) continue;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * CHUNK_SIZE;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (static_cast<float>(Slot.TopChunkZ) + 1.0f) * StrateLayoutChunkWidthVoxels;
|
||||
const float BottomZ = static_cast<float>(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition);
|
||||
if (VoxelPassageGeometry::OriginLandingFloorTouchesLattice(
|
||||
VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius))
|
||||
@@ -6149,7 +6160,7 @@ int32 UVoxelStrateManager::FindSlotIndexForChunkZ(int32 ChunkZ) const
|
||||
UVoxelStrateDefinition* UVoxelStrateManager::GetStrateAt(float WorldZ) const
|
||||
{
|
||||
// Convert world Z to chunk Z coordinate
|
||||
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / CHUNK_SIZE);
|
||||
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels);
|
||||
int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
|
||||
if (SlotIdx >= 0)
|
||||
{
|
||||
@@ -6160,7 +6171,7 @@ UVoxelStrateDefinition* UVoxelStrateManager::GetStrateAt(float WorldZ) const
|
||||
|
||||
int32 UVoxelStrateManager::GetStrateIndex(float WorldZ) const
|
||||
{
|
||||
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / CHUNK_SIZE);
|
||||
int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels);
|
||||
int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
|
||||
if (SlotIdx >= 0)
|
||||
{
|
||||
@@ -6319,9 +6330,9 @@ FSlabGenerationParams UVoxelStrateManager::GetSlabParamsForChunk(const FIntVecto
|
||||
|
||||
// Fill in the runtime Z bounds (voxel coordinates, same convention as
|
||||
// FStrateGenerationParams::StrateTopWorldZ / StrateBottomWorldZ).
|
||||
// TopChunkZ+1 because the top chunk's CEILING is at (TopChunkZ+1)*CHUNK_SIZE.
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
// TopChunkZ+1 because the top chunk's CEILING is at (TopChunkZ+1)*StrateLayoutChunkWidthVoxels.
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
|
||||
return Result;
|
||||
}
|
||||
@@ -6354,8 +6365,8 @@ FMazeGenerationParams UVoxelStrateManager::GetMazeParamsForChunk(const FIntVecto
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -6381,8 +6392,8 @@ FSurfaceGenerationParams UVoxelStrateManager::GetSurfaceParamsForChunk(const FIn
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -6408,8 +6419,8 @@ FVerticalShaftParams UVoxelStrateManager::GetVerticalShaftParamsForChunk(const F
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -6435,8 +6446,8 @@ FFloatingIslandParams UVoxelStrateManager::GetFloatingIslandParamsForChunk(const
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -6470,14 +6481,14 @@ FBiomeContext UVoxelStrateManager::GetBiomeContextForChunk(const FIntVector& Chu
|
||||
|
||||
bool UVoxelStrateManager::GetStrateUnrealZRange(float WorldZ, float& OutTopZ, float& OutBottomZ) const
|
||||
{
|
||||
const int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / CHUNK_SIZE);
|
||||
const int32 ChunkZ = FMath::FloorToInt((WorldZ / VOXEL_SIZE) / StrateLayoutChunkWidthVoxels);
|
||||
const int32 SlotIdx = FindSlotIndexForChunkZ(ChunkZ);
|
||||
if (SlotIdx < 0) return false;
|
||||
|
||||
const FStrateSlot& Slot = StrateLayout[SlotIdx];
|
||||
// Voxel-space Z bounds → Unreal units. Ceiling = top chunk's upper edge.
|
||||
OutTopZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE * VOXEL_SIZE;
|
||||
OutBottomZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE * VOXEL_SIZE;
|
||||
OutTopZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels * VOXEL_SIZE;
|
||||
OutBottomZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels * VOXEL_SIZE;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6490,8 +6501,8 @@ FStrateDisturbanceParams UVoxelStrateManager::GetDisturbanceParamsForChunk(const
|
||||
}
|
||||
const FStrateSlot& Slot = StrateLayout[SlotIdx];
|
||||
FStrateDisturbanceParams Result = Slot.Definition->Disturbances;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -6523,8 +6534,8 @@ float UVoxelStrateManager::GetWaterLevelWorldZForChunk(const FIntVector& ChunkCo
|
||||
}
|
||||
if (Rel <= 0.0f) return -FLT_MAX;
|
||||
|
||||
const float BottomZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
const float TopZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
const float BottomZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
const float TopZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
return FMath::Lerp(BottomZ, TopZ, FMath::Clamp(Rel, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
@@ -6568,8 +6579,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
FindComposerOverride(Slot.StrateIndex))
|
||||
{
|
||||
FStrateGenerationParams Result = Override->Params.TunnelNetworkParams;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
// A candidate is a hard replacement of this slot. Do not blend its rolled vector with an
|
||||
// authored neighbour at a boundary; this is also how the offline fixture measures it.
|
||||
return ResolveFinalParams(Result);
|
||||
@@ -6579,8 +6590,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
if (SeasonStrates.IsValidIndex(SlotIdx))
|
||||
{
|
||||
FStrateGenerationParams Result = SeasonStrates[SlotIdx].Params.TunnelNetworkParams;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * CHUNK_SIZE;
|
||||
Result.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
Result.StrateBottomWorldZ = (float)Slot.BottomChunkZ * StrateLayoutChunkWidthVoxels;
|
||||
// Season vectors are already final, measured slot records. Vertical structural posts own
|
||||
// their boundary; blending them with another selected recipe would describe neither one.
|
||||
return ResolveFinalParams(Result);
|
||||
@@ -6595,10 +6606,17 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
// to seal the top and bottom with solid rock. This prevents caves from
|
||||
// carving through strate boundaries.
|
||||
//
|
||||
// TopChunkZ=0, CHUNK_SIZE=32: top of the strate = chunk 0's top edge = voxel Z=32
|
||||
// TopChunkZ=0: top of the strate is one resolved layout chunk above zero.
|
||||
// BottomChunkZ=-3: bottom of the strate = chunk -3's bottom edge = voxel Z=-3*32 = -96
|
||||
BaseParams.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
BaseParams.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * CHUNK_SIZE;
|
||||
BaseParams.StrateTopWorldZ = (float)(Slot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
BaseParams.StrateBottomWorldZ = (float)(Slot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
|
||||
// Core composes one field per strate. For this assembly, Gradient and Interleaved
|
||||
// metadata use the declared Hard fallback across legacy parameter lookups too.
|
||||
if (bHardTransitionFallbackForAssembly)
|
||||
{
|
||||
return ResolveFinalParams(BaseParams);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// BOUNDARY BLENDING (transition-type aware)
|
||||
@@ -6671,8 +6689,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
if (EffectiveBlend > 0 && DistFromBottom < EffectiveBlend)
|
||||
{
|
||||
FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition);
|
||||
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * CHUNK_SIZE;
|
||||
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
|
||||
// Linear alpha: 0 at EffectiveBlend chunks away, 1 at the boundary
|
||||
float Alpha = 1.0f - ((float)DistFromBottom / (float)EffectiveBlend);
|
||||
@@ -6709,8 +6727,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
if (DistFromBottom < CheckRange)
|
||||
{
|
||||
FStrateGenerationParams BelowParams = BuildParamsFromDefinition(BelowSlot.Definition);
|
||||
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * CHUNK_SIZE;
|
||||
BelowParams.StrateTopWorldZ = (float)(BelowSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
BelowParams.StrateBottomWorldZ = (float)(BelowSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
|
||||
// Sample 3D Perlin noise to warp the boundary position.
|
||||
// XY frequency 0.15 gives medium-scale variation (~6-7 chunks per cycle).
|
||||
@@ -6770,8 +6788,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
if (EffectiveBlend > 0 && DistFromTop < EffectiveBlend)
|
||||
{
|
||||
FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition);
|
||||
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * CHUNK_SIZE;
|
||||
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
|
||||
float Alpha = 1.0f - ((float)DistFromTop / (float)EffectiveBlend);
|
||||
Alpha = FMath::Clamp(Alpha, 0.0f, 1.0f);
|
||||
@@ -6792,8 +6810,8 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
||||
if (DistFromTop < CheckRange)
|
||||
{
|
||||
FStrateGenerationParams AboveParams = BuildParamsFromDefinition(AboveSlot.Definition);
|
||||
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * CHUNK_SIZE;
|
||||
AboveParams.StrateTopWorldZ = (float)(AboveSlot.TopChunkZ + 1) * StrateLayoutChunkWidthVoxels;
|
||||
AboveParams.StrateBottomWorldZ = (float)(AboveSlot.BottomChunkZ) * StrateLayoutChunkWidthVoxels;
|
||||
|
||||
// Same noise function but with a different seed offset to avoid
|
||||
// symmetry between top and bottom boundaries of adjacent strates
|
||||
|
||||
@@ -391,6 +391,11 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates", meta = (ClampMin = "0", ClampMax = "16"))
|
||||
int32 InterStrateGapChunks = 0;
|
||||
|
||||
// Physical width of one vertical strate-layout chunk, independent of Core's 32^3 tile.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates|World Assembly",
|
||||
meta = (ClampMin = "0.25", ClampMax = "64.0", Units = "m"))
|
||||
float StrateLayoutChunkSizeMeters = 8.0f;
|
||||
|
||||
//=========================================================================
|
||||
// SPINE & INTER-STRATE CONNECTIONS (the (0,0) descent axis)
|
||||
//=========================================================================
|
||||
|
||||
@@ -234,6 +234,10 @@ public:
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Strate")
|
||||
UVoxelStrateDefinition* GetStrateAt(float WorldZ) const;
|
||||
float GetStrateLayoutChunkWidthVoxels() const { return StrateLayoutChunkWidthVoxels; }
|
||||
uint32 GetStrateLayoutChunkSizeMicrometres() const { return StrateLayoutChunkSizeMicrometres; }
|
||||
void SetHardTransitionFallbackForAssembly(bool bEnabled)
|
||||
{ bHardTransitionFallbackForAssembly = bEnabled; }
|
||||
|
||||
/**
|
||||
* Get the strate index at a world Z coordinate.
|
||||
@@ -492,6 +496,10 @@ protected:
|
||||
// The stacked strate layout (index 0 = topmost strate)
|
||||
UPROPERTY()
|
||||
TArray<FStrateSlot> StrateLayout;
|
||||
// Resolved once from VoxelSettings::StrateLayoutChunkSizeMeters during Initialize().
|
||||
float StrateLayoutChunkWidthVoxels = 32.0f;
|
||||
uint32 StrateLayoutChunkSizeMicrometres = 8000000;
|
||||
bool bHardTransitionFallbackForAssembly = false;
|
||||
|
||||
// Parallel to StrateLayout only while a cooked season is active. Plain immutable data is
|
||||
// copied into worker-local caches on the existing layout-versioned refetch path.
|
||||
|
||||
@@ -208,14 +208,18 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
const FIntVector chunk(0,0,surfaceChunkZ);
|
||||
const FSurfaceGenerationParams surface=owner.Manager->GetSurfaceParamsForChunk(chunk);
|
||||
const FBiomeContext biomeContext=owner.Manager->GetBiomeContextForChunk(chunk);
|
||||
const std::int64_t bottomVoxel=static_cast<std::int64_t>(bottomChunkZ)*CHUNK_SIZE;
|
||||
const std::int64_t heightVoxels=static_cast<std::int64_t>(topChunkZ-bottomChunkZ+1)*CHUNK_SIZE;
|
||||
const double layoutChunkWidthVoxels=static_cast<double>(owner.StrateLayoutChunkWidthSubvoxels)
|
||||
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t bottomVoxel=static_cast<std::int64_t>(std::llround(bottomChunkZ*layoutChunkWidthVoxels));
|
||||
const std::int64_t heightVoxels=static_cast<std::int64_t>(std::llround(
|
||||
(topChunkZ-bottomChunkZ+1)*layoutChunkWidthVoxels));
|
||||
const FBiomeMapParams& map=biomeContext.Map;
|
||||
const UVoxelStrateDefinition* definition=owner.Manager->GetStrateForChunk(chunk);
|
||||
|
||||
VoxelForgeCore::WorldAssemblyManifest manifest;
|
||||
auto& recipe=manifest.recipe;
|
||||
recipe.seed=static_cast<uint32>(owner.Seed);
|
||||
recipe.layoutChunkWidthSubvoxels=owner.StrateLayoutChunkWidthSubvoxels;
|
||||
recipe.biomeCellSizeSubvoxels=WorldAssemblyFixed(map.CellSize);
|
||||
recipe.biomeWarpSubvoxels=WorldAssemblyFixed(map.WarpStrength);
|
||||
recipe.biomeWarpWavelengthSubvoxels=WorldAssemblyWavelength(map.WarpFrequency,556.0);
|
||||
@@ -291,8 +295,9 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
if(recipe.biomes.empty())recipe.biomes.push_back(makeProfile(0x1001u,surface,nullptr));
|
||||
|
||||
VoxelForgeCore::WorldAssemblyStratumPlanV1 stratum;
|
||||
stratum.strateIndex=owner.Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(surfaceChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
|
||||
stratum.strateIndex=owner.Manager->GetStrateIndex(static_cast<float>(
|
||||
(static_cast<double>(surfaceChunkZ)+0.5)
|
||||
*owner.StrateLayoutChunkSizeMicrometres/10000.0));
|
||||
stratum.topChunkZ=topChunkZ;
|
||||
stratum.bottomChunkZ=bottomChunkZ;
|
||||
const double strateBottom=static_cast<double>(bottomVoxel);
|
||||
@@ -370,7 +375,7 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
shelf.minimumSubvoxels={WorldAssemblyFixed(minX),WorldAssemblyFixed(minY),
|
||||
WorldAssemblyFixed(std::max<double>(bottomVoxel,lowestGround-4.0))};
|
||||
shelf.maximumSubvoxels={WorldAssemblyFixed(maxX),WorldAssemblyFixed(maxY),
|
||||
WorldAssemblyFixed(std::min<double>((topChunkZ+1)*CHUNK_SIZE,
|
||||
WorldAssemblyFixed(std::min<double>((topChunkZ+1)*layoutChunkWidthVoxels,
|
||||
highestGround+height+4.0))};
|
||||
shelf.parametersSubvoxels={WorldAssemblyFixed(dirX),WorldAssemblyFixed(dirY),
|
||||
WorldAssemblyFixed(height),WorldAssemblyFixed(reach)};
|
||||
@@ -418,6 +423,20 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
if(!rootObject)
|
||||
{error="owner snapshot root is not a JSON object";return false;}
|
||||
(*rootObject)["world_assembly"]=std::move(manifestJson);
|
||||
if(!manifest.plan.strates.empty()&&!manifest.plan.strates.front().route.empty())
|
||||
{
|
||||
const auto& spawn=manifest.plan.strates.front().route.front();
|
||||
VFJson::Value::Object viewer;VFJson::Value found;found.Data=true;viewer["found_open_space"]=std::move(found);
|
||||
const auto add=[&](const char* key,std::int64_t q)
|
||||
{VFJson::Value coordinate;coordinate.Data=static_cast<double>(q)/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;viewer[key]=std::move(coordinate);};
|
||||
add("x",spawn.positionSubvoxels[0]);add("y",spawn.positionSubvoxels[1]);add("z",spawn.positionSubvoxels[2]);
|
||||
VFJson::Value viewerValue;viewerValue.Data=std::move(viewer);(*rootObject)["viewer_start"]=std::move(viewerValue);
|
||||
owner.ViewerStartVoxels=FVector(
|
||||
static_cast<float>(static_cast<double>(spawn.positionSubvoxels[0])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel),
|
||||
static_cast<float>(static_cast<double>(spawn.positionSubvoxels[1])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel),
|
||||
static_cast<float>(static_cast<double>(spawn.positionSubvoxels[2])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
|
||||
owner.ViewerStartFound=true;
|
||||
}
|
||||
std::printf("[WorldAssemblerPrototype] recipe_version=%u evaluator_version=%u strata=%zu biomes=%zu strate_index=%d chunk_z=%d..%d serialized_bytes=%zu recipe_hash=%016llx plan_hash=%016llx manifest_hash=%016llx source=owner_surface_params_and_biome_map route_points=%zu features=%zu\n",
|
||||
manifest.recipe.schemaVersion,manifest.recipe.evaluatorVersion,manifest.plan.strates.size(),
|
||||
manifest.recipe.biomes.size(),manifest.plan.strates.front().strateIndex,topChunkZ,bottomChunkZ,
|
||||
@@ -486,25 +505,50 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
{error="owner/Core voxel import ratio is invalid for the floating-island recipe";return false;}
|
||||
|
||||
VoxelForgeCore::FloatingIslandRecipeV1 recipe;
|
||||
const FFloatingIslandParams params=owner.Manager->GetFloatingIslandParamsForChunk(
|
||||
FIntVector(0,0,islandSlot->TopChunkZ));
|
||||
recipe.seed=static_cast<std::uint32_t>(owner.Seed);
|
||||
recipe.geometryVersion=4;
|
||||
recipe.ownerVoxelSizeMicrons=owner.OwnerVoxelSizeMicrons;
|
||||
recipe.coreVoxelSizeMicrons=owner.CoreVoxelSizeMicrons;
|
||||
recipe.ownerToCoreScaleNumerator=owner.OwnerVoxelSizeMicrons/static_cast<std::uint32_t>(divisor);
|
||||
recipe.ownerToCoreScaleDenominator=owner.CoreVoxelSizeMicrons/static_cast<std::uint32_t>(divisor);
|
||||
const std::int64_t coreVoxelsPerOwnerChunk=static_cast<std::int64_t>(CHUNK_SIZE)
|
||||
*recipe.ownerToCoreScaleNumerator/recipe.ownerToCoreScaleDenominator;
|
||||
const std::int64_t minimumZ=static_cast<std::int64_t>(islandSlot->BottomChunkZ)
|
||||
*coreVoxelsPerOwnerChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
*owner.StrateLayoutChunkWidthSubvoxels;
|
||||
const std::int64_t maximumZ=static_cast<std::int64_t>(islandSlot->TopChunkZ+1)
|
||||
*coreVoxelsPerOwnerChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
*owner.StrateLayoutChunkWidthSubvoxels;
|
||||
if(maximumZ<=minimumZ)
|
||||
{error="FloatingIslands strate is too short for a bounded Core recipe";return false;}
|
||||
recipe.layoutMinimumZSubvoxels=minimumZ;
|
||||
recipe.layoutMaximumZSubvoxels=maximumZ;
|
||||
recipe.layoutCenterTopZSubvoxels=minimumZ+(maximumZ-minimumZ)/2;
|
||||
const double ownerToCoreScale=static_cast<double>(recipe.ownerToCoreScaleNumerator)
|
||||
/recipe.ownerToCoreScaleDenominator;
|
||||
recipe.islandDensityQ16=WorldAssemblyQ16(std::clamp<double>(params.IslandDensity,0.0,1.0));
|
||||
recipe.spacingSubvoxels=WorldAssemblyFixed(params.IslandSpacing*ownerToCoreScale);
|
||||
recipe.minimumRadiusSubvoxels=WorldAssemblyFixed(params.IslandMinRadius*ownerToCoreScale);
|
||||
recipe.maximumRadiusSubvoxels=WorldAssemblyFixed(params.IslandMaxRadius*ownerToCoreScale);
|
||||
recipe.thicknessRatioQ16=WorldAssemblyQ16(params.ThicknessRatio);
|
||||
recipe.verticalJitterQ16=WorldAssemblyQ16(params.VerticalJitter);
|
||||
recipe.topFlattenQ16=WorldAssemblyQ16(params.TopFlatten);
|
||||
recipe.blendRadiusSubvoxels=WorldAssemblyFixed(params.SDFBlendRadius*ownerToCoreScale);
|
||||
recipe.roughnessSubvoxels=WorldAssemblyFixed(params.SurfaceRoughness*ownerToCoreScale);
|
||||
recipe.boundarySealSubvoxels=WorldAssemblyFixed(params.BoundarySealThickness*ownerToCoreScale);
|
||||
|
||||
auto planned=VoxelForgeCore::PlanFloatingIslandCluster(recipe,error);
|
||||
if(!planned)return false;
|
||||
const double metresPerSubvoxel=static_cast<double>(owner.CoreVoxelSizeMicrons)
|
||||
/(1000000.0*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
const double bedrockTopMetres=(minimumZ+recipe.boundarySealSubvoxels)*metresPerSubvoxel;
|
||||
const double ceilingBottomMetres=(maximumZ-recipe.boundarySealSubvoxels)*metresPerSubvoxel;
|
||||
for(const auto& island:planned->plan.islands)
|
||||
std::printf("[FloatingIslandPlacement] ordinal=%u x_m=%.3f y_m=%.3f bottom_m=%.3f top_m=%.3f bedrock_clearance_m=%.3f ceiling_clearance_m=%.3f\n",
|
||||
island.ordinal,island.centerSubvoxels[0]*metresPerSubvoxel,
|
||||
island.centerSubvoxels[1]*metresPerSubvoxel,
|
||||
island.bottomZSubvoxels*metresPerSubvoxel,
|
||||
island.topZSubvoxels*metresPerSubvoxel,
|
||||
island.bottomZSubvoxels*metresPerSubvoxel-bedrockTopMetres,
|
||||
ceilingBottomMetres-island.topZSubvoxels*metresPerSubvoxel);
|
||||
VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1 islands;
|
||||
islands.strateIndex=islandSlot->StrateIndex;
|
||||
islands.topChunkZ=islandSlot->TopChunkZ;
|
||||
@@ -554,7 +598,7 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
|
||||
inline bool BuildResolvedLayoutSnapshot(const FStandaloneOwnerWorld& owner,
|
||||
const VoxelForgeCore::WorldAssemblyManifest& assembly,std::string_view recipeVariant,
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV1& out,std::string& error)
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV2& out,std::string& error)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
if(!owner.Manager){error="resolved layout builder requires the initialized director";return false;}
|
||||
@@ -562,120 +606,110 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
out={};out.worldIdentity="owner-world/seed/"+std::to_string(owner.Seed);
|
||||
out.ownerVoxelSizeMicrometres=owner.OwnerVoxelSizeMicrons;
|
||||
out.coreVoxelSizeMicrometres=owner.CoreVoxelSizeMicrons;
|
||||
const std::int64_t coreMicrons=std::max<std::uint32_t>(1,owner.CoreVoxelSizeMicrons);
|
||||
const std::int64_t managerChunkMicrons=CHUNK_SIZE*static_cast<std::int64_t>(owner.OwnerVoxelSizeMicrons);
|
||||
out.strateLayoutChunkSizeMicrometres=owner.StrateLayoutChunkSizeMicrometres;
|
||||
out.strateLayoutChunkWidthSubvoxels=owner.StrateLayoutChunkWidthSubvoxels;
|
||||
const auto fixedToMicrons=[&](std::int64_t fixed)
|
||||
{return static_cast<std::int64_t>(std::llround(static_cast<double>(fixed)
|
||||
*static_cast<double>(coreMicrons)/(WorldAssemblySubvoxelsPerVoxel)));};
|
||||
*owner.CoreVoxelSizeMicrons/WorldAssemblySubvoxelsPerVoxel));};
|
||||
const auto sealAndDensity=[](const UVoxelStrateDefinition& d,ECaveGeneratorType type)
|
||||
{
|
||||
std::array<float,2> result{d.GenerationParams.BoundarySealThickness,d.GenerationParams.BaseDensity};
|
||||
switch(type)
|
||||
{
|
||||
case ECaveGeneratorType::SurfaceWorld:result={d.SurfaceParams.BoundarySealThickness,d.SurfaceParams.BaseDensity};break;
|
||||
case ECaveGeneratorType::FloatingIslands:result={d.FloatingIslandParams.BoundarySealThickness,d.FloatingIslandParams.BaseDensity};break;
|
||||
case ECaveGeneratorType::Maze:result={d.MazeParams.BoundarySealThickness,d.MazeParams.BaseDensity};break;
|
||||
case ECaveGeneratorType::VerticalShafts:result={d.VerticalShaftParams.BoundarySealThickness,d.VerticalShaftParams.BaseDensity};break;
|
||||
default:break;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
for(const FStrateSlot& slot:owner.Manager->GetLayout())
|
||||
{
|
||||
if(!slot.Definition)continue;
|
||||
const auto type=owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ));
|
||||
ResolvedLayoutStratumV1 s;s.strateIndex=slot.StrateIndex;s.generatorKind=GeneratorKind(type);
|
||||
s.stableId=out.worldIdentity+"/strate/"+std::to_string(slot.StrateIndex)
|
||||
+"/generator/"+GeneratorKind(type);
|
||||
const auto authored=sealAndDensity(*slot.Definition,type);
|
||||
const std::int64_t bottom=static_cast<std::int64_t>(slot.BottomChunkZ)*owner.StrateLayoutChunkSizeMicrometres;
|
||||
const std::int64_t top=static_cast<std::int64_t>(slot.TopChunkZ+1)*owner.StrateLayoutChunkSizeMicrometres;
|
||||
const std::int64_t seal=static_cast<std::int64_t>(std::llround(
|
||||
std::max(0.0f,authored[0])*owner.OwnerVoxelSizeMicrons));
|
||||
if(seal<=0||2*seal>=top-bottom)
|
||||
{error="strate "+std::to_string(slot.StrateIndex)+" has an invalid BoundarySealThickness for two boundary bands";return false;}
|
||||
ResolvedLayoutStratumV2 s;s.strateIndex=slot.StrateIndex;s.generatorKind=GeneratorKind(type);
|
||||
s.stableId=out.worldIdentity+"/strate/"+std::to_string(slot.StrateIndex)+"/generator/"+GeneratorKind(type);
|
||||
s.recipeId=type==ECaveGeneratorType::SurfaceWorld?"SurfaceWorld.Heightfield2D"
|
||||
:type==ECaveGeneratorType::FloatingIslands?"FloatingIslands.ClusterField":"legacy."+std::string(GeneratorKind(type));
|
||||
s.recipeVersion=type==ECaveGeneratorType::SurfaceWorld?assembly.recipe.schemaVersion
|
||||
:type==ECaveGeneratorType::FloatingIslands&&!assembly.floatingIslands.empty()
|
||||
?assembly.floatingIslands.front().manifest.recipe.geometryVersion:0;
|
||||
s.voxelSizeMicrometres=owner.CoreVoxelSizeMicrons;
|
||||
s.latticeOriginMicrometres={0,0,0};
|
||||
std::int64_t bottom=static_cast<std::int64_t>(slot.BottomChunkZ)*CHUNK_SIZE*coreMicrons;
|
||||
std::int64_t top=static_cast<std::int64_t>(slot.TopChunkZ+1)*CHUNK_SIZE*coreMicrons;
|
||||
s.voxelSizeMicrometres=owner.CoreVoxelSizeMicrons;s.latticeOriginMicrometres={0,0,0};
|
||||
if(type==ECaveGeneratorType::FloatingIslands)
|
||||
{
|
||||
const auto it=std::find_if(assembly.floatingIslands.begin(),assembly.floatingIslands.end(),
|
||||
[&](const auto& p){return p.strateIndex==slot.StrateIndex;});
|
||||
if(it!=assembly.floatingIslands.end())
|
||||
{bottom=fixedToMicrons(it->manifest.recipe.layoutMinimumZSubvoxels);
|
||||
top=fixedToMicrons(it->manifest.recipe.layoutMaximumZSubvoxels);}
|
||||
}
|
||||
s.interiorBottomMicrometres=bottom;s.interiorTopMicrometres=top;
|
||||
s.content={"content",bottom,top,true,true,false,"current generator interior","none","none"};
|
||||
s.transitionMode=TransitionKind(slot.Definition->TransitionType);
|
||||
s.transitionBlendChunks=slot.Definition->TransitionBlendChunks;
|
||||
s.transitionWidthMicrometres=static_cast<std::int64_t>(s.transitionBlendChunks)*managerChunkMicrons;
|
||||
s.transitionWarpAmplitudeMicrometres=s.transitionMode=="Interleaved"
|
||||
?2*managerChunkMicrons:0;
|
||||
s.transitionInterpenetrationMicrometres=0;
|
||||
s.transitionHonouredInCore=false;
|
||||
s.transitionDispatchPath="upper strate owns lower interface; UVoxelStrateManager::GetGenerationParams can blend legacy vectors, but Core's compiled field selects per-strate/native recipe fields without transition blending";
|
||||
s.managerLookup="UVoxelStrateManager::GetGeneratorTypeForChunk; upper strate TransitionType owns lower interface";
|
||||
s.managerChunkWidthMicrometres=managerChunkMicrons;
|
||||
s.dispatchChunkWidthMicrometres=type==ECaveGeneratorType::FloatingIslands
|
||||
?static_cast<std::int64_t>(CHUNK_SIZE)*owner.OwnerVoxelSizeMicrons
|
||||
:static_cast<std::int64_t>(CHUNK_SIZE)*coreMicrons;
|
||||
if(type==ECaveGeneratorType::SurfaceWorld)
|
||||
{s.boundDispatchPath="BuildSurfaceStratumPlan: (chunkZ*CHUNK_SIZE) Core voxels; 32 Core voxels per chunk";
|
||||
s.queryDispatchPath="FStandaloneShapeWorld::NativeSurfaceDensityAt: floor(Z/CHUNK_SIZE), manager lookup; 32 Core voxels per chunk";}
|
||||
else if(type==ECaveGeneratorType::FloatingIslands)
|
||||
{s.boundDispatchPath="BuildFloatingIslandStratumPlan: owner chunk * 32 * owner/Core voxel ratio";
|
||||
s.queryDispatchPath="FStandaloneShapeWorld::NativeFloatingIslandDensityAt: floor(Z/(32*owner/Core ratio)), manager lookup";}
|
||||
else
|
||||
{s.boundDispatchPath="legacy strata slot bounds scaled by Core chunk width";
|
||||
s.queryDispatchPath="legacy Core field; manager lookup uses floor(Z/CHUNK_SIZE)";}
|
||||
const std::int64_t legacySealMicrons=static_cast<std::int64_t>(std::llround(
|
||||
std::max(0.0f,slot.Definition->GenerationParams.BoundarySealThickness)
|
||||
*owner.OwnerVoxelSizeMicrons));
|
||||
if(type==ECaveGeneratorType::FloatingIslands)
|
||||
{
|
||||
const auto it=std::find_if(assembly.floatingIslands.begin(),assembly.floatingIslands.end(),
|
||||
[&](const auto& p){return p.strateIndex==slot.StrateIndex;});
|
||||
const std::int64_t seal=it==assembly.floatingIslands.end()?0:fixedToMicrons(
|
||||
it->manifest.recipe.boundarySealSubvoxels);
|
||||
s.ceiling={"ceiling",top-seal,top,seal>0,true,false,
|
||||
"v3 shared upper boundary density seal; one sided; inset from upper edge","upper/interior side","VF_ApplyUpperBoundarySeal"};
|
||||
s.bedrock={"bedrock",bottom,bottom,false,true,false,
|
||||
"no lower boundary seal in geometryVersion 3","none","none"};
|
||||
if(it!=assembly.floatingIslands.end())
|
||||
{
|
||||
const auto recipeBottom=fixedToMicrons(it->manifest.recipe.layoutMinimumZSubvoxels);
|
||||
const auto recipeTop=fixedToMicrons(it->manifest.recipe.layoutMaximumZSubvoxels);
|
||||
if(recipeBottom!=bottom||recipeTop!=top)
|
||||
{error="FloatingIslands recipe bounds differ from the resolved 8 m layout slot";return false;}
|
||||
s.recipeVersion=it->manifest.recipe.geometryVersion;
|
||||
for(std::size_t n=0;n<it->manifest.plan.islands.size();++n)
|
||||
{const auto& a=it->manifest.plan.islands[n];ResolvedLayoutAnchorV1 anchor;
|
||||
{
|
||||
const auto& a=it->manifest.plan.islands[n];ResolvedLayoutAnchorV2 anchor;
|
||||
anchor.kind="island_anchor";anchor.stableId="island/"+std::to_string(n);anchor.units="1/256 Core voxel";
|
||||
anchor.values={a.centerSubvoxels[0],a.centerSubvoxels[1],a.centerSubvoxels[2],a.radiusSubvoxels,a.topHalfSubvoxels};
|
||||
s.anchors.push_back(std::move(anchor));}
|
||||
s.anchors.push_back(std::move(anchor));
|
||||
}
|
||||
}
|
||||
else if(type==ECaveGeneratorType::SurfaceWorld)
|
||||
}
|
||||
s.interiorBottomMicrometres=bottom;s.interiorTopMicrometres=top;
|
||||
s.transitionMode=TransitionKind(slot.Definition->TransitionType);
|
||||
s.transitionBlendChunks=slot.Definition->TransitionBlendChunks;
|
||||
s.transitionWidthMicrometres=static_cast<std::int64_t>(s.transitionBlendChunks)*owner.StrateLayoutChunkSizeMicrometres;
|
||||
s.transitionWarpAmplitudeMicrometres=s.transitionMode=="Interleaved"
|
||||
?2*static_cast<std::int64_t>(owner.StrateLayoutChunkSizeMicrometres):0;
|
||||
s.transitionInterpenetrationMicrometres=0;
|
||||
s.transitionHonouredInCore=s.transitionMode=="Hard";
|
||||
s.transitionFallsBackToHard=s.transitionMode!="Hard";
|
||||
s.transitionDispatchPath=s.transitionFallsBackToHard
|
||||
?"declared Gradient/Interleaved recorded; Core uses explicit Hard fallback with zero interpenetration"
|
||||
:"declared Hard transition applied at the strate interface";
|
||||
s.managerLookup="UVoxelStrateManager selects the slot from the director-resolved layout chunk width";
|
||||
s.managerChunkWidthMicrometres=owner.StrateLayoutChunkSizeMicrometres;
|
||||
s.dispatchChunkWidthMicrometres=owner.StrateLayoutChunkSizeMicrometres;
|
||||
s.boundDispatchPath="all generator plans use ResolvedLayoutSnapshotV2 layoutChunkWidthSubvoxels";
|
||||
s.queryDispatchPath="all generator dispatch uses floor(CoreZ / ResolvedLayoutSnapshotV2 layoutChunkWidthSubvoxels)";
|
||||
const std::int64_t solidDensity=std::max<std::int64_t>(1,WorldAssemblyQ16(std::max(0.05f,authored[1])));
|
||||
s.ceiling={"ceiling",top-seal,top,true,true,false,"solid ceiling from strate BoundarySealThickness; breakable by default",
|
||||
"interior-facing lower face","shared resolved-layout density band",solidDensity};
|
||||
s.content={"content",bottom+seal,top-seal,true,true,false,"resolved generator content","none","none",solidDensity};
|
||||
s.bedrock={"bedrock",bottom,bottom+seal,true,true,false,"solid floor from strate BoundarySealThickness; breakable by default",
|
||||
"interior-facing upper face","shared resolved-layout density band",solidDensity};
|
||||
if(type==ECaveGeneratorType::SurfaceWorld)
|
||||
{
|
||||
s.ceiling={"ceiling",top,top,false,true,false,
|
||||
"no separate volumetric ceiling band; cap profile is internal to content","none",
|
||||
"Heightfield2D cap profile + AppendStratumBoundaryCeilings fallback mesh"};
|
||||
s.bedrock={"bedrock",bottom,bottom,false,true,false,"no lower bedrock band","none","none"};
|
||||
for(const auto& p:assembly.recipe.biomes)
|
||||
if(p.hasCapProfile)
|
||||
{ResolvedLayoutAnchorV1 a;a.kind="cap_profile";a.stableId="biome/"+std::to_string(p.stableId);
|
||||
{ResolvedLayoutAnchorV2 a;a.kind="cap_profile";a.stableId="biome/"+std::to_string(p.stableId);
|
||||
a.units="1/256 Core voxel";a.values={p.capHeightSubvoxels,p.capUndulationSubvoxels,
|
||||
p.capWavelengthSubvoxels,p.capDetailAmplitudeSubvoxels,p.capRidgeAmplitudeSubvoxels};s.anchors.push_back(std::move(a));}
|
||||
const auto plan=std::find_if(assembly.plan.strates.begin(),assembly.plan.strates.end(),
|
||||
[&](const auto& p){return p.strateIndex==slot.StrateIndex;});
|
||||
if(plan!=assembly.plan.strates.end())
|
||||
{
|
||||
for(std::size_t n=0;n<plan->route.size();++n)
|
||||
{const auto& p=plan->route[n];ResolvedLayoutAnchorV1 a;a.kind="route_point";a.stableId="route/"+std::to_string(n);
|
||||
{const auto& p=plan->route[n];ResolvedLayoutAnchorV2 a;a.kind="route_point";a.stableId="route/"+std::to_string(n);
|
||||
a.units="1/256 Core voxel";a.values={p.positionSubvoxels[0],p.positionSubvoxels[1],p.positionSubvoxels[2],p.radiusSubvoxels};
|
||||
s.anchors.push_back(std::move(a));}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
s.ceiling={"ceiling",top,top+legacySealMicrons,false,true,false,
|
||||
"manager configures a two-sided legacy seal, but Core's compiled analytic field does not apply this density band",
|
||||
"configured upper side","legacy VF_ApplyBoundarySeal; absent from current Core density path"};
|
||||
s.bedrock={"bedrock",bottom-legacySealMicrons,bottom,false,true,false,
|
||||
"manager configures a two-sided legacy seal, but Core's compiled analytic field does not apply this density band; breakable by default",
|
||||
"configured lower side","legacy VF_ApplyBoundarySeal; absent from current Core density path"};
|
||||
}
|
||||
for(std::size_t n=0;n<owner.Manager->GetPassages().Num();++n)
|
||||
{
|
||||
const auto& passage=owner.Manager->GetPassages()[static_cast<int32>(n)];
|
||||
if(passage.UpperStrateIndex!=slot.StrateIndex&&passage.LowerStrateIndex!=slot.StrateIndex)continue;
|
||||
ResolvedLayoutAnchorV1 a;a.kind="passage";a.stableId="passage/"+std::to_string(n);
|
||||
a.units="milli owner voxels";a.values={std::llround(passage.UpperPoint.X*1000.0),
|
||||
std::llround(passage.UpperPoint.Y*1000.0),std::llround(passage.UpperPoint.Z*1000.0),
|
||||
std::llround(passage.LowerPoint.X*1000.0),std::llround(passage.LowerPoint.Y*1000.0),
|
||||
std::llround(passage.LowerPoint.Z*1000.0)};s.anchors.push_back(std::move(a));
|
||||
ResolvedLayoutAnchorV2 a;a.kind="passage";a.stableId="passage/"+std::to_string(n);a.units="milli owner voxels";
|
||||
a.values={std::llround(passage.UpperPoint.X*1000.0),std::llround(passage.UpperPoint.Y*1000.0),
|
||||
std::llround(passage.UpperPoint.Z*1000.0),std::llround(passage.LowerPoint.X*1000.0),
|
||||
std::llround(passage.LowerPoint.Y*1000.0),std::llround(passage.LowerPoint.Z*1000.0)};
|
||||
s.anchors.push_back(std::move(a));
|
||||
}
|
||||
out.strates.push_back(std::move(s));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user