// Copyright Epic Games, Inc. All Rights Reserved. #include "WaterBodyMeshBuilder.h" #include "WaterBodyComponent.h" #include "WaterBodyStaticMeshComponent.h" #include "WaterBodyInfoMeshComponent.h" #if WITH_EDITOR #include "Materials/MaterialInstanceDynamic.h" #include "WaterUtils.h" #include "DynamicMesh/DynamicMeshAABBTree3.h" #include "Operations/MeshPlaneCut.h" #include "Engine/StaticMesh.h" #include "DynamicMesh/DynamicMesh3.h" #include "DynamicMeshToMeshDescription.h" #include "StaticMeshAttributes.h" #include "DynamicMesh/DynamicMesh3.h" #include "PrimitiveSceneProxy.h" #include "StaticMeshResources.h" #include "DynamicMeshBuilder.h" #include "MeshDescriptionBuilder.h" #include "DynamicMesh/DynamicMeshAttributeSet.h" #include "DynamicMesh/MeshNormals.h" #include "DynamicMesh/MeshTangents.h" #include "Algo/RemoveIf.h" #include "PhysicsEngine/BodySetup.h" #include "Engine/CollisionProfile.h" #include "UObject/Package.h" #include "StaticMeshCompiler.h" #include "Algo/AnyOf.h" #endif // WITH_EDITOR #if WITH_EDITOR void FWaterBodyMeshBuilder::BuildWaterInfoMeshes(UWaterBodyComponent* WaterBodyComponent, UWaterBodyInfoMeshComponent* WaterInfoMeshComponent, UWaterBodyInfoMeshComponent* WaterInfoDilatedMeshComponent, bool bMakeWaterInfoMeshConservativeRasterCompatible) const { TRACE_CPUPROFILER_EVENT_SCOPE(FWaterBodyMeshBuilder::BuildWaterInfoMesh); using namespace UE::Geometry; if (!ensureMsgf(WaterInfoMeshComponent != nullptr && WaterInfoDilatedMeshComponent != nullptr, TEXT("BuildWaterInfoMeshes called on a water body with null water info mesh component %s"), *WaterBodyComponent->GetOwner()->GetActorNameOrLabel())) { return; } FDynamicMesh3 WaterInfoMesh(EMeshComponents::None); FDynamicMesh3 WaterInfoDilatedMesh(EMeshComponents::None); GetDynamicMesh(WaterBodyComponent, WaterInfoMesh, &WaterInfoDilatedMesh, bMakeWaterInfoMeshConservativeRasterCompatible); UMaterialInterface* WaterInfoMID = WaterBodyComponent->GetWaterInfoMaterialInstance(); UObject* Outer = WaterBodyComponent->GetOwner(); auto BuildWaterInfoMesh = [this, Outer, WaterInfoMID](const FDynamicMesh3& DynamicMesh, UWaterBodyInfoMeshComponent* MeshComponent, FName BaseName, bool bIsConservativeRasterMesh) -> UStaticMesh* { if (DynamicMesh.TriangleCount() == 0) { return nullptr; } FName WaterInfoMeshName = MakeUniqueObjectName(Outer, UStaticMesh::StaticClass(), BaseName); UStaticMesh* StaticMesh = CreateUStaticMesh(Outer, WaterInfoMeshName); UpdateStaticMesh(StaticMesh, ConvertDynamicMeshToMeshDescription(DynamicMesh), bIsConservativeRasterMesh); StaticMesh->AddMaterial(WaterInfoMID); MeshComponent->SetStaticMesh(StaticMesh); MeshComponent->bIsConservativeRasterCompatible = bIsConservativeRasterMesh; return StaticMesh; }; TArray StaticMeshes = { BuildWaterInfoMesh(WaterInfoMesh, WaterInfoMeshComponent, TEXT("WaterInfoMesh"), bMakeWaterInfoMeshConservativeRasterCompatible), BuildWaterInfoMesh(WaterInfoDilatedMesh, WaterInfoDilatedMeshComponent, TEXT("WaterInfoDilatedMesh"), false /*bIsConservativeRasterMesh*/), }; UStaticMesh::BatchBuild(StaticMeshes); } FMeshDescription FWaterBodyMeshBuilder::BuildMeshDescription(const UWaterBodyComponent* WaterBodyComponent) const { using namespace UE::Geometry; FDynamicMesh3 WaterBodyMesh(EMeshComponents::None); GetDynamicMesh(WaterBodyComponent, WaterBodyMesh); return ConvertDynamicMeshToMeshDescription(WaterBodyMesh); } TArray> FWaterBodyMeshBuilder::BuildWaterBodyStaticMesh(UWaterBodyComponent* WaterBodyComponent, const FWaterBodyStaticMeshSettings& Settings, const TArray>& ReusableComponents) const { TRACE_CPUPROFILER_EVENT_SCOPE(FWaterBodyMeshBuilder::BuildWaterBodyStaticMesh); using namespace UE::Geometry; FDynamicMesh3 EditMesh(EMeshComponents::None); GetDynamicMesh(WaterBodyComponent, EditMesh); const FBox MeshBounds3d = FBox(EditMesh.GetBounds()); const FAxisAlignedBox2d MeshBounds2d(FVector2d(MeshBounds3d.Min), FVector2d(MeshBounds3d.Max)); const double WaterMeshSectionSize = Settings.SectionSize; const EWaterBodyType WaterBodyType = WaterBodyComponent->GetWaterBodyType(); const double ConstantSurfaceZ = WaterBodyComponent->GetConstantSurfaceZ(); /** Mesh descriptions for sections which cannot be simplified to quads */ TArray SectionMeshDescriptions; std::atomic NumSectionMeshDescriptions = {0}; /** Positions of sections which can be simplified as quads. */ TArray QuadSectionPositions; std::atomic NumQuadSectionPositions = {0}; TArray> StaticMeshComponents; // Skip trying to section the mesh if the mesh bounds are smaller than size of a section. if (Settings.bSectionWaterBodyStaticMesh && (MeshBounds2d.Area() > WaterMeshSectionSize * WaterMeshSectionSize)) { /** * Sectioning algorithm outline: * * 1. Cut source mesh along X and Y axes at intervals of the section size. This prepares the geometry to then be sorted into buckets for each cell * 2. Sort the mesh into an AABB tree to be able to quickly test if vertices belong within a section's cell. * 3. In parallel for each section within the bounds of the mesh: * a. Traverse the AABB tree to collect any triangles that are contained within or intersect with the bounds of that section. * b. Identify if any triangles within the section are boundary triangles (they lie on the edge of the mesh). * c. If this is the case, the geometry of the cell must be preserved since it defines the edge of our 2d polygon. * d. If this is not the case, a simple quad mesh can be substituted which completely fills the section. * e. A MeshDescription is then created for either the simplified quad or the triangles within the cell. * 4. After all the section meshes are created, create or reuse UWaterBodyStaticMeshComponents to render the individual meshes separately. */ // Round up when considering the regions to slice const uint32 NumberOfMeshSectionsX = FMath::DivideAndRoundUp(MeshBounds2d.Extents().X * 2., WaterMeshSectionSize); const uint32 NumberOfMeshSectionsY = FMath::DivideAndRoundUp(MeshBounds2d.Extents().Y * 2., WaterMeshSectionSize); { TRACE_CPUPROFILER_EVENT_SCOPE(BuildWaterBodyStaticMesh::CutMesh); for (uint32 SectionIndexX = 0; SectionIndexX < NumberOfMeshSectionsX; ++SectionIndexX) { const FVector3d SlicePos = FVector3d(MeshBounds2d.Min + SectionIndexX * FVector2d(WaterMeshSectionSize, 0.0), 0.0); const FVector3d SliceNormal(-1.0, 0.0, 0.0); FMeshPlaneCut Cut(&EditMesh, SlicePos, SliceNormal); ensure(Cut.CutWithoutDelete(false)); } for (uint32 SectionIndexY = 0; SectionIndexY < NumberOfMeshSectionsY; ++SectionIndexY) { const FVector3d SlicePos = FVector3d(MeshBounds2d.Min + SectionIndexY * FVector2d(0.0, WaterMeshSectionSize), 0.0); const FVector3d SliceNormal(0.0, -1.0, 0.0); FMeshPlaneCut Cut(&EditMesh, SlicePos, SliceNormal); ensure(Cut.CutWithoutDelete(false)); } } FDynamicMeshAABBTree3 AABBTree(&EditMesh, true); const FDynamicMeshColorOverlay* ColorOverlay = EditMesh.Attributes()->PrimaryColors(); const uint32 TotalNumberOfSections = NumberOfMeshSectionsX * NumberOfMeshSectionsY; SectionMeshDescriptions.SetNum(TotalNumberOfSections); QuadSectionPositions.SetNum(TotalNumberOfSections); ParallelFor(TotalNumberOfSections, [&](int32 SectionIndex) { TRACE_CPUPROFILER_EVENT_SCOPE(BuildWaterBodyStaticMesh::BuildMeshSection); const int32 SectionIndexX = SectionIndex % NumberOfMeshSectionsX; const int32 SectionIndexY = SectionIndex / NumberOfMeshSectionsX; const FVector2d SectionPos = MeshBounds2d.Min + FVector2d(SectionIndexX, SectionIndexY) * WaterMeshSectionSize; const FAxisAlignedBox2d GridRectXY(SectionPos, SectionPos + WaterMeshSectionSize); FDynamicMeshAABBTree3::FTreeTraversal Traversal; int SelectAllDepth = TNumericLimits::Max(); int CurrentDepth = -1; TArray Triangles; bool bHasBoundaryTriangle = false; { TRACE_CPUPROFILER_EVENT_SCOPE(BuildWaterBodyStaticMesh::Traversal); Traversal.NextBoxF = [&GridRectXY, &SelectAllDepth, &CurrentDepth](const FAxisAlignedBox3d& Box, int Depth) { CurrentDepth = Depth; if (Depth > SelectAllDepth) { // We are deeper than the depth whose AABB was first detected to be contained in the RectangleXY, // descend and collect all leaf triangles return true; } SelectAllDepth = TNumericLimits::Max(); const FAxisAlignedBox2d BoxXY(FVector2d(Box.Min.X, Box.Min.Y), FVector2d(Box.Max.X, Box.Max.Y)); if (GridRectXY.Intersects(BoxXY)) { if (GridRectXY.Contains(BoxXY)) { SelectAllDepth = Depth; } return true; } return false; }; Traversal.NextTriangleF = [&GridRectXY, &SelectAllDepth, &CurrentDepth, &Triangles, &EditMesh, &bHasBoundaryTriangle] (int TriangleID) { if (CurrentDepth >= SelectAllDepth) { // This TriangleID is entirely contained in the selection rectangle so we can skip intersection testing Triangles.Add(TriangleID); bHasBoundaryTriangle |= EditMesh.IsBoundaryTriangle(TriangleID); return; } FAxisAlignedBox2d ExpandedGridRect(GridRectXY); // Expand the rect by 1 unit to add a floating point error margin. ExpandedGridRect.Expand(1); // this triangle is either on the edge or intersecting with the edge but since we did the plane cuts we know it can't be crossing over into the next cell FIndex3i Triangle = EditMesh.GetTriangle(TriangleID); FVector3d VertexA; FVector3d VertexB; FVector3d VertexC; EditMesh.GetTriVertices(TriangleID, VertexA, VertexB, VertexC); if ((ExpandedGridRect.Contains(FVector2d(VertexA))) && (ExpandedGridRect.Contains(FVector2d(VertexB))) && (ExpandedGridRect.Contains(FVector2d(VertexC)))) { Triangles.Add(TriangleID); bHasBoundaryTriangle |= EditMesh.IsBoundaryTriangle(TriangleID); } }; AABBTree.DoTraversal(Traversal); } TArray VertexPositions; TArray VertexColors; TArray VertexNormals; TArray VertexTangents; TArray Indices; // Simplification can occur for lakes/rivers since they have flat geometry and no vertex color data. if (!bHasBoundaryTriangle && (WaterBodyType != EWaterBodyType::River) && Triangles.Num() > 0) { const uint32 QuadSectionIndex = NumQuadSectionPositions.fetch_add(1); QuadSectionPositions[QuadSectionIndex] = FVector(SectionPos.X, SectionPos.Y, 0); return; } TMap VertexMap; for (int32 TriangleID : Triangles) { FIndex3i Triangle = EditMesh.GetTriangle(TriangleID); TArray> TriIndices; for (int32 TriIndex = 0; TriIndex < 3; ++TriIndex) { // Vertex data for this index already exists if (uint32* VertexIndexPtr = VertexMap.Find(Triangle[TriIndex])) { TriIndices.Add(*VertexIndexPtr); } // Vertex data for this index doesn't exist. Add it now: else { const int32 VertexID = Triangle[TriIndex]; const FVector3d VertexPos = EditMesh.GetVertex(VertexID); // Only rivers have vertex colors for the velocity data. const FVector4f Color = WaterBodyType == EWaterBodyType::River ? ColorOverlay->GetElement(VertexID) : FVector4f(0); const uint32 NewIndex = VertexPositions.Add(VertexPos); VertexColors.Add(Color); VertexNormals.Add(FVector(0, 0, 1)); VertexTangents.Add(FVector(1, 0, 0)); VertexMap.Add(VertexID, NewIndex); TriIndices.Add(NewIndex); } } Indices.Append(TriIndices); } if (VertexPositions.Num() != 0) { const int32 NumVertices = VertexPositions.Num(); check(VertexNormals.Num() == NumVertices); check(VertexColors.Num() == NumVertices); const int32 NumTriangles = Indices.Num() / 3; FMeshDescription MeshDescription; FStaticMeshAttributes(MeshDescription).Register(); MeshDescription.Empty(); FMeshDescriptionBuilder Builder; Builder.SetMeshDescription(&MeshDescription); Builder.SuspendMeshDescriptionIndexing(); Builder.EnablePolyGroups(); Builder.SetNumUVLayers(1); Builder.ReserveNewUVs(NumVertices, 0); Builder.ReserveNewVertices(NumVertices); for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { const FVertexID VertexID = Builder.AppendVertex(VertexPositions[VertexIndex]); const FVertexInstanceID VertexInstance = Builder.AppendInstance(VertexID); Builder.SetInstanceColor(VertexInstance, VertexColors[VertexIndex]); Builder.SetInstanceNormal(VertexInstance, VertexNormals[VertexIndex]); Builder.SetInstanceTangentSpace(VertexInstance, VertexNormals[VertexIndex], VertexTangents[VertexIndex], 1); } FPolygonGroupID GroupID = Builder.AppendPolygonGroup(); for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex) { FVertexInstanceID Triangle[3]; for (int32 Vertex = 0; Vertex < 3; ++Vertex) { Triangle[Vertex] = Indices[TriangleIndex * 3 + Vertex]; } Builder.AppendTriangle(Triangle[0], Triangle[1], Triangle[2], GroupID); } Builder.ResumeMeshDescriptionIndexing(); const uint32 SectionMeshDescriptionIndex = NumSectionMeshDescriptions.fetch_add(1); SectionMeshDescriptions[SectionMeshDescriptionIndex] = MoveTemp(MeshDescription); } }); } else { SectionMeshDescriptions.EmplaceAt(0, ConvertDynamicMeshToMeshDescription(EditMesh)); ++NumSectionMeshDescriptions; } //SectionMeshDescriptions.SetNum(Algo::RemoveIf(SectionMeshDescriptions, [](const FMeshDescription& MeshDescription) { return MeshDescription.Vertices().Num() == 0; })); SectionMeshDescriptions.SetNum(NumSectionMeshDescriptions); QuadSectionPositions.SetNum(NumQuadSectionPositions); // Attempt to reuse any of the components passed in from the ReusableComponents list int32 NumComponentsRequired = SectionMeshDescriptions.Num() + QuadSectionPositions.Num(); StaticMeshComponents.Reserve(NumComponentsRequired); for (int32 Index = 0; Index < FMath::Min(NumComponentsRequired, ReusableComponents.Num()); ++Index) { StaticMeshComponents.Add(ReusableComponents[Index]); } // Create any needed new components AActor* ActorOwner = WaterBodyComponent->GetOwner(); for (int32 Index = StaticMeshComponents.Num(); Index < NumComponentsRequired; ++Index) { const FName Name = MakeUniqueObjectName(ActorOwner, UWaterBodyStaticMeshComponent::StaticClass()); UWaterBodyStaticMeshComponent* WaterBodyStaticMeshComponent = NewObject(ActorOwner, Name); WaterBodyStaticMeshComponent->RegisterComponent(); WaterBodyStaticMeshComponent->SetMobility(WaterBodyComponent->Mobility); WaterBodyStaticMeshComponent->AttachToComponent(WaterBodyComponent, FAttachmentTransformRules::SnapToTargetNotIncludingScale); StaticMeshComponents.Add(WaterBodyStaticMeshComponent); } check(StaticMeshComponents.Num() == NumComponentsRequired); TArray StaticMeshes; StaticMeshes.Reserve(NumComponentsRequired); UMaterialInterface* WaterStaticMeshMID = WaterBodyComponent->GetWaterStaticMeshMaterialInstance(); UObject* MeshComponentOuter = WaterBodyComponent->GetOwner(); int32 StaticMeshComponentIndex = 0; { TRACE_CPUPROFILER_EVENT_SCOPE(FWaterBodyMeshBuilder::SetupMeshComponents); for (int32 Index = 0; Index < SectionMeshDescriptions.Num(); ++Index) { UWaterBodyStaticMeshComponent* WaterBodyStaticMeshComponent = StaticMeshComponents[StaticMeshComponentIndex]; const FMeshDescription& MeshDescription = SectionMeshDescriptions[StaticMeshComponentIndex]; check(IsValid(WaterBodyStaticMeshComponent)); FName WaterBodyMeshName = MakeUniqueObjectName(MeshComponentOuter, UStaticMesh::StaticClass(), TEXT("WaterBodyMesh")); UStaticMesh* StaticMesh = CreateUStaticMesh(MeshComponentOuter, WaterBodyMeshName); StaticMesh->AddMaterial(WaterStaticMeshMID); UpdateStaticMesh(StaticMesh, MeshDescription, false /*bIsConservativeRasterMesh*/); WaterBodyStaticMeshComponent->SetStaticMesh(StaticMesh); // Reset any location/scale offsets which might have been set if this component is being reused. WaterBodyStaticMeshComponent->SetRelativeTransform(FTransform::Identity); StaticMeshes.Add(StaticMesh); ++StaticMeshComponentIndex; } } if (QuadSectionPositions.Num() > 0) { TRACE_CPUPROFILER_EVENT_SCOPE(FWaterBodyMeshBuilder::SetupQuadComponents); // Create a shared UStaticMesh for all the quad sections used by this water body FName WaterBodyMeshName = MakeUniqueObjectName(MeshComponentOuter, UStaticMesh::StaticClass(), TEXT("WaterBodyQuadMesh")); UStaticMesh* QuadStaticMesh = CreateUStaticMesh(MeshComponentOuter, WaterBodyMeshName); QuadStaticMesh->AddMaterial(WaterStaticMeshMID); FMeshDescription QuadMeshDescription; { TArray VertexPositions = { {0., 0., 0.}, {1., 0., 0}, {1., 1., 0.}, {0., 1., 0.} }; TArray Indices = { 0, 2, 1, 0, 3, 2 }; const int32 NumVertices = VertexPositions.Num(); const int32 NumTriangles = Indices.Num() / 3; FStaticMeshAttributes(QuadMeshDescription).Register(); QuadMeshDescription.Empty(); FMeshDescriptionBuilder Builder; Builder.SetMeshDescription(&QuadMeshDescription); Builder.SuspendMeshDescriptionIndexing(); Builder.EnablePolyGroups(); Builder.SetNumUVLayers(1); Builder.ReserveNewUVs(NumVertices, 0); Builder.ReserveNewVertices(NumVertices); for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { const FVertexID VertexID = Builder.AppendVertex(VertexPositions[VertexIndex]); const FVertexInstanceID VertexInstance = Builder.AppendInstance(VertexID); Builder.SetInstanceColor(VertexInstance, FVector4f(0.)); Builder.SetInstanceNormal(VertexInstance, FVector(0., 0., 1.)); Builder.SetInstanceTangentSpace(VertexInstance, FVector(0., 0., 1.0), FVector(1.0, .0, .0), 1); } FPolygonGroupID GroupID = Builder.AppendPolygonGroup(); for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex) { FVertexInstanceID Triangle[3]; for (int32 Vertex = 0; Vertex < 3; ++Vertex) { Triangle[Vertex] = Indices[TriangleIndex * 3 + Vertex]; } Builder.AppendTriangle(Triangle[0], Triangle[1], Triangle[2], GroupID); } Builder.ResumeMeshDescriptionIndexing(); } StaticMeshes.Add(QuadStaticMesh); UpdateStaticMesh(QuadStaticMesh, QuadMeshDescription, false /*bIsConservativeRasterMesh*/); for (const FVector& QuadMeshPosition : QuadSectionPositions) { UWaterBodyStaticMeshComponent* WaterBodyStaticMeshComponent = StaticMeshComponents[StaticMeshComponentIndex]; WaterBodyStaticMeshComponent->SetStaticMesh(QuadStaticMesh); WaterBodyStaticMeshComponent->SetRelativeLocation(QuadMeshPosition); WaterBodyStaticMeshComponent->SetRelativeScale3D(FVector(WaterMeshSectionSize, WaterMeshSectionSize, 1.0)); ++StaticMeshComponentIndex; } } check(SectionMeshDescriptions.Num() + QuadSectionPositions.Num() == StaticMeshComponents.Num()); UStaticMesh::BatchBuild(StaticMeshes); return StaticMeshComponents; } void FWaterBodyMeshBuilder::GetDynamicMesh(const UWaterBodyComponent* WaterBodyComponent, UE::Geometry::FDynamicMesh3& InMesh, UE::Geometry::FDynamicMesh3* InDilatedMesh, bool bMakeWaterInfoMeshConservativeRasterCompatible) const { using namespace UE::Geometry; for (FDynamicMesh3* Mesh : { &InMesh, InDilatedMesh }) { if (Mesh) { Mesh->EnableAttributes(); Mesh->Attributes()->EnablePrimaryColors(); Mesh->Attributes()->EnableTangents(); } } WaterBodyComponent->GenerateWaterBodyMesh(InMesh, InDilatedMesh); // Store positions of the previous and next vertex within each triangle in three UV channels. // This is needed for conservative rasterization of the mesh when building the GPU water quadtree. if (bMakeWaterInfoMeshConservativeRasterCompatible) { InMesh.Attributes()->SetNumUVLayers(3); FDynamicMeshUVOverlay* UVOverlay0 = InMesh.Attributes()->GetUVLayer(0); FDynamicMeshUVOverlay* UVOverlay1 = InMesh.Attributes()->GetUVLayer(1); FDynamicMeshUVOverlay* UVOverlay2 = InMesh.Attributes()->GetUVLayer(2); for (int TriangleID : InMesh.TriangleIndicesItr()) { FVector3d TriangleVertices[3]; InMesh.GetTriVertices(TriangleID, TriangleVertices[0], TriangleVertices[1], TriangleVertices[2]); FIndex3i UVTriangle0; FIndex3i UVTriangle1; FIndex3i UVTriangle2; for (int TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) { const FVector3f PrevVertex = FVector3f(TriangleVertices[(TriangleVertexIndex + 2) % 3]); const FVector3f NextVertex = FVector3f(TriangleVertices[(TriangleVertexIndex + 1) % 3]); UVTriangle0[TriangleVertexIndex] = UVOverlay0->AppendElement(FVector2f(PrevVertex.X, PrevVertex.Y)); UVTriangle1[TriangleVertexIndex] = UVOverlay1->AppendElement(FVector2f(PrevVertex.Z, NextVertex.X)); UVTriangle2[TriangleVertexIndex] = UVOverlay2->AppendElement(FVector2f(NextVertex.Y, NextVertex.Z)); } UVOverlay0->SetTriangle(TriangleID, UVTriangle0); UVOverlay1->SetTriangle(TriangleID, UVTriangle1); UVOverlay2->SetTriangle(TriangleID, UVTriangle2); } } for (FDynamicMesh3* Mesh : { &InMesh, InDilatedMesh }) { if (Mesh) { FDynamicMeshNormalOverlay* NormalOverlay = Mesh->Attributes()->PrimaryNormals(); FMeshNormals::InitializeOverlayToPerVertexNormals(NormalOverlay); FMeshTangentsf::ComputeDefaultOverlayTangents(*Mesh); } } } FMeshDescription FWaterBodyMeshBuilder::ConvertDynamicMeshToMeshDescription(const UE::Geometry::FDynamicMesh3& Mesh) const { FMeshDescription Result; FStaticMeshAttributes StaticMeshAttributes(Result); StaticMeshAttributes.Register(); FDynamicMeshToMeshDescription DynamicMeshToMeshDescription; DynamicMeshToMeshDescription.Convert(&Mesh, Result); return Result; } void FWaterBodyMeshBuilder::UpdateStaticMesh(UStaticMesh* WaterMesh, const FMeshDescription& MeshDescription, bool bIsConservativeRasterMesh) const { if (!WaterMesh->IsSourceModelValid(0)) { WaterMesh->AddSourceModel(); } FStaticMeshSourceModel& SrcModel = WaterMesh->GetSourceModel(0); SrcModel.BuildSettings.bRecomputeNormals = true; SrcModel.BuildSettings.bRecomputeTangents = true; SrcModel.BuildSettings.bRemoveDegenerates = false; SrcModel.BuildSettings.bUseHighPrecisionTangentBasis = false; SrcModel.BuildSettings.bUseFullPrecisionUVs = bIsConservativeRasterMesh; // CR mesh stores vertex positions in the UVs and needs full 32bit precision SrcModel.BuildSettings.bGenerateLightmapUVs = false; WaterMesh->CreateMeshDescription(0, MeshDescription); UStaticMesh::FCommitMeshDescriptionParams CommitParams; CommitParams.bMarkPackageDirty = true; CommitParams.bUseHashAsGuid = true; WaterMesh->CommitMeshDescription(0,CommitParams); WaterMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; } UStaticMesh* FWaterBodyMeshBuilder::CreateUStaticMesh(UObject* Outer, FName MeshName) const { UStaticMesh* StaticMesh = NewObject(Outer, MeshName, RF_TextExportTransient | RF_NonPIEDuplicateTransient); // Disable navigation StaticMesh->MarkAsNotHavingNavigationData(); // Disable ray tracing as we don't want water meshes to show up in LumenScene. StaticMesh->bSupportRayTracing = false; // Always call CreateBodySetup before attempting to modify it. The BodySetup is normally created when the mesh data is built, but can be created ahead of time to set default data StaticMesh->CreateBodySetup(); if (UBodySetup* BodySetup = StaticMesh->GetBodySetup()) { BodySetup->DefaultInstance.SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); BodySetup->CollisionTraceFlag = CTF_UseSimpleAsComplex; // We won't ever enable collisions (since collisions are handled by the dedicated water body collision components), ensure we don't even cook or load any collision data on this mesh: BodySetup->bNeverNeedsCookedCollisionData = true; BodySetup->bHasCookedCollisionData = false; } return StaticMesh; } #endif // WITH_EDITOR