wip(standalone): owner LOD0 defects -- skirt curtains clipped, exact edge roots; interrupted (OpenAI capacity)
Not verified as a whole. In the tree when Luna's session ended ("model at capacity"):
- fins: LOD seam skirts could hang into tunnel air; skirt triangles are now clipped against the solid
half-space and carry a per-face mask so a host can hide skirts between same-LOD neighbours.
- choppy LOD0 stairs: marching cubes' linear edge interpolation misplaced vertices where the relief
bends within one cell; edge crossings corrected with the exact field (82 patches, midpoint-edge
errors 18,818 -> 3,288); normal halo now ignores the +-4.125 fallback samples.
- an explicit unclamped Core distance query for audits.
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
08ceed200d
commit
b3af0e589e
7 files changed
+1304
-62
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -36,6 +36,9 @@ struct FStandaloneCpuTile
|
||||
uint64 RequestedCrossing=0;
|
||||
std::vector<FStandaloneCpuVertex> Vertices;
|
||||
std::vector<uint32> Indices;
|
||||
std::vector<uint8> TriangleSkirtFaceMasks;
|
||||
uint32 SurfaceVertexCount=0;
|
||||
uint32 SkirtTriangleCount=0;
|
||||
uint64 DensitySamples=0;
|
||||
double WorkerSeconds=0.0;
|
||||
std::chrono::steady_clock::time_point CompletedAt{};
|
||||
@@ -79,6 +82,8 @@ inline uint64 HashStandaloneMeshPayload(const FVoxelMeshData& Mesh)
|
||||
{ AddWord(Color.R,1);AddWord(Color.G,1);AddWord(Color.B,1);AddWord(Color.A,1); }
|
||||
AddWord(static_cast<uint64>(Mesh.Triangles.Num()),8);
|
||||
for(const int32 Index:Mesh.Triangles)AddWord(static_cast<uint32>(Index),4);
|
||||
AddWord(static_cast<uint64>(Mesh.TriangleSkirtFaceMasks.Num()),8);
|
||||
for(const uint8 Faces:Mesh.TriangleSkirtFaceMasks)AddWord(Faces,1);
|
||||
AddWord(static_cast<uint32>(Mesh.NumSurfaceVertices),4);
|
||||
AddWord(static_cast<uint32>(Mesh.NumSkirtTriangles),4);
|
||||
AddWord(static_cast<uint32>(Mesh.NumCeilingTriangles),4);
|
||||
@@ -614,6 +619,8 @@ private:
|
||||
Out.CompletedAt=FClock::now();return Out;
|
||||
}
|
||||
Out.DensitySamples=Result->densitySampleCount;
|
||||
Out.SurfaceVertexCount=Result->groups.surfaceVertexCount;
|
||||
Out.SkirtTriangleCount=Result->groups.skirtTriangleCount;
|
||||
Out.MeshPayloadAllocatedBytes=Result->meshPayloadAllocatedBytes;
|
||||
Out.DensityHash=Result->densityHash;Out.MeshHash=Result->meshHash;
|
||||
Out.PayloadHash=Result->payloadHash;Out.TopologyHash=Result->topologyHash;
|
||||
@@ -627,6 +634,7 @@ private:
|
||||
{static_cast<float>(Vertex.normal.x),static_cast<float>(Vertex.normal.y),
|
||||
static_cast<float>(Vertex.normal.z)}});
|
||||
Out.Indices=std::move(Result->indices);
|
||||
Out.TriangleSkirtFaceMasks=std::move(Result->skirtFaceMasks);
|
||||
if(bProfile)
|
||||
{
|
||||
const VoxelDensityProfile::FSnapshot After=VoxelDensityProfile::SnapshotCurrentThread();
|
||||
@@ -660,10 +668,13 @@ private:
|
||||
}
|
||||
}
|
||||
Out.DensitySamples=static_cast<uint64>(FMath::Max<int64>(0,SampleCount));
|
||||
Out.SurfaceVertexCount=static_cast<uint32>(std::clamp(Mesh.NumSurfaceVertices,0,Mesh.Vertices.Num()));
|
||||
Out.SkirtTriangleCount=static_cast<uint32>(std::max(0,Mesh.NumSkirtTriangles));
|
||||
Out.MeshPayloadAllocatedBytes=static_cast<uint64>(Mesh.Vertices.GetAllocatedSize())
|
||||
+static_cast<uint64>(Mesh.Normals.GetAllocatedSize())
|
||||
+static_cast<uint64>(Mesh.UVs.GetAllocatedSize())
|
||||
+static_cast<uint64>(Mesh.Colors.GetAllocatedSize())
|
||||
+static_cast<uint64>(Mesh.TriangleSkirtFaceMasks.GetAllocatedSize())
|
||||
+static_cast<uint64>(Mesh.Triangles.GetAllocatedSize());
|
||||
if(bHashDensity)
|
||||
{
|
||||
@@ -680,6 +691,9 @@ private:
|
||||
}
|
||||
Out.Indices.reserve(static_cast<size_t>(Mesh.Triangles.Num()));
|
||||
for(const int32 Index:Mesh.Triangles)Out.Indices.push_back(static_cast<uint32>(Index));
|
||||
Out.TriangleSkirtFaceMasks.assign(Mesh.TriangleSkirtFaceMasks.begin(),
|
||||
Mesh.TriangleSkirtFaceMasks.end());
|
||||
Out.TriangleSkirtFaceMasks.resize(Out.Indices.size()/3,0);
|
||||
if(bHashDensity)
|
||||
{Out.MeshHash=HashStandaloneCpuTileMesh(Out);Out.bHasMeshHash=true;}
|
||||
Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count();
|
||||
|
||||
@@ -214,9 +214,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
return BiomeTileGridMemo.Add(ChunkZ, Samples);
|
||||
};
|
||||
|
||||
// Normale fournie par l'appelant (gradient lu dans la grille de densité, T1.b) —
|
||||
// plus d'échantillonnage de densité par vertex. RawNormal pointe solide→air ; on la
|
||||
// normalise ici (fallback up si dégénérée).
|
||||
// Normale fournie par l'appelant (gradient lu dans la grille de densité, T1.b).
|
||||
auto GetOrCreateVertex = [&](const FVector& WorldPos, const FVector& RawNormal) -> int32
|
||||
{
|
||||
const FIntVector Key(
|
||||
@@ -1299,13 +1297,26 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
{
|
||||
return DensityGrid[((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1)];
|
||||
};
|
||||
auto SampleGradientG = [&](int32 gx, int32 gy, int32 gz) -> float
|
||||
{
|
||||
const float Cached = SampleG(gx, gy, gz);
|
||||
if (!ShapeWorldContext || Step != 1
|
||||
|| !ShapeWorldContext->IsProxyBandFallback(Cached))
|
||||
{
|
||||
return Cached;
|
||||
}
|
||||
return ShapeWorldContext->DensityAtUnclamped(
|
||||
static_cast<float>(OriginVoxels.X + gx * Step),
|
||||
static_cast<float>(OriginVoxels.Y + gy * Step),
|
||||
static_cast<float>(OriginVoxels.Z + gz * Step));
|
||||
};
|
||||
auto GradAt = [&](int32 gx, int32 gy, int32 gz) -> FVector
|
||||
{
|
||||
// Densité négative=solide, positive=air → le gradient pointe vers l'air (sortant).
|
||||
return FVector(
|
||||
SampleG(gx + 1, gy, gz) - SampleG(gx - 1, gy, gz),
|
||||
SampleG(gx, gy + 1, gz) - SampleG(gx, gy - 1, gz),
|
||||
SampleG(gx, gy, gz + 1) - SampleG(gx, gy, gz - 1));
|
||||
SampleGradientG(gx + 1, gy, gz) - SampleGradientG(gx - 1, gy, gz),
|
||||
SampleGradientG(gx, gy + 1, gz) - SampleGradientG(gx, gy - 1, gz),
|
||||
SampleGradientG(gx, gy, gz + 1) - SampleGradientG(gx, gy, gz - 1));
|
||||
};
|
||||
|
||||
//=========================================================================
|
||||
@@ -1477,9 +1488,32 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const int32 A = EdgeCorners[i][0];
|
||||
const int32 B = EdgeCorners[i][1];
|
||||
const float D1 = Densities[A], D2 = Densities[B];
|
||||
const float T = (FMath::Abs(D2 - D1) < KINDA_SMALL_NUMBER)
|
||||
float T = (FMath::Abs(D2 - D1) < KINDA_SMALL_NUMBER)
|
||||
? 0.5f
|
||||
: FMath::Clamp((IsoLevel - D1) / (D2 - D1), 0.0f, 1.0f);
|
||||
if(ShapeWorldContext&&Step==1&&T>=0.375f&&T<=0.625f)
|
||||
{
|
||||
const FVector Midpoint=(Positions[A]+Positions[B])*0.5f;
|
||||
const float MidDensity=ShapeWorldContext->DensityAtUnclamped(
|
||||
Midpoint.X/VOXEL_SIZE,Midpoint.Y/VOXEL_SIZE,
|
||||
Midpoint.Z/VOXEL_SIZE);
|
||||
if(FMath::Abs(MidDensity-IsoLevel)<1.0e-6f)
|
||||
T=0.5f;
|
||||
else if((MidDensity<IsoLevel)==(D1<IsoLevel))
|
||||
{
|
||||
const float Denominator=D2-MidDensity;
|
||||
if(FMath::Abs(Denominator)>KINDA_SMALL_NUMBER)
|
||||
T=0.5f+0.5f*FMath::Clamp(
|
||||
(IsoLevel-MidDensity)/Denominator,0.0f,1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
const float Denominator=MidDensity-D1;
|
||||
if(FMath::Abs(Denominator)>KINDA_SMALL_NUMBER)
|
||||
T=0.5f*FMath::Clamp(
|
||||
(IsoLevel-D1)/Denominator,0.0f,1.0f);
|
||||
}
|
||||
}
|
||||
EdgeVertices[i] = Positions[A] + T * (Positions[B] - Positions[A]);
|
||||
EdgeNormals[i] = Gradients[A] + T * (Gradients[B] - Gradients[A]);
|
||||
}
|
||||
@@ -1559,6 +1593,9 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
// matériau. Une arête de surface posée sur une face de frontière a sa coordonnée d'axe EXACTE
|
||||
// (l'interpolation MC garde fixe l'axe de la face) → comparaison flottante exacte fiable.
|
||||
MeshData.NumSurfaceVertices = MeshData.Vertices.Num();
|
||||
const int32 GroundSurfaceTriangleCount=GroundTris.Num()/3;
|
||||
const int32 CapSurfaceTriangleCount=CapTris.Num()/3;
|
||||
TArray<uint8> GroundSkirtFaceMasks,CapSkirtFaceMasks;
|
||||
if (bGenerateSkirts && (GroundTris.Num() + CapTris.Num()) > 0)
|
||||
{
|
||||
const float ExtentCm = (float)(CellsPerAxis * Step) * VOXEL_SIZE;
|
||||
@@ -1566,11 +1603,16 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const float MaxX = MinX + ExtentCm, MaxY = MinY + ExtentCm, MaxZ = MinZ + ExtentCm;
|
||||
const float SkirtDepth = FMath::Max(1.0f, SkirtCells) * (float)Step * VOXEL_SIZE;
|
||||
|
||||
auto OnBoundaryPlane = [&](const FVector& A, const FVector& B) -> bool
|
||||
auto BoundaryFaceMask = [&](const FVector& A, const FVector& B) -> uint8
|
||||
{
|
||||
return (A.X == MinX && B.X == MinX) || (A.X == MaxX && B.X == MaxX)
|
||||
|| (A.Y == MinY && B.Y == MinY) || (A.Y == MaxY && B.Y == MaxY)
|
||||
|| (A.Z == MinZ && B.Z == MinZ) || (A.Z == MaxZ && B.Z == MaxZ);
|
||||
uint8 Mask=0;
|
||||
if(A.X==MinX&&B.X==MinX)Mask|=1u<<0;
|
||||
if(A.X==MaxX&&B.X==MaxX)Mask|=1u<<1;
|
||||
if(A.Y==MinY&&B.Y==MinY)Mask|=1u<<2;
|
||||
if(A.Y==MaxY&&B.Y==MaxY)Mask|=1u<<3;
|
||||
if(A.Z==MinZ&&B.Z==MinZ)Mask|=1u<<4;
|
||||
if(A.Z==MaxZ&&B.Z==MaxZ)Mask|=1u<<5;
|
||||
return Mask;
|
||||
};
|
||||
|
||||
auto AddSkirtVert = [&](const FVector& Pos, const FVector& Nrm, const FColor& Col) -> int32
|
||||
@@ -1592,10 +1634,87 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
// F17 — jupes émises PAR SEAU : chaque jupe hérite la classe (donc le polygroup /
|
||||
// matériau) de son triangle source ; ajouter les jupes après coup casserait les runs
|
||||
// d'indices contigus par groupe qu'exige RMC.
|
||||
auto EmitSkirts = [&](TArray<int32>& Tris)
|
||||
auto EmitSkirts = [&](TArray<int32>& Tris,TArray<uint8>& FaceMasks)
|
||||
{
|
||||
// On ajoute en itérant : on fige le nombre de triangles de surface et on n'ajoute qu'au-delà.
|
||||
const int32 BaseTriNum = Tris.Num();
|
||||
struct FSkirtVertex
|
||||
{
|
||||
FVector Position;
|
||||
FVector Normal;
|
||||
FColor Color;
|
||||
float Density=0.0f;
|
||||
int32 OriginalIndex=INDEX_NONE;
|
||||
};
|
||||
const auto MakeSkirtVertex=[&](const FVector& Position,const FVector& Normal,
|
||||
const FColor& Color,bool bOnSurface,int32 OriginalIndex)
|
||||
{
|
||||
FSkirtVertex Vertex{Position,Normal,Color,0.0f,OriginalIndex};
|
||||
if(!bOnSurface&&ShapeWorldContext)
|
||||
Vertex.Density=ShapeWorldContext->DensityAt(Position.X/VOXEL_SIZE,
|
||||
Position.Y/VOXEL_SIZE,Position.Z/VOXEL_SIZE);
|
||||
return Vertex;
|
||||
};
|
||||
const auto InterpolateVertex=[](const FSkirtVertex& A,const FSkirtVertex& B,float T)
|
||||
{
|
||||
if(T<=1.0e-6f)return A;
|
||||
if(T>=1.0f-1.0e-6f)return B;
|
||||
FSkirtVertex Out;
|
||||
Out.Position=FMath::Lerp(A.Position,B.Position,T);
|
||||
Out.Normal=FMath::Lerp(A.Normal,B.Normal,T).GetSafeNormal();
|
||||
Out.Color=FColor(
|
||||
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.R),static_cast<float>(B.Color.R),T)),0,255)),
|
||||
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.G),static_cast<float>(B.Color.G),T)),0,255)),
|
||||
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.B),static_cast<float>(B.Color.B),T)),0,255)),
|
||||
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.A),static_cast<float>(B.Color.A),T)),0,255)));
|
||||
Out.Density=FMath::Lerp(A.Density,B.Density,T);
|
||||
Out.OriginalIndex=INDEX_NONE;
|
||||
return Out;
|
||||
};
|
||||
const auto EmitClippedTriangle=[&](const FSkirtVertex& A,const FSkirtVertex& B,
|
||||
const FSkirtVertex& C,uint8 FaceMask)
|
||||
{
|
||||
FSkirtVertex Input[3]={A,B,C};
|
||||
FSkirtVertex Polygon[5];
|
||||
int32 PolygonCount=0;
|
||||
for(int32 Edge=0;Edge<3;++Edge)
|
||||
{
|
||||
const FSkirtVertex& Start=Input[Edge];
|
||||
const FSkirtVertex& End=Input[(Edge+1)%3];
|
||||
const bool bStartSolid=Start.Density<=0.0f;
|
||||
const bool bEndSolid=End.Density<=0.0f;
|
||||
if(bStartSolid!=bEndSolid)
|
||||
{
|
||||
const float Denominator=Start.Density-End.Density;
|
||||
const float T=FMath::Abs(Denominator)>1.0e-8f
|
||||
?FMath::Clamp(Start.Density/Denominator,0.0f,1.0f):0.5f;
|
||||
Polygon[PolygonCount++]=InterpolateVertex(Start,End,T);
|
||||
}
|
||||
if(bEndSolid)Polygon[PolygonCount++]=End;
|
||||
}
|
||||
if(PolygonCount<3)return;
|
||||
int32 PolygonIndices[5];
|
||||
for(int32 Vertex=0;Vertex<PolygonCount;++Vertex)
|
||||
PolygonIndices[Vertex]=Polygon[Vertex].OriginalIndex>=0
|
||||
?Polygon[Vertex].OriginalIndex
|
||||
:AddSkirtVert(Polygon[Vertex].Position,Polygon[Vertex].Normal,
|
||||
Polygon[Vertex].Color);
|
||||
for(int32 Fan=1;Fan+1<PolygonCount;++Fan)
|
||||
{
|
||||
const FSkirtVertex& P0=Polygon[0];
|
||||
const FSkirtVertex& P1=Polygon[Fan];
|
||||
const FSkirtVertex& P2=Polygon[Fan+1];
|
||||
if(ShapeWorldContext)
|
||||
{
|
||||
const FVector Center=(P0.Position+P1.Position+P2.Position)
|
||||
/(3.0f*VOXEL_SIZE);
|
||||
if(ShapeWorldContext->DensityAt(Center.X,Center.Y,Center.Z)>0.0f)continue;
|
||||
}
|
||||
Tris.Add(PolygonIndices[0]);Tris.Add(PolygonIndices[Fan]);Tris.Add(PolygonIndices[Fan+1]);
|
||||
Tris.Add(PolygonIndices[0]);Tris.Add(PolygonIndices[Fan+1]);Tris.Add(PolygonIndices[Fan]);
|
||||
FaceMasks.Add(FaceMask);FaceMasks.Add(FaceMask);
|
||||
}
|
||||
};
|
||||
for (int32 t = 0; t + 2 < BaseTriNum; t += 3)
|
||||
{
|
||||
const int32 Tri[3] = { Tris[t], Tris[t + 1], Tris[t + 2] };
|
||||
@@ -1605,26 +1724,29 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
// COPIES par valeur — AddSkirtVert réalloue Vertices/Normals (invaliderait des refs).
|
||||
const FVector PA = MeshData.Vertices[iA];
|
||||
const FVector PB = MeshData.Vertices[iB];
|
||||
if (!OnBoundaryPlane(PA, PB)) continue;
|
||||
const uint8 FaceMask=BoundaryFaceMask(PA,PB);
|
||||
if (!FaceMask) continue;
|
||||
|
||||
const FVector NA = MeshData.Normals[iA];
|
||||
const FVector NB = MeshData.Normals[iB];
|
||||
const FColor CA = bEmitColors ? MeshData.Colors[iA] : FColor::White;
|
||||
const FColor CB = bEmitColors ? MeshData.Colors[iB] : FColor::White;
|
||||
const int32 iA2 = AddSkirtVert(PA - NA * SkirtDepth, NA, CA);
|
||||
const int32 iB2 = AddSkirtVert(PB - NB * SkirtDepth, NB, CB);
|
||||
const FSkirtVertex A=MakeSkirtVertex(PA,NA,CA,true,iA);
|
||||
const FSkirtVertex B=MakeSkirtVertex(PB,NB,CB,true,iB);
|
||||
const FSkirtVertex A2=MakeSkirtVertex(PA-NA*SkirtDepth,NA,CA,false,INDEX_NONE);
|
||||
const FSkirtVertex B2=MakeSkirtVertex(PB-NB*SkirtDepth,NB,CB,false,INDEX_NONE);
|
||||
|
||||
// Quad (iA, iB, iB2, iA2) → 2 triangles, émis dans LES DEUX orientations.
|
||||
Tris.Add(iA); Tris.Add(iB); Tris.Add(iB2);
|
||||
Tris.Add(iA); Tris.Add(iB2); Tris.Add(iA2);
|
||||
Tris.Add(iA); Tris.Add(iB2); Tris.Add(iB);
|
||||
Tris.Add(iA); Tris.Add(iA2); Tris.Add(iB2);
|
||||
// Clip both source triangles against the solid half-space before emitting
|
||||
// each orientation. A curtain that points into tunnel air must not become
|
||||
// visible geometry beyond the sampled isosurface.
|
||||
EmitClippedTriangle(A,B,B2,FaceMask);
|
||||
EmitClippedTriangle(A,B2,A2,FaceMask);
|
||||
}
|
||||
}
|
||||
MeshData.NumSkirtTriangles += (Tris.Num() - BaseTriNum) / 3;
|
||||
};
|
||||
EmitSkirts(GroundTris);
|
||||
EmitSkirts(CapTris);
|
||||
EmitSkirts(GroundTris,GroundSkirtFaceMasks);
|
||||
EmitSkirts(CapTris,CapSkirtFaceMasks);
|
||||
}
|
||||
|
||||
// F17 — concatène sol PUIS sky-cap : un run d'indices contigu par polygroup (voir
|
||||
@@ -1633,6 +1755,14 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
MeshData.Triangles.Append(GroundTris);
|
||||
MeshData.Triangles.Append(CapTris);
|
||||
MeshData.NumCeilingTriangles = CapTris.Num() / 3;
|
||||
MeshData.TriangleSkirtFaceMasks.Reset();
|
||||
MeshData.TriangleSkirtFaceMasks.Reserve(MeshData.Triangles.Num()/3);
|
||||
for(int32 Triangle=0;Triangle<GroundSurfaceTriangleCount;++Triangle)
|
||||
MeshData.TriangleSkirtFaceMasks.Add(0);
|
||||
MeshData.TriangleSkirtFaceMasks.Append(GroundSkirtFaceMasks);
|
||||
for(int32 Triangle=0;Triangle<CapSurfaceTriangleCount;++Triangle)
|
||||
MeshData.TriangleSkirtFaceMasks.Add(0);
|
||||
MeshData.TriangleSkirtFaceMasks.Append(CapSkirtFaceMasks);
|
||||
|
||||
if (VoxelDensityProfile::AreCountersEnabled())
|
||||
{
|
||||
|
||||
@@ -370,6 +370,9 @@ struct FVoxelMeshData
|
||||
TArray<FVector> Normals; // Normale lissée (gradient de densité)
|
||||
TArray<FColor> Colors; // Masques matériau F6 (R=palette biome dominant, G=pente,
|
||||
// B=poids de fondu de bordure, A=palette biome voisin)
|
||||
// Parallel to Triangles/3. Zero marks isosurface geometry; skirt triangles carry a
|
||||
// six-face mask so a streaming host can hide skirts where adjacent tiles use the same LOD.
|
||||
TArray<uint8> TriangleSkirtFaceMasks;
|
||||
// Number of vertices on the actual density isosurface. LOD seam skirts are appended after
|
||||
// this prefix and must not be used as witnesses for terrain visibility audits.
|
||||
int32 NumSurfaceVertices = 0;
|
||||
@@ -392,6 +395,7 @@ struct FVoxelMeshData
|
||||
UVs.Empty();
|
||||
Normals.Empty();
|
||||
Colors.Empty();
|
||||
TriangleSkirtFaceMasks.Empty();
|
||||
NumSurfaceVertices = 0;
|
||||
NumSkirtTriangles = 0;
|
||||
NumCeilingTriangles = 0;
|
||||
|
||||
Reference in new issue
Block a user