feat(core): ore vein records and a materialAt query, off by default (L50); research R18, R19

Versioned capsule vein records (stable ID, resource ID, endpoints, radius, seeded irregularity) resolved
into the snapshot only when requested, placed beside the route and away from passages, landings and
landmarks (forced-passage control rejected). The bound field table gains materialAt (host + two resource
IDs + Q16 weights) behind a per-tile BVH; mesh export v2 carries per-vertex materials; a measurement-only
yield computes removed resource volume (0 on already-air space). With veins on, every density, mesh,
payload and topology hash is unchanged; demo off: owner hashes identical to L47. fast/medium/full PASS.
Research R18 (cavern-graph planner) and R19 (why FloatingIslands costs ~10x SurfaceWorld).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-24 18:25:03 +02:00
1 parent b9ccff4ad6
commit ca85e876ad
15 files changed
+1573 -19

No files matched your search

@@ -283,6 +283,187 @@ namespace
}
return true;
}
double LayoutSegmentDistance(const std::array<double,3>& p0,
const std::array<double,3>& p1,const std::array<double,3>& q0,
const std::array<double,3>& q1)
{
std::array<double,3> u{},v{},w{};
double a=0.0,b=0.0,c=0.0,d=0.0,e=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
u[axis]=p1[axis]-p0[axis];v[axis]=q1[axis]-q0[axis];
w[axis]=p0[axis]-q0[axis];
a+=u[axis]*u[axis];b+=u[axis]*v[axis];
c+=v[axis]*v[axis];d+=u[axis]*w[axis];e+=v[axis]*w[axis];
}
const double denominator=a*c-b*b;
double sN=0.0,sD=denominator,tN=0.0,tD=denominator;
if(denominator<1.0e-18){sN=0.0;sD=1.0;tN=e;tD=c;}
else
{
sN=b*e-c*d;tN=a*e-b*d;
if(sN<0.0){sN=0.0;tN=e;tD=c;}
else if(sN>sD){sN=sD;tN=e+b;tD=c;}
}
if(tN<0.0)
{
tN=0.0;
if(-d<0.0)sN=0.0;
else if(-d>a)sN=sD;
else{sN=-d;sD=a;}
}
else if(tN>tD)
{
tN=tD;
if(-d+b<0.0)sN=0.0;
else if(-d+b>a)sN=sD;
else{sN=-d+b;sD=a;}
}
const double s=std::abs(sN)<1.0e-18?0.0:sN/sD;
const double t=std::abs(tN)<1.0e-18?0.0:tN/tD;
double distanceSquared=0.0;
for(std::size_t axis=0;axis<3;++axis)
{
const double delta=w[axis]+s*u[axis]-t*v[axis];
distanceSquared+=delta*delta;
}
return std::sqrt(std::max(0.0,distanceSquared));
}
std::array<double,3> LayoutOrePointUm(const std::array<std::int64_t,3>& point)
{return {static_cast<double>(point[0]),static_cast<double>(point[1]),
static_cast<double>(point[2])};}
bool LayoutOreVeinSafe(const ResolvedLayoutSnapshotV3& layout,
const ResolvedLayoutOreVeinV1& vein,std::string* reason=nullptr)
{
const auto fail=[&](const char* text)
{if(reason)*reason=text;return false;};
const auto strate=std::find_if(layout.strates.begin(),layout.strates.end(),
[&](const auto& value){return value.strateIndex==vein.strateIndex;});
if(strate==layout.strates.end()||!strate->content.presentInCurrentField)
return fail("vein strate has no current content band");
if(vein.boundsMinimumMicrometres[2]<=strate->content.bottomMicrometres
||vein.boundsMaximumMicrometres[2]>=strate->content.topMicrometres)
return fail("vein bounds touch or cross the ceiling/bedrock content faces");
for(const auto* band:{&strate->ceiling,&strate->bedrock})
if(band->presentInCurrentField
&&vein.boundsMinimumMicrometres[2]<band->topMicrometres
&&vein.boundsMaximumMicrometres[2]>band->bottomMicrometres)
return fail("vein bounds overlap a ceiling or bedrock band");
const auto veinA=LayoutOrePointUm(vein.endpointsMicrometres[0]);
const auto veinB=LayoutOrePointUm(vein.endpointsMicrometres[1]);
const double coreRadius=static_cast<double>(vein.radiusMicrometres
+vein.irregularityAmplitudeMicrometres+vein.blendWidthMicrometres);
const double coreMicrometres=std::max<std::uint32_t>(1,layout.coreVoxelSizeMicrometres);
constexpr double RouteClearanceMicrometres=100000.0;
const auto qToUm=[&](const std::array<std::int64_t,3>& q)
{return std::array<double,3>{q[0]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel,
q[1]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel,
q[2]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel};};
for(const auto& passage:layout.passages)
{
if(passage.upperStrateIndex!=vein.strateIndex
&&passage.lowerStrateIndex!=vein.strateIndex)continue;
const auto upper=qToUm(passage.upperMouthSubvoxels);
const auto lower=qToUm(passage.lowerMouthSubvoxels);
const double mouthRadius=passage.mouthRadiusSubvoxels*coreMicrometres
/WorldAssemblySubvoxelsPerVoxel;
if(LayoutSegmentDistance(veinA,veinB,upper,upper)
<=coreRadius+mouthRadius+RouteClearanceMicrometres
||LayoutSegmentDistance(veinA,veinB,lower,lower)
<=coreRadius+mouthRadius+RouteClearanceMicrometres)
return fail("vein overlaps a clear passage landing");
for(std::size_t i=0;i+1<passage.controlPoints.size();++i)
{
const auto a=qToUm(passage.controlPoints[i].positionSubvoxels);
const auto b=qToUm(passage.controlPoints[i+1].positionSubvoxels);
const double radius=std::max(passage.controlPoints[i].radiusSubvoxels,
passage.controlPoints[i+1].radiusSubvoxels)*coreMicrometres
/WorldAssemblySubvoxelsPerVoxel;
if(LayoutSegmentDistance(veinA,veinB,a,b)
<=coreRadius+radius+RouteClearanceMicrometres)
return fail("vein enters a clear passage corridor");
}
}
std::vector<const ResolvedLayoutAnchorV2*> route;
for(const auto& anchor:strate->anchors)
{
if(anchor.values.size()<3)continue;
if(anchor.kind=="route_point")route.push_back(&anchor);
if(anchor.kind.find("landing")!=std::string::npos)
{
const std::array<double,3> landing={
anchor.values[0]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel,
anchor.values[1]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel,
anchor.values[2]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel};
const double radius=anchor.values.size()>=4
?anchor.values[3]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel:3000000.0;
if(LayoutSegmentDistance(veinA,veinB,landing,landing)
<=coreRadius+radius+RouteClearanceMicrometres)
return fail("vein overlaps a resolved landing footprint");
}
}
for(std::size_t i=0;i+1<route.size();++i)
{
const auto qPoint=[&](const ResolvedLayoutAnchorV2& anchor)
{return std::array<double,3>{anchor.values[0]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel,
anchor.values[1]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel,
anchor.values[2]*coreMicrometres/WorldAssemblySubvoxelsPerVoxel};};
const auto a=qPoint(*route[i]),b=qPoint(*route[i+1]);
const double routeRadius=std::max(route[i]->values.size()>=4?route[i]->values[3]:0,
route[i+1]->values.size()>=4?route[i+1]->values[3]:0)
*coreMicrometres/WorldAssemblySubvoxelsPerVoxel;
if(LayoutSegmentDistance(veinA,veinB,a,b)
<=coreRadius+routeRadius+RouteClearanceMicrometres)
return fail("vein enters a clear strate route corridor");
}
for(const auto& room:layout.cavernRooms)
if(room.strateIndex==vein.strateIndex)
for(std::size_t axis=0;axis<3;++axis)
if(vein.boundsMinimumMicrometres[axis]
<=room.boundsMaximumMicrometres[axis]
&&vein.boundsMaximumMicrometres[axis]
>=room.boundsMinimumMicrometres[axis])
return fail("vein overlaps a cavern landmark footprint");
for(const auto& edge:layout.cavernEdges)
{
if(edge.strateIndex!=vein.strateIndex)continue;
std::array<double,3> previous{};
const auto bezier=[&](double t)
{
const double one=1.0-t;
const std::array<double,4> weights={one*one*one,3.0*one*one*t,
3.0*one*t*t,t*t*t};
std::array<double,3> point{};
for(std::size_t i=0;i<4;++i)
for(std::size_t axis=0;axis<3;++axis)
point[axis]+=weights[i]*edge.controlPointsMicrometres[i][axis];
return point;
};
previous=bezier(0.0);
for(int step=1;step<=256;++step)
{
const auto next=bezier(static_cast<double>(step)/256.0);
if(LayoutSegmentDistance(veinA,veinB,previous,next)
<=coreRadius+edge.boreRadiusMicrometres+RouteClearanceMicrometres)
return fail("vein enters a landmark passage corridor");
previous=next;
}
}
for(const auto& stamp:layout.stamps)
{
if(stamp.strateIndex!=vein.strateIndex
||(stamp.kind!=ResolvedSdfStampKindV1::fillDais
&&stamp.kind!=ResolvedSdfStampKindV1::fillPillar))continue;
bool overlaps=true;
for(std::size_t axis=0;axis<3;++axis)
overlaps=overlaps&&vein.boundsMinimumMicrometres[axis]
<=stamp.boundsMaximumMicrometres[axis]
&&vein.boundsMaximumMicrometres[axis]
>=stamp.boundsMinimumMicrometres[axis];
if(overlaps)return fail("vein overlaps a resolved landmark solid footprint");
}
return true;
}
std::string LayoutHex(std::uint64_t value)
{std::ostringstream out;out<<std::hex<<std::setfill('0')<<std::setw(16)<<value;return out.str();}
std::string LayoutBandJson(const ResolvedLayoutBandV2& band)
@@ -454,6 +635,32 @@ namespace
}
out<<']';
}
if(!s.oreVeins.empty())
{
out<<",\"ore_vein_vocabulary_version\":"<<s.oreVeinVocabularyVersion
<<",\"ore_veins\":[";
for(std::size_t i=0;i<s.oreVeins.size();++i)
{
if(i)out<<',';const auto& vein=s.oreVeins[i];
out<<"{\"stable_id\":"<<LayoutEscape(vein.stableId)
<<",\"strate_index\":"<<vein.strateIndex
<<",\"resource_material_id\":"<<vein.resourceMaterialId
<<",\"endpoints_um\":[["<<vein.endpointsMicrometres[0][0]<<','
<<vein.endpointsMicrometres[0][1]<<','<<vein.endpointsMicrometres[0][2]
<<"],["<<vein.endpointsMicrometres[1][0]<<','
<<vein.endpointsMicrometres[1][1]<<','<<vein.endpointsMicrometres[1][2]
<<"]],\"bounds_min_um\":["<<vein.boundsMinimumMicrometres[0]<<','
<<vein.boundsMinimumMicrometres[1]<<','<<vein.boundsMinimumMicrometres[2]
<<"],\"bounds_max_um\":["<<vein.boundsMaximumMicrometres[0]<<','
<<vein.boundsMaximumMicrometres[1]<<','<<vein.boundsMaximumMicrometres[2]
<<"],\"radius_um\":"<<vein.radiusMicrometres
<<",\"irregularity_amplitude_um\":"<<vein.irregularityAmplitudeMicrometres
<<",\"irregularity_wavelength_um\":"<<vein.irregularityWavelengthMicrometres
<<",\"blend_width_um\":"<<vein.blendWidthMicrometres
<<",\"irregularity_seed\":"<<vein.irregularitySeed<<'}';
}
out<<']';
}
out<<'}';return out.str();
}
std::int64_t LayoutInt(const VFJson::Value& obj,std::string_view key)
@@ -620,6 +827,8 @@ bool ResolvedLayoutSnapshotV3::finalize(std::string& error)
{return a.stableId<b.stableId;});
std::sort(cavernEdges.begin(),cavernEdges.end(),[](const auto& a,const auto& b)
{return a.stableId<b.stableId;});
std::sort(oreVeins.begin(),oreVeins.end(),[](const auto& a,const auto& b)
{return a.stableId<b.stableId;});
for(std::size_t i=0;i<strates.size();++i)
{
auto& x=strates[i];x.order=static_cast<std::int32_t>(i);
@@ -1028,6 +1237,55 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
}
}
}
if(oreVeins.empty())
{
if(oreVeinVocabularyVersion!=0)
add("ore_vein_vocabulary_version",
"ore-vein vocabulary version is set without records");
}
else if(oreVeinVocabularyVersion!=ResolvedLayoutOreVeinVocabularyVersionV1)
add("ore_vein_vocabulary_version",
"ore-vein records require vocabulary version 1");
std::vector<std::string> oreVeinIds;
for(const auto& vein:oreVeins)
{
if(vein.stableId.empty()
||std::find(oreVeinIds.begin(),oreVeinIds.end(),vein.stableId)!=oreVeinIds.end())
add("ore_vein_identity","ore vein has an empty or duplicate stable identity");
else oreVeinIds.push_back(vein.stableId);
if(vein.resourceMaterialId==0||vein.radiusMicrometres<250000
||vein.radiusMicrometres>20000000
||vein.irregularityAmplitudeMicrometres<0
||vein.irregularityAmplitudeMicrometres>10000000
||vein.irregularityWavelengthMicrometres<0
||vein.irregularityWavelengthMicrometres>100000000
||(vein.irregularityAmplitudeMicrometres>0
&&vein.irregularityWavelengthMicrometres<=0)
||vein.blendWidthMicrometres<50000
||vein.blendWidthMicrometres>2000000
||vein.irregularitySeed>0x001fffffffffffffull)
add("ore_vein_parameters",vein.stableId+" has invalid or unbounded parameters");
const std::int64_t pad=vein.radiusMicrometres
+vein.irregularityAmplitudeMicrometres+vein.blendWidthMicrometres;
for(std::size_t axis=0;axis<3;++axis)
{
if(vein.endpointsMicrometres[0][axis]<-1000000000000ll
||vein.endpointsMicrometres[0][axis]>1000000000000ll
||vein.endpointsMicrometres[1][axis]<-1000000000000ll
||vein.endpointsMicrometres[1][axis]>1000000000000ll)
add("ore_vein_bounds",vein.stableId+" endpoint is outside the bounded world envelope");
if(vein.boundsMinimumMicrometres[axis]
!=std::min(vein.endpointsMicrometres[0][axis],
vein.endpointsMicrometres[1][axis])-pad
||vein.boundsMaximumMicrometres[axis]
!=std::max(vein.endpointsMicrometres[0][axis],
vein.endpointsMicrometres[1][axis])+pad)
add("ore_vein_bounds",vein.stableId+" conservative bounds do not match its parameters");
}
std::string safetyReason;
if(!LayoutOreVeinSafe(*this,vein,&safetyReason))
add("ore_vein_placement",vein.stableId+" is unsafe: "+safetyReason);
}
if(strict&&!issues.empty()){error="strict resolved-layout validation rejected "+issues.front().code;return false;}
return true;
}
@@ -1046,6 +1304,8 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
s.stampVocabularyVersion=static_cast<std::uint32_t>(LayoutInt(root,"stamp_vocabulary_version"));
s.cavernGraphVocabularyVersion=static_cast<std::uint32_t>(
LayoutInt(root,"cavern_graph_vocabulary_version"));
s.oreVeinVocabularyVersion=static_cast<std::uint32_t>(
LayoutInt(root,"ore_vein_vocabulary_version"));
for(const auto& v:root.Get("strates").AsArray())
{
ResolvedLayoutStratumV2 x;x.stableId=LayoutString(v,"stable_id");
@@ -1166,6 +1426,33 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
edge.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
s.cavernEdges.push_back(std::move(edge));
}
for(const auto& v:root.Get("ore_veins").AsArray())
{
ResolvedLayoutOreVeinV1 vein;vein.stableId=LayoutString(v,"stable_id");
vein.strateIndex=static_cast<std::int32_t>(LayoutInt(v,"strate_index"));
vein.resourceMaterialId=static_cast<std::uint16_t>(LayoutInt(v,"resource_material_id"));
const auto& endpoints=v.Get("endpoints_um").AsArray();
for(std::size_t endpoint=0;endpoint<2&&endpoint<endpoints.size();++endpoint)
{
const auto& values=endpoints[endpoint].AsArray();
for(std::size_t axis=0;axis<3&&axis<values.size();++axis)
vein.endpointsMicrometres[endpoint][axis]=values[axis].Integer();
}
const auto readTriplet=[&](std::string_view key,std::array<std::int64_t,3>& target)
{
const auto& values=v.Get(key).AsArray();
for(std::size_t axis=0;axis<3&&axis<values.size();++axis)
target[axis]=values[axis].Integer();
};
readTriplet("bounds_min_um",vein.boundsMinimumMicrometres);
readTriplet("bounds_max_um",vein.boundsMaximumMicrometres);
vein.radiusMicrometres=LayoutInt(v,"radius_um");
vein.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
vein.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
vein.blendWidthMicrometres=LayoutInt(v,"blend_width_um");
vein.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
s.oreVeins.push_back(std::move(vein));
}
const std::string serializedIdentity=LayoutString(root,"stable_identity");
if(!s.finalize(error))return std::nullopt;
if(s.stableIdentity!=serializedIdentity){error="resolved layout stable identity does not match its canonical payload";return std::nullopt;}
@@ -1352,6 +1639,148 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
layout=std::move(candidate);return true;
}
bool ResolveResolvedLayoutOreVeinsV1(ResolvedLayoutSnapshotV3& layout,std::int32_t seed,
std::span<const ResolvedLayoutOreVeinRequestV1> requests,std::string& error)
{
error.clear();
ResolvedLayoutSnapshotV3 candidate=layout;
candidate.oreVeins.clear();candidate.oreVeinVocabularyVersion=0;
if(requests.empty())
{
if(!candidate.finalize(error))return false;
layout=std::move(candidate);return true;
}
candidate.oreVeinVocabularyVersion=ResolvedLayoutOreVeinVocabularyVersionV1;
std::vector<ResolvedLayoutOreVeinRequestV1> ordered(requests.begin(),requests.end());
std::sort(ordered.begin(),ordered.end(),[](const auto& a,const auto& b)
{return a.stableId<b.stableId;});
std::vector<std::string> requestIds;
candidate.oreVeins.reserve(ordered.size());
for(const auto& request:ordered)
{
if(request.stableId.empty()
||std::find(requestIds.begin(),requestIds.end(),request.stableId)!=requestIds.end())
{error="ore-vein requests require non-empty unique stable IDs";return false;}
requestIds.push_back(request.stableId);
if(request.resourceMaterialId==0||request.placementRadiusMicrometres<0
||request.placementRadiusMicrometres>10000000
||request.radiusMicrometres<250000||request.radiusMicrometres>20000000
||request.irregularityAmplitudeMicrometres<0
||request.irregularityAmplitudeMicrometres>10000000
||request.irregularityWavelengthMicrometres<0
||request.irregularityWavelengthMicrometres>100000000
||(request.irregularityAmplitudeMicrometres>0
&&request.irregularityWavelengthMicrometres<=0)
||request.blendWidthMicrometres<50000
||request.blendWidthMicrometres>2000000)
{error="ore-vein request "+request.stableId+" has invalid or unbounded parameters";return false;}
const auto strate=std::find_if(candidate.strates.begin(),candidate.strates.end(),
[&](const auto& value){return value.strateIndex==request.strateIndex;});
if(strate==candidate.strates.end())
{error="ore-vein request "+request.stableId+" names an unknown strate";return false;}
const std::uint64_t baseSeed=LayoutStampSeed(seed,request.strateIndex,
std::string("ore-vein/")+request.stableId)&0x001fffffffffffffull;
std::uint64_t state=baseSeed;
const auto randomOffset=[&](std::uint64_t bits)
{
const std::int64_t radius=request.placementRadiusMicrometres;
if(radius==0)return std::int64_t{0};
const std::uint64_t span=static_cast<std::uint64_t>(radius)*2+1;
return static_cast<std::int64_t>(bits%span)-radius;
};
bool placed=false;
std::string lastUnsafeReason="all candidates were outside the bounded content volume";
const std::uint32_t attempts=request.placementRadiusMicrometres==0?1u:512u;
for(std::uint32_t attempt=0;attempt<attempts;++attempt)
{
std::array<std::int64_t,3> offset{};
if(request.placementRadiusMicrometres>0)
{
offset={randomOffset(LayoutStampNext(state)),
randomOffset(LayoutStampNext(state)),
randomOffset(LayoutStampNext(state))/3};
const long double radius=request.placementRadiusMicrometres;
const long double distanceSquared=static_cast<long double>(offset[0])*offset[0]
+static_cast<long double>(offset[1])*offset[1]
+static_cast<long double>(offset[2])*offset[2];
if(distanceSquared>radius*radius)continue;
}
ResolvedLayoutOreVeinV1 vein;vein.stableId=request.stableId;
vein.strateIndex=request.strateIndex;
vein.resourceMaterialId=request.resourceMaterialId;
vein.radiusMicrometres=request.radiusMicrometres;
vein.irregularityAmplitudeMicrometres=request.irregularityAmplitudeMicrometres;
vein.irregularityWavelengthMicrometres=request.irregularityWavelengthMicrometres;
vein.blendWidthMicrometres=request.blendWidthMicrometres;
vein.irregularitySeed=baseSeed;
for(std::size_t endpoint=0;endpoint<2;++endpoint)
for(std::size_t axis=0;axis<3;++axis)
vein.endpointsMicrometres[endpoint][axis]
=request.anchorEndpointsMicrometres[endpoint][axis]+offset[axis];
const std::int64_t pad=vein.radiusMicrometres
+vein.irregularityAmplitudeMicrometres+vein.blendWidthMicrometres;
for(std::size_t axis=0;axis<3;++axis)
{
vein.boundsMinimumMicrometres[axis]=std::min(
vein.endpointsMicrometres[0][axis],vein.endpointsMicrometres[1][axis])-pad;
vein.boundsMaximumMicrometres[axis]=std::max(
vein.endpointsMicrometres[0][axis],vein.endpointsMicrometres[1][axis])+pad;
}
if(!LayoutOreVeinSafe(candidate,vein,&lastUnsafeReason))continue;
candidate.oreVeins.push_back(std::move(vein));placed=true;break;
}
if(!placed)
{error="ore-vein request "+request.stableId
+" has no deterministic passage-safe content placement: "+lastUnsafeReason;return false;}
}
if(!candidate.finalize(error))return false;
std::vector<ResolvedLayoutIssueV2> issues;std::string strictError;
if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,strictError))
{error="strict ore-vein placement validation failed: "+strictError;return false;}
layout=std::move(candidate);return true;
}
ResolvedLayoutOreVeinPlacementAuditV1 AuditResolvedLayoutOreVeinPlacementV1(
const ResolvedLayoutSnapshotV3& layout)
{
ResolvedLayoutOreVeinPlacementAuditV1 result;
if(layout.passages.empty()||layout.passages.front().controlPoints.empty())return result;
const auto& passage=layout.passages.front();
const auto& control=passage.controlPoints.front().positionSubvoxels;
ResolvedLayoutOreVeinV1 forced;forced.stableId="negative-control/forced-clear-passage";
forced.strateIndex=passage.upperStrateIndex;forced.resourceMaterialId=1;
forced.radiusMicrometres=500000;forced.blendWidthMicrometres=500000;
forced.irregularitySeed=1;
for(std::size_t axis=0;axis<3;++axis)
{
const std::int64_t point=static_cast<std::int64_t>(std::llround(
static_cast<double>(control[axis])*layout.coreVoxelSizeMicrometres
/WorldAssemblySubvoxelsPerVoxel));
forced.endpointsMicrometres[0][axis]=point;
forced.endpointsMicrometres[1][axis]=point;
forced.boundsMinimumMicrometres[axis]=point-1000000;
forced.boundsMaximumMicrometres[axis]=point+1000000;
}
ResolvedLayoutSnapshotV3 candidate=layout;
candidate.oreVeinVocabularyVersion=ResolvedLayoutOreVeinVocabularyVersionV1;
candidate.oreVeins.push_back(forced);
std::string finalizeError;
++result.negativeControlsRun;
if(candidate.finalize(finalizeError))
{
std::vector<ResolvedLayoutIssueV2> issues;std::string validationError;
if(!candidate.validate(ResolvedLayoutValidationModeV3::strict,issues,validationError)
&&std::any_of(issues.begin(),issues.end(),[](const auto& issue)
{return issue.code=="ore_vein_placement"
&&issue.detail.find("clear passage")!=std::string::npos;}))
++result.negativeControlsRejected;
}
result.passed=result.negativeControlsRun>0
&&result.negativeControlsRejected==result.negativeControlsRun;
return result;
}
ResolvedLayoutStampPlacementAuditV1 AuditResolvedLayoutStampPlacementV1(
const ResolvedLayoutSnapshotV3& layout)
{
+297
View File
@@ -369,6 +369,7 @@ public:
}
std::sort(BoundStratumFields.begin(),BoundStratumFields.end(),
[](const auto& A,const auto& B){return A.BottomChunkZ<B.BottomChunkZ;});
BuildOreVeinSpatialIndex();
}
const FBoundStratumField* FindBoundStratumForChunk(int32 ChunkZ) const
@@ -2972,6 +2973,27 @@ public:
return AnalyticDensityAt(X,Y,Z);
}
std::vector<std::uint32_t> OreVeinTileCandidates(
const std::array<std::int64_t,3>& minimum,
const std::array<std::int64_t,3>& maximum) const
{return BuildOreVeinTileList(minimum,maximum);}
VoxelForgeCore::MaterialSampleV1 EvaluateMaterialAt(float X,float Y,float Z,
std::span<const std::uint32_t> candidates,
std::uint64_t* candidateChecks=nullptr) const
{
if(SharedDensitySource)
return SharedDensitySource->MaterialAt(X,Y,Z,candidates,candidateChecks);
return MaterialAt(X,Y,Z,candidates,candidateChecks);
}
VoxelForgeCore::MaterialSampleV1 MaterialAt(float X,float Y,float Z) const
{
if(SharedDensitySource)return SharedDensitySource->MaterialAt(X,Y,Z);
const auto candidates=BuildOreVeinPointList(X,Y,Z);
return EvaluateMaterialAt(X,Y,Z,candidates);
}
std::string DensityDispatchPathAt(float X,float Y,float Z) const
{
if(SharedDensitySource)return SharedDensitySource->DensityDispatchPathAt(X,Y,Z);
@@ -3208,6 +3230,25 @@ private:
std::uint64_t irregularitySeed=0;
std::uint8_t quarterTurns=0;
};
struct FOreVeinSpatialBound
{
std::array<std::int64_t,3> minimumSubvoxels{},maximumSubvoxels{};
};
struct FOreVeinSpatialNode
{
std::array<std::int64_t,3> minimumSubvoxels{},maximumSubvoxels{};
std::size_t first=0,end=0;
int left=-1,right=-1;
};
struct FOreVeinRuntime
{
const VoxelForgeCore::ResolvedLayoutOreVeinV1* record=nullptr;
std::array<std::array<double,3>,2> endpointsVoxels{};
double radiusVoxels=0.0,irregularityAmplitudeVoxels=0.0;
double irregularityWavelengthVoxels=0.0,blendWidthVoxels=0.0;
std::uint64_t irregularitySeed=0;
std::uint16_t resourceMaterialId=0;
};
struct FCavernRuntimeFeature
{
const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr;
@@ -3349,6 +3390,258 @@ private:
if(!StampSpatialOrder.empty())BuildStampSpatialNode(0,StampSpatialOrder.size());
}
int BuildOreVeinSpatialNode(std::size_t first,std::size_t end)
{
FOreVeinSpatialNode node;node.first=first;node.end=end;
node.minimumSubvoxels.fill(std::numeric_limits<std::int64_t>::max());
node.maximumSubvoxels.fill(std::numeric_limits<std::int64_t>::min());
for(std::size_t offset=first;offset<end;++offset)
{
const auto& bound=OreVeinSpatialBounds[OreVeinSpatialOrder[offset]];
for(std::size_t axis=0;axis<3;++axis)
{
node.minimumSubvoxels[axis]=std::min(node.minimumSubvoxels[axis],
bound.minimumSubvoxels[axis]);
node.maximumSubvoxels[axis]=std::max(node.maximumSubvoxels[axis],
bound.maximumSubvoxels[axis]);
}
}
const int nodeIndex=static_cast<int>(OreVeinSpatialNodes.size());
OreVeinSpatialNodes.push_back(node);
if(end-first>8)
{
std::size_t splitAxis=0;
for(std::size_t axis=1;axis<3;++axis)
if(static_cast<long double>(node.maximumSubvoxels[axis])
-node.minimumSubvoxels[axis]
>static_cast<long double>(node.maximumSubvoxels[splitAxis])
-node.minimumSubvoxels[splitAxis])splitAxis=axis;
const std::size_t middle=first+(end-first)/2;
std::nth_element(OreVeinSpatialOrder.begin()+first,
OreVeinSpatialOrder.begin()+middle,OreVeinSpatialOrder.begin()+end,
[this,splitAxis](std::uint32_t a,std::uint32_t b)
{
const auto& left=OreVeinSpatialBounds[a];
const auto& right=OreVeinSpatialBounds[b];
const long double leftCenter=static_cast<long double>(
left.minimumSubvoxels[splitAxis])+left.maximumSubvoxels[splitAxis];
const long double rightCenter=static_cast<long double>(
right.minimumSubvoxels[splitAxis])+right.maximumSubvoxels[splitAxis];
return leftCenter==rightCenter?a<b:leftCenter<rightCenter;
});
OreVeinSpatialNodes[nodeIndex].left=BuildOreVeinSpatialNode(first,middle);
OreVeinSpatialNodes[nodeIndex].right=BuildOreVeinSpatialNode(middle,end);
}
return nodeIndex;
}
void QueryOreVeinSpatialNode(int nodeIndex,
const std::array<std::int64_t,3>& minimum,
const std::array<std::int64_t,3>& maximum,
std::vector<std::uint32_t>& result) const
{
if(nodeIndex<0)return;
const auto& node=OreVeinSpatialNodes[static_cast<std::size_t>(nodeIndex)];
for(std::size_t axis=0;axis<3;++axis)
if(node.maximumSubvoxels[axis]<minimum[axis]
||node.minimumSubvoxels[axis]>maximum[axis])return;
if(node.left<0)
{
for(std::size_t offset=node.first;offset<node.end;++offset)
{
const std::uint32_t index=OreVeinSpatialOrder[offset];
const auto& bound=OreVeinSpatialBounds[index];bool overlap=true;
for(std::size_t axis=0;axis<3;++axis)
overlap=overlap&&bound.minimumSubvoxels[axis]<=maximum[axis]
&&bound.maximumSubvoxels[axis]>=minimum[axis];
if(overlap)result.push_back(index);
}
return;
}
QueryOreVeinSpatialNode(node.left,minimum,maximum,result);
QueryOreVeinSpatialNode(node.right,minimum,maximum,result);
}
void BuildOreVeinSpatialIndex()
{
OreVeinSpatialBounds.clear();OreVeinSpatialOrder.clear();OreVeinSpatialNodes.clear();
OreVeinRuntime.clear();
if(!ResolvedLayout||ResolvedLayout->oreVeins.empty()||!Owner
||Owner->CoreVoxelSizeMicrons==0)return;
constexpr long double Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const long double scale=Q/Owner->CoreVoxelSizeMicrons;
OreVeinSpatialBounds.reserve(ResolvedLayout->oreVeins.size());
OreVeinRuntime.reserve(ResolvedLayout->oreVeins.size());
for(const auto& vein:ResolvedLayout->oreVeins)
{
FOreVeinSpatialBound bound;
for(std::size_t axis=0;axis<3;++axis)
{
bound.minimumSubvoxels[axis]=static_cast<std::int64_t>(std::floor(
static_cast<long double>(vein.boundsMinimumMicrometres[axis])*scale));
bound.maximumSubvoxels[axis]=static_cast<std::int64_t>(std::ceil(
static_cast<long double>(vein.boundsMaximumMicrometres[axis])*scale));
}
OreVeinSpatialBounds.push_back(bound);
FOreVeinRuntime runtime;runtime.record=&vein;
for(std::size_t endpoint=0;endpoint<2;++endpoint)
for(std::size_t axis=0;axis<3;++axis)
runtime.endpointsVoxels[endpoint][axis]=
static_cast<double>(vein.endpointsMicrometres[endpoint][axis])
/Owner->CoreVoxelSizeMicrons;
runtime.radiusVoxels=static_cast<double>(vein.radiusMicrometres)
/Owner->CoreVoxelSizeMicrons;
runtime.irregularityAmplitudeVoxels=
static_cast<double>(vein.irregularityAmplitudeMicrometres)
/Owner->CoreVoxelSizeMicrons;
runtime.irregularityWavelengthVoxels=
static_cast<double>(vein.irregularityWavelengthMicrometres)
/Owner->CoreVoxelSizeMicrons;
runtime.blendWidthVoxels=static_cast<double>(vein.blendWidthMicrometres)
/Owner->CoreVoxelSizeMicrons;
runtime.irregularitySeed=vein.irregularitySeed;
runtime.resourceMaterialId=vein.resourceMaterialId;
OreVeinRuntime.push_back(runtime);
}
OreVeinSpatialOrder.resize(OreVeinSpatialBounds.size());
for(std::size_t i=0;i<OreVeinSpatialOrder.size();++i)
OreVeinSpatialOrder[i]=static_cast<std::uint32_t>(i);
if(!OreVeinSpatialOrder.empty())BuildOreVeinSpatialNode(0,OreVeinSpatialOrder.size());
}
std::vector<std::uint32_t> BuildOreVeinTileList(
const std::array<std::int64_t,3>& minimum,
const std::array<std::int64_t,3>& maximum) const
{
std::vector<std::uint32_t> result;
if(OreVeinSpatialBounds.empty())return result;
for(std::size_t axis=0;axis<3;++axis)
if(minimum[axis]>maximum[axis])
{
result.resize(OreVeinSpatialBounds.size());
for(std::size_t i=0;i<result.size();++i)
result[i]=static_cast<std::uint32_t>(i);
return result;
}
QueryOreVeinSpatialNode(0,minimum,maximum,result);
std::sort(result.begin(),result.end());
return result;
}
std::vector<std::uint32_t> BuildOreVeinPointList(float X,float Y,float Z) const
{
constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const auto toQ=[](float value)
{
const long double scaled=static_cast<long double>(value)*Q;
if(!std::isfinite(value)||scaled<std::numeric_limits<std::int64_t>::min()
||scaled>std::numeric_limits<std::int64_t>::max())return std::int64_t{0};
return static_cast<std::int64_t>(std::llround(scaled));
};
const std::array<std::int64_t,3> point={toQ(X),toQ(Y),toQ(Z)};
return BuildOreVeinTileList(point,point);
}
VoxelForgeCore::MaterialSampleV1 MaterialAt(float X,float Y,float Z,
std::span<const std::uint32_t> candidates,
std::uint64_t* candidateChecks=nullptr) const
{
using namespace VoxelForgeCore;
MaterialSampleV1 result;
if(!ResolvedLayout||OreVeinRuntime.empty()||candidates.empty()
||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))return result;
const FBoundStratumField* bound=FindBoundStratumForZ(Z);
if(!bound)return result;
const std::array<double,3> point={X,Y,Z};
const auto noiseAt=[](std::uint64_t seed,std::int64_t x,std::int64_t y,std::int64_t z)
{
std::uint64_t hash=14695981039346656037ull;
for(const std::uint64_t word:{seed,static_cast<std::uint64_t>(x),
static_cast<std::uint64_t>(y),static_cast<std::uint64_t>(z)})
for(int byte=0;byte<8;++byte)
{hash^=static_cast<std::uint8_t>(word>>(byte*8));hash*=1099511628211ull;}
hash^=hash>>30;hash*=0xbf58476d1ce4e5b9ull;
hash^=hash>>27;hash*=0x94d049bb133111ebull;hash^=hash>>31;
return static_cast<double>(static_cast<std::uint32_t>(hash>>32))/2147483647.5-1.0;
};
const auto fade=[](double value){return value*value*(3.0-2.0*value);};
for(const std::uint32_t candidate:candidates)
{
if(candidateChecks)++*candidateChecks;
if(candidate>=OreVeinRuntime.size())continue;
const auto& vein=OreVeinRuntime[candidate];
if(!vein.record||vein.record->strateIndex!=bound->StrateIndex)continue;
const double wavelength=vein.irregularityWavelengthVoxels;
double irregularity=0.0;
if(vein.irregularityAmplitudeVoxels>0.0&&wavelength>0.0)
{
std::array<std::int64_t,3> cell{};std::array<double,3> t{};
for(std::size_t axis=0;axis<3;++axis)
{
const double coordinate=point[axis]/wavelength;
cell[axis]=static_cast<std::int64_t>(std::floor(coordinate));
t[axis]=fade(coordinate-cell[axis]);
}
double layer[2][2]{};
for(int z=0;z<2;++z)for(int y=0;y<2;++y)
{
const double a=noiseAt(vein.irregularitySeed,cell[0],cell[1]+y,cell[2]+z);
const double b=noiseAt(vein.irregularitySeed,cell[0]+1,cell[1]+y,cell[2]+z);
layer[z][y]=std::lerp(a,b,t[0]);
}
irregularity=std::lerp(std::lerp(layer[0][0],layer[0][1],t[1]),
std::lerp(layer[1][0],layer[1][1],t[1]),t[2]);
}
const auto& a=vein.endpointsVoxels[0];const auto& b=vein.endpointsVoxels[1];
double lengthSquared=0.0,dot=0.0;
for(std::size_t axis=0;axis<3;++axis)
{const double delta=b[axis]-a[axis];lengthSquared+=delta*delta;dot+=(point[axis]-a[axis])*delta;}
const double t=lengthSquared>0.0?std::clamp(dot/lengthSquared,0.0,1.0):0.0;
double distanceSquared=0.0;
for(std::size_t axis=0;axis<3;++axis)
{const double delta=point[axis]-(a[axis]+t*(b[axis]-a[axis]));distanceSquared+=delta*delta;}
const double distance=std::sqrt(distanceSquared);
const double radius=std::max(0.0,vein.radiusVoxels
+irregularity*vein.irregularityAmplitudeVoxels);
if(distance>=radius+vein.blendWidthVoxels)continue;
double weight=1.0;
if(distance>radius)
{
const double u=std::clamp((radius+vein.blendWidthVoxels-distance)
/vein.blendWidthVoxels,0.0,1.0);
weight=u*u*(3.0-2.0*u);
}
const auto weightQ16=static_cast<std::uint16_t>(std::clamp(
std::llround(weight*65535.0),0ll,65535ll));
if(weightQ16==0)continue;
std::size_t slot=2;
for(std::size_t index=0;index<2;++index)
if(result.resourceMaterialIds[index]==vein.resourceMaterialId)
{slot=index;break;}
if(slot==2)
for(std::size_t index=0;index<2;++index)
if(result.resourceMaterialIds[index]==0){slot=index;break;}
if(slot<2)
{
result.resourceMaterialIds[slot]=vein.resourceMaterialId;
const std::uint32_t sum=static_cast<std::uint32_t>(
result.resourceWeightsQ16[slot])+weightQ16;
result.resourceWeightsQ16[slot]=static_cast<std::uint16_t>(
std::min<std::uint32_t>(65535,sum));
}
}
const std::uint32_t total=static_cast<std::uint32_t>(result.resourceWeightsQ16[0])
+result.resourceWeightsQ16[1];
if(total>65535)
{
result.resourceWeightsQ16[0]=static_cast<std::uint16_t>(
static_cast<std::uint64_t>(result.resourceWeightsQ16[0])*65535/total);
result.resourceWeightsQ16[1]=static_cast<std::uint16_t>(
65535-result.resourceWeightsQ16[0]);
}
return result;
}
std::vector<std::uint32_t> BuildStampTileList(
const std::array<std::int64_t,3>& minimum,
const std::array<std::int64_t,3>& maximum) const
@@ -9412,6 +9705,10 @@ private:
std::vector<FStampSpatialNode> StampSpatialNodes;
std::vector<std::uint32_t> StampSpatialOrder;
std::vector<FStampRuntime> StampRuntime;
std::vector<FOreVeinSpatialBound> OreVeinSpatialBounds;
std::vector<FOreVeinSpatialNode> OreVeinSpatialNodes;
std::vector<std::uint32_t> OreVeinSpatialOrder;
std::vector<FOreVeinRuntime> OreVeinRuntime;
std::vector<FCavernRuntimeFeature> CavernRuntime;
std::vector<VoxelForgeCore::WorldAssemblyFeatureBoundsV1> PassageFeatureBounds;
std::vector<FPassageTileFeature> PassageTileFeatures;
+104 -1
View File
@@ -12,6 +12,7 @@
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <map>
#include <functional>
#include <thread>
#include <new>
@@ -216,6 +217,19 @@ namespace VoxelForgeCore
return hash;
}
std::uint64_t HashMaterialSamples(std::span<const MeshVertex> vertices)
{
std::uint64_t hash=14695981039346656037ull;
AddWord(hash,vertices.size(),8);
for(const MeshVertex& vertex:vertices)
{
AddWord(hash,vertex.hostMaterialId,2);
for(const std::uint16_t id:vertex.resourceMaterialIds)AddWord(hash,id,2);
for(const std::uint16_t weight:vertex.resourceWeightsQ16)AddWord(hash,weight,2);
}
return hash;
}
std::uint64_t HashBrushTransaction(const EditTransaction& transaction)
{
std::uint64_t hash=14695981039346656037ull;
@@ -1706,6 +1720,62 @@ namespace VoxelForgeCore
float World::densityAt(Vec3d position) const
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
MaterialSampleV1 World::materialAt(Vec3d position) const
{
if(!impl_||!impl_->shapeReady.load(std::memory_order_acquire)||!impl_->shape)
return {};
return impl_->shape->MaterialAt(static_cast<float>(position.x),
static_cast<float>(position.y),static_cast<float>(position.z));
}
OreMiningYieldV1 World::measureOreMiningYieldForSphere(Vec3d centerVoxel,
double radiusMetres,double sampleSpacingMetres) const
{
OreMiningYieldV1 result;result.sampleSpacingMetres=sampleSpacingMetres;
if(!impl_||!impl_->shapeReady.load(std::memory_order_acquire)||!impl_->shape
||!std::isfinite(radiusMetres)||!std::isfinite(sampleSpacingMetres)
||radiusMetres<=0.0||sampleSpacingMetres<0.05||sampleSpacingMetres>2.0)
return result;
const std::uint32_t voxelMicrometres=impl_->configuration.voxelSizeMicrons;
if(voxelMicrometres==0)return result;
const double voxelMetres=static_cast<double>(voxelMicrometres)/1000000.0;
const double radiusVoxels=radiusMetres/voxelMetres;
const double spacingVoxels=sampleSpacingMetres/voxelMetres;
const int steps=static_cast<int>(std::ceil(radiusMetres/sampleSpacingMetres));
const double radiusSquared=radiusMetres*radiusMetres;
std::map<std::uint16_t,std::uint64_t> weightedSamples;
for(int z=-steps;z<=steps;++z)
for(int y=-steps;y<=steps;++y)
for(int x=-steps;x<=steps;++x)
{
const double dx=x*sampleSpacingMetres;
const double dy=y*sampleSpacingMetres;
const double dz=z*sampleSpacingMetres;
const double distanceSquared=dx*dx+dy*dy+dz*dz;
if(distanceSquared>radiusSquared)continue;
const Vec3d point={centerVoxel.x+x*spacingVoxels,
centerVoxel.y+y*spacingVoxels,centerVoxel.z+z*spacingVoxels};
const float before=impl_->shape->DensityAt(static_cast<float>(point.x),
static_cast<float>(point.y),static_cast<float>(point.z));
const double carveDensity=radiusVoxels
-std::sqrt(distanceSquared)/voxelMetres;
const double after=std::max(static_cast<double>(before),carveDensity);
if(!(before<0.0f&&after>=0.0))continue;
++result.solidSamplesRemoved;
const MaterialSampleV1 material=materialAt(point);
for(std::size_t slot=0;slot<2;++slot)
if(material.resourceMaterialIds[slot]
&&material.resourceWeightsQ16[slot])
weightedSamples[material.resourceMaterialIds[slot]]
+=material.resourceWeightsQ16[slot];
}
const double sampleVolume=sampleSpacingMetres*sampleSpacingMetres
*sampleSpacingMetres;
result.resources.reserve(weightedSamples.size());
for(const auto& [id,weighted]:weightedSamples)
result.resources.push_back({id,weighted,
static_cast<double>(weighted)/65535.0*sampleVolume});
return result;
}
std::vector<PassageVoidCrossingAuditV1> World::passageVoidCrossings() const
{
std::vector<PassageVoidCrossingAuditV1> result;
@@ -2636,6 +2706,21 @@ namespace VoxelForgeCore
out.skirtFaceMasks.assign(mesh.TriangleSkirtFaceMasks.begin(),
mesh.TriangleSkirtFaceMasks.end());
out.skirtFaceMasks.resize(mesh.Triangles.Num()/3,0);
constexpr std::int64_t MaterialSubvoxelsPerVoxel=WorldAssemblySubvoxelsPerVoxel;
const std::array<std::int64_t,3> materialTileMinimum={
(static_cast<std::int64_t>(origin.X)-step)*MaterialSubvoxelsPerVoxel,
(static_cast<std::int64_t>(origin.Y)-step)*MaterialSubvoxelsPerVoxel,
(static_cast<std::int64_t>(origin.Z)-step)*MaterialSubvoxelsPerVoxel};
const std::array<std::int64_t,3> materialTileMaximum={
(static_cast<std::int64_t>(origin.X)+extent+step)*MaterialSubvoxelsPerVoxel,
(static_cast<std::int64_t>(origin.Y)+extent+step)*MaterialSubvoxelsPerVoxel,
(static_cast<std::int64_t>(origin.Z)+extent+step)*MaterialSubvoxelsPerVoxel};
const auto materialCandidates=tileShape.OreVeinTileCandidates(
materialTileMinimum,materialTileMaximum);
out.diagnostics.materialTileOutsideEveryVein=materialCandidates.empty();
const double geometryVoxelSizeCm=static_cast<double>(world.CoreVoxelSizeMicrons)/10000.0;
std::uint64_t materialQueries=0,materialCandidateChecks=0;
const auto materialQueryStart=std::chrono::steady_clock::now();
out.vertices.reserve(static_cast<std::size_t>(mesh.Vertices.Num()));
for(int32 i=0;i<mesh.Vertices.Num();++i)
{
@@ -2643,9 +2728,25 @@ namespace VoxelForgeCore
const FVector normal=i<mesh.Normals.Num()?mesh.Normals[i]:FVector::ZeroVector;
const FVector2f uv=i<mesh.UVs.Num()?mesh.UVs[i]:FVector2f(0.0f,0.0f);
const FColor color=i<mesh.Colors.Num()?mesh.Colors[i]:FColor(0,0,0,0);
MaterialSampleV1 material;
if(!materialCandidates.empty())
{
++materialQueries;
material=tileShape.EvaluateMaterialAt(
static_cast<float>(p.X/geometryVoxelSizeCm),
static_cast<float>(p.Y/geometryVoxelSizeCm),
static_cast<float>(p.Z/geometryVoxelSizeCm),materialCandidates,
&materialCandidateChecks);
}
out.vertices.push_back({{p.X,p.Y,p.Z},{normal.X,normal.Y,normal.Z},{uv.X,uv.Y},
{color.R,color.G,color.B,color.A}});
{color.R,color.G,color.B,color.A},material.hostMaterialId,
material.resourceMaterialIds,material.resourceWeightsQ16});
}
out.diagnostics.materialQueries=materialQueries;
out.diagnostics.materialCandidateChecks=materialCandidateChecks;
out.diagnostics.materialQueryMilliseconds=materialQueries
?std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-materialQueryStart).count():0.0;
out.indices.reserve(static_cast<std::size_t>(mesh.Triangles.Num()));
for(const int32 index:mesh.Triangles)out.indices.push_back(static_cast<std::uint32_t>(index));
if(options.captureDensitySamples)out.debugDensitySamples=std::move(densityValues);
@@ -2657,6 +2758,7 @@ namespace VoxelForgeCore
{ densityHash^=std::bit_cast<std::uint32_t>(value);densityHash*=1099511628211ull; }
out.densityHash=densityHash;out.meshHash=HashViewerMesh(mesh);
out.payloadHash=HashPayload(mesh);out.topologyHash=HashTopology(mesh);
out.materialHash=HashMaterialSamples(out.vertices);
if(key.lod>=4)
{
out.meshHash=SaltFarHorizonHash(out.meshHash,options.farHorizonVersion);
@@ -2664,6 +2766,7 @@ namespace VoxelForgeCore
out.topologyHash=SaltFarHorizonHash(out.topologyHash,options.farHorizonVersion);
}
out.hasDensityHash=out.hasMeshHash=out.hasPayloadHash=out.hasTopologyHash=true;
out.hasMaterialHash=true;
}
out.diagnostics.meshAssemblyMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MeshAssemblyStart).count();