#include "World/SurfaceNets.h" #include "World/DescentWorldDef.h" #include "Core/DescentMath.h" #include "Core/Tuning/DescentTune_Terrain.h" #include "Core/Tuning/DescentTune_World.h" namespace TTune = Descent::Tune::Terrain; namespace WTune = Descent::Tune::World; namespace { // The v2 mesher's edge and corner tables: corner c has offset (c & 1, (c >> 1) & 1, (c >> 2) & 1). constexpr int32 NetsEdges[12][2] = { {0,1},{2,3},{4,5},{6,7},{0,2},{1,3},{4,6},{5,7},{0,4},{1,5},{2,6},{3,7} }; constexpr int32 NetsCorner[8][3] = { {0,0,0},{1,0,0},{0,1,0},{1,1,0},{0,0,1},{1,0,1},{0,1,1},{1,1,1} }; /** The rock palette of section 2.4 as linear colours (the hex values are the v2 sRGB ones). */ struct FNetsPalette { FLinearColor TBase, TDark, TSilt, TRust, TBio, HBase, HDark, HCrust, SBase, SBand; FNetsPalette() : TBase(FLinearColor::FromSRGBColor(FColor::FromHex(TEXT("3a3d40")))) , TDark(FLinearColor::FromSRGBColor(FColor::FromHex(TEXT("222428")))) , TSilt(FLinearColor::FromSRGBColor(FColor::FromHex(TEXT("6b6252")))) , TRust(FLinearColor::FromSRGBColor(FColor::FromHex(TEXT("6a3e2a")))) , TBio(FLinearColor::FromSRGBColor(FColor::FromHex(TEXT("3c5a44")))) , HBase(FLinearColor::FromSRGBColor(FColor::FromHex(TEXT("5a4232")))) , HDark(FLinearColor::FromSRGBColor(FColor::FromHex(TEXT("2e231c")))) , HCrust(FLinearColor::FromSRGBColor(FColor::FromHex(TEXT("9a9282")))) , SBase(FLinearColor::FromSRGBColor(FColor::FromHex(TEXT("2a2432")))) , SBand(FLinearColor::FromSRGBColor(FColor::FromHex(TEXT("4a3f58")))) { } }; const FNetsPalette& NetsGetPalette() { static const FNetsPalette Palette; return Palette; } FLinearColor NetsLerp(const FLinearColor& A, const FLinearColor& B, double T) { const float F = static_cast(T); return FLinearColor(A.R + (B.R - A.R) * F, A.G + (B.G - A.G) * F, A.B + (B.B - A.B) * F, 1.f); } } namespace Descent::World { FColor FSurfaceNets::RockColor(const FDescentWorldDef& Def, const FVector& P, const FVector& N) { const FNetsPalette& K = NetsGetPalette(); const FDescentSDF& Sdf = Def.Sdf(); const double X = P.X, Y = P.Y, Z = P.Z; const double WH = Descent::Smooth(-330.0, -400.0, Z); const double WS = Descent::Smooth(-930.0, -1000.0, Z); const double Nn = Sdf.Noise3()(0.05 * X, 0.05 * Y, 0.08 * Z); const double M = Sdf.NoiseB()(0.018 * X, 0.018 * Y, 0.03 * Z); const double Up = Descent::Smooth(0.35, 0.85, N.Z); FLinearColor C = NetsLerp(K.TBase, K.TDark, Descent::Smooth(-0.2, 0.6, Nn)); C = NetsLerp(C, K.TRust, 0.6 * Descent::Smooth(0.45, 0.7, M)); C = NetsLerp(C, K.TSilt, 0.85 * Up * Descent::Smooth(-0.4, 0.3, Nn)); if (Z > -110.0) { C = NetsLerp(C, K.TBio, 0.6 * Up * Descent::Smooth(0.0, 0.6, M) * Descent::Smooth(-110.0, -40.0, Z)); } if (WH > 0.0) { FLinearColor H = NetsLerp(K.HBase, K.HDark, Descent::Smooth(-0.3, 0.5, Nn)); H = NetsLerp(H, K.HCrust, 0.5 * Descent::Smooth(0.5, 0.8, M) + 0.25 * Up); C = NetsLerp(C, H, WH); } if (WS > 0.0) { C = NetsLerp(C, NetsLerp(K.SBase, K.SBand, 0.5 + 0.5 * FMath::Sin(0.8 * Z + 3.0 * Nn)), WS); } return C.ToFColorSRGB(); } bool FSurfaceNets::Mesh(const FSurfaceNetsInput& In, FProcMeshSection& Out, int32& OutTriangles) { using namespace TTune; Out.Reset(); OutTriangles = 0; const float* D = In.Data; const uint8* CutA = In.Cut; const FDescentWorldDef& Def = *In.Def; const FVector OriginM = FVector(In.Key) * ChunkM; struct FVertex { FVector Pos; FVector Nrm; FColor Col; float OreType; float OreStrength; float Basalt; }; TArray Verts; Verts.Reserve(2048); TArray Idx; Idx.Reserve(8192); int32 SVert[SC3]; float Cv[8]; auto Grad = [&](int32 I, int32 J, int32 K, double* O) { const int32 B = I + J * P + K * P2; O[0] = D[B + 1] - D[B - 1]; O[1] = D[B + P] - D[B - P]; O[2] = D[B + P2] - D[B - P2]; }; for (int32 Z = 1; Z <= CoreN + 1; ++Z) { for (int32 Y = 1; Y <= CoreN + 1; ++Y) { for (int32 X = 1; X <= CoreN + 1; ++X) { const int32 Base = X + Y * P + Z * P2; int32 Mask = 0; for (int32 C = 0; C < 8; ++C) { const int32* O = NetsCorner[C]; const float V = D[Base + O[0] + O[1] * P + O[2] * P2]; Cv[C] = V; if (V > 0.f) { Mask |= 1 << C; } } const int32 Ci = (X - 1) + (Y - 1) * SC + (Z - 1) * SC * SC; if (Mask == 0 || Mask == 255) { SVert[Ci] = -1; continue; } double Px = 0, Py = 0, Pz = 0; int32 Cnt = 0; for (int32 E = 0; E < 12; ++E) { const int32 A = NetsEdges[E][0], B = NetsEdges[E][1]; const float Va = Cv[A], Vb = Cv[B]; if ((Va > 0.f) == (Vb > 0.f)) { continue; } const double T = Va / (Va - Vb); const int32* Oa = NetsCorner[A]; const int32* Ob = NetsCorner[B]; Px += Oa[0] + (Ob[0] - Oa[0]) * T; Py += Oa[1] + (Ob[1] - Oa[1]) * T; Pz += Oa[2] + (Ob[2] - Oa[2]) * T; ++Cnt; } Px /= Cnt; Py /= Cnt; Pz /= Cnt; // Trilinear blend of the central-difference gradients at the eight corners. double Gx = 0, Gy = 0, Gz = 0; for (int32 C = 0; C < 8; ++C) { const int32* O = NetsCorner[C]; double Gc[3]; Grad(X + O[0], Y + O[1], Z + O[2], Gc); const double W = (O[0] ? Px : 1 - Px) * (O[1] ? Py : 1 - Py) * (O[2] ? Pz : 1 - Pz); Gx += Gc[0] * W; Gy += Gc[1] * W; Gz += Gc[2] * W; } double Gl = FMath::Sqrt(Gx * Gx + Gy * Gy + Gz * Gz); if (Gl < 1e-12) { Gl = 1.0; } const FVector Nrm(-Gx / Gl, -Gy / Gl, -Gz / Gl); // Local metres from the chunk origin: sample index i sits at (i - Pad) * VS. const FVector Local((X + Px - Pad) * VoxelM, (Y + Py - Pad) * VoxelM, (Z + Pz - Pad) * VoxelM); const FVector World = OriginM + Local; uint8 Cut = 0; for (int32 C = 0; C < 8; ++C) { const int32* O = NetsCorner[C]; Cut = FMath::Max(Cut, CutA[Base + O[0] + O[1] * P + O[2] * P2]); } FVertex V; V.Pos = Local * Descent::UUPerMetre; V.Nrm = Nrm; V.Col = RockColor(Def, World, Nrm); V.Col.A = Cut; V.OreType = 0.f; V.OreStrength = 0.f; for (int32 Di = 0; Di < In.Deposits.Num(); ++Di) { const FDeposit& Dep = Def.Deposits[In.Deposits[Di]]; const double S = FDescentSDF::SegDist3(Dep.A, Dep.B, World.X, World.Y, World.Z); if (S < Dep.R) { if (!(Di < In.Exhausted.Num() && In.Exhausted[Di])) { V.OreType = static_cast(static_cast(Dep.Type) + 1); V.OreStrength = static_cast(FMath::Min(1.0, (1.0 - S / Dep.R) * 1.6)); } break; } } V.Basalt = static_cast(FMath::Clamp(FDescentWorldDef::ClaimDist(World) / WTune::BasaltRampM, 0.0, 1.0)); SVert[Ci] = Verts.Add(V); } } } auto Vi = [&](int32 X, int32 Y, int32 Z) { return SVert[(X - 1) + (Y - 1) * SC + (Z - 1) * SC * SC]; }; auto Quad = [&](int32 A, int32 B, int32 C, int32 E, bool bFlip) { if (A < 0 || B < 0 || C < 0 || E < 0) { return; } if (bFlip) { Idx.Add(A); Idx.Add(B); Idx.Add(C); Idx.Add(A); Idx.Add(C); Idx.Add(E); } else { Idx.Add(A); Idx.Add(C); Idx.Add(B); Idx.Add(A); Idx.Add(E); Idx.Add(C); } }; constexpr bool bFlipAll = FlipWinding; for (int32 Z = 2; Z <= CoreN + 1; ++Z) { for (int32 Y = 2; Y <= CoreN + 1; ++Y) { for (int32 X = 2; X <= CoreN + 1; ++X) { const bool S0 = D[X + Y * P + Z * P2] > 0.f; if ((D[X + 1 + Y * P + Z * P2] > 0.f) != S0) { Quad(Vi(X, Y - 1, Z - 1), Vi(X, Y, Z - 1), Vi(X, Y, Z), Vi(X, Y - 1, Z), S0 != bFlipAll); } if ((D[X + (Y + 1) * P + Z * P2] > 0.f) != S0) { Quad(Vi(X - 1, Y, Z - 1), Vi(X, Y, Z - 1), Vi(X, Y, Z), Vi(X - 1, Y, Z), (!S0) != bFlipAll); } if ((D[X + Y * P + (Z + 1) * P2] > 0.f) != S0) { Quad(Vi(X - 1, Y - 1, Z), Vi(X, Y - 1, Z), Vi(X, Y, Z), Vi(X - 1, Y, Z), S0 != bFlipAll); } } } } if (Idx.Num() == 0) { return false; } // Only the vertices that triangles use are kept (a vertex whose neighbours are all in the padding is not). TArray Remap; Remap.Init(-1, Verts.Num()); Out.ProcVertexBuffer.Reserve(Verts.Num()); FBox Box(ForceInit); for (uint32& Ix : Idx) { int32& R = Remap[Ix]; if (R < 0) { const FVertex& V = Verts[Ix]; FProcMeshVertex PV; PV.Position = V.Pos; PV.Normal = V.Nrm; const FVector Axis = FMath::Abs(V.Nrm.Z) < 0.9 ? FVector::UpVector : FVector::ForwardVector; PV.Tangent = FProcMeshTangent(FVector::CrossProduct(V.Nrm, Axis).GetSafeNormal(), false); PV.Color = V.Col; PV.UV0 = FVector2D(V.OreType, V.OreStrength); PV.UV1 = FVector2D(V.Basalt, 0.0); R = Out.ProcVertexBuffer.Add(PV); Box += V.Pos; } Ix = static_cast(R); } Out.ProcIndexBuffer = MoveTemp(Idx); Out.SectionLocalBox = Box; Out.bEnableCollision = false; Out.bSectionVisible = true; OutTriangles = Out.ProcIndexBuffer.Num() / 3; return true; } }