wip(assembler): M3 round 2 -- islands get real mass: deep crust, separate tapered roots, arched spans
Pinch cause: SmoothStep taper drove the underside radius to zero at one point. Version 2: depth 0.75x diameter (thin-pad control at 0.30 fails), 3-4 seeded roots, bounded contour warp + roughness, one off-route side island, 3 m rock spans. Bounds/route/W1-W8-W16 pass. Open: legacy probe metadata says 25 cm but production legacy is 12.5 cm -- round 3 must fix the import scale contract. 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
53a1e69af7
commit
4afea7c802
15 files changed
+1007
-99
No files matched your search
+14
-6
@@ -153,16 +153,24 @@ radius range, top flattening, taper, bounded contour warp, roughness, blend, con
|
||||
route constraints. `PlanFloatingIslandCluster` emits stable island and connector IDs, a connected
|
||||
cluster graph, conservative influence AABBs, and a centerline route. At least the primary chain is
|
||||
always planned; `islandDensityQ16` controls up to two optional side sites. Connectors carry a sharp-tag
|
||||
ready bit for a later hybrid-mesher adapter.
|
||||
ready bit for a later hybrid-mesher adapter. The schema accepts geometry version 1 and 2. Manifests
|
||||
without a geometry version retain version-1 evaluation and canonical hashes.
|
||||
|
||||
`FloatingIslandFieldV1::densitySubvoxels` evaluates the canonical island and connector field.
|
||||
`interval` returns unknown for boxes overlapping any influence AABB and a certified all-air range
|
||||
outside them. AABBs include contour displacement, roughness, taper height and smoothing reach.
|
||||
outside them. Geometry version 2 builds a flat walkable crust plus deterministic lobed rock roots,
|
||||
keeps bounded oblique surface detail inside its certified envelope, and arches the connector
|
||||
undersides. AABBs include root depth, contour displacement, detail, arch sag and smoothing reach.
|
||||
`AuditFloatingIslandBounds` checks sampled boxes and void beside connectors and includes a deliberately
|
||||
shrunken island-bound negative control. `AuditFloatingIslandRoute` samples the player capsule and
|
||||
support along the serialized route. The current field block is not yet composed into
|
||||
`WorldAssemblyManifest` or the Unreal adapter; M3 round 1 proves the standalone Core representation.
|
||||
The frozen owner evaluator is used as a visual and timing oracle, not as a hash target.
|
||||
shrunken island-bound negative control. `AuditFloatingIslandThickness` reports each island's
|
||||
`(topZ - bottomZ) / (2 * radius)` and checks a caller-supplied minimum; `AuditFloatingIslandRoute`
|
||||
samples the player capsule and support along the serialized route. M3 round 2 passed at a minimum
|
||||
depth-to-diameter ratio of 0.75 against a 0.70 threshold, while a 0.30 thin-pad control failed.
|
||||
|
||||
The family is still separate from `WorldAssemblyManifest` and the Unreal adapter. M3 rounds 1–2 prove
|
||||
the standalone Core representation, bounded geometry, route, determinism and matched legacy timing.
|
||||
The frozen owner evaluator is a visual and timing oracle, not a hash target. Unreal import and cooked
|
||||
collision are planned for M3 round 3.
|
||||
|
||||
## Queries and tiles
|
||||
|
||||
|
||||
@@ -301,29 +301,36 @@ namespace VoxelForgeCore
|
||||
struct FloatingIslandRecipeV1
|
||||
{
|
||||
std::uint32_t schemaVersion = 1;
|
||||
// Profile 1 preserves the original shallow pads. Profile 2 adds a thick crust,
|
||||
// bounded lobed roots and arched spans without changing the feature-family schema.
|
||||
std::uint32_t geometryVersion = 2;
|
||||
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
|
||||
std::uint32_t seed = 0;
|
||||
std::int64_t layoutOriginXSubvoxels = 0;
|
||||
std::int64_t layoutOriginYSubvoxels = 0;
|
||||
std::int64_t layoutCenterTopZSubvoxels = -128 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t layoutMinimumZSubvoxels = -256 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t layoutCenterTopZSubvoxels = -64 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t layoutMinimumZSubvoxels = -128 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t layoutMaximumZSubvoxels = 0;
|
||||
std::uint32_t primaryIslandCount = 5;
|
||||
std::uint32_t primaryIslandCount = 3;
|
||||
std::int64_t islandDensityQ16 = 1ll << 15;
|
||||
std::int64_t spacingSubvoxels = 112 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t minimumRadiusSubvoxels = 40 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t maximumRadiusSubvoxels = 48 * WorldAssemblySubvoxelsPerVoxel;
|
||||
// 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.
|
||||
std::int64_t spacingSubvoxels = 56 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t minimumRadiusSubvoxels = 12 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t maximumRadiusSubvoxels = 24 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t thicknessRatioQ16 = 39322; // 0.60
|
||||
std::int64_t verticalJitterQ16 = 26214; // 0.40
|
||||
std::int64_t topFlattenQ16 = 51118; // 0.78
|
||||
std::int64_t verticalJitterQ16 = 39322; // 0.60
|
||||
std::int64_t topFlattenQ16 = 39322; // 0.60
|
||||
std::int64_t taperEndMinimumQ16 = 29491; // 0.45
|
||||
std::int64_t taperEndMaximumQ16 = 45875; // 0.70
|
||||
std::int64_t blendRadiusSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t contourWarpSubvoxels = 15 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t roughnessSubvoxels = WorldAssemblySubvoxelsPerVoxel / 2;
|
||||
std::int64_t boundarySealSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t bridgeHalfWidthSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t bridgeThicknessSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t crustDepthBonusQ16 = 9830; // 0.15 radius, profile 2 only
|
||||
std::int64_t rootDepthRatioQ16 = 49152; // 0.75 radius, profile 2 only
|
||||
std::int64_t blendRadiusSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t contourWarpSubvoxels = 6 * WorldAssemblySubvoxelsPerVoxel + 77; // 6.3
|
||||
std::int64_t roughnessSubvoxels = 3 * WorldAssemblySubvoxelsPerVoxel / 2;
|
||||
std::int64_t boundarySealSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t bridgeHalfWidthSubvoxels = 6 * WorldAssemblySubvoxelsPerVoxel;
|
||||
std::int64_t bridgeThicknessSubvoxels = 4 * WorldAssemblySubvoxelsPerVoxel;
|
||||
WorldAssemblyRouteConstraintsV1 routeConstraints;
|
||||
std::uint64_t contentHash = 0;
|
||||
};
|
||||
@@ -409,6 +416,16 @@ namespace VoxelForgeCore
|
||||
bool passed = false;
|
||||
};
|
||||
|
||||
struct FloatingIslandThicknessAuditResult
|
||||
{
|
||||
std::uint64_t islandsTested = 0;
|
||||
std::uint64_t islandsBelowMinimum = 0;
|
||||
std::uint64_t minimumIslandStableId = 0;
|
||||
double minimumDepthToDiameterRatio = 0.0;
|
||||
double requiredDepthToDiameterRatio = 0.70;
|
||||
bool passed = false;
|
||||
};
|
||||
|
||||
struct FloatingIslandInfluenceBounds
|
||||
{
|
||||
bool finite = false;
|
||||
@@ -451,6 +468,8 @@ namespace VoxelForgeCore
|
||||
std::uint64_t seed = 0x49534c414e444231ull);
|
||||
VFCORE_API FloatingIslandRouteAuditResult AuditFloatingIslandRoute(
|
||||
const FloatingIslandFieldV1& field, std::int64_t routeOffsetSubvoxels = 0);
|
||||
VFCORE_API FloatingIslandThicknessAuditResult AuditFloatingIslandThickness(
|
||||
const FloatingIslandPlanV1& plan, double minimumDepthToDiameterRatio = 0.70);
|
||||
|
||||
struct HeightfieldPointSample
|
||||
{
|
||||
|
||||
@@ -174,6 +174,12 @@ namespace floating_island_detail
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.topFlattenQ16), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.taperEndMinimumQ16), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.taperEndMaximumQ16), 8);
|
||||
if (recipe.geometryVersion >= 2)
|
||||
{
|
||||
HashWord(hash, recipe.geometryVersion, 4);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.crustDepthBonusQ16), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.rootDepthRatioQ16), 8);
|
||||
}
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.blendRadiusSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.contourWarpSubvoxels), 8);
|
||||
HashWord(hash, static_cast<std::uint64_t>(recipe.roughnessSubvoxels), 8);
|
||||
@@ -233,7 +239,7 @@ namespace floating_island_detail
|
||||
|
||||
inline bool ValidateRecipe(const FloatingIslandRecipeV1& recipe, std::string& error)
|
||||
{
|
||||
const std::array<std::int64_t, 23> values = {
|
||||
const std::array<std::int64_t, 25> values = {
|
||||
recipe.layoutOriginXSubvoxels, recipe.layoutOriginYSubvoxels,
|
||||
recipe.layoutCenterTopZSubvoxels, recipe.layoutMinimumZSubvoxels,
|
||||
recipe.layoutMaximumZSubvoxels, recipe.islandDensityQ16,
|
||||
@@ -241,22 +247,28 @@ namespace floating_island_detail
|
||||
recipe.maximumRadiusSubvoxels, recipe.thicknessRatioQ16,
|
||||
recipe.verticalJitterQ16, recipe.topFlattenQ16,
|
||||
recipe.taperEndMinimumQ16, recipe.taperEndMaximumQ16,
|
||||
recipe.crustDepthBonusQ16, recipe.rootDepthRatioQ16,
|
||||
recipe.blendRadiusSubvoxels, recipe.contourWarpSubvoxels,
|
||||
recipe.roughnessSubvoxels, recipe.boundarySealSubvoxels,
|
||||
recipe.bridgeHalfWidthSubvoxels, recipe.bridgeThicknessSubvoxels,
|
||||
recipe.routeConstraints.capsuleRadiusSubvoxels,
|
||||
recipe.routeConstraints.capsuleHeightSubvoxels,
|
||||
recipe.routeConstraints.maximumGradeQ16};
|
||||
if (recipe.schemaVersion != 1 || recipe.unitsPerVoxel != Q
|
||||
if (recipe.schemaVersion != 1 || (recipe.geometryVersion != 1 && recipe.geometryVersion != 2)
|
||||
|| (recipe.geometryVersion == 1
|
||||
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|
||||
|| recipe.unitsPerVoxel != Q
|
||||
|| recipe.primaryIslandCount < 3 || recipe.primaryIslandCount > 9
|
||||
|| recipe.layoutMaximumZSubvoxels <= recipe.layoutMinimumZSubvoxels
|
||||
|| recipe.layoutMaximumZSubvoxels - recipe.layoutMinimumZSubvoxels > 2048 * Q
|
||||
|| std::any_of(values.begin(), values.end(), [](auto value) { return !ValidScalar(value); }))
|
||||
{ error = "floating-island recipe version, scale, layout or scalar range is invalid"; return false; }
|
||||
const auto& constraints = recipe.routeConstraints;
|
||||
const std::int64_t minimumSpacing = recipe.geometryVersion >= 2 ? 32 * Q : 64 * Q;
|
||||
const std::int64_t minimumRadius = recipe.geometryVersion >= 2 ? 12 * Q : 24 * Q;
|
||||
if (recipe.islandDensityQ16 < 6554 || recipe.islandDensityQ16 > (1ll << 15)
|
||||
|| recipe.spacingSubvoxels < 64 * Q || recipe.spacingSubvoxels > 256 * Q
|
||||
|| recipe.minimumRadiusSubvoxels < 24 * Q
|
||||
|| recipe.spacingSubvoxels < minimumSpacing || recipe.spacingSubvoxels > 256 * Q
|
||||
|| recipe.minimumRadiusSubvoxels < minimumRadius
|
||||
|| recipe.maximumRadiusSubvoxels < recipe.minimumRadiusSubvoxels
|
||||
|| recipe.maximumRadiusSubvoxels > 64 * Q
|
||||
|| recipe.thicknessRatioQ16 < 29491 || recipe.thicknessRatioQ16 > 49152
|
||||
@@ -264,6 +276,9 @@ namespace floating_island_detail
|
||||
|| recipe.topFlattenQ16 < 39322 || recipe.topFlattenQ16 > 52429
|
||||
|| recipe.taperEndMinimumQ16 < 26214 || recipe.taperEndMaximumQ16 > 52429
|
||||
|| recipe.taperEndMinimumQ16 > recipe.taperEndMaximumQ16
|
||||
|| (recipe.geometryVersion >= 2
|
||||
&& (recipe.crustDepthBonusQ16 < 6554 || recipe.crustDepthBonusQ16 > 16384
|
||||
|| recipe.rootDepthRatioQ16 < 32768 || recipe.rootDepthRatioQ16 > 65536))
|
||||
|| recipe.blendRadiusSubvoxels < 2 * Q || recipe.blendRadiusSubvoxels > 6 * Q
|
||||
|| recipe.contourWarpSubvoxels <= 0
|
||||
|| recipe.contourWarpSubvoxels > recipe.maximumRadiusSubvoxels / 2
|
||||
@@ -325,18 +340,30 @@ namespace floating_island_detail
|
||||
}
|
||||
|
||||
inline double Roughness(const FloatingIslandRecipeV1& recipe,
|
||||
const FloatingIslandAnchorV1& island,
|
||||
double x, double y, double z)
|
||||
std::uint64_t featureId, double x, double y, double z)
|
||||
{
|
||||
const double phase = static_cast<double>(Roll(recipe.seed, island.stableId, 0x524f5547ull)
|
||||
const double phase = static_cast<double>(Roll(recipe.seed, featureId, 0x524f5547ull)
|
||||
& 0xffffffffull) * (2.0 * Pi / 4294967296.0);
|
||||
const double xv = x / static_cast<double>(Q);
|
||||
const double yv = y / static_cast<double>(Q);
|
||||
const double zv = z / static_cast<double>(Q);
|
||||
if (recipe.geometryVersion < 2)
|
||||
{
|
||||
const double oldNoise = 0.52 * std::sin(xv * 0.071 + yv * 0.053 + zv * 0.089 + phase)
|
||||
+ 0.31 * std::sin(xv * 0.127 - yv * 0.097 + zv * 0.061 - phase * 0.73)
|
||||
+ 0.17 * std::cos(xv * 0.043 + yv * 0.139 - zv * 0.113 + phase * 1.39);
|
||||
return static_cast<double>(recipe.roughnessSubvoxels) * oldNoise;
|
||||
}
|
||||
// Four oblique bands span about 7 m down to 0.9 m at 25 cm per voxel.
|
||||
// Their absolute weights sum to one, keeping displacement inside the 3-voxel envelope.
|
||||
const double noise = 0.52 * std::sin(xv * 0.071 + yv * 0.053 + zv * 0.089 + phase)
|
||||
+ 0.31 * std::sin(xv * 0.127 - yv * 0.097 + zv * 0.061 - phase * 0.73)
|
||||
+ 0.17 * std::cos(xv * 0.043 + yv * 0.139 - zv * 0.113 + phase * 1.39);
|
||||
return static_cast<double>(recipe.roughnessSubvoxels) * noise;
|
||||
const double fine = 0.46 * std::sin(xv * 0.22 + yv * 0.31 - zv * 0.17 + phase * 0.41)
|
||||
+ 0.32 * std::cos(xv * 0.38 - yv * 0.21 + zv * 0.29 - phase * 0.67)
|
||||
+ 0.22 * std::sin(xv * 0.74 + yv * 0.53 + zv * 0.61 + phase * 1.17);
|
||||
return static_cast<double>(recipe.roughnessSubvoxels)
|
||||
* std::clamp(0.42 * noise + 0.58 * fine, -1.0, 1.0);
|
||||
}
|
||||
|
||||
inline double IslandTopSurface(const FloatingIslandRecipeV1& recipe,
|
||||
@@ -355,31 +382,138 @@ namespace floating_island_detail
|
||||
return static_cast<double>(island.topZSubvoxels) - drop;
|
||||
}
|
||||
|
||||
inline std::int64_t ScaledQ16(std::int64_t value, std::int64_t scaleQ16)
|
||||
{ return (value * scaleQ16 + Q16 / 2) / Q16; }
|
||||
|
||||
inline std::int64_t IslandCrustBottom(const FloatingIslandRecipeV1& recipe,
|
||||
const FloatingIslandAnchorV1& island)
|
||||
{
|
||||
const std::int64_t ratio = recipe.thicknessRatioQ16
|
||||
+ (recipe.geometryVersion >= 2 ? recipe.crustDepthBonusQ16 : 0);
|
||||
return island.topZSubvoxels - ScaledQ16(island.radiusSubvoxels, ratio);
|
||||
}
|
||||
|
||||
inline std::int64_t IslandBottom(const FloatingIslandRecipeV1& recipe,
|
||||
std::int64_t topZ, std::int64_t radius)
|
||||
{
|
||||
const std::int64_t crustRatio = recipe.thicknessRatioQ16
|
||||
+ (recipe.geometryVersion >= 2 ? recipe.crustDepthBonusQ16 : 0);
|
||||
const std::int64_t rootRatio = recipe.geometryVersion >= 2
|
||||
? recipe.rootDepthRatioQ16 : 0;
|
||||
return topZ - ScaledQ16(radius, crustRatio) - ScaledQ16(radius, rootRatio);
|
||||
}
|
||||
|
||||
inline std::int32_t RootCount(const FloatingIslandRecipeV1& recipe,
|
||||
const FloatingIslandAnchorV1& island)
|
||||
{
|
||||
return 3 + static_cast<std::int32_t>(Roll(recipe.seed, island.stableId,
|
||||
0x524f4f54434e54ull) & 1ull);
|
||||
}
|
||||
|
||||
inline double UnitHash(std::uint64_t hash)
|
||||
{ return static_cast<double>(hash & 0xffffull) / 65535.0; }
|
||||
|
||||
inline double RootLobeDensity(const FloatingIslandRecipeV1& recipe,
|
||||
const FloatingIslandAnchorV1& island,
|
||||
std::int32_t rootIndex, double x, double y, double z)
|
||||
{
|
||||
constexpr std::array<std::array<double, 2>, 4> pattern{{
|
||||
{{-0.92, -0.12}}, {{0.28, 0.98}}, {{0.83, -0.42}}, {{-0.16, -0.94}}}};
|
||||
const std::uint64_t salt = 0x524f4f540000ull + static_cast<std::uint64_t>(rootIndex);
|
||||
const std::uint64_t roll = Roll(recipe.seed, island.stableId, salt);
|
||||
const double radius = static_cast<double>(island.radiusSubvoxels);
|
||||
const double rootDepth = radius * Q16ToDouble(recipe.rootDepthRatioQ16);
|
||||
const double crustBottom = static_cast<double>(IslandCrustBottom(recipe, island));
|
||||
const double orbit = radius * (0.17 + 0.07 * UnitHash(roll));
|
||||
const double rootX = static_cast<double>(island.centerSubvoxels[0])
|
||||
+ pattern[static_cast<std::size_t>(rootIndex)][0] * orbit;
|
||||
const double rootY = static_cast<double>(island.centerSubvoxels[1])
|
||||
+ pattern[static_cast<std::size_t>(rootIndex)][1] * orbit;
|
||||
const double shortRootVariation = UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x4c454eull));
|
||||
const double lengthRatio = rootIndex == 0 ? 1.0
|
||||
: Q16ToDouble(island.taperEndQ16) * (0.82 + 0.18 * shortRootVariation);
|
||||
const double lowerDepth = rootDepth * lengthRatio;
|
||||
const double embedDepth = rootDepth * (rootIndex == 0 ? 0.30 : 0.22);
|
||||
const double verticalRadius = 0.5 * (lowerDepth + embedDepth);
|
||||
const double centerZ = crustBottom + 0.5 * (embedDepth - lowerDepth);
|
||||
const double rx = radius * (0.15 + 0.055 * UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x5258ull)));
|
||||
const double ry = radius * (0.15 + 0.065 * UnitHash(Roll(recipe.seed, island.stableId, salt ^ 0x5259ull)));
|
||||
const double q = (x - rootX) * (x - rootX) / (rx * rx)
|
||||
+ (y - rootY) * (y - rootY) / (ry * ry)
|
||||
+ (z - centerZ) * (z - centerZ) / (verticalRadius * verticalRadius);
|
||||
return (std::sqrt(std::max(q, 0.0)) - 1.0) * std::min({rx, ry, verticalRadius});
|
||||
}
|
||||
|
||||
inline double ApplySurfaceDetail(const FloatingIslandRecipeV1& recipe,
|
||||
std::uint64_t featureId, double structural,
|
||||
double x, double y, double z, double detailScale = 1.0)
|
||||
{
|
||||
if (recipe.geometryVersion < 2)
|
||||
return structural + Roughness(recipe, featureId, x, y, z);
|
||||
const double amplitude = static_cast<double>(recipe.roughnessSubvoxels) * detailScale;
|
||||
const double band = 2.0 * amplitude;
|
||||
if (!(amplitude > 0.0) || std::abs(structural) >= band) return structural;
|
||||
const double fade = 1.0 - SmoothStep(std::abs(structural) / band);
|
||||
return structural + Roughness(recipe, featureId, x, y, z) * detailScale * fade;
|
||||
}
|
||||
|
||||
inline double IslandDensity(const FloatingIslandRecipeV1& recipe,
|
||||
const FloatingIslandAnchorV1& island,
|
||||
double x, double y, double z)
|
||||
{
|
||||
if (recipe.geometryVersion < 2)
|
||||
{
|
||||
const double dx = x - static_cast<double>(island.centerSubvoxels[0]);
|
||||
const double dy = y - static_cast<double>(island.centerSubvoxels[1]);
|
||||
const double radial = std::hypot(dx, dy);
|
||||
const double topZ = static_cast<double>(island.topZSubvoxels);
|
||||
const double bottomZ = static_cast<double>(island.bottomZSubvoxels);
|
||||
const double verticalSpan = std::max(topZ - bottomZ, 1.0);
|
||||
const double height = std::clamp((z - bottomZ) / verticalSpan, 0.0, 1.0);
|
||||
const double taperEnd = Q16ToDouble(island.taperEndQ16);
|
||||
const double taper = SmoothStep(std::clamp(height / std::max(taperEnd, 0.01), 0.0, 1.0));
|
||||
const double effectiveRadius = std::max(0.0,
|
||||
static_cast<double>(island.radiusSubvoxels) + LobeOffset(recipe, island, dx, dy, z));
|
||||
const double envelope = effectiveRadius * taper;
|
||||
const double top = IslandTopSurface(recipe, island,
|
||||
static_cast<double>(island.centerSubvoxels[0]) + dx,
|
||||
static_cast<double>(island.centerSubvoxels[1]) + dy);
|
||||
return ApplySurfaceDetail(recipe, island.stableId,
|
||||
std::max({z - top, radial - envelope, bottomZ - z}), x, y, z);
|
||||
}
|
||||
|
||||
const double dx = x - static_cast<double>(island.centerSubvoxels[0]);
|
||||
const double dy = y - static_cast<double>(island.centerSubvoxels[1]);
|
||||
const double radial = std::hypot(dx, dy);
|
||||
const double topZ = static_cast<double>(island.topZSubvoxels);
|
||||
const double bottomZ = static_cast<double>(island.bottomZSubvoxels);
|
||||
const double verticalSpan = std::max(topZ - bottomZ, 1.0);
|
||||
const double height = std::clamp((z - bottomZ) / verticalSpan, 0.0, 1.0);
|
||||
const double taperEnd = Q16ToDouble(island.taperEndQ16);
|
||||
const double taper = SmoothStep(std::clamp(height / std::max(taperEnd, 0.01), 0.0, 1.0));
|
||||
const double radius = static_cast<double>(island.radiusSubvoxels);
|
||||
const double effectiveRadius = std::max(0.0,
|
||||
static_cast<double>(island.radiusSubvoxels) + LobeOffset(recipe, island, dx, dy, z));
|
||||
const double envelope = effectiveRadius * taper;
|
||||
radius + LobeOffset(recipe, island, dx, dy, z));
|
||||
const double edge = std::clamp(radial / std::max(effectiveRadius, 1.0), 0.0, 1.0);
|
||||
const double lowerSurface = static_cast<double>(IslandCrustBottom(recipe, island))
|
||||
+ radius * 0.16 * edge * edge;
|
||||
const double top = IslandTopSurface(recipe, island,
|
||||
static_cast<double>(island.centerSubvoxels[0]) + dx,
|
||||
static_cast<double>(island.centerSubvoxels[1]) + dy);
|
||||
const double sdf = std::max({z - top, radial - envelope, bottomZ - z});
|
||||
return sdf + Roughness(recipe, island, x, y, z);
|
||||
const double topDistance = z - top;
|
||||
const double sideDistance = radial - effectiveRadius;
|
||||
const double undersideDistance = lowerSurface - z;
|
||||
double sdf = std::max({topDistance, sideDistance, undersideDistance});
|
||||
const std::int32_t rootCount = RootCount(recipe, island);
|
||||
const double rootBlend = std::clamp(radius * 0.12, 2.0 * Q, 5.0 * Q);
|
||||
for (std::int32_t root = 0; root < rootCount; ++root)
|
||||
sdf = SmoothMin(sdf, RootLobeDensity(recipe, island, root, x, y, z), rootBlend);
|
||||
const double topDetailScale = topDistance >= std::max(sideDistance, undersideDistance)
|
||||
|| z >= static_cast<double>(IslandCrustBottom(recipe, island)) + radius * 0.45
|
||||
? 0.25 : 1.0;
|
||||
return ApplySurfaceDetail(recipe, island.stableId, sdf, x, y, z, topDetailScale);
|
||||
}
|
||||
|
||||
inline double ConnectorDensity(const FloatingIslandConnectorV1& edge,
|
||||
double x, double y, double z)
|
||||
inline double ConnectorDensity(const FloatingIslandRecipeV1& recipe,
|
||||
const FloatingIslandConnectorV1& edge,
|
||||
double x, double y, double z,
|
||||
bool* topSurface = nullptr,
|
||||
double* alongFraction = nullptr)
|
||||
{
|
||||
const double ax = static_cast<double>(edge.startSubvoxels[0]);
|
||||
const double ay = static_cast<double>(edge.startSubvoxels[1]);
|
||||
@@ -397,9 +531,15 @@ namespace floating_island_detail
|
||||
const double outside = std::hypot(std::max(qx, 0.0), std::max(qy, 0.0));
|
||||
const double horizontal = outside + std::min(std::max(qx, qy), 0.0);
|
||||
const double t = std::clamp(along / length, 0.0, 1.0);
|
||||
if (alongFraction) *alongFraction = t;
|
||||
const double deck = az + (static_cast<double>(edge.endSubvoxels[2]) - az) * t;
|
||||
return std::max({horizontal, z - deck,
|
||||
deck - static_cast<double>(edge.thicknessSubvoxels) - z});
|
||||
const double arch = recipe.geometryVersion >= 2
|
||||
? 2.0 * static_cast<double>(edge.thicknessSubvoxels) * (4.0 * t * (1.0 - t))
|
||||
: 0.0;
|
||||
const double upper = z - deck;
|
||||
const double lower = deck - static_cast<double>(edge.thicknessSubvoxels) - arch - z;
|
||||
if (topSurface) *topSurface = upper >= std::max(horizontal, lower);
|
||||
return std::max({horizontal, upper, lower});
|
||||
}
|
||||
|
||||
inline std::int64_t FieldDensity(const FloatingIslandRecipeV1& recipe,
|
||||
@@ -416,8 +556,16 @@ namespace floating_island_detail
|
||||
for (const auto& edge : plan.clusterEdges)
|
||||
{
|
||||
if (edge.fromIslandId != island.stableId) continue;
|
||||
group = SmoothMin(group, ConnectorDensity(edge,
|
||||
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z)), blend);
|
||||
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);
|
||||
}
|
||||
result = std::min(result, group);
|
||||
}
|
||||
@@ -439,11 +587,14 @@ namespace floating_island_detail
|
||||
FloatingIslandConnectorV1& edge)
|
||||
{
|
||||
const std::int64_t pad = recipe.blendRadiusSubvoxels + recipe.roughnessSubvoxels;
|
||||
const std::int64_t archDepth = recipe.geometryVersion >= 2
|
||||
? 2 * edge.thicknessSubvoxels : 0;
|
||||
const std::int64_t xyPad = edge.halfWidthSubvoxels + pad;
|
||||
edge.influenceMinimumSubvoxels = {
|
||||
std::min(edge.startSubvoxels[0], edge.endSubvoxels[0]) - xyPad,
|
||||
std::min(edge.startSubvoxels[1], edge.endSubvoxels[1]) - xyPad,
|
||||
std::min(edge.startSubvoxels[2], edge.endSubvoxels[2]) - edge.thicknessSubvoxels - pad};
|
||||
edge.influenceMinimumSubvoxels[2] -= archDepth;
|
||||
edge.influenceMaximumSubvoxels = {
|
||||
std::max(edge.startSubvoxels[0], edge.endSubvoxels[0]) + xyPad,
|
||||
std::max(edge.startSubvoxels[1], edge.endSubvoxels[1]) + xyPad,
|
||||
@@ -483,6 +634,10 @@ namespace floating_island_detail
|
||||
|| island.taperEndQ16 > recipe.taperEndMaximumQ16
|
||||
|| island.centerSubvoxels[2] != island.topZSubvoxels - island.topHalfSubvoxels)
|
||||
{ error = "floating-island anchor has an invalid or unstable identity or profile"; return false; }
|
||||
if (recipe.geometryVersion >= 2
|
||||
&& static_cast<double>(island.topZSubvoxels - island.bottomZSubvoxels)
|
||||
/ (2.0 * static_cast<double>(island.radiusSubvoxels)) < 0.70)
|
||||
{ error = "floating-island crust/root depth is below its minimum width ratio"; return false; }
|
||||
for (std::size_t axis = 0; axis < 3; ++axis)
|
||||
{
|
||||
if (!ValidScalar(island.centerSubvoxels[axis])
|
||||
@@ -527,6 +682,7 @@ namespace floating_island_detail
|
||||
|| edge.influenceMaximumSubvoxels[1] < std::max(edge.startSubvoxels[1], edge.endSubvoxels[1]) + xyPad
|
||||
|| edge.influenceMinimumSubvoxels[2] > std::min(edge.startSubvoxels[2], edge.endSubvoxels[2])
|
||||
- edge.thicknessSubvoxels - pad
|
||||
- (recipe.geometryVersion >= 2 ? 2 * edge.thicknessSubvoxels : 0)
|
||||
|| edge.influenceMaximumSubvoxels[2] < std::max(edge.startSubvoxels[2], edge.endSubvoxels[2]) + pad)
|
||||
{ error = "floating-island connector bound omits its swept bridge, blend or thickness"; return false; }
|
||||
adjacency[edge.fromIslandId].push_back(edge.toIslandId);
|
||||
@@ -593,8 +749,7 @@ namespace floating_island_detail
|
||||
* radiusRoll / 65535;
|
||||
island.topHalfSubvoxels = std::max<std::int64_t>(1, island.radiusSubvoxels / 5);
|
||||
island.topZSubvoxels = topZ;
|
||||
island.bottomZSubvoxels = topZ
|
||||
- island.radiusSubvoxels * recipe.thicknessRatioQ16 / Q16;
|
||||
island.bottomZSubvoxels = IslandBottom(recipe, topZ, island.radiusSubvoxels);
|
||||
island.centerSubvoxels = {x, y, topZ - island.topHalfSubvoxels};
|
||||
island.taperEndQ16 = recipe.taperEndMinimumQ16
|
||||
+ (recipe.taperEndMaximumQ16 - recipe.taperEndMinimumQ16)
|
||||
@@ -619,14 +774,16 @@ namespace floating_island_detail
|
||||
// Land each connector well inside the broad top so the full capsule footprint remains
|
||||
// supported across the bridge-to-island transition, including bounded surface roughness.
|
||||
// Les connecteurs recouvrent le plateau pour soutenir toute la capsule au raccord.
|
||||
const double fromReach = static_cast<double>(from.radiusSubvoxels) * 0.62;
|
||||
const double toReach = static_cast<double>(to.radiusSubvoxels) * 0.62;
|
||||
const double landingFraction = recipe.geometryVersion >= 2 ? 0.38 : 0.62;
|
||||
const double fromReach = static_cast<double>(from.radiusSubvoxels) * landingFraction;
|
||||
const double toReach = static_cast<double>(to.radiusSubvoxels) * landingFraction;
|
||||
const double sx = from.centerSubvoxels[0] + ux * fromReach;
|
||||
const double sy = from.centerSubvoxels[1] + uy * fromReach;
|
||||
const double ex = to.centerSubvoxels[0] - ux * toReach;
|
||||
const double ey = to.centerSubvoxels[1] - uy * toReach;
|
||||
const double sz = IslandTopSurface(recipe, from, sx, sy);
|
||||
const double ez = IslandTopSurface(recipe, to, ex, ey);
|
||||
const double routeDeckLift = recipe.geometryVersion >= 2 ? 1.0 * Q : 0.0;
|
||||
const double sz = IslandTopSurface(recipe, from, sx, sy) + routeDeckLift;
|
||||
const double ez = IslandTopSurface(recipe, to, ex, ey) + routeDeckLift;
|
||||
edge.startSubvoxels = {RoundQ(sx), RoundQ(sy), RoundQ(sz)};
|
||||
edge.endSubvoxels = {RoundQ(ex), RoundQ(ey), RoundQ(ez)};
|
||||
edge.halfWidthSubvoxels = recipe.bridgeHalfWidthSubvoxels;
|
||||
@@ -734,6 +891,23 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
|
||||
const auto& recipeValue = root.Get("recipe");
|
||||
const auto& planValue = root.Get("plan");
|
||||
std::uint64_t manifestHash = 0, recipeHash = 0, planHash = 0;
|
||||
const auto& geometryVersionValue = recipeValue.Get("geometry_version");
|
||||
if (geometryVersionValue.IsNumber())
|
||||
{
|
||||
if (!floating_island_detail::ReadU32(geometryVersionValue, manifest.recipe.geometryVersion)
|
||||
|| !floating_island_detail::ReadInteger(recipeValue.Get("crust_depth_bonus_q16"),
|
||||
manifest.recipe.crustDepthBonusQ16)
|
||||
|| !floating_island_detail::ReadInteger(recipeValue.Get("root_depth_ratio_q16"),
|
||||
manifest.recipe.rootDepthRatioQ16))
|
||||
{ error = "floating-island geometry profile is incomplete"; return std::nullopt; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Frozen M3 round-1 manifests omit profile metadata and retain their original field.
|
||||
manifest.recipe.geometryVersion = 1;
|
||||
manifest.recipe.crustDepthBonusQ16 = 0;
|
||||
manifest.recipe.rootDepthRatioQ16 = 0;
|
||||
}
|
||||
if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema)
|
||||
|| !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash)
|
||||
|| !floating_island_detail::ReadU32(recipeValue.Get("schema_version"), manifest.recipe.schemaVersion)
|
||||
@@ -867,8 +1041,12 @@ std::string FloatingIslandManifestV1::ToJson() const
|
||||
<< ",\"vertical_jitter_q16\":" << recipe.verticalJitterQ16
|
||||
<< ",\"top_flatten_q16\":" << recipe.topFlattenQ16
|
||||
<< ",\"taper_end_min_q16\":" << recipe.taperEndMinimumQ16
|
||||
<< ",\"taper_end_max_q16\":" << recipe.taperEndMaximumQ16
|
||||
<< ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels
|
||||
<< ",\"taper_end_max_q16\":" << recipe.taperEndMaximumQ16;
|
||||
if (recipe.geometryVersion >= 2)
|
||||
out << ",\"geometry_version\":" << recipe.geometryVersion
|
||||
<< ",\"crust_depth_bonus_q16\":" << recipe.crustDepthBonusQ16
|
||||
<< ",\"root_depth_ratio_q16\":" << recipe.rootDepthRatioQ16;
|
||||
out << ",\"blend_radius_q\":" << recipe.blendRadiusSubvoxels
|
||||
<< ",\"contour_warp_q\":" << recipe.contourWarpSubvoxels
|
||||
<< ",\"roughness_q\":" << recipe.roughnessSubvoxels
|
||||
<< ",\"boundary_seal_q\":" << recipe.boundarySealSubvoxels
|
||||
@@ -961,11 +1139,40 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
|
||||
radii[i] = recipe.minimumRadiusSubvoxels
|
||||
+ (recipe.maximumRadiusSubvoxels - recipe.minimumRadiusSubvoxels) * radiusRoll / 65535;
|
||||
}
|
||||
std::vector<std::array<std::int64_t, 2>> primaryXY(count);
|
||||
const std::int64_t rowJitter = recipe.spacingSubvoxels / 10;
|
||||
for (std::uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
const std::uint64_t site = 0x100000000ull + i;
|
||||
if (recipe.geometryVersion < 2)
|
||||
{
|
||||
const std::int64_t yOffset = i == middle ? 0
|
||||
: floating_island_detail::SignedRange(recipe.seed, site, 0x594a4954544552ull, rowJitter);
|
||||
primaryXY[i] = {recipe.layoutOriginXSubvoxels
|
||||
- static_cast<std::int64_t>(middle) * recipe.spacingSubvoxels
|
||||
+ static_cast<std::int64_t>(i) * recipe.spacingSubvoxels,
|
||||
recipe.layoutOriginYSubvoxels + yOffset};
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::int64_t along = (static_cast<std::int64_t>(i)
|
||||
- static_cast<std::int64_t>(middle)) * recipe.spacingSubvoxels * 7 / 10;
|
||||
const std::int64_t side = (i % 2 == 0 ? 1 : -1) * recipe.spacingSubvoxels / 3;
|
||||
const std::int64_t jitter = floating_island_detail::SignedRange(
|
||||
recipe.seed, site, 0x594a4954544552ull, rowJitter / 2);
|
||||
primaryXY[i] = {recipe.layoutOriginXSubvoxels + along,
|
||||
recipe.layoutOriginYSubvoxels + side + jitter};
|
||||
}
|
||||
}
|
||||
const double maxGrade = floating_island_detail::Q16ToDouble(recipe.routeConstraints.maximumGradeQ16);
|
||||
const double levelFactor = floating_island_detail::Q16ToDouble(recipe.verticalJitterQ16) * 1.5;
|
||||
const auto riseLimit = [&](std::uint32_t a, std::uint32_t b)
|
||||
{
|
||||
const double gap = static_cast<double>(recipe.spacingSubvoxels)
|
||||
const double centerDistance = recipe.geometryVersion < 2
|
||||
? static_cast<double>(recipe.spacingSubvoxels)
|
||||
: std::hypot(static_cast<double>(primaryXY[b][0] - primaryXY[a][0]),
|
||||
static_cast<double>(primaryXY[b][1] - primaryXY[a][1]));
|
||||
const double gap = centerDistance
|
||||
- 0.78 * (static_cast<double>(radii[a]) + static_cast<double>(radii[b]));
|
||||
return std::max(0.0, gap * maxGrade * levelFactor);
|
||||
};
|
||||
@@ -983,22 +1190,16 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
|
||||
static_cast<std::int64_t>(std::ceil(riseLimit(static_cast<std::uint32_t>(i),
|
||||
static_cast<std::uint32_t>(i + 1)))));
|
||||
}
|
||||
const std::int64_t rowJitter = recipe.spacingSubvoxels / 10;
|
||||
const std::int64_t startX = recipe.layoutOriginXSubvoxels
|
||||
- static_cast<std::int64_t>(middle) * recipe.spacingSubvoxels;
|
||||
for (std::uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
const std::uint64_t site = 0x100000000ull + i;
|
||||
const std::int64_t yOffset = i == middle ? 0
|
||||
: floating_island_detail::SignedRange(recipe.seed, site, 0x594a4954544552ull, rowJitter);
|
||||
auto island = floating_island_detail::MakeIsland(recipe, i,
|
||||
startX + static_cast<std::int64_t>(i) * recipe.spacingSubvoxels,
|
||||
recipe.layoutOriginYSubvoxels + yOffset, tops[i], site);
|
||||
primaryXY[i][0], primaryXY[i][1], tops[i], site);
|
||||
island.radiusSubvoxels = radii[i];
|
||||
island.topHalfSubvoxels = std::max<std::int64_t>(1, island.radiusSubvoxels / 5);
|
||||
island.centerSubvoxels[2] = island.topZSubvoxels - island.topHalfSubvoxels;
|
||||
island.bottomZSubvoxels = island.topZSubvoxels
|
||||
- island.radiusSubvoxels * recipe.thicknessRatioQ16 / floating_island_detail::Q16;
|
||||
island.bottomZSubvoxels = floating_island_detail::IslandBottom(recipe,
|
||||
island.topZSubvoxels, island.radiusSubvoxels);
|
||||
floating_island_detail::SetIslandBounds(recipe, island);
|
||||
islands.push_back(island);
|
||||
}
|
||||
@@ -1016,26 +1217,41 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
|
||||
const std::uint64_t site = 0x300000000ull + branch;
|
||||
if (floating_island_detail::UnitQ16(recipe.seed, site, 0x53495445524f4c4cull)
|
||||
>= static_cast<std::uint32_t>(recipe.islandDensityQ16)) continue;
|
||||
const std::uint32_t hostIndex = branch == 0 ? 0 : count - 1;
|
||||
const std::uint32_t hostIndex = recipe.geometryVersion < 2
|
||||
? (branch == 0 ? 0 : count - 1)
|
||||
: (branch == 0 ? middle : std::min(middle + 1, count - 1));
|
||||
const auto& host = islands[hostIndex];
|
||||
const std::int64_t xOffset = recipe.geometryVersion < 2 ? 0
|
||||
: (branch == 0 ? -1 : 1) * recipe.spacingSubvoxels * 9 / 20;
|
||||
const std::int64_t yOffset = branch == 0 ? -recipe.spacingSubvoxels : recipe.spacingSubvoxels;
|
||||
const std::int64_t gap = recipe.spacingSubvoxels - static_cast<std::int64_t>(
|
||||
std::llround(0.78 * static_cast<double>(host.radiusSubvoxels + recipe.minimumRadiusSubvoxels)));
|
||||
const std::int64_t rise = static_cast<std::int64_t>(std::ceil(std::max(0.0,
|
||||
static_cast<double>(gap) * maxGrade * levelFactor)));
|
||||
const std::int64_t topZ = host.topZSubvoxels
|
||||
+ floating_island_detail::SignedRange(recipe.seed, site, 0x4252414e43484c56ull, rise);
|
||||
const std::int64_t sideRise = recipe.geometryVersion < 2 ? rise
|
||||
: std::min<std::int64_t>(24 * floating_island_detail::Q,
|
||||
(recipe.layoutMaximumZSubvoxels - recipe.layoutMinimumZSubvoxels)
|
||||
* recipe.verticalJitterQ16 / (8 * floating_island_detail::Q16));
|
||||
std::int64_t topZ = host.topZSubvoxels
|
||||
+ floating_island_detail::SignedRange(recipe.seed, site, 0x4252414e43484c56ull, sideRise);
|
||||
const std::uint32_t radiusRoll = floating_island_detail::UnitQ16(recipe.seed, site, 0x524144495553ull);
|
||||
const std::int64_t branchRadius = recipe.minimumRadiusSubvoxels
|
||||
+ (recipe.maximumRadiusSubvoxels - recipe.minimumRadiusSubvoxels) * radiusRoll / 65535;
|
||||
if (recipe.geometryVersion >= 2)
|
||||
{
|
||||
const std::int64_t maximumTop = recipe.layoutMaximumZSubvoxels
|
||||
- recipe.boundarySealSubvoxels - branchRadius / 5;
|
||||
const std::int64_t minimumTop = recipe.layoutMinimumZSubvoxels
|
||||
+ recipe.boundarySealSubvoxels + floating_island_detail::IslandBottom(recipe, 0, branchRadius) * -1;
|
||||
topZ = std::clamp(topZ, minimumTop, maximumTop);
|
||||
}
|
||||
auto island = floating_island_detail::MakeIsland(recipe,
|
||||
recipe.primaryIslandCount + branch,
|
||||
host.centerSubvoxels[0], host.centerSubvoxels[1] + yOffset, topZ, site);
|
||||
host.centerSubvoxels[0] + xOffset, host.centerSubvoxels[1] + yOffset, topZ, site);
|
||||
island.radiusSubvoxels = branchRadius;
|
||||
island.topHalfSubvoxels = std::max<std::int64_t>(1, branchRadius / 5);
|
||||
island.centerSubvoxels[2] = topZ - island.topHalfSubvoxels;
|
||||
island.bottomZSubvoxels = topZ - branchRadius * recipe.thicknessRatioQ16
|
||||
/ floating_island_detail::Q16;
|
||||
island.bottomZSubvoxels = floating_island_detail::IslandBottom(recipe, topZ, branchRadius);
|
||||
floating_island_detail::SetIslandBounds(recipe, island);
|
||||
const std::uint64_t islandId = island.stableId;
|
||||
islands.push_back(island);
|
||||
@@ -1074,7 +1290,8 @@ std::optional<FloatingIslandManifestV1> PlanFloatingIslandCluster(
|
||||
point.stableFeatureId = floating_island_detail::StableId(recipe.seed,
|
||||
0x500000000ull + routeOrdinal, 0x524f555445ull);
|
||||
point.positionSubvoxels = {x, y, *surface + capsuleHalf
|
||||
+ recipe.routeConstraints.minimumClearanceSubvoxels};
|
||||
+ recipe.routeConstraints.minimumClearanceSubvoxels
|
||||
+ (recipe.geometryVersion >= 2 ? floating_island_detail::Q / 8 : 0)};
|
||||
point.radiusSubvoxels = routeRadius;
|
||||
route.push_back(point);
|
||||
++routeOrdinal;
|
||||
@@ -1471,4 +1688,37 @@ FloatingIslandRouteAuditResult AuditFloatingIslandRoute(const FloatingIslandFiel
|
||||
return result;
|
||||
}
|
||||
|
||||
FloatingIslandThicknessAuditResult AuditFloatingIslandThickness(
|
||||
const FloatingIslandPlanV1& plan, double minimumDepthToDiameterRatio)
|
||||
{
|
||||
FloatingIslandThicknessAuditResult result;
|
||||
result.requiredDepthToDiameterRatio = minimumDepthToDiameterRatio;
|
||||
if (plan.islands.empty() || !std::isfinite(minimumDepthToDiameterRatio)
|
||||
|| minimumDepthToDiameterRatio <= 0.0) return result;
|
||||
result.minimumDepthToDiameterRatio = std::numeric_limits<double>::infinity();
|
||||
for (const auto& island : plan.islands)
|
||||
{
|
||||
++result.islandsTested;
|
||||
if (island.radiusSubvoxels <= 0 || island.topZSubvoxels <= island.bottomZSubvoxels)
|
||||
{
|
||||
++result.islandsBelowMinimum;
|
||||
result.minimumDepthToDiameterRatio = 0.0;
|
||||
result.minimumIslandStableId = island.stableId;
|
||||
continue;
|
||||
}
|
||||
const double ratio = static_cast<double>(island.topZSubvoxels - island.bottomZSubvoxels)
|
||||
/ (2.0 * static_cast<double>(island.radiusSubvoxels));
|
||||
if (ratio < result.minimumDepthToDiameterRatio)
|
||||
{
|
||||
result.minimumDepthToDiameterRatio = ratio;
|
||||
result.minimumIslandStableId = island.stableId;
|
||||
}
|
||||
if (ratio + 1.0e-12 < minimumDepthToDiameterRatio) ++result.islandsBelowMinimum;
|
||||
}
|
||||
if (!std::isfinite(result.minimumDepthToDiameterRatio)) result.minimumDepthToDiameterRatio = 0.0;
|
||||
result.passed = result.islandsTested == plan.islands.size()
|
||||
&& result.islandsBelowMinimum == 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user