wip(assembler): M2 round 3 -- opt-in hybrid sharp mesher (WorldAssemblerHybridV1) in the Unreal Core route
Default MarchingCubesV1 unchanged (W1/W8/W16 = round-6 hashes). Hybrid DC only in LOD0 interior cells with tagged features; tile-border cells and LOD1+ stay MC. BuildHost: fixtures repeat exactly, cooked collision bounds = render bounds, same-LOD borders pass, sampled SurfaceWorld cliff bit-identical to MC. Open: tagged tiles cost 5.7-6.7x MC; corner-top field contact error 20 cm; 5 MC doorway LOD cases. 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
a3b7d2015e
commit
22a6b30494
21 files changed
+3009
-23
No files matched your search
@@ -0,0 +1,603 @@
|
||||
#pragma once
|
||||
|
||||
#include "MarchingCubesTables.h"
|
||||
#include "VoxelMarchingCubesMesher.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// Feature-aware dual-contouring support for the opt-in Core recipe.
|
||||
// Support du dual contouring sensible aux fonctions pour la recette Core optionnelle.
|
||||
namespace VoxelHybridSharpMesher
|
||||
{
|
||||
constexpr int32 EdgeCount = 12;
|
||||
constexpr int32 CornerCount = 8;
|
||||
constexpr double SubvoxelsPerVoxel = 256.0;
|
||||
|
||||
constexpr int32 CornerOffsets[CornerCount][3] = {
|
||||
{0,0,0}, {1,0,0}, {1,1,0}, {0,1,0},
|
||||
{0,0,1}, {1,0,1}, {1,1,1}, {0,1,1}
|
||||
};
|
||||
constexpr int32 EdgeCorners[EdgeCount][2] = {
|
||||
{0,1}, {1,2}, {2,3}, {3,0}, {4,5}, {5,6},
|
||||
{6,7}, {7,4}, {0,4}, {1,5}, {2,6}, {3,7}
|
||||
};
|
||||
|
||||
inline double AxisValue(const FVector& V, int32 Axis)
|
||||
{
|
||||
return Axis == 0 ? V.X : (Axis == 1 ? V.Y : V.Z);
|
||||
}
|
||||
|
||||
inline void SetAxisValue(FVector& V, int32 Axis, double Value)
|
||||
{
|
||||
if (Axis == 0) V.X = Value;
|
||||
else if (Axis == 1) V.Y = Value;
|
||||
else V.Z = Value;
|
||||
}
|
||||
|
||||
struct FPlane
|
||||
{
|
||||
uint64 Id = 0;
|
||||
FVector Normal = FVector::UpVector;
|
||||
};
|
||||
|
||||
struct FRoot
|
||||
{
|
||||
bool bActive = false;
|
||||
FVector Position = FVector::ZeroVector;
|
||||
FVector Gradient = FVector::UpVector;
|
||||
std::vector<FPlane> Planes;
|
||||
uint64 FeatureId = 0;
|
||||
};
|
||||
|
||||
struct FTriangle
|
||||
{
|
||||
FVector Positions[3]{};
|
||||
FVector Normals[3]{};
|
||||
uint64 SplitKeys[3]{};
|
||||
};
|
||||
|
||||
struct FResult
|
||||
{
|
||||
std::vector<uint8> CoreCellDCEdges;
|
||||
std::vector<FTriangle> Triangles;
|
||||
uint32 SharpCells = 0;
|
||||
uint32 DualCells = 0;
|
||||
uint32 BorderFallbackPatches = 0;
|
||||
|
||||
bool UsesDC(int32 X, int32 Y, int32 Z, int32 Cells) const
|
||||
{
|
||||
if (X < 0 || Y < 0 || Z < 0 || X >= Cells || Y >= Cells || Z >= Cells)
|
||||
return false;
|
||||
const std::size_t Cell = (static_cast<std::size_t>(Z) * Cells + Y) * Cells + X;
|
||||
const std::size_t Offset = Cell * EdgeCount;
|
||||
for (int32 Edge = 0; Edge < EdgeCount && Offset + Edge < CoreCellDCEdges.size(); ++Edge)
|
||||
if (CoreCellDCEdges[Offset + Edge] != 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UsesDCEdge(int32 X, int32 Y, int32 Z, int32 Edge, int32 Cells) const
|
||||
{
|
||||
if (X < 0 || Y < 0 || Z < 0 || X >= Cells || Y >= Cells || Z >= Cells)
|
||||
return false;
|
||||
const std::size_t Cell = (static_cast<std::size_t>(Z) * Cells + Y) * Cells + X;
|
||||
const std::size_t Offset = Cell * EdgeCount + static_cast<std::size_t>(Edge);
|
||||
return Offset < CoreCellDCEdges.size() && (CoreCellDCEdges[Offset] & 1u) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct FCell
|
||||
{
|
||||
uint8 Case = 0;
|
||||
uint8 ComponentCount = 0;
|
||||
std::array<uint8, EdgeCount> EdgeComponent{};
|
||||
std::array<uint8, EdgeCount> ComponentSharp{};
|
||||
std::array<uint8, EdgeCount> ComponentUseDC{};
|
||||
std::array<FVector, EdgeCount> Dual{};
|
||||
|
||||
FCell() { EdgeComponent.fill(0xff); }
|
||||
};
|
||||
|
||||
inline int32 FindLocal(std::array<uint8, EdgeCount>& Parent, int32 Value)
|
||||
{
|
||||
while (Parent[Value] != Value)
|
||||
{
|
||||
Parent[Value] = Parent[Parent[Value]];
|
||||
Value = Parent[Value];
|
||||
}
|
||||
return Value;
|
||||
}
|
||||
|
||||
inline void UniteLocal(std::array<uint8, EdgeCount>& Parent, int32 A, int32 B)
|
||||
{
|
||||
A = FindLocal(Parent, A);
|
||||
B = FindLocal(Parent, B);
|
||||
if (A == B) return;
|
||||
if (A > B) std::swap(A, B);
|
||||
Parent[B] = static_cast<uint8>(A);
|
||||
}
|
||||
|
||||
inline int32 FindLocalEdge(int32 CellX, int32 CellY, int32 CellZ,
|
||||
int32 Axis, int32 EdgeX, int32 EdgeY, int32 EdgeZ)
|
||||
{
|
||||
for (int32 Edge = 0; Edge < EdgeCount; ++Edge)
|
||||
{
|
||||
const int32 A = EdgeCorners[Edge][0];
|
||||
const int32 B = EdgeCorners[Edge][1];
|
||||
const int32 AX = CornerOffsets[A][0], AY = CornerOffsets[A][1], AZ = CornerOffsets[A][2];
|
||||
const int32 BX = CornerOffsets[B][0], BY = CornerOffsets[B][1], BZ = CornerOffsets[B][2];
|
||||
const int32 LocalAxis = AX != BX ? 0 : AY != BY ? 1 : 2;
|
||||
if (LocalAxis == Axis
|
||||
&& CellX + FMath::Min(AX, BX) == EdgeX
|
||||
&& CellY + FMath::Min(AY, BY) == EdgeY
|
||||
&& CellZ + FMath::Min(AZ, BZ) == EdgeZ)
|
||||
return Edge;
|
||||
}
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
inline FVector SolveQEF(const std::vector<std::pair<FVector, FVector>>& Constraints,
|
||||
const FVector& Fallback, const FVector& Low, const FVector& High)
|
||||
{
|
||||
if (Constraints.empty()) return Fallback;
|
||||
double A[3][4]{};
|
||||
constexpr double Lambda = 1.0e-10;
|
||||
const double Center[3] = {Fallback.X, Fallback.Y, Fallback.Z};
|
||||
for (int32 I = 0; I < 3; ++I) { A[I][I] = Lambda; A[I][3] = Lambda * Center[I]; }
|
||||
for (const auto& Constraint : Constraints)
|
||||
{
|
||||
FVector N = Constraint.second.GetSafeNormal();
|
||||
const double B = FVector::DotProduct(N, Constraint.first);
|
||||
const double NV[3] = {N.X, N.Y, N.Z};
|
||||
for (int32 R = 0; R < 3; ++R)
|
||||
{
|
||||
for (int32 C = 0; C < 3; ++C) A[R][C] += NV[R] * NV[C];
|
||||
A[R][3] += NV[R] * B;
|
||||
}
|
||||
}
|
||||
for (int32 Column = 0; Column < 3; ++Column)
|
||||
{
|
||||
int32 Pivot = Column;
|
||||
for (int32 Row = Column + 1; Row < 3; ++Row)
|
||||
if (FMath::Abs(A[Row][Column]) > FMath::Abs(A[Pivot][Column])) Pivot = Row;
|
||||
if (FMath::Abs(A[Pivot][Column]) < 1.0e-14) return Fallback;
|
||||
if (Pivot != Column)
|
||||
for (int32 K = Column; K < 4; ++K) std::swap(A[Pivot][K], A[Column][K]);
|
||||
const double Divisor = A[Column][Column];
|
||||
for (int32 K = Column; K < 4; ++K) A[Column][K] /= Divisor;
|
||||
for (int32 Row = 0; Row < 3; ++Row)
|
||||
{
|
||||
if (Row == Column) continue;
|
||||
const double Factor = A[Row][Column];
|
||||
for (int32 K = Column; K < 4; ++K) A[Row][K] -= Factor * A[Column][K];
|
||||
}
|
||||
}
|
||||
return FVector(FMath::Clamp(A[0][3], Low.X, High.X),
|
||||
FMath::Clamp(A[1][3], Low.Y, High.Y),
|
||||
FMath::Clamp(A[2][3], Low.Z, High.Z));
|
||||
}
|
||||
|
||||
inline double BoxSDF(const FVoxelHybridSharpFeatureBox& Box, const FVector& P,
|
||||
std::vector<FPlane>& OutPlanes)
|
||||
{
|
||||
FVector Low, High;
|
||||
for (int32 Axis = 0; Axis < 3; ++Axis)
|
||||
{
|
||||
SetAxisValue(Low, Axis,
|
||||
static_cast<double>(Box.MinimumSubvoxels[Axis]) / SubvoxelsPerVoxel);
|
||||
SetAxisValue(High, Axis,
|
||||
static_cast<double>(Box.MaximumSubvoxels[Axis]) / SubvoxelsPerVoxel);
|
||||
}
|
||||
const FVector Center = (Low + High) * 0.5;
|
||||
const FVector Half = (High - Low) * 0.5;
|
||||
const FVector Delta = P - Center;
|
||||
const FVector Q(FMath::Abs(Delta.X) - Half.X,
|
||||
FMath::Abs(Delta.Y) - Half.Y,
|
||||
FMath::Abs(Delta.Z) - Half.Z);
|
||||
const double MaxQ = FMath::Max(Q.X, FMath::Max(Q.Y, Q.Z));
|
||||
const FVector Outside(FMath::Max(Q.X, 0.0), FMath::Max(Q.Y, 0.0), FMath::Max(Q.Z, 0.0));
|
||||
const double OutsideLength = Outside.Size();
|
||||
const double SDF = OutsideLength + FMath::Min(MaxQ, 0.0);
|
||||
const double Tolerance = 1.0e-4;
|
||||
if (FMath::Abs(MaxQ) <= Tolerance)
|
||||
{
|
||||
for (int32 Axis = 0; Axis < 3; ++Axis)
|
||||
{
|
||||
if (FMath::Abs(AxisValue(Q, Axis) - MaxQ) > Tolerance) continue;
|
||||
FVector Normal = FVector::ZeroVector;
|
||||
const bool bNegativeFace = AxisValue(Delta, Axis) < 0.0;
|
||||
SetAxisValue(Normal, Axis, bNegativeFace ? -1.0 : 1.0);
|
||||
uint64 FaceId = Box.StableId ^ (0x9e3779b97f4a7c15ull
|
||||
+ static_cast<uint64>(Axis * 2 + (bNegativeFace ? 0 : 1) + 1));
|
||||
if (FaceId == 0) FaceId = static_cast<uint64>(Axis + 1);
|
||||
OutPlanes.push_back({FaceId, Normal});
|
||||
}
|
||||
}
|
||||
return SDF;
|
||||
}
|
||||
|
||||
inline uint64 SelectFeature(const FRoot& Root, int32 Axis)
|
||||
{
|
||||
if (Root.Planes.empty()) return 0;
|
||||
std::size_t Selected = 0;
|
||||
const auto Alignment = [Axis](const FVector& Normal)
|
||||
{ return FMath::Abs(AxisValue(Normal, Axis)); };
|
||||
for (std::size_t I = 1; I < Root.Planes.size(); ++I)
|
||||
{
|
||||
const double Candidate = Alignment(Root.Planes[I].Normal);
|
||||
const double Current = Alignment(Root.Planes[Selected].Normal);
|
||||
if (Candidate > Current + 1.0e-12
|
||||
|| (FMath::Abs(Candidate - Current) <= 1.0e-12
|
||||
&& Root.Planes[I].Id < Root.Planes[Selected].Id)) Selected = I;
|
||||
}
|
||||
return Root.Planes[Selected].Id;
|
||||
}
|
||||
|
||||
inline FVector NormalForFeature(const FRoot& Root, uint64 Feature)
|
||||
{
|
||||
for (const FPlane& Plane : Root.Planes) if (Plane.Id == Feature) return Plane.Normal;
|
||||
return Root.Gradient;
|
||||
}
|
||||
|
||||
inline FResult Generate(int32 Cells, int32 Step, float IsoLevel, float GeometryVoxelSize,
|
||||
const FIntVector& OriginVoxels, const FVector& ChunkWorldPos,
|
||||
const TArray<FVoxelHybridSharpFeatureBox>& Features,
|
||||
const std::function<float(int32,int32,int32)>& Sample,
|
||||
const std::function<FVector(int32,int32,int32)>& Gradient)
|
||||
{
|
||||
FResult Result;
|
||||
if (Cells <= 0 || Features.IsEmpty()) return Result;
|
||||
const int32 CellDim = Cells + 2;
|
||||
const int32 NodeDim = Cells + 3;
|
||||
const std::size_t CellCount = static_cast<std::size_t>(CellDim) * CellDim * CellDim;
|
||||
std::vector<FCell> CellData(CellCount);
|
||||
const auto CellIndex = [CellDim](int32 X, int32 Y, int32 Z)
|
||||
{ return (static_cast<std::size_t>(Z + 1) * CellDim + (Y + 1)) * CellDim + (X + 1); };
|
||||
const auto NodePosition = [&](int32 X, int32 Y, int32 Z)
|
||||
{ return FVector(X * Step * GeometryVoxelSize, Y * Step * GeometryVoxelSize,
|
||||
Z * Step * GeometryVoxelSize) + ChunkWorldPos; };
|
||||
|
||||
std::array<std::vector<FRoot>, 3> Roots;
|
||||
Roots[0].resize(static_cast<std::size_t>(CellDim) * NodeDim * NodeDim);
|
||||
Roots[1].resize(static_cast<std::size_t>(NodeDim) * CellDim * NodeDim);
|
||||
Roots[2].resize(static_cast<std::size_t>(NodeDim) * NodeDim * CellDim);
|
||||
const auto RootIndex = [CellDim, NodeDim](int32 Axis, int32 X, int32 Y, int32 Z)
|
||||
{
|
||||
const std::size_t NX = static_cast<std::size_t>(X + 1);
|
||||
const std::size_t NY = static_cast<std::size_t>(Y + 1);
|
||||
const std::size_t NZ = static_cast<std::size_t>(Z + 1);
|
||||
if (Axis == 0) return (NZ * NodeDim + NY) * CellDim + NX;
|
||||
if (Axis == 1) return (NZ * CellDim + NY) * NodeDim + NX;
|
||||
return (NZ * NodeDim + NY) * NodeDim + NX;
|
||||
};
|
||||
|
||||
for (int32 Z = -1; Z <= Cells + 1; ++Z)
|
||||
for (int32 Y = -1; Y <= Cells + 1; ++Y)
|
||||
for (int32 X = -1; X <= Cells; ++X)
|
||||
{
|
||||
const float D0 = Sample(X, Y, Z), D1 = Sample(X + 1, Y, Z);
|
||||
if ((D0 < IsoLevel) == (D1 < IsoLevel)) continue;
|
||||
const double T = FMath::Clamp(static_cast<double>(IsoLevel - D0) / (D1 - D0), 0.0, 1.0);
|
||||
FRoot& Root = Roots[0][RootIndex(0, X, Y, Z)];
|
||||
Root.bActive = true;
|
||||
Root.Position = FMath::Lerp(NodePosition(X, Y, Z), NodePosition(X + 1, Y, Z), T);
|
||||
Root.Gradient = FMath::Lerp(Gradient(X, Y, Z), Gradient(X + 1, Y, Z), T).GetSafeNormal();
|
||||
}
|
||||
for (int32 Z = -1; Z <= Cells + 1; ++Z)
|
||||
for (int32 Y = -1; Y <= Cells; ++Y)
|
||||
for (int32 X = -1; X <= Cells + 1; ++X)
|
||||
{
|
||||
const float D0 = Sample(X, Y, Z), D1 = Sample(X, Y + 1, Z);
|
||||
if ((D0 < IsoLevel) == (D1 < IsoLevel)) continue;
|
||||
const double T = FMath::Clamp(static_cast<double>(IsoLevel - D0) / (D1 - D0), 0.0, 1.0);
|
||||
FRoot& Root = Roots[1][RootIndex(1, X, Y, Z)];
|
||||
Root.bActive = true;
|
||||
Root.Position = FMath::Lerp(NodePosition(X, Y, Z), NodePosition(X, Y + 1, Z), T);
|
||||
Root.Gradient = FMath::Lerp(Gradient(X, Y, Z), Gradient(X, Y + 1, Z), T).GetSafeNormal();
|
||||
}
|
||||
for (int32 Z = -1; Z <= Cells; ++Z)
|
||||
for (int32 Y = -1; Y <= Cells + 1; ++Y)
|
||||
for (int32 X = -1; X <= Cells + 1; ++X)
|
||||
{
|
||||
const float D0 = Sample(X, Y, Z), D1 = Sample(X, Y, Z + 1);
|
||||
if ((D0 < IsoLevel) == (D1 < IsoLevel)) continue;
|
||||
const double T = FMath::Clamp(static_cast<double>(IsoLevel - D0) / (D1 - D0), 0.0, 1.0);
|
||||
FRoot& Root = Roots[2][RootIndex(2, X, Y, Z)];
|
||||
Root.bActive = true;
|
||||
Root.Position = FMath::Lerp(NodePosition(X, Y, Z), NodePosition(X, Y, Z + 1), T);
|
||||
Root.Gradient = FMath::Lerp(Gradient(X, Y, Z), Gradient(X, Y, Z + 1), T).GetSafeNormal();
|
||||
}
|
||||
|
||||
const auto GetRoot = [&](int32 Axis, int32 X, int32 Y, int32 Z) -> FRoot&
|
||||
{ return Roots[Axis][RootIndex(Axis, X, Y, Z)]; };
|
||||
const auto GetCellEdgeRoot = [&](int32 X, int32 Y, int32 Z, int32 Edge) -> FRoot&
|
||||
{
|
||||
const int32 A = EdgeCorners[Edge][0], B = EdgeCorners[Edge][1];
|
||||
const int32 AX = CornerOffsets[A][0], AY = CornerOffsets[A][1], AZ = CornerOffsets[A][2];
|
||||
const int32 BX = CornerOffsets[B][0], BY = CornerOffsets[B][1], BZ = CornerOffsets[B][2];
|
||||
const int32 Axis = AX != BX ? 0 : AY != BY ? 1 : 2;
|
||||
return GetRoot(Axis, X + FMath::Min(AX, BX), Y + FMath::Min(AY, BY), Z + FMath::Min(AZ, BZ));
|
||||
};
|
||||
|
||||
for (int32 Z = -1; Z <= Cells; ++Z)
|
||||
for (int32 Y = -1; Y <= Cells; ++Y)
|
||||
for (int32 X = -1; X <= Cells; ++X)
|
||||
{
|
||||
FCell& Cell = CellData[CellIndex(X, Y, Z)];
|
||||
float Densities[CornerCount]{};
|
||||
for (int32 Corner = 0; Corner < CornerCount; ++Corner)
|
||||
{
|
||||
const int32 GX = X + CornerOffsets[Corner][0];
|
||||
const int32 GY = Y + CornerOffsets[Corner][1];
|
||||
const int32 GZ = Z + CornerOffsets[Corner][2];
|
||||
Densities[Corner] = Sample(GX, GY, GZ);
|
||||
if (Densities[Corner] >= IsoLevel) Cell.Case |= static_cast<uint8>(1u << Corner);
|
||||
}
|
||||
if (Cell.Case == 0 || Cell.Case == 255 || MCEdgeTable[Cell.Case] == 0) continue;
|
||||
std::array<uint8, EdgeCount> Parent{};
|
||||
for (int32 Edge = 0; Edge < EdgeCount; ++Edge) Parent[Edge] = static_cast<uint8>(Edge);
|
||||
for (int32 I = 0; I < 16 && MCTriTable[Cell.Case][I] >= 0; I += 3)
|
||||
{
|
||||
const int32 A = MCTriTable[Cell.Case][I];
|
||||
const int32 B = MCTriTable[Cell.Case][I + 1];
|
||||
const int32 C = MCTriTable[Cell.Case][I + 2];
|
||||
UniteLocal(Parent, A, B); UniteLocal(Parent, B, C);
|
||||
}
|
||||
std::array<uint8, EdgeCount> RootToComponent{};
|
||||
RootToComponent.fill(0xff);
|
||||
for (int32 Edge = 0; Edge < EdgeCount; ++Edge)
|
||||
{
|
||||
const int32 A = EdgeCorners[Edge][0], B = EdgeCorners[Edge][1];
|
||||
if ((Densities[A] < IsoLevel) == (Densities[B] < IsoLevel)) continue;
|
||||
FRoot& Root = GetCellEdgeRoot(X, Y, Z, Edge);
|
||||
if (!Root.bActive) continue;
|
||||
const int32 LocalRoot = FindLocal(Parent, Edge);
|
||||
if (RootToComponent[LocalRoot] == 0xff)
|
||||
RootToComponent[LocalRoot] = Cell.ComponentCount++;
|
||||
const uint8 Component = RootToComponent[LocalRoot];
|
||||
Cell.EdgeComponent[Edge] = Component;
|
||||
|
||||
const int32 OA = EdgeCorners[Edge][0];
|
||||
const int32 OB = EdgeCorners[Edge][1];
|
||||
const int32 AX = X + CornerOffsets[OA][0], AY = Y + CornerOffsets[OA][1], AZ = Z + CornerOffsets[OA][2];
|
||||
const int32 BX = X + CornerOffsets[OB][0], BY = Y + CornerOffsets[OB][1], BZ = Z + CornerOffsets[OB][2];
|
||||
const FVector RootVoxel = FVector(OriginVoxels.X, OriginVoxels.Y, OriginVoxels.Z)
|
||||
+ FVector(AX, AY, AZ) * Step
|
||||
+ FVector(BX - AX, BY - AY, BZ - AZ) * (Step *
|
||||
FMath::Clamp(static_cast<double>(IsoLevel - Densities[OA])
|
||||
/ (Densities[OB] - Densities[OA]), 0.0, 1.0));
|
||||
for (const FVoxelHybridSharpFeatureBox& Feature : Features)
|
||||
{
|
||||
std::vector<FPlane> CandidatePlanes;
|
||||
const double SDF = BoxSDF(Feature, RootVoxel, CandidatePlanes);
|
||||
if (FMath::Abs(SDF) > 1.0e-4 || CandidatePlanes.empty()) continue;
|
||||
for (const FPlane& Plane : CandidatePlanes) Root.Planes.push_back(Plane);
|
||||
}
|
||||
if (!Root.Planes.empty())
|
||||
{
|
||||
std::sort(Root.Planes.begin(), Root.Planes.end(),
|
||||
[](const FPlane& A, const FPlane& B) { return A.Id < B.Id; });
|
||||
Root.Planes.erase(std::unique(Root.Planes.begin(), Root.Planes.end(),
|
||||
[](const FPlane& A, const FPlane& B) { return A.Id == B.Id; }), Root.Planes.end());
|
||||
Cell.ComponentSharp[Component] = 1;
|
||||
if (X >= 0 && Y >= 0 && Z >= 0 && X < Cells && Y < Cells && Z < Cells)
|
||||
++Result.SharpCells;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::size_t PatchCount = CellCount * EdgeCount;
|
||||
std::vector<uint32> PatchParent(PatchCount);
|
||||
std::vector<uint8> PatchRank(PatchCount, 0), PatchSharp(PatchCount, 0), PatchTouchesBorder(PatchCount, 0);
|
||||
std::iota(PatchParent.begin(), PatchParent.end(), 0u);
|
||||
const auto FindPatch = [&](uint32 Value, auto&& Self) -> uint32
|
||||
{
|
||||
if (PatchParent[Value] != Value) PatchParent[Value] = Self(PatchParent[Value], Self);
|
||||
return PatchParent[Value];
|
||||
};
|
||||
const auto UnitePatch = [&](uint32 A, uint32 B)
|
||||
{
|
||||
uint32 RA = FindPatch(A, FindPatch), RB = FindPatch(B, FindPatch);
|
||||
if (RA == RB) return;
|
||||
if (PatchRank[RA] < PatchRank[RB]) std::swap(RA, RB);
|
||||
PatchParent[RB] = RA;
|
||||
PatchSharp[RA] = static_cast<uint8>(PatchSharp[RA] | PatchSharp[RB]);
|
||||
PatchTouchesBorder[RA] = static_cast<uint8>(PatchTouchesBorder[RA] | PatchTouchesBorder[RB]);
|
||||
if (PatchRank[RA] == PatchRank[RB]) ++PatchRank[RA];
|
||||
};
|
||||
for (int32 Z = -1; Z <= Cells; ++Z)
|
||||
for (int32 Y = -1; Y <= Cells; ++Y)
|
||||
for (int32 X = -1; X <= Cells; ++X)
|
||||
{
|
||||
const FCell& Cell = CellData[CellIndex(X, Y, Z)];
|
||||
for (uint8 Component = 0; Component < Cell.ComponentCount; ++Component)
|
||||
{
|
||||
const uint32 Id = static_cast<uint32>(CellIndex(X, Y, Z) * EdgeCount + Component);
|
||||
PatchSharp[Id] = Cell.ComponentSharp[Component];
|
||||
const bool bCoreBorder = X >= 0 && Y >= 0 && Z >= 0 && X < Cells && Y < Cells && Z < Cells
|
||||
&& (X == 0 || Y == 0 || Z == 0 || X == Cells - 1 || Y == Cells - 1 || Z == Cells - 1);
|
||||
PatchTouchesBorder[Id] = bCoreBorder ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32 Axis = 0; Axis < 3; ++Axis)
|
||||
for (int32 Z = -1; Z <= Cells + (Axis == 2 ? 0 : 1); ++Z)
|
||||
for (int32 Y = -1; Y <= Cells + (Axis == 1 ? 0 : 1); ++Y)
|
||||
for (int32 X = -1; X <= Cells + (Axis == 0 ? 0 : 1); ++X)
|
||||
{
|
||||
if (!GetRoot(Axis, X, Y, Z).bActive) continue;
|
||||
std::array<std::array<int32, 3>, 4> Around{};
|
||||
if (Axis == 0) Around = {{{X,Y-1,Z-1},{X,Y,Z-1},{X,Y,Z},{X,Y-1,Z}}};
|
||||
else if (Axis == 1) Around = {{{X-1,Y,Z-1},{X-1,Y,Z},{X,Y,Z},{X,Y,Z-1}}};
|
||||
else Around = {{{X-1,Y-1,Z},{X,Y-1,Z},{X,Y,Z},{X-1,Y,Z}}};
|
||||
std::array<uint32, 4> Ids{};
|
||||
bool bValid = true;
|
||||
for (int32 I = 0; I < 4; ++I)
|
||||
{
|
||||
const int32 CX = Around[I][0], CY = Around[I][1], CZ = Around[I][2];
|
||||
if (CX < -1 || CY < -1 || CZ < -1 || CX > Cells || CY > Cells || CZ > Cells)
|
||||
{ bValid = false; break; }
|
||||
const int32 LocalEdge = FindLocalEdge(CX, CY, CZ, Axis, X, Y, Z);
|
||||
const FCell& Cell = CellData[CellIndex(CX, CY, CZ)];
|
||||
if (LocalEdge == INDEX_NONE || Cell.EdgeComponent[LocalEdge] == 0xff)
|
||||
{ bValid = false; break; }
|
||||
Ids[I] = static_cast<uint32>(CellIndex(CX, CY, CZ) * EdgeCount + Cell.EdgeComponent[LocalEdge]);
|
||||
}
|
||||
if (bValid) for (int32 I = 1; I < 4; ++I) UnitePatch(Ids[0], Ids[I]);
|
||||
}
|
||||
|
||||
bool bSawBorderPatch = false;
|
||||
std::vector<uint8> PatchUseDC(PatchCount, 0);
|
||||
for (int32 Z = -1; Z <= Cells; ++Z)
|
||||
for (int32 Y = -1; Y <= Cells; ++Y)
|
||||
for (int32 X = -1; X <= Cells; ++X)
|
||||
{
|
||||
const FCell& Cell = CellData[CellIndex(X, Y, Z)];
|
||||
for (uint8 Component = 0; Component < Cell.ComponentCount; ++Component)
|
||||
{
|
||||
const uint32 Id = static_cast<uint32>(CellIndex(X, Y, Z) * EdgeCount + Component);
|
||||
const uint32 Root = FindPatch(Id, FindPatch);
|
||||
PatchUseDC[Root] = static_cast<uint8>(PatchSharp[Root] && !PatchTouchesBorder[Root]);
|
||||
if (PatchSharp[Root] && PatchTouchesBorder[Root]) bSawBorderPatch = true;
|
||||
}
|
||||
}
|
||||
Result.BorderFallbackPatches = bSawBorderPatch ? 1u : 0u;
|
||||
|
||||
for (int32 Z = -1; Z <= Cells; ++Z)
|
||||
for (int32 Y = -1; Y <= Cells; ++Y)
|
||||
for (int32 X = -1; X <= Cells; ++X)
|
||||
{
|
||||
FCell& Cell = CellData[CellIndex(X, Y, Z)];
|
||||
if (Cell.ComponentCount == 0) continue;
|
||||
for (uint8 Component = 0; Component < Cell.ComponentCount; ++Component)
|
||||
{
|
||||
const uint32 Id = static_cast<uint32>(CellIndex(X, Y, Z) * EdgeCount + Component);
|
||||
Cell.ComponentUseDC[Component] = PatchUseDC[FindPatch(Id, FindPatch)];
|
||||
if (!Cell.ComponentUseDC[Component]) continue;
|
||||
std::vector<std::pair<FVector, FVector>> Constraints;
|
||||
FVector RootSum = FVector::ZeroVector;
|
||||
int32 RootCount = 0;
|
||||
for (int32 Edge = 0; Edge < EdgeCount; ++Edge)
|
||||
{
|
||||
if (Cell.EdgeComponent[Edge] != Component) continue;
|
||||
FRoot& Root = GetCellEdgeRoot(X, Y, Z, Edge);
|
||||
if (!Root.bActive) continue;
|
||||
RootSum += Root.Position;
|
||||
++RootCount;
|
||||
if (!Root.Planes.empty())
|
||||
for (const FPlane& Plane : Root.Planes) Constraints.emplace_back(Root.Position, Plane.Normal);
|
||||
else Constraints.emplace_back(Root.Position, Root.Gradient);
|
||||
}
|
||||
const FVector Low = NodePosition(X, Y, Z);
|
||||
const FVector High = NodePosition(X + 1, Y + 1, Z + 1);
|
||||
const FVector Center = RootCount > 0 ? RootSum / RootCount : (Low + High) * 0.5;
|
||||
Cell.Dual[Component] = SolveQEF(Constraints, Center, Low, High);
|
||||
if (X >= 0 && Y >= 0 && Z >= 0 && X < Cells && Y < Cells && Z < Cells)
|
||||
++Result.DualCells;
|
||||
}
|
||||
}
|
||||
|
||||
Result.CoreCellDCEdges.assign(static_cast<std::size_t>(Cells) * Cells * Cells * EdgeCount, 0);
|
||||
const auto AddTriangle = [&](const FVector& A, const FVector& B, const FVector& C,
|
||||
const FVector& NA, const FVector& NB, const FVector& NC,
|
||||
uint64 FA, uint64 FB, uint64 FC)
|
||||
{
|
||||
if (FVector::CrossProduct(B - A, C - A).SizeSquared() < 1.0e-12) return;
|
||||
FTriangle T;
|
||||
T.Positions[0] = A; T.Positions[1] = B; T.Positions[2] = C;
|
||||
T.Normals[0] = NA.GetSafeNormal(); T.Normals[1] = NB.GetSafeNormal(); T.Normals[2] = NC.GetSafeNormal();
|
||||
T.SplitKeys[0] = FA; T.SplitKeys[1] = FB; T.SplitKeys[2] = FC;
|
||||
Result.Triangles.push_back(T);
|
||||
};
|
||||
|
||||
for (int32 Axis = 0; Axis < 3; ++Axis)
|
||||
for (int32 Z = -1; Z <= Cells + (Axis == 2 ? 0 : 1); ++Z)
|
||||
for (int32 Y = -1; Y <= Cells + (Axis == 1 ? 0 : 1); ++Y)
|
||||
for (int32 X = -1; X <= Cells + (Axis == 0 ? 0 : 1); ++X)
|
||||
{
|
||||
FRoot& Root = GetRoot(Axis, X, Y, Z);
|
||||
if (!Root.bActive) continue;
|
||||
std::array<std::array<int32, 3>, 4> Around{};
|
||||
if (Axis == 0) Around = {{{X,Y-1,Z-1},{X,Y,Z-1},{X,Y,Z},{X,Y-1,Z}}};
|
||||
else if (Axis == 1) Around = {{{X-1,Y,Z-1},{X-1,Y,Z},{X,Y,Z},{X,Y,Z-1}}};
|
||||
else Around = {{{X-1,Y-1,Z},{X,Y-1,Z},{X,Y,Z},{X-1,Y,Z}}};
|
||||
std::array<const FCell*, 4> Incident{};
|
||||
std::array<uint8, 4> Components{};
|
||||
bool bAllDC = true;
|
||||
bool bValid = true;
|
||||
for (int32 I = 0; I < 4; ++I)
|
||||
{
|
||||
const int32 CX = Around[I][0], CY = Around[I][1], CZ = Around[I][2];
|
||||
if (CX < -1 || CY < -1 || CZ < -1 || CX > Cells || CY > Cells || CZ > Cells)
|
||||
{ bValid = false; break; }
|
||||
const int32 LocalEdge = FindLocalEdge(CX, CY, CZ, Axis, X, Y, Z);
|
||||
const FCell& Cell = CellData[CellIndex(CX, CY, CZ)];
|
||||
if (LocalEdge == INDEX_NONE || Cell.EdgeComponent[LocalEdge] == 0xff)
|
||||
{ bValid = false; break; }
|
||||
const uint8 Component = Cell.EdgeComponent[LocalEdge];
|
||||
Incident[I] = &Cell;
|
||||
Components[I] = Component;
|
||||
bAllDC &= Cell.ComponentUseDC[Component] != 0;
|
||||
}
|
||||
if (!bValid || !bAllDC) continue;
|
||||
const int32 OwnerX = FMath::Min(FMath::Min(Around[0][0], Around[1][0]), FMath::Min(Around[2][0], Around[3][0]));
|
||||
const int32 OwnerY = FMath::Min(FMath::Min(Around[0][1], Around[1][1]), FMath::Min(Around[2][1], Around[3][1]));
|
||||
const int32 OwnerZ = FMath::Min(FMath::Min(Around[0][2], Around[1][2]), FMath::Min(Around[2][2], Around[3][2]));
|
||||
if (OwnerX < 0 || OwnerY < 0 || OwnerZ < 0 || OwnerX >= Cells || OwnerY >= Cells || OwnerZ >= Cells)
|
||||
continue;
|
||||
|
||||
std::array<FVector, 4> Positions{};
|
||||
std::array<FVector, 4> Normals{};
|
||||
std::array<uint64, 4> SplitKeys{};
|
||||
for (int32 I = 0; I < 4; ++I)
|
||||
{
|
||||
const int32 CX = Around[I][0], CY = Around[I][1], CZ = Around[I][2];
|
||||
const uint8 Component = Components[I];
|
||||
Positions[I] = Incident[I]->Dual[Component];
|
||||
SplitKeys[I] = SelectFeature(Root, Axis);
|
||||
Normals[I] = SplitKeys[I] != 0 ? NormalForFeature(Root, SplitKeys[I]) : Root.Gradient;
|
||||
}
|
||||
if (FVector::DotProduct(FVector::CrossProduct(Positions[1] - Positions[0],
|
||||
Positions[2] - Positions[0]), Root.Gradient) < 0.0)
|
||||
{
|
||||
std::swap(Positions[1], Positions[3]);
|
||||
std::swap(Normals[1], Normals[3]);
|
||||
std::swap(SplitKeys[1], SplitKeys[3]);
|
||||
}
|
||||
const int32 Tie = (X + Y + Z + Axis) & 1;
|
||||
if (Tie == 0)
|
||||
{
|
||||
AddTriangle(Positions[0], Positions[1], Positions[2], Normals[0], Normals[1], Normals[2], SplitKeys[0], SplitKeys[1], SplitKeys[2]);
|
||||
AddTriangle(Positions[0], Positions[2], Positions[3], Normals[0], Normals[2], Normals[3], SplitKeys[0], SplitKeys[2], SplitKeys[3]);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddTriangle(Positions[0], Positions[1], Positions[3], Normals[0], Normals[1], Normals[3], SplitKeys[0], SplitKeys[1], SplitKeys[3]);
|
||||
AddTriangle(Positions[1], Positions[2], Positions[3], Normals[1], Normals[2], Normals[3], SplitKeys[1], SplitKeys[2], SplitKeys[3]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int32 Z = 0; Z < Cells; ++Z)
|
||||
for (int32 Y = 0; Y < Cells; ++Y)
|
||||
for (int32 X = 0; X < Cells; ++X)
|
||||
{
|
||||
const FCell& Cell = CellData[CellIndex(X, Y, Z)];
|
||||
if (!Cell.ComponentCount) continue;
|
||||
const std::size_t CoreCell = (static_cast<std::size_t>(Z) * Cells + Y) * Cells + X;
|
||||
for (int32 Edge = 0; Edge < EdgeCount; ++Edge)
|
||||
{
|
||||
const uint8 Component = Cell.EdgeComponent[Edge];
|
||||
if (Component != 0xff && Cell.ComponentUseDC[Component])
|
||||
Result.CoreCellDCEdges[CoreCell * EdgeCount + Edge] = 1;
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Implémentation du marching cubes density-only.
|
||||
|
||||
#include "VoxelMarchingCubesMesher.h"
|
||||
#include "VoxelHybridSharpMesher.h"
|
||||
#include "MarchingCubesTables.h"
|
||||
#include "StandaloneShapeWorld.h"
|
||||
#include "VoxelDensityProfile.h"
|
||||
@@ -164,6 +165,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
// plus de (ré)allocation de hash-map par tuile (chaque worker a son propre exemplaire).
|
||||
static thread_local TMap<FIntVector, int32> VertexMap;
|
||||
VertexMap.Reset();
|
||||
static thread_local TMap<FIntVector, TMap<uint64, int32>> SplitNormalVertexMap;
|
||||
SplitNormalVertexMap.Reset();
|
||||
const int32 VertexCapacityHint=FMath::Clamp(InCellsPerAxis,2,CHUNK_SIZE);
|
||||
VertexMap.Reserve(VertexCapacityHint*VertexCapacityHint*2);
|
||||
|
||||
@@ -230,7 +233,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
};
|
||||
|
||||
// Normale fournie par l'appelant (gradient lu dans la grille de densité, T1.b).
|
||||
auto GetOrCreateVertex = [&](const FVector& WorldPos, const FVector& RawNormal) -> int32
|
||||
auto GetOrCreateVertex = [&](const FVector& WorldPos, const FVector& RawNormal,
|
||||
uint64 NormalSplitKey = 0) -> int32
|
||||
{
|
||||
const FIntVector Key(
|
||||
FMath::RoundToInt(WorldPos.X * 100.0f),
|
||||
@@ -238,13 +242,27 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
FMath::RoundToInt(WorldPos.Z * 100.0f)
|
||||
);
|
||||
|
||||
if (int32* Existing = VertexMap.Find(Key))
|
||||
if (NormalSplitKey != 0)
|
||||
{
|
||||
if (TMap<uint64, int32>* SplitMap = SplitNormalVertexMap.Find(Key))
|
||||
{
|
||||
if (int32* Existing = SplitMap->Find(NormalSplitKey)) return *Existing;
|
||||
}
|
||||
}
|
||||
else if (int32* Existing = VertexMap.Find(Key))
|
||||
{
|
||||
return *Existing;
|
||||
}
|
||||
|
||||
const int32 NewIndex = MeshData.Vertices.Num();
|
||||
VertexMap.Add(Key, NewIndex);
|
||||
if (NormalSplitKey != 0)
|
||||
{
|
||||
SplitNormalVertexMap.FindOrAdd(Key).Add(NormalSplitKey, NewIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
VertexMap.Add(Key, NewIndex);
|
||||
}
|
||||
|
||||
MeshData.Vertices.Add(WorldPos);
|
||||
|
||||
@@ -1349,6 +1367,20 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
SampleGradientG(gx, gy, gz + 1) - SampleGradientG(gx, gy, gz - 1));
|
||||
};
|
||||
|
||||
// DC is limited to tagged LOD0 patches. Coarse tiles and a connected patch that reaches a
|
||||
// tile edge use MC there, preserving the existing MC-to-MC clipmap transition and skirts.
|
||||
// Le DC reste limite aux surfaces LOD0 balisees; les patches connectes au bord restent MC.
|
||||
VoxelHybridSharpMesher::FResult HybridResult;
|
||||
if (bUseHybridSharpMesher && Step == 1 && CellsPerAxis == CHUNK_SIZE
|
||||
&& CzLo == 0 && CzHi == CellsPerAxis - 1 && !HybridSharpFeatures.IsEmpty())
|
||||
{
|
||||
HybridResult = VoxelHybridSharpMesher::Generate(
|
||||
CellsPerAxis, Step, IsoLevel, GeometryVoxelSize, OriginVoxels, ChunkWorldPos,
|
||||
HybridSharpFeatures,
|
||||
[&](int32 X, int32 Y, int32 Z) { return SampleG(X, Y, Z); },
|
||||
[&](int32 X, int32 Y, int32 Z) { return GradAt(X, Y, Z); });
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// ITÉRATION SUR LES CELLULES
|
||||
//=========================================================================
|
||||
@@ -1564,6 +1596,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const int32 E0 = MCTriTable[CaseIndex][i];
|
||||
const int32 E1 = MCTriTable[CaseIndex][i + 1];
|
||||
const int32 E2 = MCTriTable[CaseIndex][i + 2];
|
||||
if (HybridResult.UsesDCEdge(cx, cy, cz, E0, CellsPerAxis))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const int32 Idx0 = GetOrCreateVertex(EdgeVertices[E0], EdgeNormals[E0]);
|
||||
const int32 Idx1 = GetOrCreateVertex(EdgeVertices[E1], EdgeNormals[E1]);
|
||||
const int32 Idx2 = GetOrCreateVertex(EdgeVertices[E2], EdgeNormals[E2]);
|
||||
@@ -1591,6 +1627,26 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
}
|
||||
}
|
||||
|
||||
// Add DC geometry to the same material runs as MC. Analytic feature IDs split corner normals.
|
||||
// Ajouter la geometrie DC dans les memes groupes que MC; les IDs separent les normales nettes.
|
||||
for (const VoxelHybridSharpMesher::FTriangle& Triangle : HybridResult.Triangles)
|
||||
{
|
||||
const int32 Idx0 = GetOrCreateVertex(
|
||||
Triangle.Positions[0], Triangle.Normals[0], Triangle.SplitKeys[0]);
|
||||
const int32 Idx1 = GetOrCreateVertex(
|
||||
Triangle.Positions[1], Triangle.Normals[1], Triangle.SplitKeys[1]);
|
||||
const int32 Idx2 = GetOrCreateVertex(
|
||||
Triangle.Positions[2], Triangle.Normals[2], Triangle.SplitKeys[2]);
|
||||
const int32 CapVotes = bStandaloneSkipMaterialGroups ? 0
|
||||
: static_cast<int32>(VertexClasses[Idx0])
|
||||
+ static_cast<int32>(VertexClasses[Idx1])
|
||||
+ static_cast<int32>(VertexClasses[Idx2]);
|
||||
TArray<int32>& Dst = (CapVotes >= 2) ? CapTris : GroundTris;
|
||||
Dst.Add(Idx0);
|
||||
Dst.Add(Idx2);
|
||||
Dst.Add(Idx1);
|
||||
}
|
||||
|
||||
if (bProfileMesher)
|
||||
{
|
||||
VoxelDensityProfile::AddMeasurement(
|
||||
|
||||
@@ -41,6 +41,15 @@ struct FVoxelSharedDensityGrid
|
||||
}
|
||||
};
|
||||
|
||||
// Stable plan data copied onto one worker-owned mesher for hybrid LOD0 generation.
|
||||
// Donnees stables du plan copiees dans un mailleur de worker pour le mode hybride LOD0.
|
||||
struct FVoxelHybridSharpFeatureBox
|
||||
{
|
||||
uint64 StableId = 0;
|
||||
int64 MinimumSubvoxels[3] = {};
|
||||
int64 MaximumSubvoxels[3] = {};
|
||||
};
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class VOXELFORGE_API UVoxelMarchingCubesMesher : public UObject
|
||||
{
|
||||
@@ -111,6 +120,14 @@ public:
|
||||
// set this pointer and continue through Generator.
|
||||
void SetStandaloneShapeWorld(const FStandaloneShapeWorld* InWorld) { ShapeWorldContext = InWorld; }
|
||||
|
||||
// The caller owns this mesher instance for one tile task; no shared mutable feature state.
|
||||
// L'appelant possede cette instance pour une tuile; aucune donnee partagee modifiable.
|
||||
void SetHybridSharpFeatures(TArray<FVoxelHybridSharpFeatureBox>&& InFeatures)
|
||||
{
|
||||
HybridSharpFeatures = MoveTemp(InFeatures);
|
||||
bUseHybridSharpMesher = true;
|
||||
}
|
||||
|
||||
private:
|
||||
FORCEINLINE bool ShouldAbortWork() const
|
||||
{
|
||||
@@ -120,6 +137,8 @@ private:
|
||||
const std::atomic<bool>* ShutdownFlag = nullptr;
|
||||
const FVoxelSharedDensityGrid* SharedDensityGrid = nullptr;
|
||||
const FStandaloneShapeWorld* ShapeWorldContext = nullptr;
|
||||
TArray<FVoxelHybridSharpFeatureBox> HybridSharpFeatures;
|
||||
bool bUseHybridSharpMesher = false;
|
||||
float GeometryVoxelSizeCm = VOXEL_SIZE;
|
||||
|
||||
public:
|
||||
|
||||
Reference in new issue
Block a user