wip(assembler): round S2 (Sol) -- shared per-tile feature queries for every Core family; island placement 30x faster
Island-only worker context replaced by a shared worker-local candidate domain (WorldAssemblyFeatureTileListV1 / ScopedWorldAssemblyFeatureQueriesV1) serving SurfaceWorld, FloatingIslands and far-shell queries; a per-domain cost hint keeps the full scan on small plans. Content placement prepares the same queries and skips proven air: owner island cell p95 7.07 -> 0.24 ms; populated fixture 572 exact placements, 3.27 s -> 29 ms. Full/candidate hashes equal with dropped-feature controls on both families; S1 default W1/W8/W16 hashes reproduced; Core-off and M2 fixtures pass. Default contract for future families in ARCHITECTURE 8.23. Run on gpt-6.1-sol medium. 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
d6e69420e8
commit
6ddb834b1c
19 files changed
+1849
-132
No files matched your search
@@ -56,6 +56,18 @@ namespace VoxelForgeCore
|
||||
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
||||
const std::array<std::int64_t, 3>& tileMaximumSubvoxels);
|
||||
|
||||
struct WorldAssemblyFeatureColumnRangeV1
|
||||
{
|
||||
bool known = false;
|
||||
bool empty = false;
|
||||
std::int64_t minimumZSubvoxels = 0, maximumZSubvoxels = 0;
|
||||
};
|
||||
// Only families whose base is air may use this solid-support union to shorten a march.
|
||||
// Reserve aux familles a fond d'air : union conservative du support solide de la colonne.
|
||||
VFCORE_API WorldAssemblyFeatureColumnRangeV1 BuildWorldAssemblyFeatureColumnRangeV1(
|
||||
std::span<const WorldAssemblyFeatureBoundsV1> solidSupportBounds,
|
||||
std::int64_t xSubvoxels, std::int64_t ySubvoxels);
|
||||
|
||||
struct VFCORE_API TileKeyHash
|
||||
{
|
||||
std::size_t operator()(const TileKey& key) const noexcept;
|
||||
@@ -114,6 +126,8 @@ namespace VoxelForgeCore
|
||||
double classificationMilliseconds = 0.0;
|
||||
double columnComputeMilliseconds = 0.0;
|
||||
double meshingMilliseconds = 0.0;
|
||||
double featureListMilliseconds = 0.0;
|
||||
double farShellMilliseconds = 0.0;
|
||||
bool tileWasClassified = false;
|
||||
bool meshingSkipped = false;
|
||||
bool featureRefinedResolution = false;
|
||||
@@ -386,25 +400,60 @@ namespace VoxelForgeCore
|
||||
std::uint64_t contentHash = 0;
|
||||
};
|
||||
|
||||
struct FloatingIslandFeatureGroupBoundsV1
|
||||
struct WorldAssemblyFeatureGroupBoundsV1
|
||||
{
|
||||
std::array<std::int64_t, 3> minimumSubvoxels{};
|
||||
std::array<std::int64_t, 3> maximumSubvoxels{};
|
||||
};
|
||||
|
||||
struct FloatingIslandTileFeatureListV1
|
||||
struct WorldAssemblyFeatureTileListV1
|
||||
{
|
||||
// Flat stable-order IDs for island and connector features that touch this tile.
|
||||
// Identifiants ordonnes des features qui touchent ce carreau.
|
||||
// Stable recipe-order indices for features that touch this query domain.
|
||||
// Indices dans l'ordre de recette des features touchant ce domaine.
|
||||
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;
|
||||
// Optional ordered-union group certificates preserve exact positive density values.
|
||||
// Certificats optionnels de groupes d'union pour conserver les valeurs positives exactes.
|
||||
std::vector<WorldAssemblyFeatureGroupBoundsV1> 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;
|
||||
// Performance hint only: the shared engine may prefer the exact full scan on small plans.
|
||||
// Indication de cout seulement : le moteur peut preferer le parcours complet exact.
|
||||
bool candidateEvaluationUseful = true;
|
||||
// Exact-domain guard: outside queries always fall back to the canonical full scan.
|
||||
// Hors du domaine prepare, revenir au parcours complet canonique.
|
||||
std::array<std::int64_t, 3> minimumSubvoxels{};
|
||||
std::array<std::int64_t, 3> maximumSubvoxels{};
|
||||
};
|
||||
|
||||
using FloatingIslandTileFeatureListV1 = WorldAssemblyFeatureTileListV1;
|
||||
using FloatingIslandFeatureGroupBoundsV1 = WorldAssemblyFeatureGroupBoundsV1;
|
||||
|
||||
struct WorldAssemblyFeatureQueryV1
|
||||
{
|
||||
const void* recipe = nullptr;
|
||||
const void* plan = nullptr;
|
||||
WorldAssemblyFeatureTileListV1 features;
|
||||
};
|
||||
|
||||
// One worker-local, nestable scope for meshing, shells and placement in every family.
|
||||
// Une portee locale au worker, emboitable, pour toutes les familles et tous les consommateurs.
|
||||
class VFCORE_API ScopedWorldAssemblyFeatureQueriesV1
|
||||
{
|
||||
public:
|
||||
explicit ScopedWorldAssemblyFeatureQueriesV1(std::span<const WorldAssemblyFeatureQueryV1> queries,
|
||||
bool forceCandidatesForValidation = false) noexcept;
|
||||
~ScopedWorldAssemblyFeatureQueriesV1();
|
||||
ScopedWorldAssemblyFeatureQueriesV1(const ScopedWorldAssemblyFeatureQueriesV1&) = delete;
|
||||
ScopedWorldAssemblyFeatureQueriesV1& operator=(const ScopedWorldAssemblyFeatureQueriesV1&) = delete;
|
||||
static const WorldAssemblyFeatureTileListV1* find(const void* recipe, const void* plan,
|
||||
std::int64_t x, std::int64_t y, std::int64_t z, bool columnOnly = false) noexcept;
|
||||
private:
|
||||
std::span<const WorldAssemblyFeatureQueryV1> queries_;
|
||||
bool forceCandidates_ = false;
|
||||
const ScopedWorldAssemblyFeatureQueriesV1* previous_ = nullptr;
|
||||
};
|
||||
|
||||
struct VFCORE_API FloatingIslandManifestV1
|
||||
@@ -510,6 +559,7 @@ namespace VoxelForgeCore
|
||||
FloatingIslandTileFeatureListV1 buildFeatureTileList(
|
||||
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
||||
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const;
|
||||
std::vector<WorldAssemblyFeatureBoundsV1> solidSupportBounds() const;
|
||||
std::int64_t densitySubvoxelsForTile(std::int64_t xSubvoxels,
|
||||
std::int64_t ySubvoxels, std::int64_t zSubvoxels,
|
||||
const FloatingIslandTileFeatureListV1& featureList) const noexcept;
|
||||
@@ -601,6 +651,9 @@ namespace VoxelForgeCore
|
||||
bool isValid() const noexcept;
|
||||
HeightfieldPointSample point(std::int64_t xSubvoxels, std::int64_t ySubvoxels,
|
||||
std::int64_t zSubvoxels) const noexcept;
|
||||
WorldAssemblyFeatureTileListV1 buildFeatureTileList(
|
||||
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
||||
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const;
|
||||
HeightfieldBoxClass classifyLatticeBox(std::int64_t minimumXSubvoxels,
|
||||
std::int64_t minimumYSubvoxels, std::int64_t minimumZSubvoxels,
|
||||
std::int64_t maximumXSubvoxels, std::int64_t maximumYSubvoxels,
|
||||
@@ -928,6 +981,9 @@ namespace VoxelForgeCore
|
||||
bool skipUv = false;
|
||||
bool skipColors = false;
|
||||
bool skipMaterialGroups = false;
|
||||
// Validation control only; never participates in recipe/world identity.
|
||||
// Controle de validation seulement, sans effet sur l'identite du monde.
|
||||
bool useAssemblyFeatureQueries = true;
|
||||
};
|
||||
|
||||
class VFCORE_API World
|
||||
|
||||
@@ -1645,18 +1645,18 @@ bool FloatingIslandFieldV1::isValid() const noexcept
|
||||
std::int64_t FloatingIslandFieldV1::densitySubvoxels(std::int64_t x,
|
||||
std::int64_t y, std::int64_t z) const noexcept
|
||||
{
|
||||
// The selected evaluator validates once; do not scan the plan twice per scoped sample.
|
||||
// L'evaluateur choisi valide une seule fois; ne pas reparcourir le plan a chaque echantillon.
|
||||
if (const auto* list = ScopedWorldAssemblyFeatureQueriesV1::find(recipe_, plan_, x, y, z))
|
||||
return densitySubvoxelsForTile(x, y, z, *list);
|
||||
if (!isValid() || !floating_island_detail::ValidScalar(x)
|
||||
|| !floating_island_detail::ValidScalar(y) || !floating_island_detail::ValidScalar(z))
|
||||
return floating_island_detail::MaxDensity;
|
||||
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
|
||||
std::vector<WorldAssemblyFeatureBoundsV1> FloatingIslandFieldV1::solidSupportBounds() 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)
|
||||
@@ -1666,6 +1666,22 @@ FloatingIslandTileFeatureListV1 FloatingIslandFieldV1::buildFeatureTileList(
|
||||
features.push_back({edge.stableId, edge.influenceMinimumSubvoxels,
|
||||
edge.influenceMaximumSubvoxels});
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
FloatingIslandTileFeatureListV1 FloatingIslandFieldV1::buildFeatureTileList(
|
||||
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
||||
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const
|
||||
{
|
||||
FloatingIslandTileFeatureListV1 result;
|
||||
// The measured four-island plan is cheaper to scan; larger plans amortize group certificates.
|
||||
// Le plan mesure de quatre iles coute moins en parcours complet; les grands plans amortissent les bornes.
|
||||
result.candidateEvaluationUseful=plan_->islands.size()>4;
|
||||
result.minimumSubvoxels = tileMinimumSubvoxels;
|
||||
result.maximumSubvoxels = tileMaximumSubvoxels;
|
||||
if (!isValid()) return result;
|
||||
const auto features = solidSupportBounds();
|
||||
|
||||
result.featureIndices = BuildWorldAssemblyFeatureTileListV1(features,
|
||||
tileMinimumSubvoxels, tileMaximumSubvoxels);
|
||||
const std::size_t islandCount = plan_->islands.size();
|
||||
|
||||
@@ -32,43 +32,6 @@
|
||||
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
|
||||
@@ -1140,12 +1103,6 @@ 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;
|
||||
@@ -1961,13 +1918,7 @@ private:
|
||||
const VoxelForgeCore::FloatingIslandFieldV1 Field(
|
||||
Plan->manifest.recipe,Plan->manifest.plan);
|
||||
if(!Field.isValid())return false;
|
||||
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));
|
||||
std::int64_t 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;
|
||||
|
||||
@@ -1154,68 +1154,51 @@ namespace VoxelForgeCore
|
||||
}
|
||||
}
|
||||
}
|
||||
const WorldAssemblyFloatingIslandPlanV1* IslandTilePlan=nullptr;
|
||||
FloatingIslandTileFeatureListV1 IslandTileFeatureList;
|
||||
if(bHasWorldAssembly)
|
||||
// Shared query domain includes the normal halo; all overlapping strate families participate.
|
||||
// Domaine partage avec marge de normales; inclure toutes les familles des strates traversees.
|
||||
const std::int64_t queryScale=WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::array<std::int64_t,3> queryMinimum={
|
||||
(static_cast<std::int64_t>(origin.X)-step)*queryScale,
|
||||
(static_cast<std::int64_t>(origin.Y)-step)*queryScale,
|
||||
(static_cast<std::int64_t>(origin.Z)-step)*queryScale};
|
||||
const std::array<std::int64_t,3> queryMaximum={
|
||||
(static_cast<std::int64_t>(origin.X)+extent+step)*queryScale,
|
||||
(static_cast<std::int64_t>(origin.Y)+extent+step)*queryScale,
|
||||
(static_cast<std::int64_t>(origin.Z)+extent+step)*queryScale};
|
||||
const auto FeatureListStart=std::chrono::steady_clock::now();
|
||||
std::vector<WorldAssemblyFeatureQueryV1> featureQueries;
|
||||
if(bHasWorldAssembly&&options.useAssemblyFeatureQueries)
|
||||
{
|
||||
for(const auto& plan:impl_->worldAssembly->plan.strates)
|
||||
{
|
||||
const Heightfield2D field(impl_->worldAssembly->recipe,plan);
|
||||
featureQueries.push_back({field.recipe(),field.plan(),
|
||||
field.buildFeatureTileList(queryMinimum,queryMaximum)});
|
||||
}
|
||||
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);
|
||||
const FloatingIslandFieldV1 field(entry.manifest.recipe,entry.manifest.plan);
|
||||
featureQueries.push_back({field.recipe(),field.plan(),
|
||||
field.buildFeatureTileList(queryMinimum,queryMaximum)});
|
||||
}
|
||||
}
|
||||
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);
|
||||
out.diagnostics.featureListMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-FeatureListStart).count();
|
||||
const ScopedWorldAssemblyFeatureQueriesV1 featureScope(featureQueries);
|
||||
FVoxelMeshData 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 auto FarShellStart=std::chrono::steady_clock::now();
|
||||
const FStandaloneShapeWorld& farShape=tileShape.HasFarWorld()?tileShape:*impl_->shape;
|
||||
if(farShape.HasFarWorld())
|
||||
{
|
||||
AppendFarStratumShells(world,farShape,nativeKey,step,mesh);
|
||||
AppendFarConnectorShells(farShape,nativeKey,step,world.CoarseTileCells,mesh);
|
||||
}
|
||||
out.diagnostics.farShellMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-FarShellStart).count();
|
||||
if(impl_->worldAssembly&&key.lod>=8)
|
||||
{
|
||||
const auto CapStats=RefineLod8NativeCap(world,*impl_->worldAssembly,nativeKey,mesh);
|
||||
|
||||
@@ -15,6 +15,45 @@
|
||||
|
||||
namespace VoxelForgeCore
|
||||
{
|
||||
namespace
|
||||
{
|
||||
thread_local const ScopedWorldAssemblyFeatureQueriesV1* ActiveAssemblyQueries = nullptr;
|
||||
}
|
||||
|
||||
ScopedWorldAssemblyFeatureQueriesV1::ScopedWorldAssemblyFeatureQueriesV1(
|
||||
std::span<const WorldAssemblyFeatureQueryV1> queries, bool forceCandidatesForValidation) noexcept
|
||||
: queries_(queries), forceCandidates_(forceCandidatesForValidation), previous_(ActiveAssemblyQueries)
|
||||
{
|
||||
// Resolve cost hints once per domain, never rediscover them in the sample loop.
|
||||
// Resoudre les indications de cout une fois par domaine, hors de la boucle d'echantillons.
|
||||
ActiveAssemblyQueries = nullptr;
|
||||
for (const auto& query : queries_)
|
||||
if (forceCandidates_ || query.features.candidateEvaluationUseful)
|
||||
{ ActiveAssemblyQueries = this; break; }
|
||||
}
|
||||
|
||||
ScopedWorldAssemblyFeatureQueriesV1::~ScopedWorldAssemblyFeatureQueriesV1()
|
||||
{ ActiveAssemblyQueries = previous_; }
|
||||
|
||||
const WorldAssemblyFeatureTileListV1* ScopedWorldAssemblyFeatureQueriesV1::find(
|
||||
const void* recipe, const void* plan, std::int64_t x, std::int64_t y,
|
||||
std::int64_t z, bool columnOnly) noexcept
|
||||
{
|
||||
const std::array<std::int64_t, 3> point{x,y,z};
|
||||
if (ActiveAssemblyQueries)
|
||||
for (const auto& query : ActiveAssemblyQueries->queries_)
|
||||
{
|
||||
if (query.recipe != recipe || query.plan != plan
|
||||
|| (!ActiveAssemblyQueries->forceCandidates_ && !query.features.candidateEvaluationUseful)) continue;
|
||||
bool inside = true;
|
||||
for (std::size_t axis=0; axis<(columnOnly ? 2u : 3u); ++axis)
|
||||
inside = inside && point[axis]>=query.features.minimumSubvoxels[axis]
|
||||
&& point[axis]<=query.features.maximumSubvoxels[axis];
|
||||
if (inside) return &query.features;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> BuildWorldAssemblyFeatureTileListV1(
|
||||
std::span<const WorldAssemblyFeatureBoundsV1> features,
|
||||
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
|
||||
@@ -52,8 +91,54 @@ std::vector<std::uint32_t> BuildWorldAssemblyFeatureTileListV1(
|
||||
return result;
|
||||
}
|
||||
|
||||
WorldAssemblyFeatureColumnRangeV1 BuildWorldAssemblyFeatureColumnRangeV1(
|
||||
std::span<const WorldAssemblyFeatureBoundsV1> bounds, std::int64_t x, std::int64_t y)
|
||||
{
|
||||
WorldAssemblyFeatureColumnRangeV1 result;
|
||||
result.known=true;
|
||||
result.empty=true;
|
||||
for(const auto& bound:bounds)
|
||||
{
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
if(bound.minimumSubvoxels[axis]>bound.maximumSubvoxels[axis]) return {};
|
||||
if(x<bound.minimumSubvoxels[0]||x>bound.maximumSubvoxels[0]
|
||||
||y<bound.minimumSubvoxels[1]||y>bound.maximumSubvoxels[1])continue;
|
||||
if(result.empty)
|
||||
{
|
||||
result.minimumZSubvoxels=bound.minimumSubvoxels[2];
|
||||
result.maximumZSubvoxels=bound.maximumSubvoxels[2];
|
||||
result.empty=false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.minimumZSubvoxels=std::min(result.minimumZSubvoxels,bound.minimumSubvoxels[2]);
|
||||
result.maximumZSubvoxels=std::max(result.maximumZSubvoxels,bound.maximumSubvoxels[2]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace world_assembly_detail
|
||||
{
|
||||
// Preserve recipe order without allocating or looking up metadata in the point loop.
|
||||
// Garder l'ordre de recette sans allocation ni recherche de metadonnees par point.
|
||||
struct FeatureRange
|
||||
{
|
||||
const std::vector<WorldAssemblyFeatureV1>& features;
|
||||
const WorldAssemblyFeatureTileListV1* list;
|
||||
struct Iterator
|
||||
{
|
||||
const FeatureRange* range;
|
||||
std::size_t index;
|
||||
const WorldAssemblyFeatureV1& operator*() const
|
||||
{ return range->features[range->list ? range->list->featureIndices[index] : index]; }
|
||||
Iterator& operator++() { ++index; return *this; }
|
||||
bool operator!=(const Iterator& other) const { return index!=other.index; }
|
||||
};
|
||||
Iterator begin() const { return {this,0}; }
|
||||
Iterator end() const { return {this,list ? list->featureIndices.size() : features.size()}; }
|
||||
};
|
||||
|
||||
constexpr std::int64_t Q16 = 1ll << 16;
|
||||
constexpr std::int64_t MinimumCliffShelfThicknessSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel;
|
||||
constexpr std::int64_t MaxSerializedMagnitude = 1000000000000ll;
|
||||
@@ -1294,7 +1379,8 @@ namespace world_assembly_detail
|
||||
:CapHeightV2(recipe,plan,neighbor,x,y);
|
||||
std::int64_t cap=primaryCap+((neighborCap-primaryCap)
|
||||
*(static_cast<std::int64_t>(Q16)-selection.primaryWeightQ16))/Q16;
|
||||
for(const auto& feature:plan.features)
|
||||
const auto* list=ScopedWorldAssemblyFeatureQueriesV1::find(&recipe,&plan,x,y,0,true);
|
||||
for(const auto& feature:FeatureRange{plan.features,list})
|
||||
{
|
||||
if(feature.type==1)
|
||||
{
|
||||
@@ -1730,6 +1816,42 @@ bool Heightfield2D::isValid() const noexcept
|
||||
});
|
||||
}
|
||||
|
||||
WorldAssemblyFeatureTileListV1 Heightfield2D::buildFeatureTileList(
|
||||
const std::array<std::int64_t,3>& minimum,
|
||||
const std::array<std::int64_t,3>& maximum) const
|
||||
{
|
||||
WorldAssemblyFeatureTileListV1 result;
|
||||
// One shelf in the staged owner plan has no useful scan to shorten. Keep its full evaluator.
|
||||
// Une seule corniche dans le plan auteur ne justifie pas la selection par echantillon.
|
||||
result.candidateEvaluationUseful=plan_->features.size()>1;
|
||||
result.minimumSubvoxels=minimum;
|
||||
result.maximumSubvoxels=maximum;
|
||||
std::vector<WorldAssemblyFeatureBoundsV1> bounds;
|
||||
bounds.reserve(plan_->features.size());
|
||||
for(const auto& feature:plan_->features)
|
||||
{
|
||||
WorldAssemblyFeatureBoundsV1 bound{feature.stableId,
|
||||
feature.minimumSubvoxels,feature.maximumSubvoxels};
|
||||
// Column writers affect every Z; finite volumes have an exact no-op outside their box.
|
||||
// Les modificateurs de colonne agissent sur tout Z; les volumes sont neutres hors boite.
|
||||
if(feature.type==1||feature.type==3)
|
||||
{
|
||||
bound.minimumSubvoxels[2]=std::numeric_limits<std::int64_t>::min();
|
||||
bound.maximumSubvoxels[2]=std::numeric_limits<std::int64_t>::max();
|
||||
}
|
||||
else if(feature.type!=2&&feature.type!=4)
|
||||
{
|
||||
// Unknown future operations must opt into a proof before they can be culled.
|
||||
// Une operation inconnue reste active tant que sa preuve manque.
|
||||
bound.minimumSubvoxels.fill(std::numeric_limits<std::int64_t>::min());
|
||||
bound.maximumSubvoxels.fill(std::numeric_limits<std::int64_t>::max());
|
||||
}
|
||||
bounds.push_back(bound);
|
||||
}
|
||||
result.featureIndices=BuildWorldAssemblyFeatureTileListV1(bounds,minimum,maximum);
|
||||
return result;
|
||||
}
|
||||
|
||||
HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
|
||||
std::int64_t z) const noexcept
|
||||
{
|
||||
@@ -1744,7 +1866,8 @@ HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
|
||||
out.capHeightSubvoxels-z);
|
||||
if(recipe_->evaluatorVersion>=2)
|
||||
{
|
||||
for(const auto& feature:plan_->features)
|
||||
const auto* list=ScopedWorldAssemblyFeatureQueriesV1::find(recipe_,plan_,x,y,z);
|
||||
for(const auto& feature:FeatureRange{plan_->features,list})
|
||||
{
|
||||
if(feature.type==2)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user