wip: cave planner squash-merged into wip/combined (L173, RED - not for main)
Five conflicts resolved (CURRENT-STATE, ResolvedLayoutInternal.h, StreamBenchmarkHeadless.cpp, StreamBenchmarkPipeline.h, WorldAssemblyPrototype.h) keeping liminal + cave planner; L156 band fit only; evaluator v239 with v239 baselines (18/18 Clang/GCC W1/W8/W12). Fast tier FAILS (FI upper arrival: 40 unsupported chamber samples, 10 chamber-floor shell violations); TN and VS composed sweeps 0/16 (passage, seam, shell gates). Living plane loads at PostConfigInit (UE smoke passes). Source/: VoxelForgeCoreAdapter.cpp; VoxelForge.uplugin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
82eb5b1c66
commit
b09728354d
243 files changed
+23019
-307
No files matched your search
@@ -74,6 +74,7 @@ namespace
|
||||
bool DensityDispatchReport=false;
|
||||
bool CavesRecipe=false;
|
||||
bool LatticeRecipe=false;
|
||||
bool LegacyTunnelNetwork=false;
|
||||
int LatticeStrateIndex=-1;
|
||||
bool AuditHeightfieldProfile=false;
|
||||
bool LandmarkChamberDemo=false;
|
||||
@@ -1436,6 +1437,7 @@ namespace
|
||||
" --core-snapshot=FILE (benchmark the exact resolved Core snapshot from Unreal)\n"
|
||||
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n"
|
||||
" --seed=N --recipe=surface-world|floating-islands|caves|lattice\n"
|
||||
" --legacy-tunnel-network (keep TN on its compiled-analytic comparison path)\n"
|
||||
" --native-owner-fi (build the existing owner FI plan; leave other strates intact)\n"
|
||||
" --density-dispatch-report (print bound path per strate and tile hashes)\n"
|
||||
" --lattice-strate=N (opt one Maze or VerticalShafts strate into Core.Lattice)\n"
|
||||
@@ -1505,6 +1507,7 @@ namespace
|
||||
else if(arg=="--native-owner-fi")options.NativeOwnerFloatingIslandsRecipe=true;
|
||||
else if(arg=="--density-dispatch-report")
|
||||
{options.DensityDispatchReport=true;options.StreamHash=true;options.StreamHashTiles=true;}
|
||||
else if(arg=="--legacy-tunnel-network")options.LegacyTunnelNetwork=true;
|
||||
else if(arg=="--audit-landmark-reachability")options.AuditLandmarkReachability=true;
|
||||
else if(arg=="--audit-native-caves")options.AuditNativeCaves=true;
|
||||
else if(arg=="--audit-native-lattice")options.AuditNativeLattice=true;
|
||||
@@ -1745,7 +1748,8 @@ int main(int argc,char** argv)
|
||||
options.DataPath.string().c_str(),options.WorkerCount,options.ClipRadius,options.MaxClipLevel,
|
||||
options.MaxLevel,options.StreamHash?"on":"off",
|
||||
options.StreamHashAllowUniformSkips?"on":"off",options.ProfileColumnOverlap?"on":"off",
|
||||
options.ForceGenerate?"on":"off",options.CavesRecipe?"caves"
|
||||
options.ForceGenerate?"on":"off",options.LegacyTunnelNetwork?"legacy-tunnel-network"
|
||||
:options.CavesRecipe?"caves"
|
||||
:options.LatticeRecipe?"lattice"
|
||||
:options.FloatingIslandsRecipe?"floating-islands":"surface-world",
|
||||
options.LatticeStrateIndex,
|
||||
@@ -1850,7 +1854,9 @@ int main(int argc,char** argv)
|
||||
const VFJson::Value& coreLiminalConfig=ownerJsonRoot.Get("core_liminal_field");
|
||||
const bool hasCoreLiminalField=!std::holds_alternative<std::nullptr_t>(
|
||||
coreLiminalConfig.Data);
|
||||
const std::string variant=options.LatticeRecipe
|
||||
const std::string variant=options.LegacyTunnelNetwork
|
||||
?"legacy-tunnel-network"
|
||||
:options.LatticeRecipe
|
||||
?"lattice/"+std::to_string(options.LatticeStrateIndex)
|
||||
:options.CavesRecipe?"caves"
|
||||
:(options.FloatingIslandsRecipe||options.NativeOwnerFloatingIslandsRecipe)
|
||||
@@ -1864,6 +1870,11 @@ int main(int argc,char** argv)
|
||||
!hasCoreLiminalField);
|
||||
if(!layoutResolved)
|
||||
{std::fprintf(stderr,"Resolved-layout snapshot failed: %s\n",error.c_str());return 2;}
|
||||
if(useExportedCorePlan&&!options.NativeOwnerFloatingIslandsRecipe
|
||||
&&!options.LegacyTunnelNetwork&&!options.CavesRecipe
|
||||
&&!StandaloneWorldAssemblyPrototype::PlanOwnerTunnelNetworkCaves(
|
||||
*ownerWorld,resolvedLayout,error))
|
||||
{std::fprintf(stderr,"Imported owner Tunnel Network cave planning failed: %s\n",error.c_str());return 2;}
|
||||
if(useExportedCorePlan&&!options.NativeOwnerFloatingIslandsRecipe
|
||||
&&!StandaloneWorldAssemblyPrototype::ValidateExportedCorePlanMatchesOwner(
|
||||
*ownerWorld,worldAssembly,resolvedLayout,error))
|
||||
@@ -2218,8 +2229,11 @@ int main(int argc,char** argv)
|
||||
{
|
||||
std::vector<VoxelForgeCore::ResolvedCavernRoomV1> rooms;
|
||||
std::vector<VoxelForgeCore::ResolvedCavernEdgeV1> edges;
|
||||
if(!MakeNativeCavesSpine(resolvedLayout,rooms,edges,error)
|
||||
||!VoxelForgeCore::ResolveResolvedCavernGraphV1(
|
||||
if(!MakeNativeCavesSpine(resolvedLayout,rooms,edges,error))
|
||||
{std::fprintf(stderr,"Native caves spine resolution failed: %s\n",error.c_str());return 2;}
|
||||
rooms.insert(rooms.end(),resolvedLayout.cavernRooms.begin(),resolvedLayout.cavernRooms.end());
|
||||
edges.insert(edges.end(),resolvedLayout.cavernEdges.begin(),resolvedLayout.cavernEdges.end());
|
||||
if(!VoxelForgeCore::ResolveResolvedCavernGraphV1(
|
||||
resolvedLayout,seed,rooms,edges,error))
|
||||
{std::fprintf(stderr,"Native caves spine resolution failed: %s\n",error.c_str());return 2;}
|
||||
}
|
||||
@@ -2230,8 +2244,11 @@ int main(int argc,char** argv)
|
||||
std::vector<VoxelForgeCore::ResolvedCavernRoomV1> rooms;
|
||||
std::vector<VoxelForgeCore::ResolvedCavernEdgeV1> edges;
|
||||
if(!MakeNativeLatticeSpine(resolvedLayout,seed,options.LatticeStrateIndex,
|
||||
options.LatticeOrphanControl,rooms,edges,error)
|
||||
||!VoxelForgeCore::ResolveResolvedCavernGraphV1(
|
||||
options.LatticeOrphanControl,rooms,edges,error))
|
||||
{std::fprintf(stderr,"Native lattice graph resolution failed: %s\n",error.c_str());return 2;}
|
||||
rooms.insert(rooms.end(),resolvedLayout.cavernRooms.begin(),resolvedLayout.cavernRooms.end());
|
||||
edges.insert(edges.end(),resolvedLayout.cavernEdges.begin(),resolvedLayout.cavernEdges.end());
|
||||
if(!VoxelForgeCore::ResolveResolvedCavernGraphV1(
|
||||
resolvedLayout,seed,rooms,edges,error))
|
||||
{std::fprintf(stderr,"Native lattice graph resolution failed: %s\n",error.c_str());return 2;}
|
||||
}
|
||||
@@ -2352,12 +2369,16 @@ int main(int argc,char** argv)
|
||||
const bool hasNativeLatticeRecipe=std::any_of(resolvedLayout.strates.begin(),resolvedLayout.strates.end(),
|
||||
[](const auto& stratum){return VoxelForgeCore::IsNativeLatticeFieldStratum(stratum)
|
||||
&&stratum.passageCapabilities.hasNativeLatticeField;});
|
||||
const auto actualRecipe=hasNativeLatticeRecipe?std::string_view("lattice")
|
||||
:hasNativeCavesRecipe?std::string_view("caves")
|
||||
const auto actualRecipe=requestedRecipe=="lattice"&&hasNativeLatticeRecipe
|
||||
?std::string_view("lattice")
|
||||
:requestedRecipe=="caves"&&hasNativeCavesRecipe
|
||||
?std::string_view("caves")
|
||||
:requestedRecipe=="surface-world"&&hasSurfaceWorldPlan
|
||||
?std::string_view("surface-world")
|
||||
:requestedRecipe=="floating-islands"&&hasFloatingIslands
|
||||
?std::string_view("floating-islands")
|
||||
:hasNativeLatticeRecipe?std::string_view("lattice")
|
||||
:hasNativeCavesRecipe?std::string_view("caves")
|
||||
:hasFloatingIslands?std::string_view("floating-islands")
|
||||
:hasSurfaceWorldPlan?std::string_view("surface-world")
|
||||
:std::string_view("missing");
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1580,10 +1580,13 @@ private:
|
||||
static_cast<unsigned long long>(D.islandSharedCacheConflictReplacements),
|
||||
static_cast<double>(D.islandSharedCacheLockWaitNanoseconds)/1000.0);
|
||||
if(D.passageComposerProfiled)
|
||||
std::printf("[PassageComposerTile] key=(%d,%d,%d,lod%d) worker_ms=%.3f samples=%llu native_field_calls=%llu features=%llu route_candidates=%llu boundary_band_queries=%llu queries=%llu passage_iterations=%llu carve_segment_visits=%llu relief_field_calls=%llu sweep_segments=%llu sweep_pairs=%llu sweep_prepare_ms=%.3f sweep_outside_pairs=%llu sweep_shell_support_skips=%llu sweep_min_updates=%llu sweep_base_samples=%llu sweep_detail_samples=%llu sweep_grid_samples=%llu sweep_no_influence_samples=%llu sweep_lookups=%llu sweep_cache_hits=%llu sweep_base_hits=%llu sweep_outside_hits=%llu sweep_missing=%llu\n",
|
||||
std::printf("[PassageComposerTile] key=(%d,%d,%d,lod%d) worker_ms=%.3f samples=%llu field_calls=%llu native_cave_samples=%llu room_candidates=%llu edge_candidates=%llu features=%llu route_candidates=%llu boundary_band_queries=%llu queries=%llu passage_iterations=%llu carve_segment_visits=%llu relief_field_calls=%llu sweep_segments=%llu sweep_pairs=%llu sweep_prepare_ms=%.3f sweep_outside_pairs=%llu sweep_shell_support_skips=%llu sweep_min_updates=%llu sweep_base_samples=%llu sweep_detail_samples=%llu sweep_grid_samples=%llu sweep_no_influence_samples=%llu sweep_lookups=%llu sweep_cache_hits=%llu sweep_base_hits=%llu sweep_outside_hits=%llu sweep_missing=%llu\n",
|
||||
Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
|
||||
Event.WorkerMilliseconds,static_cast<unsigned long long>(Event.DensitySamples),
|
||||
static_cast<unsigned long long>(D.fieldCalls),
|
||||
static_cast<unsigned long long>(D.nativeCaveFieldSamples),
|
||||
static_cast<unsigned long long>(D.cavernRoomCandidates),
|
||||
static_cast<unsigned long long>(D.cavernEdgeCandidates),
|
||||
static_cast<unsigned long long>(D.passageTileFeatureCount),
|
||||
static_cast<unsigned long long>(D.passageTileRouteSegmentCandidates),
|
||||
static_cast<unsigned long long>(D.resolvedBoundaryBandQueries),
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -283,6 +285,330 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
}
|
||||
}
|
||||
}
|
||||
std::uint64_t terrainPathCount=0,terrainGridSamples=0;
|
||||
std::int32_t terrainGridStepQ=0;
|
||||
const auto appendTerrainFallback=[&](std::int32_t stepQ)
|
||||
{
|
||||
if(!fallbackSearch
|
||||
||!((arrivalRadiusSubvoxels>0)!=(departureRadiusSubvoxels>0)))
|
||||
return;
|
||||
{
|
||||
// A bounded one-mouth world can have no walkable straight chord even
|
||||
// when a contour route exists. Link real ground columns only when their
|
||||
// grade meets the same limit, then use the ordinary capsule and support
|
||||
// sweep to accept or reject each resulting path.
|
||||
struct FGroundNode
|
||||
{
|
||||
std::int64_t ground=0;
|
||||
bool supportReachable=false;
|
||||
};
|
||||
struct FQueueEntry
|
||||
{
|
||||
std::uint64_t cost=0;
|
||||
std::size_t index=0;
|
||||
bool operator>(const FQueueEntry& other) const
|
||||
{return cost>other.cost||(cost==other.cost&&index>other.index);}
|
||||
};
|
||||
constexpr std::array<std::array<std::int64_t,2>,5> supportOffsets{{
|
||||
{{0,0}},{{ProbeRadiusQ,0}},{{-ProbeRadiusQ,0}},
|
||||
{{0,ProbeRadiusQ}},{{0,-ProbeRadiusQ}}}};
|
||||
constexpr std::int64_t SupportStepQ=WorldAssemblySubvoxelsPerVoxel/4;
|
||||
const std::int64_t routeBaseLiftQ=constraints.minimumClearanceSubvoxels/4;
|
||||
const auto hasRouteSupportAt=[&](std::int64_t centerX,std::int64_t centerY,
|
||||
std::int64_t footZ)
|
||||
{
|
||||
std::uint32_t reachable=0;
|
||||
for(const auto& offset:supportOffsets)
|
||||
{
|
||||
for(std::int64_t depth=SupportStepQ;depth<=SupportDepthQ;
|
||||
depth+=SupportStepQ)
|
||||
{
|
||||
if(finalField.point(centerX+offset[0],centerY+offset[1],
|
||||
footZ-depth).densitySubvoxels<0)
|
||||
{++reachable;break;}
|
||||
}
|
||||
}
|
||||
return reachable>=constraints.minimumSupportProbes;
|
||||
};
|
||||
const auto tryTerrainPaths=[&](std::int32_t terrainStepQ)
|
||||
{
|
||||
terrainPathCount=0;terrainGridSamples=0;terrainGridStepQ=0;
|
||||
const std::int64_t boundedRadius=std::max(arrivalRadiusSubvoxels,
|
||||
departureRadiusSubvoxels);
|
||||
const std::int32_t boundRadiusVoxels=static_cast<std::int32_t>(
|
||||
(boundedRadius+WorldAssemblySubvoxelsPerVoxel-1)
|
||||
/WorldAssemblySubvoxelsPerVoxel);
|
||||
const std::int32_t searchRadiusVoxels=std::clamp(
|
||||
boundRadiusVoxels+64,64,256);
|
||||
const std::int64_t searchRadiusQ=static_cast<std::int64_t>(
|
||||
searchRadiusVoxels)*WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int32_t radiusSteps=static_cast<std::int32_t>(
|
||||
searchRadiusQ/terrainStepQ);
|
||||
const std::int32_t dimension=radiusSteps*2+1;
|
||||
const std::size_t nodeCount=static_cast<std::size_t>(dimension)*dimension;
|
||||
const auto indexOf=[dimension](std::int32_t x,std::int32_t y)
|
||||
{return static_cast<std::size_t>(y)*dimension+x;};
|
||||
std::vector<FGroundNode> grid(nodeCount);
|
||||
const auto safeGroundAt=[&](std::int64_t centerX,std::int64_t centerY)
|
||||
{
|
||||
constexpr std::int64_t FootprintStepQ=WorldAssemblySubvoxelsPerVoxel/2;
|
||||
std::int64_t highestGround=std::numeric_limits<std::int64_t>::min();
|
||||
for(std::int64_t oy=-PlayerRadiusQ;oy<=PlayerRadiusQ;
|
||||
oy+=FootprintStepQ)
|
||||
for(std::int64_t ox=-PlayerRadiusQ;ox<=PlayerRadiusQ;
|
||||
ox+=FootprintStepQ)
|
||||
{
|
||||
if(ox*ox+oy*oy>PlayerRadiusQ*PlayerRadiusQ)continue;
|
||||
highestGround=std::max(highestGround,finalField.point(
|
||||
centerX+ox,centerY+oy,0).groundHeightSubvoxels);
|
||||
}
|
||||
return FGroundNode{highestGround,
|
||||
hasRouteSupportAt(centerX,centerY,
|
||||
highestGround+routeBaseLiftQ)};
|
||||
};
|
||||
for(std::int32_t gy=0;gy<dimension;++gy)
|
||||
for(std::int32_t gx=0;gx<dimension;++gx)
|
||||
{
|
||||
const std::int64_t xQ=static_cast<std::int64_t>(gx-radiusSteps)
|
||||
*terrainStepQ;
|
||||
const std::int64_t yQ=static_cast<std::int64_t>(gy-radiusSteps)
|
||||
*terrainStepQ;
|
||||
grid[indexOf(gx,gy)]=safeGroundAt(xQ,yQ);
|
||||
++terrainGridSamples;
|
||||
}
|
||||
for(std::size_t directionIndex=0;directionIndex<1;++directionIndex)
|
||||
{
|
||||
std::vector<std::uint64_t> costs(nodeCount,
|
||||
std::numeric_limits<std::uint64_t>::max());
|
||||
std::vector<std::size_t> parent(nodeCount,nodeCount);
|
||||
std::priority_queue<FQueueEntry,std::vector<FQueueEntry>,
|
||||
std::greater<FQueueEntry>> open;
|
||||
std::vector<std::int8_t> edgeSupportCache(nodeCount*4,-1);
|
||||
for(std::int32_t gy=0;gy<dimension;++gy)
|
||||
for(std::int32_t gx=0;gx<dimension;++gx)
|
||||
{
|
||||
if(gx!=0&&gy!=0&&gx!=dimension-1&&gy!=dimension-1)continue;
|
||||
const std::size_t start=indexOf(gx,gy);
|
||||
if(!grid[start].supportReachable)continue;
|
||||
costs[start]=0;
|
||||
open.push({0,start});
|
||||
}
|
||||
if(open.empty())continue;
|
||||
std::size_t goal=nodeCount;
|
||||
std::uint64_t goalCost=std::numeric_limits<std::uint64_t>::max();
|
||||
std::uint64_t goalDistanceSquared=std::numeric_limits<std::uint64_t>::max();
|
||||
while(!open.empty())
|
||||
{
|
||||
const FQueueEntry current=open.top();open.pop();
|
||||
if(current.cost!=costs[current.index])continue;
|
||||
if(goalCost!=std::numeric_limits<std::uint64_t>::max()
|
||||
&¤t.cost>goalCost)break;
|
||||
const std::int32_t gx=static_cast<std::int32_t>(current.index%dimension);
|
||||
const std::int32_t gy=static_cast<std::int32_t>(current.index/dimension);
|
||||
if(parent[current.index]!=nodeCount)
|
||||
{
|
||||
const std::int64_t xQ=static_cast<std::int64_t>(gx-radiusSteps)
|
||||
*terrainStepQ;
|
||||
const std::int64_t yQ=static_cast<std::int64_t>(gy-radiusSteps)
|
||||
*terrainStepQ;
|
||||
const std::uint64_t distanceSquared=static_cast<std::uint64_t>(
|
||||
xQ*xQ+yQ*yQ);
|
||||
if(distanceSquared<=static_cast<std::uint64_t>(
|
||||
boundedRadius*boundedRadius)
|
||||
&&(current.cost<goalCost||(current.cost==goalCost
|
||||
&&distanceSquared<goalDistanceSquared)))
|
||||
{goal=current.index;goalCost=current.cost;
|
||||
goalDistanceSquared=distanceSquared;}
|
||||
}
|
||||
for(std::int32_t oy=-1;oy<=1;++oy)
|
||||
for(std::int32_t ox=-1;ox<=1;++ox)
|
||||
{
|
||||
if(ox==0&&oy==0)continue;
|
||||
const std::int32_t nx=gx+ox,ny=gy+oy;
|
||||
if(nx<0||ny<0||nx>=dimension||ny>=dimension)continue;
|
||||
const std::size_t next=indexOf(nx,ny);
|
||||
if(!grid[next].supportReachable)continue;
|
||||
const std::int64_t horizontalQ=static_cast<std::int64_t>(
|
||||
std::llround(terrainStepQ*std::hypot(static_cast<double>(ox),
|
||||
static_cast<double>(oy))));
|
||||
const std::int64_t riseQ=std::abs(grid[next].ground
|
||||
-grid[current.index].ground);
|
||||
if(horizontalQ<=0||riseQ*65536>horizontalQ*GradeQ16)continue;
|
||||
const std::int32_t edgeDx=nx-gx,edgeDy=ny-gy;
|
||||
std::int32_t baseX=gx,baseY=gy,edgeDirection=0;
|
||||
if(edgeDx==0)
|
||||
{baseY=std::min(gy,ny);edgeDirection=1;}
|
||||
else if(edgeDy==0)
|
||||
{baseX=std::min(gx,nx);edgeDirection=0;}
|
||||
else if(edgeDx==edgeDy)
|
||||
{baseX=std::min(gx,nx);baseY=std::min(gy,ny);edgeDirection=2;}
|
||||
else
|
||||
{baseX=std::max(gx,nx);baseY=std::min(gy,ny);edgeDirection=3;}
|
||||
const std::size_t edgeCacheIndex=indexOf(baseX,baseY)*4
|
||||
+static_cast<std::size_t>(edgeDirection);
|
||||
if(edgeSupportCache[edgeCacheIndex]<0)
|
||||
{
|
||||
const std::uint64_t intervals=std::max<std::uint64_t>(2,
|
||||
(static_cast<std::uint64_t>(horizontalQ)
|
||||
+WorldAssemblySubvoxelsPerVoxel/8-1)
|
||||
/(WorldAssemblySubvoxelsPerVoxel/8));
|
||||
bool supported=true;
|
||||
const std::int64_t fromX=static_cast<std::int64_t>(
|
||||
gx-radiusSteps)*terrainStepQ;
|
||||
const std::int64_t fromY=static_cast<std::int64_t>(
|
||||
gy-radiusSteps)*terrainStepQ;
|
||||
const std::int64_t toX=static_cast<std::int64_t>(
|
||||
nx-radiusSteps)*terrainStepQ;
|
||||
const std::int64_t toY=static_cast<std::int64_t>(
|
||||
ny-radiusSteps)*terrainStepQ;
|
||||
for(std::uint64_t sample=1;sample<intervals;++sample)
|
||||
{
|
||||
const std::int64_t x=fromX+(toX-fromX)
|
||||
*static_cast<std::int64_t>(sample)
|
||||
/static_cast<std::int64_t>(intervals);
|
||||
const std::int64_t y=fromY+(toY-fromY)
|
||||
*static_cast<std::int64_t>(sample)
|
||||
/static_cast<std::int64_t>(intervals);
|
||||
const std::int64_t footZ=grid[current.index].ground
|
||||
+(grid[next].ground-grid[current.index].ground)
|
||||
*static_cast<std::int64_t>(sample)
|
||||
/static_cast<std::int64_t>(intervals)
|
||||
+routeBaseLiftQ;
|
||||
if(!hasRouteSupportAt(x,y,footZ))
|
||||
{supported=false;break;}
|
||||
}
|
||||
edgeSupportCache[edgeCacheIndex]=supported?1:0;
|
||||
}
|
||||
if(edgeSupportCache[edgeCacheIndex]==0)continue;
|
||||
const std::uint64_t edgeCost=static_cast<std::uint64_t>(
|
||||
horizontalQ+riseQ*4);
|
||||
if(current.cost>std::numeric_limits<std::uint64_t>::max()-edgeCost)
|
||||
continue;
|
||||
const std::uint64_t nextCost=current.cost+edgeCost;
|
||||
if(nextCost>=costs[next])continue;
|
||||
costs[next]=nextCost;parent[next]=current.index;
|
||||
open.push({nextCost,next});
|
||||
}
|
||||
}
|
||||
if(goal==nodeCount)continue;
|
||||
std::vector<std::size_t> path;
|
||||
for(std::size_t cursor=goal;cursor!=nodeCount;cursor=parent[cursor])
|
||||
{
|
||||
path.push_back(cursor);
|
||||
if(parent[cursor]==nodeCount)break;
|
||||
if(path.size()>nodeCount)break;
|
||||
}
|
||||
if(path.size()<2||parent[path.back()]!=nodeCount)continue;
|
||||
std::reverse(path.begin(),path.end());
|
||||
if(arrivalRadiusSubvoxels>0)
|
||||
std::reverse(path.begin(),path.end());
|
||||
Candidate candidate;
|
||||
candidate.centerX=static_cast<std::int32_t>(
|
||||
(static_cast<std::int32_t>(path.front()%dimension)-radiusSteps)
|
||||
*terrainStepQ);
|
||||
candidate.centerY=static_cast<std::int32_t>(
|
||||
(static_cast<std::int32_t>(path.front()/dimension)-radiusSteps)
|
||||
*terrainStepQ);
|
||||
candidate.direction=static_cast<std::int32_t>(directionIndex);
|
||||
candidate.score=static_cast<std::int64_t>(goalCost/path.size());
|
||||
candidate.endpointProximityCost=goalDistanceSquared
|
||||
*(arrivalRadiusSubvoxels>0?16ull:1ull);
|
||||
candidate.points.reserve(path.size());
|
||||
bool gradePass=true;
|
||||
for(std::size_t pointIndex=0;pointIndex<path.size();++pointIndex)
|
||||
{
|
||||
const std::size_t index=path[pointIndex];
|
||||
const std::int32_t gx=static_cast<std::int32_t>(index%dimension);
|
||||
const std::int32_t gy=static_cast<std::int32_t>(index/dimension);
|
||||
const std::int64_t xQ=static_cast<std::int64_t>(gx-radiusSteps)
|
||||
*terrainStepQ;
|
||||
const std::int64_t yQ=static_cast<std::int64_t>(gy-radiusSteps)
|
||||
*terrainStepQ;
|
||||
WorldAssemblyRoutePointV1 point;
|
||||
point.positionSubvoxels={xQ,yQ,
|
||||
grid[index].ground+routeBaseLiftQ+HalfHeightQ};
|
||||
point.radiusSubvoxels=RouteRadiusQ;
|
||||
point.stableFeatureId=WorldAssemblyFeatureId(recipe.seed,
|
||||
xQ,yQ,
|
||||
0x52550000u+static_cast<std::uint32_t>(pointIndex));
|
||||
if(pointIndex>0)
|
||||
{
|
||||
const auto& previous=candidate.points.back().positionSubvoxels;
|
||||
const std::int64_t dx=point.positionSubvoxels[0]-previous[0];
|
||||
const std::int64_t dy=point.positionSubvoxels[1]-previous[1];
|
||||
const std::int64_t dz=std::abs(point.positionSubvoxels[2]-previous[2]);
|
||||
const std::int64_t run=static_cast<std::int64_t>(std::llround(
|
||||
std::hypot(static_cast<double>(dx),static_cast<double>(dy))));
|
||||
if(run<=0||dz*65536>run*GradeQ16)gradePass=false;
|
||||
}
|
||||
candidate.points.push_back(point);
|
||||
}
|
||||
if(!gradePass)continue;
|
||||
++terrainPathCount;
|
||||
const std::array<std::array<std::int64_t,2>,8> lateralNudges{{
|
||||
{{-ProbeRadiusQ,-ProbeRadiusQ}},{{-ProbeRadiusQ,0}},
|
||||
{{0,-ProbeRadiusQ}},{{ProbeRadiusQ,-ProbeRadiusQ}},
|
||||
{{-ProbeRadiusQ,ProbeRadiusQ}},{{0,ProbeRadiusQ}},
|
||||
{{ProbeRadiusQ,0}},{{ProbeRadiusQ,ProbeRadiusQ}}}};
|
||||
for(std::size_t nudgeIndex=0;nudgeIndex<lateralNudges.size();++nudgeIndex)
|
||||
{
|
||||
Candidate shifted=candidate;
|
||||
shifted.direction=candidate.direction*9
|
||||
+static_cast<std::int32_t>(nudgeIndex+1);
|
||||
shifted.score+=static_cast<std::int64_t>(nudgeIndex+1);
|
||||
bool shiftedGradePass=true;
|
||||
for(std::size_t pointIndex=0;pointIndex<shifted.points.size();++pointIndex)
|
||||
{
|
||||
const std::int64_t tQ16=shifted.points.size()>1
|
||||
?static_cast<std::int64_t>(pointIndex)*65536
|
||||
/static_cast<std::int64_t>(shifted.points.size()-1):0;
|
||||
const std::int64_t t2Q16=tQ16*tQ16/65536;
|
||||
const std::int64_t oneMinusQ16=65536-tQ16;
|
||||
const std::int64_t oneMinus2Q16=
|
||||
oneMinusQ16*oneMinusQ16/65536;
|
||||
const std::int64_t weightQ16=std::min<std::int64_t>(65536,
|
||||
16*t2Q16*oneMinus2Q16/65536);
|
||||
auto& point=shifted.points[pointIndex];
|
||||
point.positionSubvoxels[0]+=lateralNudges[nudgeIndex][0]
|
||||
*weightQ16/65536;
|
||||
point.positionSubvoxels[1]+=lateralNudges[nudgeIndex][1]
|
||||
*weightQ16/65536;
|
||||
const auto ground=safeGroundAt(point.positionSubvoxels[0],
|
||||
point.positionSubvoxels[1]);
|
||||
if(!ground.supportReachable)shiftedGradePass=false;
|
||||
point.positionSubvoxels[2]=ground.ground+routeBaseLiftQ
|
||||
+HalfHeightQ;
|
||||
point.stableFeatureId=WorldAssemblyFeatureId(recipe.seed,
|
||||
point.positionSubvoxels[0],point.positionSubvoxels[1],
|
||||
0x52550000u+static_cast<std::uint32_t>(
|
||||
(nudgeIndex+1)*0x10000u+pointIndex));
|
||||
if(pointIndex==0)continue;
|
||||
const auto& previous=shifted.points[pointIndex-1]
|
||||
.positionSubvoxels;
|
||||
const std::int64_t dx=point.positionSubvoxels[0]-previous[0];
|
||||
const std::int64_t dy=point.positionSubvoxels[1]-previous[1];
|
||||
const std::int64_t dz=std::abs(point.positionSubvoxels[2]-previous[2]);
|
||||
const std::int64_t run=static_cast<std::int64_t>(std::llround(
|
||||
std::hypot(static_cast<double>(dx),static_cast<double>(dy))));
|
||||
if(run<=0||dz*65536>run*GradeQ16)shiftedGradePass=false;
|
||||
}
|
||||
if(!shiftedGradePass)continue;
|
||||
candidates.push_back(std::move(shifted));
|
||||
++terrainPathCount;
|
||||
}
|
||||
candidates.push_back(std::move(candidate));
|
||||
}
|
||||
if(terrainPathCount>0)terrainGridStepQ=terrainStepQ;
|
||||
};
|
||||
tryTerrainPaths(stepQ);
|
||||
tested+=terrainPathCount;
|
||||
std::printf("[WorldAssemblerRouteTerrainSearch] grid_step_m=%.3f grid_samples=%llu candidate_paths=%llu grade_limit_deg=15.000 status=%s\n",
|
||||
terrainGridStepQ*coreVoxelMetres/WorldAssemblySubvoxelsPerVoxel,
|
||||
static_cast<unsigned long long>(terrainGridSamples),
|
||||
static_cast<unsigned long long>(terrainPathCount),
|
||||
terrainPathCount>0?"FOUND":"EMPTY");
|
||||
}
|
||||
};
|
||||
if(gradeRejected>0||placementRejected>0)
|
||||
{
|
||||
const double metresPerSubvoxel=static_cast<double>(coreVoxelSizeMicrons)
|
||||
@@ -318,8 +644,33 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
});
|
||||
std::uint64_t sweepAttempt=0,repairAttempt=0;
|
||||
VoxelForgeCore::WorldAssemblyRouteAuditResult lastAudit{};
|
||||
for(auto& candidate:candidates)
|
||||
std::size_t candidateCursor=0;
|
||||
constexpr std::array<std::int32_t,4> TerrainFallbackSteps{{1024,512,256,128}};
|
||||
std::size_t terrainFallbackStep=0;
|
||||
for(;;)
|
||||
{
|
||||
if(candidateCursor>=candidates.size())
|
||||
{
|
||||
if(terrainFallbackStep<TerrainFallbackSteps.size()&&fallbackSearch
|
||||
&&((arrivalRadiusSubvoxels>0)!=(departureRadiusSubvoxels>0)))
|
||||
{
|
||||
const std::size_t firstTerrainCandidate=candidates.size();
|
||||
appendTerrainFallback(TerrainFallbackSteps[terrainFallbackStep++]);
|
||||
std::stable_sort(candidates.begin()+firstTerrainCandidate,
|
||||
candidates.end(),[](const Candidate& a,const Candidate& b)
|
||||
{
|
||||
if(a.endpointProximityCost!=b.endpointProximityCost)
|
||||
return a.endpointProximityCost<b.endpointProximityCost;
|
||||
if(a.score!=b.score)return a.score<b.score;
|
||||
if(a.centerY!=b.centerY)return a.centerY<b.centerY;
|
||||
if(a.centerX!=b.centerX)return a.centerX<b.centerX;
|
||||
return a.direction<b.direction;
|
||||
});
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
auto& candidate=candidates[candidateCursor++];
|
||||
stratum.route=candidate.points;
|
||||
std::int64_t liftQ=0;
|
||||
constexpr std::int64_t RepairStepQ=64;
|
||||
@@ -349,6 +700,42 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
static_cast<long long>(candidate.score));
|
||||
return true;
|
||||
}
|
||||
if(liftQ==0&&audit.blockedCapsuleSamples==0
|
||||
&&audit.unsupportedCenterSamples>0)
|
||||
{
|
||||
constexpr std::int64_t SupportAlignmentStepQ=16;
|
||||
const std::int64_t lowerLimit=std::max<std::int64_t>(0,
|
||||
audit.minimumClearanceSubvoxels
|
||||
-constraints.minimumClearanceSubvoxels);
|
||||
for(std::int64_t lowerQ=SupportAlignmentStepQ;
|
||||
lowerQ<=lowerLimit;lowerQ+=SupportAlignmentStepQ)
|
||||
{
|
||||
for(std::size_t pointIndex=0;pointIndex<stratum.route.size();++pointIndex)
|
||||
stratum.route[pointIndex].positionSubvoxels[2]
|
||||
=candidate.points[pointIndex].positionSubvoxels[2]-lowerQ;
|
||||
const auto loweredAudit=AuditHeightfieldRoute(candidateField);
|
||||
lastAudit=loweredAudit;++repairAttempt;
|
||||
if(loweredAudit.passed)
|
||||
{
|
||||
std::printf("[WorldAssemblerRouteRepaired] uniform_lower_q=%lld support_limit_q=%lld\n",
|
||||
static_cast<long long>(lowerQ),
|
||||
static_cast<long long>(lowerLimit));
|
||||
const double metresPerSubvoxel=coreVoxelMetres
|
||||
/WorldAssemblySubvoxelsPerVoxel;
|
||||
std::printf("[WorldAssemblerRouteAccepted] arrival_xy_m=(%.3f,%.3f) departure_xy_m=(%.3f,%.3f) endpoint_proximity_cost=%llu average_grade_q16=%lld\n",
|
||||
candidate.points.front().positionSubvoxels[0]*metresPerSubvoxel,
|
||||
candidate.points.front().positionSubvoxels[1]*metresPerSubvoxel,
|
||||
candidate.points.back().positionSubvoxels[0]*metresPerSubvoxel,
|
||||
candidate.points.back().positionSubvoxels[1]*metresPerSubvoxel,
|
||||
static_cast<unsigned long long>(candidate.endpointProximityCost),
|
||||
static_cast<long long>(candidate.score));
|
||||
return true;
|
||||
}
|
||||
if(loweredAudit.blockedCapsuleSamples>0
|
||||
||loweredAudit.minimumClearanceSubvoxels
|
||||
<constraints.minimumClearanceSubvoxels)break;
|
||||
}
|
||||
}
|
||||
if(audit.blockedCapsuleSamples==0||liftQ>=MaximumRepairLiftQ)break;
|
||||
const std::int64_t needed=std::max<std::int64_t>(1,
|
||||
constraints.minimumClearanceSubvoxels-audit.minimumClearanceSubvoxels);
|
||||
@@ -358,6 +745,62 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
liftQ=nextLift;
|
||||
++repairAttempt;
|
||||
}
|
||||
if(lastAudit.unsupportedCenterSamples>0&&sweepAttempt<5)
|
||||
{
|
||||
const Heightfield2D diagnosticField(recipe,stratum);
|
||||
const std::int64_t sampleSpacing=std::min<std::int64_t>(
|
||||
constraints.sweepSpacingSubvoxels,
|
||||
WorldAssemblySubvoxelsPerVoxel/2);
|
||||
const std::int64_t supportSpacing=std::min<std::int64_t>(sampleSpacing,
|
||||
WorldAssemblySubvoxelsPerVoxel/4);
|
||||
constexpr std::array<std::array<std::int64_t,2>,5> supportOffsets{{
|
||||
{{0,0}},{{ProbeRadiusQ,0}},{{-ProbeRadiusQ,0}},
|
||||
{{0,ProbeRadiusQ}},{{0,-ProbeRadiusQ}}}};
|
||||
for(std::size_t segment=1;segment<stratum.route.size();++segment)
|
||||
{
|
||||
const auto& from=stratum.route[segment-1].positionSubvoxels;
|
||||
const auto& to=stratum.route[segment].positionSubvoxels;
|
||||
const std::int64_t dx=to[0]-from[0],dy=to[1]-from[1],dz=to[2]-from[2];
|
||||
const std::uint64_t length=std::max<std::uint64_t>(1,
|
||||
static_cast<std::uint64_t>(std::llround(std::sqrt(
|
||||
static_cast<double>(dx*dx+dy*dy+dz*dz)))));
|
||||
const std::uint64_t intervals=std::max<std::uint64_t>(1,
|
||||
(length+sampleSpacing-1)/sampleSpacing);
|
||||
for(std::uint64_t step=segment==1?0:1;step<=intervals;++step)
|
||||
{
|
||||
const auto interpolate=[&](std::size_t axis)
|
||||
{return from[axis]+(to[axis]-from[axis])
|
||||
*static_cast<std::int64_t>(step)/static_cast<std::int64_t>(intervals);};
|
||||
const std::int64_t x=interpolate(0),y=interpolate(1),z=interpolate(2);
|
||||
const std::int64_t footZ=z-PlayerHeightQ/2;
|
||||
std::array<std::int64_t,5> reachedDepth{};
|
||||
for(std::size_t probe=0;probe<supportOffsets.size();++probe)
|
||||
for(std::int64_t depth=supportSpacing;
|
||||
depth<=constraints.supportSearchDepthSubvoxels;
|
||||
depth+=supportSpacing)
|
||||
{
|
||||
if(diagnosticField.point(x+supportOffsets[probe][0],
|
||||
y+supportOffsets[probe][1],footZ-depth)
|
||||
.densitySubvoxels<0)
|
||||
{reachedDepth[probe]=depth;break;}
|
||||
}
|
||||
const auto supported=static_cast<std::uint32_t>(std::count_if(
|
||||
reachedDepth.begin(),reachedDepth.end(),[](auto depth){return depth>0;}));
|
||||
if(supported>=constraints.minimumSupportProbes)continue;
|
||||
const double metresPerSubvoxel=coreVoxelMetres
|
||||
/WorldAssemblySubvoxelsPerVoxel;
|
||||
std::printf("[WorldAssemblerRouteSupportSample] ordinal=%llu x_m=%.3f y_m=%.3f center_z_m=%.3f supported=%u/5 depths_q=(%lld,%lld,%lld,%lld,%lld)\n",
|
||||
static_cast<unsigned long long>(sweepAttempt),x*metresPerSubvoxel,
|
||||
y*metresPerSubvoxel,z*metresPerSubvoxel,supported,
|
||||
static_cast<long long>(reachedDepth[0]),
|
||||
static_cast<long long>(reachedDepth[1]),
|
||||
static_cast<long long>(reachedDepth[2]),
|
||||
static_cast<long long>(reachedDepth[3]),
|
||||
static_cast<long long>(reachedDepth[4]));
|
||||
segment=stratum.route.size();break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(sweepAttempt<3)
|
||||
std::printf("[WorldAssemblerRouteCandidateRejected] ordinal=%llu blocked=%llu unsupported=%llu grade_failures=%llu minimum_clearance_q=%lld\n",
|
||||
static_cast<unsigned long long>(sweepAttempt),
|
||||
@@ -1733,6 +2176,300 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
inline bool PlanOwnerTunnelNetworkCaves(const FStandaloneOwnerWorld& owner,
|
||||
VoxelForgeCore::ResolvedLayoutSnapshotV3& layout,std::string& error)
|
||||
{
|
||||
using namespace VoxelForgeCore;
|
||||
if(!owner.Manager)
|
||||
{error="native cave planner requires the initialized owner director";return false;}
|
||||
std::vector<std::pair<const FStrateSlot*,bool>> plannerSlots;
|
||||
for(const FStrateSlot& slot:owner.Manager->GetLayout())
|
||||
{
|
||||
if(!slot.Definition)continue;
|
||||
const ECaveGeneratorType generatorType=
|
||||
owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ));
|
||||
const bool tunnelNetwork=generatorType==ECaveGeneratorType::TunnelNetwork;
|
||||
const bool verticalShafts=generatorType==ECaveGeneratorType::VerticalShafts;
|
||||
if(!tunnelNetwork&&!verticalShafts)continue;
|
||||
const bool hasIncomingMouth=std::any_of(layout.passages.begin(),
|
||||
layout.passages.end(),[&](const auto& passage)
|
||||
{return passage.lowerStrateIndex==slot.StrateIndex
|
||||
&&passage.upperStrateIndex<slot.StrateIndex;});
|
||||
const bool hasOutgoingMouth=std::any_of(layout.passages.begin(),
|
||||
layout.passages.end(),[&](const auto& passage)
|
||||
{return passage.upperStrateIndex==slot.StrateIndex
|
||||
&&passage.lowerStrateIndex>slot.StrateIndex;});
|
||||
// Tunnel Network connects two passages. The basal Vertical Shafts
|
||||
// stratum is terminal: it inherits its arrival from the preceding
|
||||
// strate and intentionally has no departure passage.
|
||||
if((tunnelNetwork&&(!hasIncomingMouth||!hasOutgoingMouth))
|
||||
||(verticalShafts&&(!hasIncomingMouth||hasOutgoingMouth)))continue;
|
||||
auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[&](const auto& item){return item.strateIndex==slot.StrateIndex;});
|
||||
if(stratum==layout.strates.end())
|
||||
{error="Tunnel Network strate is missing from the resolved layout";return false;}
|
||||
stratum->generatorKind=verticalShafts?"VerticalShafts":"TunnelNetwork";
|
||||
stratum->recipeId="Core.Caves.PrimitiveField";
|
||||
stratum->recipeVersion=1;
|
||||
stratum->stableId=layout.worldIdentity+"/strate/"
|
||||
+std::to_string(slot.StrateIndex)+"/generator/"
|
||||
+(verticalShafts?"VerticalShafts":"TunnelNetwork");
|
||||
const bool alreadyPlanned=std::any_of(layout.cavernRooms.begin(),
|
||||
layout.cavernRooms.end(),[&](const auto& room)
|
||||
{return room.strateIndex==slot.StrateIndex;})
|
||||
&&std::any_of(layout.cavernEdges.begin(),layout.cavernEdges.end(),
|
||||
[&](const auto& edge){return edge.strateIndex==slot.StrateIndex;});
|
||||
if(!alreadyPlanned)plannerSlots.emplace_back(&slot,verticalShafts);
|
||||
}
|
||||
if(!layout.finalize(error))return false;
|
||||
const auto toMicrometres=[&](double ownerVoxels)
|
||||
{
|
||||
return static_cast<std::int64_t>(std::llround(ownerVoxels
|
||||
*static_cast<double>(owner.OwnerVoxelSizeMicrons)));
|
||||
};
|
||||
const auto toQ16=[](float value)
|
||||
{
|
||||
return static_cast<std::int32_t>(std::llround(std::clamp(
|
||||
static_cast<double>(value),0.0,1.0)*65536.0));
|
||||
};
|
||||
if(!plannerSlots.empty())
|
||||
{
|
||||
std::vector<ResolvedCavernRoomV1> plannedRooms=layout.cavernRooms;
|
||||
std::vector<ResolvedCavernEdgeV1> plannedEdges=layout.cavernEdges;
|
||||
for(const auto& [slot,verticalShafts]:plannerSlots)
|
||||
{
|
||||
const FStrateGenerationParams& params=slot->Definition->GenerationParams;
|
||||
ResolvedCavernPlannerSettingsV1 settings;
|
||||
if(verticalShafts)
|
||||
{
|
||||
const FVerticalShaftParams& shaft=slot->Definition->VerticalShaftParams;
|
||||
settings.verticalShaftMode=true;
|
||||
settings.roomSpacingMicrometres=std::clamp<std::int64_t>(
|
||||
toMicrometres(shaft.ShaftSpacing),4000000,100000000);
|
||||
settings.minimumRoomRadiusMicrometres=std::max<std::int64_t>(
|
||||
500000,toMicrometres(shaft.ShaftMinRadius));
|
||||
settings.maximumRoomRadiusMicrometres=std::max(
|
||||
settings.minimumRoomRadiusMicrometres,
|
||||
toMicrometres(shaft.ShaftMaxRadius));
|
||||
settings.destinationRadiusMicrometres=std::max(
|
||||
settings.maximumRoomRadiusMicrometres,
|
||||
settings.maximumRoomRadiusMicrometres*135/100);
|
||||
settings.minimumTunnelRadiusMicrometres=std::max<std::int64_t>(0,
|
||||
toMicrometres(shaft.ConnectorRadius));
|
||||
settings.maximumTunnelRadiusMicrometres=
|
||||
settings.minimumTunnelRadiusMicrometres;
|
||||
settings.maximumTunnelLengthMicrometres=std::clamp<std::int64_t>(
|
||||
settings.roomSpacingMicrometres*2,4000000,100000000);
|
||||
settings.smoothWidthMicrometres=1500000;
|
||||
settings.floorSupportDepthMicrometres=2000000;
|
||||
settings.irregularityAmplitudeMicrometres=std::max<std::int64_t>(0,
|
||||
toMicrometres(shaft.SurfaceRoughness));
|
||||
settings.irregularityWavelengthMicrometres=std::max<std::int64_t>(
|
||||
500000,static_cast<std::int64_t>(owner.OwnerVoxelSizeMicrons)*4);
|
||||
settings.roomDensityQ16=toQ16(shaft.ShaftDensity);
|
||||
settings.tunnelDensityQ16=toQ16(shaft.CrossConnectChance);
|
||||
settings.roomShapeVarietyQ16=0;
|
||||
settings.roomHeightRatioQ16=65536;
|
||||
settings.roomFloorCutMinimumQ16=0;
|
||||
settings.roomFloorCutMaximumQ16=0;
|
||||
settings.tunnelHorizontalBiasQ16=65536;
|
||||
settings.originRoomMaximumConnections=32;
|
||||
settings.ledgeSpacingMicrometres=std::max<std::int64_t>(0,
|
||||
toMicrometres(shaft.LedgeSpacing));
|
||||
settings.ledgeDepthMicrometres=std::max<std::int64_t>(0,
|
||||
toMicrometres(shaft.LedgeDepth));
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.roomSpacingMicrometres=std::max<std::int64_t>(4000000,
|
||||
toMicrometres(params.RoomSpacing));
|
||||
settings.minimumRoomRadiusMicrometres=std::max<std::int64_t>(500000,
|
||||
toMicrometres(params.MinRoomRadius));
|
||||
settings.maximumRoomRadiusMicrometres=std::max(
|
||||
settings.minimumRoomRadiusMicrometres,toMicrometres(params.MaxRoomRadius));
|
||||
settings.destinationRadiusMicrometres=std::max(
|
||||
toMicrometres(params.OriginRoomRadius),
|
||||
settings.maximumRoomRadiusMicrometres*135/100);
|
||||
settings.minimumTunnelRadiusMicrometres=std::max<std::int64_t>(0,
|
||||
toMicrometres(params.TunnelMinRadius));
|
||||
settings.maximumTunnelRadiusMicrometres=std::max(
|
||||
settings.minimumTunnelRadiusMicrometres,toMicrometres(params.TunnelMaxRadius));
|
||||
settings.maximumTunnelLengthMicrometres=std::max<std::int64_t>(4000000,
|
||||
toMicrometres(params.MaxTunnelLength));
|
||||
settings.tunnelWarpMicrometres=std::max<std::int64_t>(0,
|
||||
toMicrometres(params.TunnelWarpStrength));
|
||||
settings.caveWarpMicrometres=std::max<std::int64_t>(0,
|
||||
toMicrometres(params.CaveWarpStrength));
|
||||
settings.smoothWidthMicrometres=std::max<std::int64_t>(1500000,
|
||||
toMicrometres(params.SDFBlendRadius));
|
||||
settings.floorSupportDepthMicrometres=std::clamp<std::int64_t>(
|
||||
settings.smoothWidthMicrometres/2,750000,2000000);
|
||||
settings.irregularityAmplitudeMicrometres=std::max<std::int64_t>(0,
|
||||
toMicrometres(params.SurfaceRoughness));
|
||||
settings.irregularityWavelengthMicrometres=params.RoughnessFrequency>0.0f
|
||||
?static_cast<std::int64_t>(std::llround(owner.OwnerVoxelSizeMicrons
|
||||
/static_cast<double>(params.RoughnessFrequency))):0;
|
||||
settings.roomDensityQ16=toQ16(params.RoomDensity);
|
||||
settings.tunnelDensityQ16=toQ16(params.TunnelDensity);
|
||||
settings.roomShapeVarietyQ16=toQ16(params.RoomShapeVariety);
|
||||
settings.roomHeightRatioQ16=toQ16(params.RoomHeightRatio);
|
||||
settings.roomFloorCutMinimumQ16=toQ16(params.RoomFloorCutMin);
|
||||
settings.roomFloorCutMaximumQ16=toQ16(params.RoomFloorCutMax);
|
||||
settings.tunnelHorizontalBiasQ16=toQ16(params.TunnelHorizontalBias);
|
||||
settings.originRoomMaximumConnections=params.OriginRoomMaxConnections;
|
||||
settings.tunnelsFlowTowardOrigin=params.bTunnelsFlowTowardOrigin;
|
||||
}
|
||||
std::vector<ResolvedCavernRoomV1> rooms;
|
||||
std::vector<ResolvedCavernEdgeV1> edges;
|
||||
if(!PlanResolvedCavernNetworkV1(layout,owner.Seed,slot->StrateIndex,
|
||||
settings,rooms,edges,error))return false;
|
||||
plannedRooms.insert(plannedRooms.end(),
|
||||
std::make_move_iterator(rooms.begin()),std::make_move_iterator(rooms.end()));
|
||||
plannedEdges.insert(plannedEdges.end(),
|
||||
std::make_move_iterator(edges.begin()),std::make_move_iterator(edges.end()));
|
||||
}
|
||||
if(!ResolveResolvedCavernGraphV1(layout,owner.Seed,
|
||||
plannedRooms,plannedEdges,error))return false;
|
||||
}
|
||||
|
||||
const auto toCoreSubvoxels=[&](std::int64_t micrometres)
|
||||
{
|
||||
return static_cast<std::int64_t>(std::llround(
|
||||
static_cast<double>(micrometres)*WorldAssemblySubvoxelsPerVoxel
|
||||
/layout.coreVoxelSizeMicrometres));
|
||||
};
|
||||
constexpr std::int64_t PlayerCenterAboveFloorMicrometres=1130000;
|
||||
for(const FStrateSlot& slot:owner.Manager->GetLayout())
|
||||
{
|
||||
if(!slot.Definition)continue;
|
||||
const ECaveGeneratorType generatorType=
|
||||
owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ));
|
||||
const bool verticalShafts=generatorType==ECaveGeneratorType::VerticalShafts;
|
||||
if(generatorType!=ECaveGeneratorType::TunnelNetwork&&!verticalShafts)continue;
|
||||
auto stratum=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[&](const auto& item){return item.strateIndex==slot.StrateIndex;});
|
||||
if(stratum==layout.strates.end()
|
||||
||!IsNativeCaveFieldStratum(*stratum))continue;
|
||||
const std::string routePrefix=(verticalShafts?"vertical-shafts/strate-"
|
||||
:"tunnel-network/strate-")
|
||||
+std::to_string(slot.StrateIndex)+"/";
|
||||
const bool hasPlannedArrival=std::any_of(layout.cavernEdges.begin(),
|
||||
layout.cavernEdges.end(),[&](const auto& edge)
|
||||
{return edge.strateIndex==slot.StrateIndex
|
||||
&&edge.stableId==routePrefix+"route-edge-0";});
|
||||
const bool hasPlannedDeparture=!verticalShafts
|
||||
&&std::any_of(layout.cavernEdges.begin(),layout.cavernEdges.end(),
|
||||
[&](const auto& edge){return edge.strateIndex==slot.StrateIndex
|
||||
&&edge.stableId==routePrefix+"departure-edge";});
|
||||
if(!hasPlannedArrival||(!verticalShafts&&!hasPlannedDeparture))continue;
|
||||
// Route-point anchors also drive the passage-mouth floor blend. The
|
||||
// native cave planner replaces the legacy finite-strate route, so
|
||||
// retaining its old anchors would join the passage floor to a route
|
||||
// that is no longer present in the composed cave field.
|
||||
stratum->anchors.erase(std::remove_if(stratum->anchors.begin(),
|
||||
stratum->anchors.end(),[](const auto& anchor)
|
||||
{return anchor.kind=="route_point";}),stratum->anchors.end());
|
||||
const ResolvedLayoutPassageV1* arrival=nullptr;
|
||||
const ResolvedLayoutPassageV1* departure=nullptr;
|
||||
for(const auto& passage:layout.passages)
|
||||
{
|
||||
if(passage.lowerStrateIndex==slot.StrateIndex
|
||||
&&passage.upperStrateIndex<slot.StrateIndex)arrival=&passage;
|
||||
if(passage.upperStrateIndex==slot.StrateIndex
|
||||
&&passage.lowerStrateIndex>slot.StrateIndex)departure=&passage;
|
||||
}
|
||||
if(!arrival||(!verticalShafts&&!departure))
|
||||
{error="native cave route anchors cannot bind their required passage mouths";return false;}
|
||||
const auto appendRoutePoint=[&](std::string id,
|
||||
const std::array<std::int64_t,3>& point)
|
||||
{
|
||||
ResolvedLayoutAnchorV2 anchor;anchor.kind="route_point";
|
||||
anchor.stableId=(verticalShafts?"vertical-shafts/route/"
|
||||
:"tunnel-network/route/")+std::move(id);
|
||||
anchor.units="1/256 Core voxel";
|
||||
anchor.values={point[0],point[1],point[2]};
|
||||
stratum->anchors.push_back(std::move(anchor));
|
||||
};
|
||||
const auto sameRoutePoint=[](const auto& a,const auto& b)
|
||||
{
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
if(std::abs(a[axis]-b[axis])>2)return false;
|
||||
return true;
|
||||
};
|
||||
auto start=arrival->lowerMouthSubvoxels;
|
||||
appendRoutePoint("000-arrival",start);
|
||||
for(std::size_t edgeIndex=0;edgeIndex<256;++edgeIndex)
|
||||
{
|
||||
const std::string stableId=routePrefix+"route-edge-"
|
||||
+std::to_string(edgeIndex);
|
||||
const auto edge=std::find_if(layout.cavernEdges.begin(),
|
||||
layout.cavernEdges.end(),[&](const auto& value)
|
||||
{return value.strateIndex==slot.StrateIndex
|
||||
&&value.stableId==stableId;});
|
||||
if(edge==layout.cavernEdges.end())break;
|
||||
bool terminalRouteEdge=false;
|
||||
if(verticalShafts)
|
||||
{
|
||||
const std::string nextRouteEdge=routePrefix+"route-edge-"
|
||||
+std::to_string(edgeIndex+1);
|
||||
terminalRouteEdge=std::none_of(layout.cavernEdges.begin(),
|
||||
layout.cavernEdges.end(),[&](const auto& candidate)
|
||||
{return candidate.strateIndex==slot.StrateIndex
|
||||
&&candidate.stableId==nextRouteEdge;});
|
||||
}
|
||||
if(edgeIndex==0)
|
||||
{
|
||||
auto inner=edge->controlPointsMicrometres.front();
|
||||
// Keep the exact authored mouth anchor and use the planner
|
||||
// edge's floor height for the first cave-side walking point.
|
||||
// Passage composition uses these same two points to blend the
|
||||
// floor profile into the native cave route.
|
||||
inner[2]+=PlayerCenterAboveFloorMicrometres;
|
||||
const std::array<std::int64_t,3> point={
|
||||
toCoreSubvoxels(inner[0]),toCoreSubvoxels(inner[1]),
|
||||
toCoreSubvoxels(inner[2])};
|
||||
if(!sameRoutePoint(point,start))
|
||||
appendRoutePoint("000-edge-start",point);
|
||||
}
|
||||
auto point=edge->controlPointsMicrometres.back();
|
||||
point[2]+=PlayerCenterAboveFloorMicrometres;
|
||||
appendRoutePoint(terminalRouteEdge?"999-basal-terminal"
|
||||
:std::to_string(edgeIndex+1),
|
||||
{toCoreSubvoxels(point[0]),toCoreSubvoxels(point[1]),
|
||||
toCoreSubvoxels(point[2])});
|
||||
}
|
||||
if(departure)
|
||||
{
|
||||
const std::string departureEdgeId=routePrefix+"departure-edge";
|
||||
const auto departureEdge=std::find_if(layout.cavernEdges.begin(),
|
||||
layout.cavernEdges.end(),[&](const auto& value)
|
||||
{return value.strateIndex==slot.StrateIndex
|
||||
&&value.stableId==departureEdgeId;});
|
||||
if(departureEdge==layout.cavernEdges.end())
|
||||
{error="native cave route lost its planned departure edge";return false;}
|
||||
auto inner=departureEdge->controlPointsMicrometres.front();
|
||||
inner[2]+=PlayerCenterAboveFloorMicrometres;
|
||||
const std::array<std::int64_t,3> innerPoint={
|
||||
toCoreSubvoxels(inner[0]),toCoreSubvoxels(inner[1]),
|
||||
toCoreSubvoxels(inner[2])};
|
||||
auto end=departure->upperMouthSubvoxels;
|
||||
if(!sameRoutePoint(innerPoint,end))
|
||||
appendRoutePoint("998-departure-inner",innerPoint);
|
||||
appendRoutePoint("999-departure",departure->upperMouthSubvoxels);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto terminal=std::find_if(layout.cavernRooms.begin(),
|
||||
layout.cavernRooms.end(),[&](const auto& room)
|
||||
{return room.strateIndex==slot.StrateIndex
|
||||
&&room.stableId==routePrefix+"basal-landing/primitive-0";});
|
||||
if(terminal==layout.cavernRooms.end())
|
||||
{error="Vertical Shafts basal route is missing its terminal landing";return false;}
|
||||
}
|
||||
}
|
||||
return layout.finalize(error);
|
||||
}
|
||||
|
||||
inline bool ImportExportedCoreWorldAssembly(const VFJson::Value& root,
|
||||
VoxelForgeCore::WorldAssemblyManifest& out,std::string& error)
|
||||
{
|
||||
@@ -1885,8 +2622,18 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
if(!slot.Definition)continue;
|
||||
const auto type=owner.Manager->GetGeneratorTypeForChunk(FIntVector(0,0,slot.TopChunkZ));
|
||||
const auto authored=sealAndDensity(*slot.Definition,type);
|
||||
const bool nativeCaves=recipeVariant=="caves"
|
||||
const bool nativeSurfaceCaves=recipeVariant=="caves"
|
||||
&&type==ECaveGeneratorType::SurfaceWorld;
|
||||
const bool hasLowerStrate=std::any_of(owner.Manager->GetLayout().begin(),
|
||||
owner.Manager->GetLayout().end(),[&](const FStrateSlot& candidate)
|
||||
{return candidate.StrateIndex<slot.StrateIndex;});
|
||||
const bool hasUpperStrate=std::any_of(owner.Manager->GetLayout().begin(),
|
||||
owner.Manager->GetLayout().end(),[&](const FStrateSlot& candidate)
|
||||
{return candidate.StrateIndex>slot.StrateIndex;});
|
||||
const bool nativeTunnelCaves=type==ECaveGeneratorType::TunnelNetwork
|
||||
&&recipeVariant!="legacy-tunnel-network"&&recipeVariant!="caves"
|
||||
&&hasLowerStrate&&hasUpperStrate;
|
||||
const bool nativeCaves=nativeSurfaceCaves||nativeTunnelCaves;
|
||||
const bool nativeLattice=recipeVariant==("lattice/"+std::to_string(slot.StrateIndex))
|
||||
&&(type==ECaveGeneratorType::Maze||type==ECaveGeneratorType::VerticalShafts);
|
||||
const std::int64_t bottom=static_cast<std::int64_t>(slot.BottomChunkZ)*owner.StrateLayoutChunkSizeMicrometres;
|
||||
@@ -1896,7 +2643,7 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
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=nativeCaves?"Caves":GeneratorKind(type);
|
||||
s.generatorKind=nativeSurfaceCaves?"Caves":GeneratorKind(type);
|
||||
s.stableId=out.worldIdentity+"/strate/"+std::to_string(slot.StrateIndex)
|
||||
+"/generator/"+s.generatorKind;
|
||||
s.recipeId=nativeLattice?"Core.Lattice.PrimitiveField"
|
||||
@@ -2083,6 +2830,8 @@ namespace StandaloneWorldAssemblyPrototype
|
||||
{
|
||||
const auto passageConfigs=BuildResolvedLayoutPassageConfigs(owner);
|
||||
if(!PlanResolvedLayoutPassagesV1(out,owner.Seed,passageConfigs,error))return false;
|
||||
if(recipeVariant!="legacy-tunnel-network"&&recipeVariant!="caves"
|
||||
&&!PlanOwnerTunnelNetworkCaves(owner,out,error))return false;
|
||||
}
|
||||
return out.finalize(error);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user