#include "Vehicles/CarrierMeshBuilder.h" #include "DescentUnits.h" #include "TheDescent.h" #include "Materials/MaterialInterface.h" #include "ProceduralMeshComponent.h" namespace Descent::Carrier { namespace { const TCHAR* const GMatNames[] = { TEXT("Wall"), TEXT("Wall2"), TEXT("Floor"), TEXT("Ceiling"), TEXT("Hazard"), TEXT("Wood"), TEXT("Fabric"), TEXT("White"), TEXT("Rust"), TEXT("Olive"), TEXT("Hull"), TEXT("DarkMetal"), TEXT("RustMetal"), TEXT("Shaft"), TEXT("LampWhite"), TEXT("LampCyan"), TEXT("LampAmber"), TEXT("LampRed"), TEXT("ScreenCyan"), TEXT("ScreenTeal"), TEXT("ScreenAmber"), TEXT("Core"), TEXT("Furnace"), TEXT("NavRed"), TEXT("NavGreen"), TEXT("NavWhite"), TEXT("WindowGlow"), TEXT("PoolWater") }; static_assert(UE_ARRAY_COUNT(GMatNames) == static_cast(EMat::Count), "material names and enum out of step"); FVector2D UVOf(const FVector& P, const FVector& N) { const FVector A(FMath::Abs(N.X), FMath::Abs(N.Y), FMath::Abs(N.Z)); if (A.Z >= A.X && A.Z >= A.Y) { return FVector2D(P.X, P.Y); } if (A.X >= A.Y) { return FVector2D(P.Y, P.Z); } return FVector2D(P.X, P.Z); } } const TCHAR* MatName(EMat M) { return GMatNames[static_cast(M)]; } UMaterialInterface* LoadCarrierMaterial(EMat M) { static TMap> Cache; static bool bWarned = false; TWeakObjectPtr& Slot = Cache.FindOrAdd(static_cast(M)); if (!Slot.IsValid()) { const FString Path = FString::Printf(TEXT("/Game/Carrier/Materials/MI_Carrier_%s.MI_Carrier_%s"), MatName(M), MatName(M)); UMaterialInterface* Mat = LoadObject(nullptr, *Path, nullptr, LOAD_NoWarn | LOAD_Quiet); if (!Mat) { if (!bWarned) { bWarned = true; UE_LOG(LogDescent, Error, TEXT("Carrier materials missing (%s): run Tools\\import_assets.ps1 (Tools/ue/assets_35_carrier_materials.py). Using the engine grid material."), *Path); } Mat = LoadObject(nullptr, TEXT("/Engine/EngineMaterials/WorldGridMaterial.WorldGridMaterial")); } Slot = Mat; } return Slot.Get(); } FSectionData& FMeshBuilder::Section(EMat M) { for (FSectionData& S : Sections) { if (S.Mat == M) { return S; } } FSectionData& S = Sections.AddDefaulted_GetRef(); S.Mat = M; return S; } void FMeshBuilder::Tri(EMat M, const FVector& A, const FVector& B, const FVector& C, const FVector& N) { FSectionData& S = Section(M); const int32 Base = S.V.Num(); FVector P1 = B, P2 = C; // Unreal's front face is the one where (p2 - p0) x (p1 - p0) points out; swap two vertices if our normal disagrees. if (((C - A) ^ (B - A)).Dot(N) < 0.0) { Swap(P1, P2); } const FVector Pts[3] = { A, P1, P2 }; for (const FVector& P : Pts) { S.V.Add(P); S.N.Add(N); S.UV.Add(UVOf(P, N)); } S.Tri.Add(Base); S.Tri.Add(Base + 1); S.Tri.Add(Base + 2); } void FMeshBuilder::Quad(EMat M, const FVector& A, const FVector& B, const FVector& C, const FVector& D, const FVector& N) { Tri(M, A, B, C, N); Tri(M, A, C, D, N); } void FMeshBuilder::Box(EMat M, const FVector& Centre, const FVector& Size, bool bCollide, const FQuat& Rot) { const FVector H = 0.5 * Size; FVector P[8]; for (int32 I = 0; I < 8; ++I) { P[I] = Centre + Rot.RotateVector(FVector((I & 1) ? H.X : -H.X, (I & 2) ? H.Y : -H.Y, (I & 4) ? H.Z : -H.Z)); } auto Face = [&](int32 A, int32 B, int32 C, int32 D, const FVector& LocalN) { Quad(M, P[A], P[B], P[C], P[D], Rot.RotateVector(LocalN)); }; Face(1, 3, 7, 5, FVector(1, 0, 0)); Face(0, 4, 6, 2, FVector(-1, 0, 0)); Face(2, 6, 7, 3, FVector(0, 1, 0)); Face(0, 1, 5, 4, FVector(0, -1, 0)); Face(4, 5, 7, 6, FVector(0, 0, 1)); Face(0, 2, 3, 1, FVector(0, 0, -1)); if (bCollide) { Convex.Add(TArray(P, 8)); } } void FMeshBuilder::ColBox(const FVector& Min, const FVector& Max) { TArray Pts; for (int32 I = 0; I < 8; ++I) { Pts.Add(FVector((I & 1) ? Max.X : Min.X, (I & 2) ? Max.Y : Min.Y, (I & 4) ? Max.Z : Min.Z)); } Convex.Add(MoveTemp(Pts)); } void FMeshBuilder::Cylinder(EMat M, const FVector& Centre, double Radius, double Length, int32 Segs, int32 Axis, bool bCollide, bool bCaps) { const FVector Ax = Axis == 0 ? FVector(1, 0, 0) : (Axis == 1 ? FVector(0, 1, 0) : FVector(0, 0, 1)); const FVector U = Axis == 0 ? FVector(0, 1, 0) : (Axis == 1 ? FVector(0, 0, 1) : FVector(1, 0, 0)); const FVector W = Ax ^ U; TArray Ring0, Ring1, Dirs; for (int32 I = 0; I < Segs; ++I) { const double A = 2.0 * UE_DOUBLE_PI * I / Segs; const FVector Dir = U * FMath::Cos(A) + W * FMath::Sin(A); Dirs.Add(Dir); Ring0.Add(Centre - Ax * (0.5 * Length) + Dir * Radius); Ring1.Add(Centre + Ax * (0.5 * Length) + Dir * Radius); } for (int32 I = 0; I < Segs; ++I) { const int32 J = (I + 1) % Segs; const FVector N = (Dirs[I] + Dirs[J]).GetSafeNormal(); Quad(M, Ring0[I], Ring0[J], Ring1[J], Ring1[I], N); } if (bCaps) { for (int32 I = 0; I < Segs; ++I) { const int32 J = (I + 1) % Segs; Tri(M, Centre - Ax * (0.5 * Length), Ring0[I], Ring0[J], -Ax); Tri(M, Centre + Ax * (0.5 * Length), Ring1[I], Ring1[J], Ax); } } if (bCollide) { // A 12-gon is plenty for collision. const int32 CSegs = FMath::Min(Segs, 12); TArray Pts; for (int32 I = 0; I < CSegs; ++I) { const double A = 2.0 * UE_DOUBLE_PI * I / CSegs; const FVector Dir = U * FMath::Cos(A) + W * FMath::Sin(A); Pts.Add(Centre - Ax * (0.5 * Length) + Dir * Radius); Pts.Add(Centre + Ax * (0.5 * Length) + Dir * Radius); } Convex.Add(MoveTemp(Pts)); } } void FMeshBuilder::ExtrudeY(EMat M, const TArray& Poly, double Y0, double Y1, bool bCollide) { const int32 N = Poly.Num(); if (N < 3) { return; } FVector2D C(0, 0); for (const FVector2D& P : Poly) { C += P; } C /= N; for (int32 I = 0; I < N; ++I) { const FVector2D A = Poly[I], B = Poly[(I + 1) % N]; const FVector2D E = B - A; FVector2D Out(E.Y, -E.X); // a normal in the X-Z plane if (Out.Dot(0.5 * (A + B) - C) < 0.0) { Out = -Out; } const FVector Nrm = FVector(Out.X, 0, Out.Y).GetSafeNormal(); Quad(M, FVector(A.X, Y0, A.Y), FVector(B.X, Y0, B.Y), FVector(B.X, Y1, B.Y), FVector(A.X, Y1, A.Y), Nrm); Tri(M, FVector(C.X, Y0, C.Y), FVector(A.X, Y0, A.Y), FVector(B.X, Y0, B.Y), FVector(0, -1, 0)); Tri(M, FVector(C.X, Y1, C.Y), FVector(A.X, Y1, A.Y), FVector(B.X, Y1, B.Y), FVector(0, 1, 0)); } if (bCollide) { TArray Pts; for (const FVector2D& P : Poly) { Pts.Add(FVector(P.X, Y0, P.Y)); Pts.Add(FVector(P.X, Y1, P.Y)); } Convex.Add(MoveTemp(Pts)); } } void FMeshBuilder::TorusX(EMat M, const FVector& Centre, double R, double Tube, int32 SegsMajor, int32 SegsMinor) { auto Pt = [&](int32 I, int32 J, FVector& OutN) -> FVector { const double A = 2.0 * UE_DOUBLE_PI * I / SegsMajor, B = 2.0 * UE_DOUBLE_PI * J / SegsMinor; const FVector Radial(0, FMath::Cos(A), FMath::Sin(A)); OutN = Radial * FMath::Cos(B) + FVector(1, 0, 0) * FMath::Sin(B); return Centre + Radial * R + OutN * Tube; }; for (int32 I = 0; I < SegsMajor; ++I) { for (int32 J = 0; J < SegsMinor; ++J) { FVector N00, N10, N11, N01; const FVector P00 = Pt(I, J, N00), P10 = Pt(I + 1, J, N10), P11 = Pt(I + 1, J + 1, N11), P01 = Pt(I, J + 1, N01); Quad(M, P00, P10, P11, P01, (N00 + N10 + N11 + N01).GetSafeNormal()); } } } int32 FMeshBuilder::NumTriangles() const { int32 N = 0; for (const FSectionData& S : Sections) { N += S.Tri.Num() / 3; } return N; } void FMeshBuilder::Append(const FMeshBuilder& O) { for (const FSectionData& S : O.Sections) { FSectionData& D = Section(S.Mat); const int32 Base = D.V.Num(); D.V.Append(S.V); D.N.Append(S.N); D.UV.Append(S.UV); for (int32 T : S.Tri) { D.Tri.Add(Base + T); } } Convex.Append(O.Convex); } void FMeshBuilder::Commit(UProceduralMeshComponent* Comp, bool bCollision) const { Comp->ClearAllMeshSections(); Comp->bUseComplexAsSimpleCollision = false; const TArray NoTangents; const TArray NoColors; int32 Index = 0; for (const FSectionData& S : Sections) { TArray V, N; V.Reserve(S.V.Num()); N.Reserve(S.N.Num()); for (const FVector& P : S.V) { V.Add(Descent::MetresToUU(P)); } for (const FVector& Nr : S.N) { N.Add(Nr); } Comp->CreateMeshSection(Index, V, S.Tri, N, S.UV, NoColors, NoTangents, false); Comp->SetMaterial(Index, LoadCarrierMaterial(S.Mat)); ++Index; } if (bCollision && Convex.Num() > 0) { TArray> Hulls; Hulls.Reserve(Convex.Num()); for (const TArray& Pts : Convex) { TArray& H = Hulls.AddDefaulted_GetRef(); for (const FVector& P : Pts) { H.Add(Descent::MetresToUU(P)); } } Comp->SetCollisionConvexMeshes(Hulls); } } }