wip(L25, side branch): FI helix enclosed in a 13 m rock root; support never created in open air
Fixes the L24 ribbon (owner request). NOT merged: the open-air support gate leaves ~4,000 unsupported samples on every SurfaceWorld-recipe passage style (TN -> SW), a walkability regression. L26 fixes that on this branch. See Linux25/REPORT.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
1 parent
cb9236c265
commit
ef789aaa00
14 files changed
+662
-110
No files matched your search
@@ -281,7 +281,7 @@ namespace floating_island_detail
|
||||
? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0;
|
||||
const std::uint32_t ExpectedDenominator = ScaleGcd > 0
|
||||
? recipe.coreVoxelSizeMicrons / ScaleGcd : 0;
|
||||
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 5
|
||||
if (recipe.schemaVersion != 1 || recipe.geometryVersion < 1 || recipe.geometryVersion > 7
|
||||
|| (recipe.geometryVersion == 1
|
||||
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|
||||
|| recipe.unitsPerVoxel != Q
|
||||
@@ -756,7 +756,9 @@ namespace floating_island_detail
|
||||
if (recipe.geometryVersion >= 5 && island.ordinal == recipe.anchorIslandOrdinal
|
||||
&& recipe.anchorRootBottomZSubvoxels < island.bottomZSubvoxels)
|
||||
{
|
||||
const double rootTop = static_cast<double>(island.bottomZSubvoxels);
|
||||
// The central buttress must meet the island crust underside. IslandBottom
|
||||
// also includes the four shorter root lobes and sits below that surface.
|
||||
const double rootTop = static_cast<double>(IslandCrustBottom(recipe, island));
|
||||
const double rootBottom = static_cast<double>(recipe.anchorRootBottomZSubvoxels);
|
||||
const double depth = std::max(1.0, rootTop - rootBottom);
|
||||
const double blend = std::clamp(static_cast<double>(recipe.anchorRootRadiusSubvoxels)
|
||||
@@ -1330,7 +1332,7 @@ std::optional<FloatingIslandManifestV1> FloatingIslandManifestV1::FromJson(
|
||||
manifest.recipe.anchorRootBottomZSubvoxels)
|
||||
||!floating_island_detail::ReadInteger(recipeValue.Get("anchor_mouth_offset_q"),
|
||||
manifest.recipe.anchorMouthOffsetSubvoxels))
|
||||
{error="floating-island geometry version 5 is missing a passage-rooted anchor field";return std::nullopt;}
|
||||
{error="floating-island geometry version 7 is missing a passage-rooted anchor field";return std::nullopt;}
|
||||
}
|
||||
if (!floating_island_detail::ReadU32(root.Get("schema_version"), schema)
|
||||
|| !floating_island_detail::ReadHash(root.Get("content_hash"), manifestHash)
|
||||
|
||||
@@ -550,6 +550,14 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
const auto routeAnchor=[](const ResolvedLayoutStratumV2& s,bool departure,
|
||||
std::array<std::int64_t,3>& out)
|
||||
{
|
||||
if(departure)
|
||||
for(const auto& a:s.anchors)
|
||||
if(a.kind=="passage_anchor"&&a.values.size()>=10)
|
||||
{out={a.values[7],a.values[8],a.values[9]};return true;}
|
||||
if(!departure)
|
||||
for(const auto& a:s.anchors)
|
||||
if(a.kind=="passage_arrival"&&a.values.size()>=3)
|
||||
{out={a.values[0],a.values[1],a.values[2]};return true;}
|
||||
const ResolvedLayoutAnchorV2* selected=nullptr;
|
||||
for(const auto& a:s.anchors)
|
||||
if(a.kind=="route_point"&&a.values.size()>=3)
|
||||
@@ -597,8 +605,9 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
// Leave room for the capsule and bounded wall relief at the floor/wall seam.
|
||||
const std::int64_t mouthRadius=std::max<std::int64_t>(static_cast<std::int64_t>(std::llround(1.5*qPerMetre)),
|
||||
cfg?cfg->mouthRadiusSubvoxels:0);
|
||||
const std::int64_t midRadius=std::max<std::int64_t>(mouthRadius,
|
||||
cfg?cfg->midRadiusSubvoxels:0);
|
||||
const std::int64_t midRadius=std::max<std::int64_t>(
|
||||
static_cast<std::int64_t>(std::llround(1.5*qPerMetre)),
|
||||
cfg?cfg->midRadiusSubvoxels:mouthRadius);
|
||||
const std::int64_t reachMinimum=cfg?std::max<std::int64_t>(0,cfg->reachMinimumSubvoxels):8*qPerMetre;
|
||||
const std::int64_t reachMaximum=cfg?std::max<std::int64_t>(reachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre;
|
||||
const std::uint32_t style=cfg?std::min<std::uint32_t>(cfg->style,3u):1u;
|
||||
@@ -624,7 +633,9 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
for(const auto& anchor:upper.anchors)
|
||||
if(anchor.kind=="passage_anchor"&&anchor.values.size()>=7)
|
||||
{passageAnchor=&anchor;break;}
|
||||
const bool anchoredSpiral=style==2&&passageAnchor!=nullptr;
|
||||
const bool anchoredIslandPassage=passageAnchor!=nullptr
|
||||
&&upper.generatorKind=="FloatingIslands";
|
||||
const bool anchoredSpiral=style==2&&anchoredIslandPassage;
|
||||
if(!hasUpperRoute||!hasLowerRoute)
|
||||
{
|
||||
const auto xy=PassageRandomXY(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
|
||||
@@ -652,7 +663,10 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
std::array<std::int64_t,2> upperTangent{},lowerTangent{};
|
||||
const bool hasUpperTangent=hasUpperRoute
|
||||
&&routeTangent(upper,true,upperTangent);
|
||||
const bool hasLowerTangent=hasLowerRoute
|
||||
const bool hasPassageArrival=std::any_of(lower.anchors.begin(),lower.anchors.end(),
|
||||
[](const auto& anchor){return anchor.kind=="passage_arrival"
|
||||
&&anchor.values.size()>=3;});
|
||||
const bool hasLowerTangent=hasLowerRoute&&!hasPassageArrival
|
||||
&&routeTangent(lower,false,lowerTangent);
|
||||
|
||||
const std::int64_t outerRingRadius=std::max<std::int64_t>(1,
|
||||
@@ -671,6 +685,8 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
std::max<std::int64_t>(0,cfg?cfg->cascadeLedgeSubvoxels:0)
|
||||
*static_cast<std::int64_t>(cfg?cfg->cascadeSteps:4)),
|
||||
mouthRadius+2*qPerMetre,outerRingRadius);
|
||||
if(anchoredIslandPassage)
|
||||
ringRadius=std::min(ringRadius,mouthRadius+qPerMetre/2);
|
||||
std::uint32_t phase=static_cast<std::uint32_t>(PassageSeed(seed,
|
||||
upper.strateIndex,lower.strateIndex,connection,0x50484153u)%16);
|
||||
if(hasUpperRoute)
|
||||
@@ -700,7 +716,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
+clockwiseTangentY*upperTangent[1]>=0.0;
|
||||
}
|
||||
const int direction=clockwise?1:-1;
|
||||
constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree contract.
|
||||
constexpr std::int64_t TargetGradeQ16=12500; // leave margin below the 15-degree helix contract.
|
||||
const std::int64_t wormRadialWobble=std::min<std::int64_t>(
|
||||
std::max<std::int64_t>(0,wander/3),2*qPerMetre);
|
||||
const std::int64_t wormCenterWobble=std::min<std::int64_t>(
|
||||
@@ -716,10 +732,14 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
const double loop=static_cast<double>(loopIndex);
|
||||
const double phaseA=loop*0.6180339887498948+0.37;
|
||||
const double phaseB=loop*0.4142135623730950+1.11;
|
||||
const double jitterX=static_cast<double>(wormCenterWobble)*std::sin(phaseB);
|
||||
const double jitterY=static_cast<double>(wormCenterWobble)*std::cos(phaseB);
|
||||
radius+=static_cast<double>(wormRadialWobble)
|
||||
*std::sin(angle+phaseA);
|
||||
centerX=static_cast<double>(wormCenterWobble)*std::sin(phaseB);
|
||||
centerY=static_cast<double>(wormCenterWobble)*std::cos(phaseB);
|
||||
centerX=jitterX+(anchoredIslandPassage&&passageAnchor
|
||||
?static_cast<double>(passageAnchor->values[0]):0.0);
|
||||
centerY=jitterY+(anchoredIslandPassage&&passageAnchor
|
||||
?static_cast<double>(passageAnchor->values[1]):0.0);
|
||||
}
|
||||
else if(pathStyle==3u)
|
||||
{
|
||||
@@ -730,6 +750,11 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
cfg->cascadeLedgeSubvoxels):0));
|
||||
radius-=ledge*(0.5-0.5*std::cos(angle*steps));
|
||||
}
|
||||
if(pathStyle!=1u&&anchoredIslandPassage&&passageAnchor)
|
||||
{
|
||||
centerX=static_cast<double>(passageAnchor->values[0]);
|
||||
centerY=static_cast<double>(passageAnchor->values[1]);
|
||||
}
|
||||
return std::array<std::int64_t,2>{
|
||||
static_cast<std::int64_t>(std::llround(centerX+radius*directions[directionIndex][0]/65536.0)),
|
||||
static_cast<std::int64_t>(std::llround(centerY+radius*directions[directionIndex][1]/65536.0))};
|
||||
@@ -748,7 +773,11 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
const std::int64_t anchorY=passageAnchor->values[1];
|
||||
const std::int64_t rootRadius=passageAnchor->values[2];
|
||||
const std::int64_t rootBottom=passageAnchor->values[5];
|
||||
helixBedrockZ=rootBottom!=0?rootBottom:contentZQ(upper.bedrock.topMicrometres);
|
||||
const std::int64_t bedrockTop=rootBottom!=0?rootBottom
|
||||
:contentZQ(upper.bedrock.topMicrometres);
|
||||
// Passage route heights are player-center positions. End one capsule
|
||||
// half-height above the bedrock top so the final ramp step is walkable.
|
||||
helixBedrockZ=bedrockTop+qPerMetre;
|
||||
const std::int64_t helixRadius=spiralRadius;
|
||||
if(rootRadius<helixRadius+midRadius+qPerMetre/4
|
||||
||std::hypot(static_cast<double>(upperMouth[0]-anchorX),
|
||||
@@ -796,6 +825,8 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
helixEndIndex=xy.size()-1;
|
||||
hasAnchorHelix=helixEndIndex>0;
|
||||
}
|
||||
const std::int64_t routeGradeQ16=hasAnchorHelix
|
||||
&&lower.generatorKind=="SurfaceWorld"?8000:TargetGradeQ16;
|
||||
const std::array<std::int64_t,2> firstRing=ringPoint(phase);
|
||||
if(hasAnchorHelix&&pathStyle==1u)
|
||||
{
|
||||
@@ -843,12 +874,14 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
const std::size_t start=hasAnchorHelix?helixEndIndex:0;
|
||||
for(std::size_t i=start+1;i<xy.size();++i)
|
||||
total+=distanceQ(xy[i][0]-xy[i-1][0],xy[i][1]-xy[i-1][1]);
|
||||
if(!hasLowerTangent&&!xy.empty())
|
||||
total+=distanceQ(lowerMouth[0]-xy.back()[0],lowerMouth[1]-xy.back()[1]);
|
||||
return total;
|
||||
};
|
||||
while(loops<256)
|
||||
{
|
||||
totalHorizontal=measurePath();
|
||||
if(totalHorizontal*static_cast<std::uint64_t>(TargetGradeQ16)
|
||||
if(totalHorizontal*static_cast<std::uint64_t>(routeGradeQ16)
|
||||
>=static_cast<std::uint64_t>(verticalDistance)*65536ull)break;
|
||||
for(int step=1;step<=16;++step)
|
||||
{
|
||||
|
||||
@@ -110,6 +110,9 @@ public:
|
||||
const auto composeRouteMouthSupport=[&](std::int32_t strateIndex,
|
||||
const std::array<std::int64_t,3>& mouth,bool departure)
|
||||
{
|
||||
// Support is allowed to reinforce native solid terrain only. Without this
|
||||
// guard the route-point tread stamps a floating shelf into an open stratum.
|
||||
if(!(density<0.0f))return;
|
||||
const double mouthDx=qx-static_cast<double>(mouth[0]);
|
||||
const double mouthDy=qy-static_cast<double>(mouth[1]);
|
||||
const double routeSupportReach=13.0*qPerMetre;
|
||||
@@ -159,26 +162,26 @@ public:
|
||||
std::clamp(Z-floorVoxels,-floorThickness,0.0)));
|
||||
}
|
||||
};
|
||||
// Seed the route floor first. The bore carve below reopens any overlap at the
|
||||
// mouth, then applies its own lower tread, so neither shape blocks the capsule.
|
||||
// Keep the native surface route connected to the entrance. Passage carving
|
||||
// then removes only the parts that overlap the tunnel, whose own tread owns
|
||||
// the interior ramp.
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
composeRouteMouthSupport(passage.upperStrateIndex,passage.upperMouthSubvoxels,true);
|
||||
// The lower mouth emerges into its native terrain. Keep the passage tread
|
||||
// support, but do not stamp short, flat route-anchor shelves over that cave mouth.
|
||||
}
|
||||
composeRouteMouthSupport(passage.upperStrateIndex,
|
||||
passage.upperMouthSubvoxels,true);
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
const double placementRadiusQ=static_cast<double>(passage.placementRadiusSubvoxels)
|
||||
+static_cast<double>(passage.midRadiusSubvoxels);
|
||||
if(qx*qx+qy*qy>placementRadiusQ*placementRadiusQ)continue;
|
||||
if(qx*qx+qy*qy>placementRadiusQ*placementRadiusQ)
|
||||
continue;
|
||||
const double firstFloorQ=passage.controlPoints.front().positionSubvoxels[2]
|
||||
-passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ;
|
||||
const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2]
|
||||
-passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ;
|
||||
if(qz>firstFloorQ+2.0*passage.mouthRadiusSubvoxels
|
||||
||qz<lastFloorQ-2.0*passage.mouthRadiusSubvoxels)continue;
|
||||
||qz<lastFloorQ-2.0*passage.mouthRadiusSubvoxels)
|
||||
continue;
|
||||
const auto floorProfile=[&](const auto& point)
|
||||
{return point.positionSubvoxels[2]-point.radiusSubvoxels+playerCenterAboveFloorQ;};
|
||||
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(passage.controlPoints.size())-1;
|
||||
@@ -237,21 +240,6 @@ public:
|
||||
const auto& nearestB=passage.controlPoints[static_cast<std::size_t>(bestIndex+1)];
|
||||
const double nearestRadius=nearestA.radiusSubvoxels+bestT*(
|
||||
static_cast<double>(nearestB.radiusSubvoxels)-nearestA.radiusSubvoxels);
|
||||
const double nearestDx=static_cast<double>(nearestB.positionSubvoxels[0])
|
||||
-nearestA.positionSubvoxels[0];
|
||||
const double nearestDy=static_cast<double>(nearestB.positionSubvoxels[1])
|
||||
-nearestA.positionSubvoxels[1];
|
||||
const double nearestHorizontalSq=nearestDx*nearestDx+nearestDy*nearestDy;
|
||||
const double supportPathT=nearestHorizontalSq>0.0
|
||||
?std::clamp(((qx-nearestA.positionSubvoxels[0])*nearestDx
|
||||
+(qy-nearestA.positionSubvoxels[1])*nearestDy)/nearestHorizontalSq,0.0,1.0)
|
||||
:bestT;
|
||||
const double supportPathRadius=nearestA.radiusSubvoxels+supportPathT*(
|
||||
static_cast<double>(nearestB.radiusSubvoxels)-nearestA.radiusSubvoxels);
|
||||
const double supportPathPlayerCenterZ=nearestA.positionSubvoxels[2]+supportPathT*(
|
||||
static_cast<double>(nearestB.positionSubvoxels[2])-nearestA.positionSubvoxels[2])
|
||||
-supportPathRadius+playerCenterAboveFloorQ;
|
||||
const double nearestPlayerCenterZ=supportPathPlayerCenterZ;
|
||||
const double distanceToCenter=std::max(1.0e-9,bestSignedDistance+nearestRadius);
|
||||
const double verticalWallAlignment=distanceToCenter>1.0e-9
|
||||
?std::clamp(-(qz-(nearestA.positionSubvoxels[2]+bestT*(
|
||||
@@ -265,11 +253,6 @@ public:
|
||||
// Positive density is air; rough rock is cut with full side/ceiling detail and
|
||||
// gentler relief on the gravity-facing wall so the supported tread stays open.
|
||||
composed=std::max(composed,static_cast<float>(-roughSignedDistance/Q));
|
||||
// At tight bends, keep the tread tied to the player-center profile selected
|
||||
// by horizontal path position so vertical probe offsets cannot shift the ramp.
|
||||
const bool fallbackSupport=qz>=nearestPlayerCenterZ-1.50*qPerMetre;
|
||||
const double supportThicknessVoxels=fallbackSupport
|
||||
?qPerMetre/Q:0.5*qPerMetre/Q;
|
||||
double bestSupportDistanceSquared=std::numeric_limits<double>::infinity();
|
||||
const auto considerSupportSegment=[&](std::ptrdiff_t index)
|
||||
{
|
||||
@@ -293,21 +276,28 @@ public:
|
||||
static_cast<double>(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2]);
|
||||
const double radius=segmentA.radiusSubvoxels+supportT*(
|
||||
static_cast<double>(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels);
|
||||
const double supportPlayerCenterZ=centerZ-radius+playerCenterAboveFloorQ;
|
||||
if(fallbackSupport)
|
||||
{
|
||||
const double supportProfileError=std::abs(
|
||||
supportPlayerCenterZ-nearestPlayerCenterZ);
|
||||
if(supportProfileError>0.06*qPerMetre)return;
|
||||
}
|
||||
const double horizontalX=qx-centerX,horizontalY=qy-centerY;
|
||||
const double horizontalDistance=DeterministicSqrt(horizontalX*horizontalX
|
||||
+horizontalY*horizontalY);
|
||||
const double supportPlayerCenterZ=centerZ-radius+playerCenterAboveFloorQ;
|
||||
const double supportQueryDepth=supportPlayerCenterZ-qz;
|
||||
// Support belongs just below the avatar's foot plane. Queries within
|
||||
// the capsule body, or a neighbouring coil's overlapping bore, must
|
||||
// not turn that tread into a ceiling or mid-air shelf.
|
||||
if(supportQueryDepth<0.93*qPerMetre||supportQueryDepth>1.30*qPerMetre)return;
|
||||
const bool fallbackSupport=qz>=supportPlayerCenterZ-1.50*qPerMetre;
|
||||
const double supportThicknessVoxels=fallbackSupport
|
||||
?qPerMetre/Q:0.5*qPerMetre/Q;
|
||||
const double floorQ=centerZ-radius+floorOffset*qPerMetre;
|
||||
const double floorVoxels=floorQ/Q;
|
||||
// Keep the supported ramp to a comfortable walking width. Extending a
|
||||
// full bore-radius shelf across overlapping helix coils can block the
|
||||
// capsule on the neighbouring turn.
|
||||
const bool lowerMouthBodyOverlap=index>=lowerMouthIndex-2
|
||||
&&supportQueryDepth<0.98*qPerMetre;
|
||||
const double supportHalfWidth=std::min(radius,
|
||||
(lowerMouthBodyOverlap?0.90:1.25)*qPerMetre);
|
||||
if(Z<floorVoxels-supportThicknessVoxels||Z>floorVoxels+1.0/Q)return;
|
||||
const double supportHalfWidth=fallbackSupport?radius
|
||||
:std::max(0.0,radius+2.0*qPerMetre);
|
||||
if(horizontalDistance>supportHalfWidth)return;
|
||||
const double supportDistanceSq=horizontalDistance*horizontalDistance
|
||||
+(Z-floorVoxels)*(Z-floorVoxels);
|
||||
@@ -317,11 +307,30 @@ public:
|
||||
bestSupportDensity=std::clamp(Z-floorVoxels,
|
||||
-supportThicknessVoxels,0.0);
|
||||
};
|
||||
const std::ptrdiff_t supportBegin=std::max<std::ptrdiff_t>(0,bestIndex-1);
|
||||
const std::ptrdiff_t supportEnd=std::min<std::ptrdiff_t>(lastIndex-1,bestIndex+1);
|
||||
std::ptrdiff_t supportCenterIndex=bestIndex;
|
||||
if(bestIndex<lowerMouthIndex)
|
||||
{
|
||||
// A helix can have multiple centerlines at one XY. Select the tread
|
||||
// whose floor is just below this density query, not the closest wall.
|
||||
const double supportProfileTarget=qz+0.93*qPerMetre;
|
||||
const auto supportProfile=std::lower_bound(passage.controlPoints.begin(),mainEnd,
|
||||
supportProfileTarget,[&](const auto& point,double value)
|
||||
{return floorProfile(point)>value;});
|
||||
const std::ptrdiff_t profileIndex=std::clamp<std::ptrdiff_t>(
|
||||
supportProfile-passage.controlPoints.begin(),0,lowerMouthIndex);
|
||||
// At floor height the nearest 3D centerline can belong to the next
|
||||
// coil below. The descending floor profile identifies the coil that
|
||||
// owns this tread, even when it is more than eight control points away.
|
||||
supportCenterIndex=profileIndex;
|
||||
}
|
||||
const std::ptrdiff_t supportBegin=std::max<std::ptrdiff_t>(0,supportCenterIndex-1);
|
||||
const std::ptrdiff_t supportEnd=std::min<std::ptrdiff_t>(lastIndex-1,
|
||||
supportCenterIndex+1);
|
||||
for(std::ptrdiff_t index=supportBegin;index<=supportEnd;++index)
|
||||
considerSupportSegment(index);
|
||||
if(hasSupportFloor)
|
||||
// Keep route treads inside the native rock envelope. Carving may reopen the
|
||||
// floor above this point, but a support cannot create a new surface in air.
|
||||
if(hasSupportFloor&&density<0.0f)
|
||||
composed=std::min(composed,static_cast<float>(bestSupportDensity));
|
||||
}
|
||||
return composed;
|
||||
|
||||
Reference in new issue
Block a user