// Copyright 2020 Phyronnaz #include "VoxelRender/Meshers/VoxelSurfaceNetMesher.h" #include "VoxelRender/Meshers/VoxelMesherUtilities.h" #include "VoxelRender/IVoxelRenderer.h" #include "VoxelData/VoxelDataIncludes.h" /** * This code is based on an original implementation kindly provided by Dexyfex * You can check out his website here: https://dexyfex.com/ */ struct FVoxelSurfaceNetFullVertex : FVoxelMesherVertex { static constexpr bool bComputeParentPosition = true; static constexpr bool bComputeNormal = true; static constexpr bool bComputeTextureCoordinate = true; static constexpr bool bComputeMaterial = true; FORCEINLINE void SetPosition(const FVector& InPosition) { Position = InPosition; } FORCEINLINE void SetParentPosition(const FVector& InParentPosition) { Tangent = FVoxelProcMeshTangent(InParentPosition, false); } FORCEINLINE void SetNormal(const FVector& InNormal) { Normal = InNormal; } FORCEINLINE void SetTextureCoordinate(const FVector2D& InTextureCoordinate) { TextureCoordinate = InTextureCoordinate; } FORCEINLINE void SetMaterial(const FVoxelMaterial& InMaterial) { Material = InMaterial; } }; static_assert(sizeof(FVoxelSurfaceNetFullVertex) == sizeof(FVoxelMesherVertex), ""); struct FVoxelSurfaceNetGeometryVertex : FVector { static constexpr bool bComputeParentPosition = false; static constexpr bool bComputeNormal = false; static constexpr bool bComputeTextureCoordinate = false; static constexpr bool bComputeMaterial = false; FORCEINLINE void SetPosition(const FVector& InPosition) { static_cast(*this) = InPosition; } FORCEINLINE void SetParentPosition(const FVector& InParentPosition) { checkVoxelSlow(false); } FORCEINLINE void SetNormal(const FVector& InNormal) { checkVoxelSlow(false); } FORCEINLINE void SetTextureCoordinate(const FVector2D& InTextureCoordinate) { checkVoxelSlow(false); } FORCEINLINE void SetMaterial(const FVoxelMaterial& InMaterial) { checkVoxelSlow(false); } }; static_assert(sizeof(FVoxelSurfaceNetGeometryVertex) == sizeof(FVector), ""); FVoxelIntBox FVoxelSurfaceNetMesher::GetBoundsToCheckIsEmptyOn() const { return FVoxelIntBox(ChunkPosition, ChunkPosition + SN_EXTENDED_CHUNK_SIZE * Step); } FVoxelIntBox FVoxelSurfaceNetMesher::GetBoundsToLock() const { return GetBoundsToCheckIsEmptyOn(); } inline float SampleIsoValue(const float Values[8], const FVector& Offset) { // TODO use FVoxelUtilities::TrilinearInterpolation const FVector i = FVector(1.0f) - Offset; const float x1 = Values[0] * i.X + Values[1] * Offset.X; const float x2 = Values[2] * i.X + Values[3] * Offset.X; const float x3 = Values[4] * i.X + Values[5] * Offset.X; const float x4 = Values[6] * i.X + Values[7] * Offset.X; const float y1 = x1 * i.Y + x2 * Offset.Y; const float y2 = x3 * i.Y + x4 * Offset.Y; const float z1 = y1 * i.Z + y2 * Offset.Z; return z1; } inline FVector GetNormal(const float Values[8], const FVector& Offset) { const float MaxX = SampleIsoValue(Values, Offset + FVector(+1, 0, 0)); const float MinX = SampleIsoValue(Values, Offset + FVector(-1, 0, 0)); const float MaxY = SampleIsoValue(Values, Offset + FVector(0, +1, 0)); const float MinY = SampleIsoValue(Values, Offset + FVector(0, -1, 0)); const float MaxZ = SampleIsoValue(Values, Offset + FVector(0, 0, +1)); const float MinZ = SampleIsoValue(Values, Offset + FVector(0, 0, -1)); return FVector(MaxX - MinX, MaxY - MinY, MaxZ - MinZ).GetSafeNormal(); } template void FVoxelSurfaceNetMesher::CreateGeometryTemplate(FVoxelMesherTimes& Times, TArray& Indices, TArray& Vertices) { VOXEL_ASYNC_FUNCTION_COUNTER(); TVoxelQueryZone QueryZone(GetBoundsToCheckIsEmptyOn(), FIntVector(SN_EXTENDED_CHUNK_SIZE), LOD, CachedValues); MESHER_TIME_VALUES(SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE, Data.Get(QueryZone, LOD)); Accelerator = MakeUnique(Data, GetBoundsToLock()); constexpr uint32 EdgeIndexOffsets[12] = { 0, SN_EXTENDED_CHUNK_SIZE * 3, SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3, SN_EXTENDED_CHUNK_SIZE * 3 + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3, 1, 4, 1 + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3, 4 + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3, 2, 5, 2 + SN_EXTENDED_CHUNK_SIZE * 3, 5 + SN_EXTENDED_CHUNK_SIZE * 3 }; constexpr uint32 ParentEdgeIndexOffsetsMin[12] = { 0, 2 * SN_EXTENDED_CHUNK_SIZE * 3, 2 * SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3, 2 * SN_EXTENDED_CHUNK_SIZE * 3 + 2 * SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3, 1, 7, 1 + 2 * SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3, 7 + 2 * SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3, 2, 8, 2 + 2 * SN_EXTENDED_CHUNK_SIZE * 3, 8 + 2 * SN_EXTENDED_CHUNK_SIZE * 3 }; constexpr uint32 ParentEdgeIndexOffsetsMax[12] = { 3, 2 * SN_EXTENDED_CHUNK_SIZE * 3 + 3, 2 * SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3 + 3, 2 * SN_EXTENDED_CHUNK_SIZE * 3 + 2 * SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3 + 3, 1 + SN_EXTENDED_CHUNK_SIZE * 3, 7 + SN_EXTENDED_CHUNK_SIZE * 3, 1 + 2 * SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3 + SN_EXTENDED_CHUNK_SIZE * 3, 7 + 2 * SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3 + SN_EXTENDED_CHUNK_SIZE * 3, 2 + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3, 8 + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3, 2 + 2 * SN_EXTENDED_CHUNK_SIZE * 3 + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3, 8 + 2 * SN_EXTENDED_CHUNK_SIZE * 3 + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE * 3 }; constexpr uint32 EdgeFirstCornerIndex[12] = { 0, 2, 4, 6, 0, 1, 4, 5, 0, 1, 2, 3 }; constexpr uint32 EdgeSecondCornerIndex[12] = { 1, 3, 5, 7, 2, 3, 6, 7, 4, 5, 6, 7 }; // TODO: Replace buffer by simple math const FVector Corners[8] = { {0,0,0}, {1,0,0}, {0,1,0}, {1,1,0}, {0,0,1}, {1,0,1}, {0,1,1}, {1,1,1} }; const FVector ParentCorners[8] = { {0,0,0}, {2,0,0}, {0,2,0}, {2,2,0}, {0,0,2}, {2,0,2}, {0,2,2}, {2,2,2} }; constexpr uint32 Offsets[3] = { 1, SN_EXTENDED_CHUNK_SIZE, SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE }; const FIntVector icorners[3] = { {1,0,0},{0,1,0},{0,0,1} }; { VOXEL_ASYNC_SCOPE_COUNTER("Find Intersections"); // find intersections and calculate the edge blend factors for all cells for (int32 LZ = 0; LZ < SN_EXTENDED_CHUNK_SIZE; LZ++) { for (int32 LY = 0; LY < SN_EXTENDED_CHUNK_SIZE; LY++) { for (int32 LX = 0; LX < SN_EXTENDED_CHUNK_SIZE; LX++) { const uint32 VoxelIndex = LX + LY * SN_EXTENDED_CHUNK_SIZE + LZ * SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE; const FVoxelValue MinCornerValue = CachedValues[VoxelIndex]; FIntVector MinCornerPosition = FIntVector(LX, LY, LZ) * Step; FIntVector MaxCornerPositions = FIntVector(LX + 1, LY + 1, LZ + 1); for (uint32 Direction = 0; Direction < 3; Direction++) { float Factor = -1; // empty value, valid factor should be between 0 and 1 if (MaxCornerPositions[Direction] < SN_EXTENDED_CHUNK_SIZE) // don't go outside of the cached area { const FVoxelValue MaxCornerInDirectionValue = CachedValues[VoxelIndex + Offsets[Direction]]; if (MinCornerValue.IsEmpty() != MaxCornerInDirectionValue.IsEmpty()) { FVoxelValue MinValue = MinCornerValue; FVoxelValue MaxValue = MaxCornerInDirectionValue; if (LOD != 0) { // for LOD chunks, search along the edge for the actual intersecting segment FIntVector MinPosition = MinCornerPosition; FIntVector MaxPosition = MinCornerPosition + icorners[Direction] * Step; float c1 = 0; float c2 = 1; for (int iStep = Step; iStep > 1; iStep >>= 1) { const float cmid = (c1 + c2) * 0.5f; const FIntVector MidPosition = (MinPosition + MaxPosition) / 2; const FVoxelValue MidValue = MESHER_TIME_RETURN_VALUES(1, Accelerator->Get(MidPosition + ChunkPosition, LOD)); if (MinValue.IsEmpty() != MidValue.IsEmpty())//intersection is between c1 and cmid { c2 = cmid; MaxValue = MidValue; MaxPosition = MidPosition; } else //intersection is between cmid and c2 { c1 = cmid; MinValue = MidValue; MinPosition = MidPosition; } } // TODO is this needed Factor = c1 + (c2 - c1) * MinValue.ToFloat() / (MinValue.ToFloat() - MaxValue.ToFloat()); } else { Factor = MinValue.ToFloat() / (MinValue.ToFloat() - MaxValue.ToFloat()); } } } EdgeFactors[3 * VoxelIndex + Direction] = Factor; } if (TVertex::bComputeMaterial) { // We need to find the min value that's a surface value if (LX < SN_EXTENDED_CHUNK_SIZE - 1 && LY < SN_EXTENDED_CHUNK_SIZE - 1 && LZ < SN_EXTENDED_CHUNK_SIZE - 1) { FVoxelValue VoxelValues[8]; VoxelValues[0] = CachedValues[VoxelIndex]; VoxelValues[1] = CachedValues[VoxelIndex + 1]; VoxelValues[2] = CachedValues[VoxelIndex + SN_EXTENDED_CHUNK_SIZE]; VoxelValues[3] = CachedValues[VoxelIndex + SN_EXTENDED_CHUNK_SIZE + 1]; VoxelValues[4] = CachedValues[VoxelIndex + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE]; VoxelValues[5] = CachedValues[VoxelIndex + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE + 1]; VoxelValues[6] = CachedValues[VoxelIndex + SN_EXTENDED_CHUNK_SIZE + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE]; VoxelValues[7] = CachedValues[VoxelIndex + SN_EXTENDED_CHUNK_SIZE + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE + 1]; FVoxelValue MinValue = FVoxelValue::Empty(); int32 MinValueIndex = 0; for (int32 Index = 0; Index < 8; Index++) { if (VoxelValues[Index] > MinValue) { continue; } bool bIsSurfaceValue = false; for (int32 Neighbor = 0; Neighbor < 3; Neighbor++) { const int32 NeighborIndex = Index ^ (1 << Neighbor); if (VoxelValues[Index].IsEmpty() != VoxelValues[NeighborIndex].IsEmpty()) { bIsSurfaceValue = true; break; } } if (!bIsSurfaceValue) { continue; } MinValue = VoxelValues[Index]; MinValueIndex = Index; } MaterialPositions[VoxelIndex] = { LX + bool(MinValueIndex & 0x1), LY + bool(MinValueIndex & 0x2), LZ + bool(MinValueIndex & 0x4) }; } else { MaterialPositions[VoxelIndex] = { LX, LY, LZ }; } } } } } } { VOXEL_ASYNC_SCOPE_COUNTER("Generate Vertices"); // generate the vertices and store the indices for the next step for (uint32 LZ = 0; LZ < SN_CHUNK_SIZE; LZ++) { for (uint32 LY = 0; LY < SN_CHUNK_SIZE; LY++) { for (uint32 LX = 0; LX < SN_CHUNK_SIZE; LX++) { const uint32 VoxelIndex = LX + LY * SN_EXTENDED_CHUNK_SIZE + LZ * SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE; const uint32 VertexIndex = LX + LY * SN_CHUNK_SIZE + LZ * SN_CHUNK_SIZE * SN_CHUNK_SIZE; uint32 VoxelIndices[8]; VoxelIndices[0] = VoxelIndex; VoxelIndices[1] = VoxelIndex + 1; VoxelIndices[2] = VoxelIndex + SN_EXTENDED_CHUNK_SIZE; VoxelIndices[3] = VoxelIndex + SN_EXTENDED_CHUNK_SIZE + 1; VoxelIndices[4] = VoxelIndex + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE; VoxelIndices[5] = VoxelIndex + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE + 1; VoxelIndices[6] = VoxelIndex + SN_EXTENDED_CHUNK_SIZE + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE; VoxelIndices[7] = VoxelIndex + SN_EXTENDED_CHUNK_SIZE + SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE + 1; FVoxelValue VoxelValues[8]; VoxelValues[0] = CachedValues[VoxelIndices[0]]; VoxelValues[1] = CachedValues[VoxelIndices[1]]; VoxelValues[2] = CachedValues[VoxelIndices[2]]; VoxelValues[3] = CachedValues[VoxelIndices[3]]; VoxelValues[4] = CachedValues[VoxelIndices[4]]; VoxelValues[5] = CachedValues[VoxelIndices[5]]; VoxelValues[6] = CachedValues[VoxelIndices[6]]; VoxelValues[7] = CachedValues[VoxelIndices[7]]; float VoxelFloats[8]; VoxelFloats[0] = VoxelValues[0].ToFloat(); VoxelFloats[1] = VoxelValues[1].ToFloat(); VoxelFloats[2] = VoxelValues[2].ToFloat(); VoxelFloats[3] = VoxelValues[3].ToFloat(); VoxelFloats[4] = VoxelValues[4].ToFloat(); VoxelFloats[5] = VoxelValues[5].ToFloat(); VoxelFloats[6] = VoxelValues[6].ToFloat(); VoxelFloats[7] = VoxelValues[7].ToFloat(); const uint32 MarchingCubesCase = (VoxelValues[0].IsEmpty() << 0) | (VoxelValues[1].IsEmpty() << 1) | (VoxelValues[2].IsEmpty() << 2) | (VoxelValues[3].IsEmpty() << 3) | (VoxelValues[4].IsEmpty() << 4) | (VoxelValues[5].IsEmpty() << 5) | (VoxelValues[6].IsEmpty() << 6) | (VoxelValues[7].IsEmpty() << 7); const uint8 SurfaceNetsCase = (VoxelValues[3].IsEmpty() << 0) | (VoxelValues[5].IsEmpty() << 1) | (VoxelValues[6].IsEmpty() << 2) | (VoxelValues[7].IsEmpty() << 3); VertexSNCases[VertexIndex] = SurfaceNetsCase; if ((MarchingCubesCase == 0) || (MarchingCubesCase == 255)) //cell is empty { VertexIndices[VertexIndex] = -1; continue; } VertexIndices[VertexIndex] = Vertices.Num(); const uint32 VoxelEdgeIndex = VoxelIndex * 3; FVector CrossingTotal = FVector(0, 0, 0); uint32 CrossingCount = 0; constexpr int32 RemoveFirstBit = 0xFFFE; // TODO: what if more than 64k vertices const FIntVector ParentPosition((LX & RemoveFirstBit), (LY & RemoveFirstBit), (LZ & RemoveFirstBit)); const uint32 ParentVoxelIndex = ParentPosition.X + ParentPosition.Y * SN_EXTENDED_CHUNK_SIZE + ParentPosition.Z * SN_EXTENDED_CHUNK_SIZE * SN_EXTENDED_CHUNK_SIZE; const uint32 ParentVoxelEdgeIndex = ParentVoxelIndex * 3; FVector ParentCrossingTotal = FVector(0, 0, 0); uint32 ParentCrossingCount = 0; for (uint32 Edge = 0; Edge < 12; Edge++) { // if this edge has a crossing, find the point and add it to the avg total, increment count const float EdgeFactor = EdgeFactors[VoxelEdgeIndex + EdgeIndexOffsets[Edge]]; if (EdgeFactor >= 0) { const FVector MinPosition = Corners[EdgeFirstCornerIndex[Edge]]; const FVector MaxPosition = Corners[EdgeSecondCornerIndex[Edge]]; const FVector MidPosition = FMath::Lerp(MinPosition, MaxPosition, EdgeFactor); // blend between corners CrossingTotal += MidPosition; CrossingCount++; } // find crossings of parent cell const float ParentEdgeFactorMin = EdgeFactors[ParentVoxelEdgeIndex + ParentEdgeIndexOffsetsMin[Edge]]; const float ParentEdgeFactorMax = EdgeFactors[ParentVoxelEdgeIndex + ParentEdgeIndexOffsetsMax[Edge]]; if ((ParentEdgeFactorMin >= 0) || (ParentEdgeFactorMax >= 0)) { const float ParentEdgeFactor = ((ParentEdgeFactorMin >= 0) ? 0.5f * ParentEdgeFactorMin : 0.5f) + ((ParentEdgeFactorMax >= 0) ? 0.5f * ParentEdgeFactorMax : 0); const FVector MinPosition = ParentCorners[EdgeFirstCornerIndex[Edge]]; const FVector MaxPosition = ParentCorners[EdgeSecondCornerIndex[Edge]]; const FVector MidPosition = FMath::Lerp(MinPosition, MaxPosition, ParentEdgeFactor); // blend between corners ParentCrossingTotal += MidPosition; ParentCrossingCount++; } } ensureVoxelSlowNoSideEffects(CrossingCount > 0); const FVector Offset = CrossingTotal / CrossingCount; const FVector ParentOffset = ParentCrossingCount == 0 ? FVector::ZeroVector : ParentCrossingTotal / ParentCrossingCount; const FIntVector CellPosition(LX * Step, LY * Step, LZ * Step); const FVector CornerPosition{ CellPosition }; const FVector FinalPosition = CornerPosition + Offset * Step; const FIntVector ParentCellPosition = ParentPosition * Step; const FVector ParentCornerPosition{ ParentCellPosition }; const FVector ParentFinalPosition = ParentCornerPosition + ParentOffset * Step; TVertex Vertex; Vertex.SetPosition(FinalPosition); if (TVertex::bComputeParentPosition) { Vertex.SetParentPosition((ParentFinalPosition - FinalPosition) / Step); // Divide by Step to avoid overflowing the tangent } if (TVertex::bComputeNormal) { Vertex.SetNormal(MESHER_TIME_RETURN(Normals, GetNormal(VoxelFloats, Offset))); } if (TVertex::bComputeMaterial) { Vertex.SetMaterial(MESHER_TIME_RETURN_MATERIALS(1, Accelerator->GetMaterial(MaterialPositions[VoxelIndex] * Step + ChunkPosition, LOD))); } if (TVertex::bComputeTextureCoordinate) { Vertex.SetTextureCoordinate(MESHER_TIME_RETURN(UVs, FVoxelMesherUtilities::GetUVs(*this, FinalPosition))); } Vertices.Add(Vertex); } } } } UnlockData(); { VOXEL_ASYNC_SCOPE_COUNTER("Generate Mesh"); // generate the mesh indices for (uint32 LZ = 0; LZ < RENDER_CHUNK_SIZE; LZ++) { for (uint32 LY = 0; LY < RENDER_CHUNK_SIZE; LY++) { for (uint32 LX = 0; LX < RENDER_CHUNK_SIZE; LX++) { const uint32 VoxelIndex = LX + LY * SN_CHUNK_SIZE + LZ * SN_CHUNK_SIZE * SN_CHUNK_SIZE; const uint8 SurfaceNetCase = VertexSNCases[VoxelIndex]; constexpr uint32 QuadsOffsets[8] = { 0, SN_CHUNK_SIZE, SN_CHUNK_SIZE + 1, 1, SN_CHUNK_SIZE * SN_CHUNK_SIZE, SN_CHUNK_SIZE * SN_CHUNK_SIZE + SN_CHUNK_SIZE, SN_CHUNK_SIZE * SN_CHUNK_SIZE + SN_CHUNK_SIZE + 1, SN_CHUNK_SIZE * SN_CHUNK_SIZE + 1 }; constexpr uint32 QuadsPositions[6][4] = { { 0, 1, 3, 2 }, { 0, 3, 4, 7 }, { 0, 4, 1, 5 }, { 0, 3, 1, 2 }, { 0, 4, 3, 7 }, { 0, 1, 4, 5 } }; uint32 QuadsIndices[3] = { 0, 0, 0 }; //indexing into quads array uint32 QuadCount = 0; switch (SurfaceNetCase) //surface nets polygonization { case 0: QuadCount = 0; break; case 1: QuadCount = 1; QuadsIndices[0] = 0; break; case 2: QuadCount = 1; QuadsIndices[0] = 1; break; case 3: QuadCount = 2; QuadsIndices[0] = 0; QuadsIndices[1] = 1; break; case 4: QuadCount = 1; QuadsIndices[0] = 2; break; case 5: QuadCount = 2; QuadsIndices[0] = 0; QuadsIndices[1] = 2; break; case 6: QuadCount = 2; QuadsIndices[0] = 1; QuadsIndices[1] = 2; break; case 7: QuadCount = 3; QuadsIndices[0] = 0; QuadsIndices[1] = 1; QuadsIndices[2] = 2; break; //^^ forward cases case 8: QuadCount = 3; QuadsIndices[0] = 3; QuadsIndices[1] = 4; QuadsIndices[2] = 5; break; //vv complement cases case 9: QuadCount = 2; QuadsIndices[0] = 4; QuadsIndices[1] = 5; break; case 10: QuadCount = 2; QuadsIndices[0] = 3; QuadsIndices[1] = 5; break; case 11: QuadCount = 1; QuadsIndices[0] = 5; break; case 12: QuadCount = 2; QuadsIndices[0] = 3; QuadsIndices[1] = 4; break; case 13: QuadCount = 1; QuadsIndices[0] = 4; break; case 14: QuadCount = 1; QuadsIndices[0] = 3; break; case 15: QuadCount = 0; break; default: checkVoxelSlow(false); } for (uint32 QuadIndex = 0; QuadIndex < QuadCount; QuadIndex++) { const uint32* Positions = QuadsPositions[QuadsIndices[QuadIndex]]; const uint32 Index0 = VertexIndices[VoxelIndex + QuadsOffsets[Positions[0]]]; const uint32 Index1 = VertexIndices[VoxelIndex + QuadsOffsets[Positions[1]]]; const uint32 Index2 = VertexIndices[VoxelIndex + QuadsOffsets[Positions[2]]]; const uint32 Index3 = VertexIndices[VoxelIndex + QuadsOffsets[Positions[3]]]; Indices.Add(Index0); Indices.Add(Index2); Indices.Add(Index3); Indices.Add(Index3); Indices.Add(Index1); Indices.Add(Index0); } } } } } } TVoxelSharedPtr FVoxelSurfaceNetMesher::CreateFullChunkImpl(FVoxelMesherTimes& Times) { TArray Indices; TArray Vertices; CreateGeometryTemplate(Times, Indices, Vertices); FVoxelMesherUtilities::SanitizeMesh(Indices, Vertices); return MESHER_TIME_RETURN(CreateChunk, FVoxelMesherUtilities::CreateChunkFromVertices( Settings, LOD, MoveTemp(Indices), MoveTemp(reinterpret_cast&>(Vertices)))); } void FVoxelSurfaceNetMesher::CreateGeometryImpl(FVoxelMesherTimes& Times, TArray& Indices, TArray& Vertices) { CreateGeometryTemplate(Times, Indices, reinterpret_cast&>(Vertices)); }