wip(assembler): round S1 -- one Core voxel-size source; per-tile feature lists (islands)

AVoxelWorld::CoreVoxelSizeCm (default 25, 32 voxels/tile) flows through plan, field,
mesher, content, water, atmosphere, edits and collision. Staged-owner BuildHost passes at
20 cm (SurfaceWorld + islands, W1/W8/W16, anchors, BP edits, collision) with a fixed-25cm
negative control; default-size hashes match round 4; Core off stays legacy 12.5 cm.
Reusable BuildWorldAssemblyFeatureTileListV1; wired into FloatingIslands only so far.
256-island stress: tile p95 112.5 -> 2.0 ms, 0 mismatches, dropped-feature control fails.
Content placement measured (no populated cells): island ground search 7.25 ms/cell vs
legacy 0.29 ms -- open. Outside-review evaluation in Round5/REPORT.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-18 18:48:38 +02:00
1 parent 111cf3d262
commit d6e69420e8
25 files changed
+2136 -250

No files matched your search

+7 -3
View File
@@ -553,7 +553,8 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
Radial.Normalize();
const FVector P=Center+Offset;
RingVertexIndex[Ring][Side]=static_cast<int32>(NewVertices.size());
NewVertices.emplace_back(P.X*25.0,P.Y*25.0,P.Z*25.0);
const double VoxelSizeCm=Shape.GetCoreVoxelSizeCm();
NewVertices.emplace_back(P.X*VoxelSizeCm,P.Y*VoxelSizeCm,P.Z*VoxelSizeCm);
NewNormals.emplace_back(-Radial.X,-Radial.Y,-Radial.Z);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>(Rings[Ring].T*Length/256.0),
static_cast<float>(Side)/SideCount);
@@ -703,7 +704,8 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
if(!ProjectToSurface(P,Normal)){bProjectionSucceeded=false;break;}
const int32 LocalIndex=static_cast<int32>(PatchVertices.size());
Grid[static_cast<size_t>(Y)*(Subdivisions+1)+X]=LocalIndex;
PatchVertices.emplace_back(P.X*25.0,P.Y*25.0,P.Z*25.0);
const double VoxelSizeCm=Shape.GetCoreVoxelSizeCm();
PatchVertices.emplace_back(P.X*VoxelSizeCm,P.Y*VoxelSizeCm,P.Z*VoxelSizeCm);
PatchNormals.push_back(Normal);
if(bHasUVs)
{
@@ -1013,7 +1015,9 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
const int32 Base=static_cast<int32>(NewVertices.size());
for(const FPatchVertex& Vertex:Patch)
{
NewVertices.emplace_back(Vertex.X*25.0,Vertex.Y*25.0,Vertex.Z*25.0);
const double VoxelSizeCm=World.CoreVoxelSizeMicrons/10000.0;
NewVertices.emplace_back(Vertex.X*VoxelSizeCm,Vertex.Y*VoxelSizeCm,
Vertex.Z*VoxelSizeCm);
NewNormals.push_back(Vertex.Normal);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>((Vertex.X-Origin.X)/256.0),
static_cast<float>((Vertex.Y-Origin.Y)/256.0));
+284 -4
View File
@@ -9,6 +9,7 @@
#include <cmath>
#include <cstdint>
#include <limits>
#include <numeric>
#include <sstream>
#include <string_view>
#include <unordered_map>
@@ -258,14 +259,20 @@ namespace floating_island_detail
recipe.routeConstraints.capsuleRadiusSubvoxels,
recipe.routeConstraints.capsuleHeightSubvoxels,
recipe.routeConstraints.maximumGradeQ16};
const std::uint32_t ScaleGcd = std::gcd(recipe.ownerVoxelSizeMicrons,
recipe.coreVoxelSizeMicrons);
const std::uint32_t ExpectedNumerator = ScaleGcd > 0
? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0;
const std::uint32_t ExpectedDenominator = ScaleGcd > 0
? recipe.coreVoxelSizeMicrons / ScaleGcd : 0;
if (recipe.schemaVersion != 1 || (recipe.geometryVersion != 1 && recipe.geometryVersion != 2)
|| (recipe.geometryVersion == 1
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|| recipe.unitsPerVoxel != Q
|| recipe.ownerVoxelSizeMicrons != 125000
|| recipe.coreVoxelSizeMicrons != 250000
|| recipe.ownerToCoreScaleNumerator != 1
|| recipe.ownerToCoreScaleDenominator != 2
|| recipe.coreVoxelSizeMicrons < 125000 || recipe.coreVoxelSizeMicrons > 250000
|| recipe.ownerToCoreScaleNumerator != ExpectedNumerator
|| recipe.ownerToCoreScaleDenominator != ExpectedDenominator
|| recipe.primaryIslandCount < 3 || recipe.primaryIslandCount > 9
|| recipe.layoutMaximumZSubvoxels <= recipe.layoutMinimumZSubvoxels
|| recipe.layoutMaximumZSubvoxels - recipe.layoutMinimumZSubvoxels > 2048 * Q
@@ -742,6 +749,103 @@ namespace floating_island_detail
return std::clamp(RoundQ(result), -MaxDensity, MaxDensity);
}
inline std::int64_t FieldDensityForTile(const FloatingIslandRecipeV1& recipe,
const FloatingIslandPlanV1& plan, std::int64_t x, std::int64_t y, std::int64_t z,
const FloatingIslandTileFeatureListV1& featureList)
{
if (plan.islands.empty()) return MaxDensity;
if (featureList.groupBounds.size() != plan.islands.size())
return FieldDensity(recipe, plan, x, y, z);
const auto& featureIndices = featureList.featureIndices;
const std::size_t islandCount = plan.islands.size();
const double blend = static_cast<double>(recipe.blendRadiusSubvoxels);
double result = static_cast<double>(MaxDensity);
const auto evaluateLocalGroup = [&](std::size_t islandIndex)
{
const auto& island = plan.islands[islandIndex];
double group = IslandDensity(recipe, island,
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z));
for (const std::uint32_t edgeIndex : featureIndices)
{
if (edgeIndex < islandCount) continue;
const std::size_t planIndex = static_cast<std::size_t>(edgeIndex) - islandCount;
if (planIndex >= plan.clusterEdges.size()) continue;
const auto& edge = plan.clusterEdges[planIndex];
if (edge.fromIslandId != island.stableId) continue;
bool topSurface = false;
double alongFraction = 0.0;
double connector = ConnectorDensity(recipe, edge,
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z),
&topSurface, &alongFraction);
if (recipe.geometryVersion >= 2)
connector = ApplySurfaceDetail(recipe, edge.stableId, connector,
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z),
topSurface || alongFraction < 0.12 || alongFraction > 0.88 ? 0.25 : 1.0);
group = SmoothMin(group, connector, blend);
}
return group;
};
const auto evaluateDistantGroup = [&](std::size_t islandIndex)
{
const auto& island = plan.islands[islandIndex];
double group = IslandDensity(recipe, island,
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z));
for (const auto& edge : plan.clusterEdges)
{
if (edge.fromIslandId != island.stableId) continue;
bool topSurface = false;
double alongFraction = 0.0;
double connector = ConnectorDensity(recipe, edge,
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z),
&topSurface, &alongFraction);
if (recipe.geometryVersion >= 2)
connector = ApplySurfaceDetail(recipe, edge.stableId, connector,
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z),
topSurface || alongFraction < 0.12 || alongFraction > 0.88 ? 0.25 : 1.0);
group = SmoothMin(group, connector, blend);
}
return group;
};
// Each local group keeps its complete ordered connector chain in the tile list.
// Les connecteurs d'un groupe local restent complets et dans leur ordre canonique.
for (const std::uint32_t index : featureIndices)
{
if (index >= islandCount) break;
result = std::min(result, evaluateLocalGroup(index));
}
// Air-side density magnitudes are part of the canonical field too. The per-tile list
// handles nearby groups; conservative group-box lower bounds prune distant exact SDFs.
// Les magnitudes d'air restent canoniques; les bornes de groupes eliminent les SDF lointains.
if (result >= 0.0)
{
if ((featureList.groupSearchOrder.size() != islandCount
|| featureList.groupDistanceLowerBoundsSubvoxels.size() != islandCount)
&& featureList.groupSearchComplete)
return FieldDensity(recipe, plan, x, y, z);
const std::size_t searchCount = std::min(featureList.groupSearchOrder.size(),
featureList.groupDistanceLowerBoundsSubvoxels.size());
for (std::size_t orderIndex = 0; orderIndex < searchCount; ++orderIndex)
{
const std::size_t islandIndex = featureList.groupSearchOrder[orderIndex];
if (islandIndex >= islandCount) return FieldDensity(recipe, plan, x, y, z);
if (std::binary_search(featureIndices.begin(), featureIndices.end(),
static_cast<std::uint32_t>(islandIndex))) continue;
const auto& bounds = featureList.groupBounds[islandIndex];
for (std::size_t axis = 0; axis < 3; ++axis)
if (bounds.minimumSubvoxels[axis] > bounds.maximumSubvoxels[axis])
return FieldDensity(recipe, plan, x, y, z);
if (static_cast<double>(featureList.groupDistanceLowerBoundsSubvoxels[orderIndex]) > result)
break;
result = std::min(result, evaluateDistantGroup(islandIndex));
}
}
return std::clamp(RoundQ(result), -MaxDensity, MaxDensity);
}
inline void SetIslandBounds(const FloatingIslandRecipeV1& recipe,
FloatingIslandAnchorV1& island)
{
@@ -1528,7 +1632,7 @@ bool FloatingIslandFieldV1::isValid() const noexcept
if (!recipe_ || !plan_ || recipe_->schemaVersion != 1
|| recipe_->unitsPerVoxel != floating_island_detail::Q
|| recipe_->primaryIslandCount < 3 || plan_->islands.size() < recipe_->primaryIslandCount
|| plan_->islands.size() > recipe_->primaryIslandCount + 2
|| plan_->islands.size() > 4096 || plan_->clusterEdges.size() > 4096
|| recipe_->minimumRadiusSubvoxels <= 0 || recipe_->maximumRadiusSubvoxels < recipe_->minimumRadiusSubvoxels
|| recipe_->blendRadiusSubvoxels < 0 || recipe_->contourWarpSubvoxels < 0) return false;
return std::all_of(plan_->islands.begin(), plan_->islands.end(), [](const auto& island)
@@ -1547,6 +1651,182 @@ std::int64_t FloatingIslandFieldV1::densitySubvoxels(std::int64_t x,
return floating_island_detail::FieldDensity(*recipe_, *plan_, x, y, z);
}
FloatingIslandTileFeatureListV1 FloatingIslandFieldV1::buildFeatureTileList(
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const
{
FloatingIslandTileFeatureListV1 result;
if (!isValid()) return result;
std::vector<WorldAssemblyFeatureBoundsV1> features;
features.reserve(plan_->islands.size() + plan_->clusterEdges.size());
for (const auto& island : plan_->islands)
features.push_back({island.stableId, island.influenceMinimumSubvoxels,
island.influenceMaximumSubvoxels});
for (const auto& edge : plan_->clusterEdges)
features.push_back({edge.stableId, edge.influenceMinimumSubvoxels,
edge.influenceMaximumSubvoxels});
result.featureIndices = BuildWorldAssemblyFeatureTileListV1(features,
tileMinimumSubvoxels, tileMaximumSubvoxels);
const std::size_t islandCount = plan_->islands.size();
result.groupBounds.resize(islandCount);
for (std::size_t islandIndex = 0; islandIndex < islandCount; ++islandIndex)
{
result.groupBounds[islandIndex].minimumSubvoxels = plan_->islands[islandIndex].influenceMinimumSubvoxels;
result.groupBounds[islandIndex].maximumSubvoxels = plan_->islands[islandIndex].influenceMaximumSubvoxels;
}
const auto findSourceIsland = [&](const FloatingIslandConnectorV1& edge)
{
return std::find_if(plan_->islands.begin(), plan_->islands.end(),
[&edge](const auto& island) { return island.stableId == edge.fromIslandId; });
};
for (const auto& edge : plan_->clusterEdges)
{
const auto source = findSourceIsland(edge);
if (source == plan_->islands.end())
{ result.groupBounds.clear(); result.featureIndices.clear(); return result; }
auto& group = result.groupBounds[static_cast<std::size_t>(source - plan_->islands.begin())];
for (std::size_t axis = 0; axis < 3; ++axis)
{
group.minimumSubvoxels[axis] = std::min(group.minimumSubvoxels[axis],
edge.influenceMinimumSubvoxels[axis]);
group.maximumSubvoxels[axis] = std::max(group.maximumSubvoxels[axis],
edge.influenceMaximumSubvoxels[axis]);
}
}
result.groupSearchOrder.resize(islandCount);
std::iota(result.groupSearchOrder.begin(), result.groupSearchOrder.end(), 0u);
const auto tileDistanceLowerBound = [&](
const std::array<std::int64_t, 3>& featureMinimum,
const std::array<std::int64_t, 3>& featureMaximum)
{
std::int64_t maximumGap = 0;
for (std::size_t axis = 0; axis < 3; ++axis)
{
if (featureMinimum[axis] > tileMaximumSubvoxels[axis])
maximumGap = std::max(maximumGap,
featureMinimum[axis] - tileMaximumSubvoxels[axis]);
else if (featureMaximum[axis] < tileMinimumSubvoxels[axis])
maximumGap = std::max(maximumGap,
tileMinimumSubvoxels[axis] - featureMaximum[axis]);
}
return maximumGap;
};
result.groupDistanceLowerBoundsSubvoxels.resize(islandCount);
for (std::size_t islandIndex = 0; islandIndex < islandCount; ++islandIndex)
{
const auto& island = plan_->islands[islandIndex];
const std::int64_t islandGap = tileDistanceLowerBound(
island.influenceMinimumSubvoxels, island.influenceMaximumSubvoxels);
double islandLowerBound = static_cast<double>(islandGap)
- floating_island_detail::IslandBlendMargin(*recipe_) - recipe_->roughnessSubvoxels;
if (recipe_->geometryVersion >= 2)
{
// Root lobes use a scaled ellipsoid distance, so their positive values can be
// substantially smaller than distance to the island AABB. Supported root radii
// guarantee minAxis/maxAxis >= 0.22; include their smooth-min budget before pruning.
// Les racines utilisent une distance d'ellipsoide mise a l'echelle; borner leur pente.
const double radius = static_cast<double>(island.radiusSubvoxels);
const double rootBlend = std::clamp(radius * 0.12,
2.0 * floating_island_detail::Q, 5.0 * floating_island_detail::Q);
const double rootSmoothBudget = static_cast<double>(
floating_island_detail::RootCount(*recipe_, island)) * rootBlend * 0.25;
const double rootLowerBound = 0.22 * static_cast<double>(islandGap)
- recipe_->roughnessSubvoxels - rootSmoothBudget;
islandLowerBound = std::min(islandLowerBound, rootLowerBound);
}
double groupLowerBound = islandLowerBound;
for (const auto& edge : plan_->clusterEdges)
{
if (edge.fromIslandId != island.stableId) continue;
const std::int64_t edgeGap = tileDistanceLowerBound(
edge.influenceMinimumSubvoxels, edge.influenceMaximumSubvoxels);
const double edgeLowerBound = static_cast<double>(edgeGap) - recipe_->roughnessSubvoxels;
groupLowerBound = std::min(groupLowerBound, edgeLowerBound)
- static_cast<double>(recipe_->blendRadiusSubvoxels) * 0.25;
}
// Floor with one subvoxel of slack so quantization cannot make a lower bound optimistic.
// Arrondir vers le bas avec une marge pour ne jamais surestimer la borne.
result.groupDistanceLowerBoundsSubvoxels[islandIndex] =
static_cast<std::int64_t>(std::floor(groupLowerBound)) - 1;
}
std::sort(result.groupSearchOrder.begin(), result.groupSearchOrder.end(),
[&result](std::uint32_t a, std::uint32_t b)
{
const auto distanceA = result.groupDistanceLowerBoundsSubvoxels[a];
const auto distanceB = result.groupDistanceLowerBoundsSubvoxels[b];
return distanceA == distanceB ? a < b : distanceA < distanceB;
});
// Store distance in search order so query samples use one sequential early-out walk.
// Ranger les distances dans le meme ordre pour arreter le parcours au premier echec.
std::vector<std::int64_t> orderedDistances;
orderedDistances.reserve(islandCount);
for (const std::uint32_t islandIndex : result.groupSearchOrder)
orderedDistances.push_back(result.groupDistanceLowerBoundsSubvoxels[islandIndex]);
result.groupDistanceLowerBoundsSubvoxels = std::move(orderedDistances);
std::vector<std::uint8_t> selectedGroups(islandCount, 0);
for (const std::uint32_t featureIndex : result.featureIndices)
{
if (featureIndex < islandCount) selectedGroups[featureIndex] = 1;
else
{
const std::size_t edgeIndex = static_cast<std::size_t>(featureIndex) - islandCount;
if (edgeIndex >= plan_->clusterEdges.size())
{ result.groupBounds.clear(); result.featureIndices.clear(); return result; }
const auto source = findSourceIsland(plan_->clusterEdges[edgeIndex]);
if (source == plan_->islands.end())
{ result.groupBounds.clear(); result.featureIndices.clear(); return result; }
selectedGroups[static_cast<std::size_t>(source - plan_->islands.begin())] = 1;
}
}
// A connector is composed into its source island's group. Keep that source and every
// outgoing connector in the tile list so the ordered smooth-min chain remains exact.
// Un connecteur fait partie de son ile source : garder toute la chaine de fondu exacte.
for (std::size_t islandIndex = 0; islandIndex < islandCount; ++islandIndex)
{
if (!selectedGroups[islandIndex]) continue;
auto insertSorted = [&](std::uint32_t value)
{
const auto insertion = std::lower_bound(result.featureIndices.begin(),
result.featureIndices.end(), value);
if (insertion == result.featureIndices.end() || *insertion != value)
result.featureIndices.insert(insertion, value);
};
insertSorted(static_cast<std::uint32_t>(islandIndex));
for (std::size_t edgeIndex = 0; edgeIndex < plan_->clusterEdges.size(); ++edgeIndex)
if (plan_->clusterEdges[edgeIndex].fromIslandId == plan_->islands[islandIndex].stableId)
insertSorted(static_cast<std::uint32_t>(islandCount + edgeIndex));
}
return result;
}
std::int64_t FloatingIslandFieldV1::densitySubvoxelsForTile(std::int64_t x,
std::int64_t y, std::int64_t z,
const FloatingIslandTileFeatureListV1& featureList) const noexcept
{
if (!isValid() || !floating_island_detail::ValidScalar(x)
|| !floating_island_detail::ValidScalar(y) || !floating_island_detail::ValidScalar(z))
return floating_island_detail::MaxDensity;
std::uint32_t previous = 0;
bool first = true;
const std::size_t featureCount = plan_->islands.size() + plan_->clusterEdges.size();
if (featureList.groupBounds.size() != plan_->islands.size())
return floating_island_detail::FieldDensity(*recipe_, *plan_, x, y, z);
for (const std::uint32_t index : featureList.featureIndices)
{
if (index >= featureCount || (!first && index <= previous))
return floating_island_detail::FieldDensity(*recipe_, *plan_, x, y, z);
first = false;
previous = index;
}
return floating_island_detail::FieldDensityForTile(*recipe_, *plan_, x, y, z, featureList);
}
FieldInterval FloatingIslandFieldV1::interval(std::int64_t minX, std::int64_t minY,
std::int64_t minZ, std::int64_t maxX, std::int64_t maxY, std::int64_t maxZ) const noexcept
{
+73 -17
View File
@@ -32,6 +32,58 @@
class FStandaloneShapeWorld
{
public:
struct FNativeIslandTileFeatureContext
{
const VoxelForgeCore::FloatingIslandPlanV1* Plan = nullptr;
const VoxelForgeCore::FloatingIslandTileFeatureListV1* FeatureList = nullptr;
};
class FScopedNativeIslandTileFeatureContext
{
public:
FScopedNativeIslandTileFeatureContext(const VoxelForgeCore::FloatingIslandPlanV1* plan,
const VoxelForgeCore::FloatingIslandTileFeatureListV1* featureList)
{
Context.Plan = plan;
Context.FeatureList = featureList;
Previous = SetNativeIslandTileFeatureContext(plan && featureList ? &Context : nullptr);
}
~FScopedNativeIslandTileFeatureContext()
{ RestoreNativeIslandTileFeatureContext(Previous); }
FScopedNativeIslandTileFeatureContext(const FScopedNativeIslandTileFeatureContext&) = delete;
FScopedNativeIslandTileFeatureContext& operator=(const FScopedNativeIslandTileFeatureContext&) = delete;
private:
FNativeIslandTileFeatureContext Context{};
const FNativeIslandTileFeatureContext* Previous = nullptr;
};
static const FNativeIslandTileFeatureContext* SetNativeIslandTileFeatureContext(
const FNativeIslandTileFeatureContext* context)
{
auto& active = ActiveNativeIslandTileFeatureContext();
const auto* previous = active;
active = context;
return previous;
}
static void RestoreNativeIslandTileFeatureContext(const FNativeIslandTileFeatureContext* context)
{ ActiveNativeIslandTileFeatureContext() = context; }
static const FNativeIslandTileFeatureContext* GetNativeIslandTileFeatureContext()
{ return ActiveNativeIslandTileFeatureContext(); }
double GetCoreVoxelSizeCm() const
{
return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0
? static_cast<double>(VOXEL_SIZE)
: static_cast<double>(Owner->CoreVoxelSizeMicrons) / 10000.0;
}
double GetCoreVoxelsPerOwnerChunk() 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();
}
enum class EDetailVariant : uint8 { Coherent, Smooth, PerVoxel };
struct FBoxClassification
{
@@ -1088,6 +1140,12 @@ public:
}
private:
static const FNativeIslandTileFeatureContext*& ActiveNativeIslandTileFeatureContext()
{
static thread_local const FNativeIslandTileFeatureContext* Context = nullptr;
return Context;
}
static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z)
{
constexpr uint64 Mask=0x1FFFFFull;
@@ -1249,10 +1307,9 @@ public:
bOtherStrateOverlap=true;
}
// Floating islands use the explicit 12.5 cm owner to 25 cm Core mapping:
// one legacy 4 m chunk occupies 16 Core voxels.
// Les iles flottantes suivent l'echelle explicite proprietaire/Core: 16 voxels Core.
constexpr int32 CoreVoxelsPerIslandChunk=CHUNK_SIZE/2;
// 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])
@@ -1340,11 +1397,8 @@ public:
{
if(!IslandPlan)return Finish(EVoxelTileClass::Mixed,true);
constexpr std::int64_t Scale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr std::int64_t CoreVoxelsPerLegacyChunk=CHUNK_SIZE/2;
const std::int64_t Bottom=static_cast<std::int64_t>(IslandPlan->bottomChunkZ)
*CoreVoxelsPerLegacyChunk*Scale;
const std::int64_t Top=(static_cast<std::int64_t>(IslandPlan->topChunkZ)+1)
*CoreVoxelsPerLegacyChunk*Scale;
const std::int64_t Bottom=IslandPlan->manifest.recipe.layoutMinimumZSubvoxels;
const std::int64_t Top=IslandPlan->manifest.recipe.layoutMaximumZSubvoxels;
const std::int64_t Seal=IslandPlan->manifest.recipe.boundarySealSubvoxels;
const std::int64_t BoxBottom=static_cast<std::int64_t>(BMin[2])*Scale;
const std::int64_t BoxTop=static_cast<std::int64_t>(BMax[2])*Scale;
@@ -1889,7 +1943,7 @@ private:
||!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.
constexpr int32 CoreVoxelsPerLegacyChunk=CHUNK_SIZE/2;
const double CoreVoxelsPerLegacyChunk=GetCoreVoxelsPerOwnerChunk();
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CoreVoxelsPerLegacyChunk));
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
!=ECaveGeneratorType::FloatingIslands)return false;
@@ -1907,13 +1961,15 @@ private:
const VoxelForgeCore::FloatingIslandFieldV1 Field(
Plan->manifest.recipe,Plan->manifest.plan);
if(!Field.isValid())return false;
std::int64_t Density=Field.densitySubvoxels(ToFixed(X),ToFixed(Y),ToFixed(Z));
// A legacy 4 m chunk is 16 Core voxels at the published 12.5 cm to 25 cm scale.
// Un chunk legacy de 4 m represente 16 voxels Core avec l'echelle publiee.
const std::int64_t Bottom=static_cast<std::int64_t>(Plan->bottomChunkZ)
*CoreVoxelsPerLegacyChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const std::int64_t Top=(static_cast<std::int64_t>(Plan->topChunkZ)+1)
*CoreVoxelsPerLegacyChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
std::int64_t Density=0;
const auto* TileContext=GetNativeIslandTileFeatureContext();
if(TileContext&&TileContext->Plan==&Plan->manifest.plan&&TileContext->FeatureList)
Density=Field.densitySubvoxelsForTile(ToFixed(X),ToFixed(Y),ToFixed(Z),
*TileContext->FeatureList);
else
Density=Field.densitySubvoxels(ToFixed(X),ToFixed(Y),ToFixed(Z));
const std::int64_t Bottom=Plan->manifest.recipe.layoutMinimumZSubvoxels;
const std::int64_t Top=Plan->manifest.recipe.layoutMaximumZSubvoxels;
const std::int64_t Seal=Plan->manifest.recipe.boundarySealSubvoxels;
const std::int64_t Zq=ToFixed(Z);
if(Zq>=Bottom&&Zq<Bottom+Seal)Density=std::min(Density,-std::max<std::int64_t>(1,Zq-Bottom));
+75 -12
View File
@@ -21,6 +21,8 @@ namespace vfcore::detail
namespace VoxelForgeCore
{
using namespace ::vfcore::detail;
static_assert(CHUNK_SIZE == static_cast<int32>(CoreTileVoxelCount),
"Core tile indexing and VoxelForgeCore::CoreTileVoxelCount must agree");
struct ResolvedWorldSnapshot::Data
{
@@ -75,7 +77,9 @@ namespace VoxelForgeCore
WorldConfiguration MakeConfiguration(const FStandaloneOwnerWorld& owner)
{
WorldConfiguration out;
WorldConfiguration out;
out.voxelSizeMicrons=owner.CoreVoxelSizeMicrons;
out.tileVoxelCount=CoreTileVoxelCount;
out.seed=owner.Seed;
out.clipRadius=owner.ClipRadius;
out.maxLod=owner.MaxClipLevel;
@@ -370,8 +374,9 @@ namespace VoxelForgeCore
const int32 GY=static_cast<int32>(Index/static_cast<std::size_t>(Cells+1));
const double XSub=static_cast<double>(TileOrigin.X+GX*GridStepVoxels)*Scale;
const double YSub=static_cast<double>(TileOrigin.Y+GY*GridStepVoxels)*Scale;
AddedVertices.emplace_back(XSub/Scale*25.0,YSub/Scale*25.0,
Sample.Cap/Scale*25.0);
const double VoxelSizeCm=static_cast<double>(Owner.CoreVoxelSizeMicrons)/10000.0;
AddedVertices.emplace_back(XSub/Scale*VoxelSizeCm,YSub/Scale*VoxelSizeCm,
Sample.Cap/Scale*VoxelSizeCm);
AddedNormals.push_back(Sample.Normal);
if(bHasUVs)AddedUVs.emplace_back(static_cast<float>(XSub/Scale),
static_cast<float>(YSub/Scale));
@@ -426,8 +431,9 @@ namespace VoxelForgeCore
for(int32 I=0;I<Count;++I)
{
const auto& V=PolygonB[I];
AddedVertices.emplace_back(V.XSub/Scale*25.0,
V.YSub/Scale*25.0,V.ZSub/Scale*25.0);
const double VoxelSizeCm=static_cast<double>(Owner.CoreVoxelSizeMicrons)/10000.0;
AddedVertices.emplace_back(V.XSub/Scale*VoxelSizeCm,
V.YSub/Scale*VoxelSizeCm,V.ZSub/Scale*VoxelSizeCm);
AddedNormals.push_back(V.Normal);
if(bHasUVs)AddedUVs.emplace_back(static_cast<float>(V.XSub/Scale),
static_cast<float>(V.YSub/Scale));
@@ -894,12 +900,14 @@ namespace VoxelForgeCore
const FStandaloneShapeWorld* tileShapeContext=page?page.get():impl_->shape.get();
UVoxelMarchingCubesMesher mesher;
mesher.SetGenerator(world.Generator.get());
mesher.SetGeometryVoxelSizeCm(25.0f);
mesher.SetGeometryVoxelSizeCm(static_cast<float>(world.CoreVoxelSizeMicrons)/10000.0f);
mesher.bStandaloneSkipUvAndColors=options.skipUvAndColors;
mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors;
mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups;
mesher.bGenerateSkirts=world.bGenerateSkirts;
mesher.SkirtCells=static_cast<float>(world.SkirtCells)*12.5f/25.0f;
mesher.SkirtCells=static_cast<float>(world.SkirtCells)
*static_cast<float>(world.OwnerVoxelSizeMicrons)
/static_cast<float>(world.CoreVoxelSizeMicrons);
mesher.LODOctaveDrop=world.LODOctaveDrop;
const int32 level=key.lod,extent=CHUNK_SIZE<<level;
const FVoxelTileKey nativeKey=ToNative(key);
@@ -1052,9 +1060,13 @@ namespace VoxelForgeCore
// La surface 2D et les bornes de volume du manifeste definissent tout le support Z.
// The 2D surface and manifest volume bounds cover every possible density crossing.
const double TileCenterZ=static_cast<double>(origin.Z)+static_cast<double>(extent)*0.5;
const int32 CenterChunkZ=FMath::FloorToInt(TileCenterZ/static_cast<double>(CHUNK_SIZE));
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 int32 CenterChunkZ=FMath::FloorToInt(TileCenterZ/CoreVoxelsPerOwnerChunk);
const int32 StrateIndex=world.Manager->GetStrateIndex(
static_cast<float>(TileCenterZ*VOXEL_SIZE));
static_cast<float>(TileCenterZ*CoreVoxelSizeCm));
const auto PlanIt=std::find_if(impl_->worldAssembly->plan.strates.begin(),
impl_->worldAssembly->plan.strates.end(),[StrateIndex,CenterChunkZ](const auto& Plan)
{return Plan.strateIndex==StrateIndex&&CenterChunkZ<=Plan.topChunkZ
@@ -1142,9 +1154,60 @@ namespace VoxelForgeCore
}
}
}
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
const WorldAssemblyFloatingIslandPlanV1* IslandTilePlan=nullptr;
FloatingIslandTileFeatureListV1 IslandTileFeatureList;
if(bHasWorldAssembly)
{
for(const auto& entry:impl_->worldAssembly->floatingIslands)
{
const auto& scale=entry.manifest.recipe;
const std::int64_t ownerToCoreNumerator=scale.ownerToCoreScaleNumerator;
const std::int64_t ownerToCoreDenominator=scale.ownerToCoreScaleDenominator;
if(ownerToCoreNumerator<=0||ownerToCoreDenominator<=0)continue;
const std::int64_t coreVoxelsPerOwnerChunk=static_cast<std::int64_t>(CHUNK_SIZE)
*ownerToCoreNumerator/ownerToCoreDenominator;
if(coreVoxelsPerOwnerChunk<=0)continue;
const std::int64_t stratumBottom=static_cast<std::int64_t>(entry.bottomChunkZ)
*coreVoxelsPerOwnerChunk;
const std::int64_t stratumTop=(static_cast<std::int64_t>(entry.topChunkZ)+1)
*coreVoxelsPerOwnerChunk;
if(static_cast<std::int64_t>(origin.Z)>stratumTop
||static_cast<std::int64_t>(origin.Z)+extent<stratumBottom)continue;
IslandTilePlan=&entry;
break;
}
if(IslandTilePlan)
{
const FloatingIslandFieldV1 field(IslandTilePlan->manifest.recipe,
IslandTilePlan->manifest.plan);
const std::int64_t scale=WorldAssemblySubvoxelsPerVoxel;
// Include the mesher's one-sample gradient halo on each face.
// Inclure la marge d'un echantillon du maillage sur chaque face.
const std::int64_t sampleHalo=static_cast<std::int64_t>(step)*scale;
const std::array<std::int64_t,3> tileMinimum={
static_cast<std::int64_t>(origin.X)*scale-sampleHalo,
static_cast<std::int64_t>(origin.Y)*scale-sampleHalo,
static_cast<std::int64_t>(origin.Z)*scale-sampleHalo};
const std::array<std::int64_t,3> tileMaximum={
(static_cast<std::int64_t>(origin.X)+extent)*scale+sampleHalo,
(static_cast<std::int64_t>(origin.Y)+extent)*scale+sampleHalo,
(static_cast<std::int64_t>(origin.Z)+extent)*scale+sampleHalo};
IslandTileFeatureList=field.buildFeatureTileList(tileMinimum,tileMaximum);
}
}
FVoxelMeshData mesh;
if(IslandTilePlan)
{
FStandaloneShapeWorld::FScopedNativeIslandTileFeatureContext tileFeatureContext(
&IslandTilePlan->manifest.plan,&IslandTileFeatureList);
mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
}
else
mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MeshingStart).count();
const FStandaloneShapeWorld& farShape=tileShape.HasFarWorld()?tileShape:*impl_->shape;
+50 -6
View File
@@ -11,9 +11,47 @@
#include <iomanip>
#include <limits>
#include <sstream>
#include <stdexcept>
namespace VoxelForgeCore
{
std::vector<std::uint32_t> BuildWorldAssemblyFeatureTileListV1(
std::span<const WorldAssemblyFeatureBoundsV1> features,
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
const std::array<std::int64_t, 3>& tileMaximumSubvoxels)
{
if (features.size() > std::numeric_limits<std::uint32_t>::max())
throw std::length_error("world-assembly feature list exceeds 32-bit indexing");
const auto allFeatures = [&]()
{
std::vector<std::uint32_t> result;
result.reserve(features.size());
for (std::size_t index = 0; index < features.size(); ++index)
result.push_back(static_cast<std::uint32_t>(index));
return result;
};
for (std::size_t axis = 0; axis < 3; ++axis)
if (tileMinimumSubvoxels[axis] > tileMaximumSubvoxels[axis]) return allFeatures();
for (const auto& feature : features)
for (std::size_t axis = 0; axis < 3; ++axis)
if (feature.minimumSubvoxels[axis] > feature.maximumSubvoxels[axis]) return allFeatures();
std::vector<std::uint32_t> result;
result.reserve(features.size());
for (std::size_t index = 0; index < features.size(); ++index)
{
const auto& feature = features[index];
bool intersects = true;
for (std::size_t axis = 0; axis < 3; ++axis)
intersects = intersects
&& feature.minimumSubvoxels[axis] <= tileMaximumSubvoxels[axis]
&& feature.maximumSubvoxels[axis] >= tileMinimumSubvoxels[axis];
if (intersects) result.push_back(static_cast<std::uint32_t>(index));
}
return result;
}
namespace world_assembly_detail
{
constexpr std::int64_t Q16 = 1ll << 16;
@@ -1514,14 +1552,20 @@ bool WorldAssemblyManifest::finalize(std::string& error)
if (!islands.manifest.validate(error)) return false;
if (islands.manifest.recipe.seed != recipe.seed)
{ error = "floating-island seed does not match the world assembly season seed"; return false; }
constexpr std::int64_t CoreVoxelsPerLegacyChunk = 16;
const std::int64_t bottomQ = static_cast<std::int64_t>(islands.bottomChunkZ)
* CoreVoxelsPerLegacyChunk * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t topQ = (static_cast<std::int64_t>(islands.topChunkZ) + 1)
* CoreVoxelsPerLegacyChunk * WorldAssemblySubvoxelsPerVoxel;
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 (islands.manifest.recipe.layoutMinimumZSubvoxels < bottomQ
|| islands.manifest.recipe.layoutMaximumZSubvoxels > topQ)
{ error = "floating-island recipe layout escapes the resolved 12.5 cm legacy strate bounds"; return false; }
{ error = "floating-island recipe layout escapes the resolved owner strate bounds"; return false; }
islandStrateIds.push_back(islands.strateIndex);
}
recipe.contentHash = world_assembly_detail::HashRecipe(recipe);