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:
1 parent
111cf3d262
commit
d6e69420e8
25 files changed
+2136
-250
No files matched your search
@@ -24,6 +24,8 @@
|
||||
|
||||
namespace VoxelForgeCore
|
||||
{
|
||||
inline constexpr std::uint32_t CoreTileVoxelCount = 32;
|
||||
|
||||
struct Vec2f { float x = 0.0f, y = 0.0f; };
|
||||
struct Vec3d { double x = 0.0, y = 0.0, z = 0.0; };
|
||||
struct Vec3i
|
||||
@@ -34,11 +36,26 @@ namespace VoxelForgeCore
|
||||
|
||||
struct TileKey
|
||||
{
|
||||
Vec3i coordinate{}; // Units of (32 << lod) voxels.
|
||||
Vec3i coordinate{}; // Units of (CoreTileVoxelCount << lod) voxels.
|
||||
std::int32_t lod = 0;
|
||||
friend bool operator==(const TileKey&, const TileKey&) = default;
|
||||
};
|
||||
|
||||
// Reusable broad-phase input for any bounded world-assembly feature family. Bounds are
|
||||
// conservative and inclusive; an invalid box makes the builder fail open by returning all
|
||||
// feature indices. Les bornes sont inclusives; une borne invalide garde toutes les features.
|
||||
struct WorldAssemblyFeatureBoundsV1
|
||||
{
|
||||
std::uint64_t stableId = 0;
|
||||
std::array<std::int64_t, 3> minimumSubvoxels{};
|
||||
std::array<std::int64_t, 3> maximumSubvoxels{};
|
||||
};
|
||||
|
||||
VFCORE_API 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);
|
||||
|
||||
struct VFCORE_API TileKeyHash
|
||||
{
|
||||
std::size_t operator()(const TileKey& key) const noexcept;
|
||||
@@ -125,6 +142,11 @@ namespace VoxelForgeCore
|
||||
|
||||
struct WorldConfiguration
|
||||
{
|
||||
// Physical resolution is part of the resolved world identity. Tile coordinates remain in
|
||||
// abstract voxel units; meshing, collision and Unreal transforms use this size in cm.
|
||||
// La resolution physique fait partie de l'identite resolue; les tuiles restent en voxels.
|
||||
std::uint32_t voxelSizeMicrons = 250000;
|
||||
std::uint32_t tileVoxelCount = CoreTileVoxelCount;
|
||||
std::int32_t seed = 0;
|
||||
std::int32_t clipRadius = 0;
|
||||
std::int32_t maxLod = 0;
|
||||
@@ -292,8 +314,8 @@ namespace VoxelForgeCore
|
||||
// bounded lobed roots and arched spans without changing the feature-family schema.
|
||||
std::uint32_t geometryVersion = 2;
|
||||
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
|
||||
// Import scale is explicit: legacy owner voxels are 12.5 cm and Core voxels are 25 cm.
|
||||
// L'echelle d'import est explicite : voxel owner de 12,5 cm et voxel Core de 25 cm.
|
||||
// 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.
|
||||
std::uint32_t ownerVoxelSizeMicrons = 125000;
|
||||
std::uint32_t coreVoxelSizeMicrons = 250000;
|
||||
std::uint32_t ownerToCoreScaleNumerator = 1;
|
||||
@@ -306,8 +328,8 @@ namespace VoxelForgeCore
|
||||
std::int64_t layoutMaximumZSubvoxels = 0;
|
||||
std::uint32_t primaryIslandCount = 3;
|
||||
std::int64_t islandDensityQ16 = 1ll << 15;
|
||||
// Legacy owner lengths use 12.5 cm voxels; Core uses 25 cm, so halve the owner lengths.
|
||||
// Les longueurs owner utilisent des voxels de 12,5 cm ; Core utilise 25 cm, donc les diviser par deux.
|
||||
// Legacy owner lengths use ownerVoxelSizeMicrons; the scale ratio converts them to Core voxels.
|
||||
// Les longueurs owner utilisent ownerVoxelSizeMicrons; le rapport les convertit en voxels Core.
|
||||
std::int64_t spacingSubvoxels = 56 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t minimumRadiusSubvoxels = 12 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t maximumRadiusSubvoxels = 24 * WorldAssemblySubvoxelsPerVoxel;
|
||||
@@ -364,6 +386,27 @@ namespace VoxelForgeCore
|
||||
std::uint64_t contentHash = 0;
|
||||
};
|
||||
|
||||
struct FloatingIslandFeatureGroupBoundsV1
|
||||
{
|
||||
std::array<std::int64_t, 3> minimumSubvoxels{};
|
||||
std::array<std::int64_t, 3> maximumSubvoxels{};
|
||||
};
|
||||
|
||||
struct FloatingIslandTileFeatureListV1
|
||||
{
|
||||
// Flat stable-order IDs for island and connector features that touch this tile.
|
||||
// Identifiants ordonnes des features qui touchent ce carreau.
|
||||
std::vector<std::uint32_t> featureIndices;
|
||||
// Cheap conservative group bounds preserve exact positive SDF distances while distant
|
||||
// feature evaluators are skipped. Bornes legeres pour garder les distances SDF exactes.
|
||||
std::vector<FloatingIslandFeatureGroupBoundsV1> groupBounds;
|
||||
// Groups are ordered by a tile-wide conservative distance lower bound. Samples stop at
|
||||
// the first group that cannot beat their current density. Ordre par distance minimale.
|
||||
std::vector<std::uint32_t> groupSearchOrder;
|
||||
std::vector<std::int64_t> groupDistanceLowerBoundsSubvoxels;
|
||||
bool groupSearchComplete = true;
|
||||
};
|
||||
|
||||
struct VFCORE_API FloatingIslandManifestV1
|
||||
{
|
||||
FloatingIslandRecipeV1 recipe;
|
||||
@@ -464,6 +507,12 @@ namespace VoxelForgeCore
|
||||
bool isValid() const noexcept;
|
||||
std::int64_t densitySubvoxels(std::int64_t xSubvoxels,
|
||||
std::int64_t ySubvoxels, std::int64_t zSubvoxels) const noexcept;
|
||||
FloatingIslandTileFeatureListV1 buildFeatureTileList(
|
||||
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
||||
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const;
|
||||
std::int64_t densitySubvoxelsForTile(std::int64_t xSubvoxels,
|
||||
std::int64_t ySubvoxels, std::int64_t zSubvoxels,
|
||||
const FloatingIslandTileFeatureListV1& featureList) const noexcept;
|
||||
FieldInterval interval(std::int64_t minimumXSubvoxels,
|
||||
std::int64_t minimumYSubvoxels, std::int64_t minimumZSubvoxels,
|
||||
std::int64_t maximumXSubvoxels, std::int64_t maximumYSubvoxels,
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user