// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= StaticMeshEdit.cpp: Static mesh edit functions. =============================================================================*/ #include "CoreMinimal.h" #include "Misc/CoreMisc.h" #include "Misc/FeedbackContext.h" #include "Engine/EngineTypes.h" #include "Model.h" #include "EditorFramework/AssetImportData.h" #include "EditorFramework/ThumbnailInfo.h" #include "Engine/MeshMerging.h" #include "Engine/StaticMesh.h" #include "StaticMeshAttributes.h" #include "Engine/StaticMeshSocket.h" #include "Engine/Polys.h" #include "Editor.h" #include "StaticMeshResources.h" #include "BSPOps.h" #include "PhysicsEngine/ConvexElem.h" #include "PhysicsEngine/BoxElem.h" #include "PhysicsEngine/SphereElem.h" #include "PhysicsEngine/BodySetup.h" #include "FbxImporter.h" #include "Misc/ScopedSlowTask.h" #include "Materials/MaterialInterface.h" #include "Materials/Material.h" #include "PerPlatformProperties.h" #include "Interfaces/ITargetPlatformManagerModule.h" #include "Interfaces/ITargetPlatform.h" #include "Settings/EditorExperimentalSettings.h" #include "Modules/ModuleManager.h" #include "IMeshReductionManagerModule.h" #include "IMeshReductionInterfaces.h" #include "UObject/MetaData.h" bool GBuildStaticMeshCollision = 1; DEFINE_LOG_CATEGORY_STATIC(LogStaticMeshEdit, Log, All); #define LOCTEXT_NAMESPACE "StaticMeshEdit" /** * Creates a static mesh object from raw triangle data. */ UStaticMesh* CreateStaticMesh(FMeshDescription& RawMesh, TArray& Materials, UObject* InOuter, FName InName) { // Create the UStaticMesh object. FStaticMeshComponentRecreateRenderStateContext RecreateRenderStateContext(FindObject(InOuter, *InName.ToString())); auto StaticMesh = NewObject(InOuter, InName, RF_Public | RF_Standalone); // Add one LOD for the base mesh StaticMesh->AddSourceModel(); FMeshDescription* MeshDescription = StaticMesh->CreateMeshDescription(0, RawMesh); StaticMesh->CommitMeshDescription(0); StaticMesh->SetStaticMaterials(Materials); int32 NumSections = StaticMesh->GetStaticMaterials().Num(); // Set up the SectionInfoMap to enable collision for (int32 SectionIdx = 0; SectionIdx < NumSections; ++SectionIdx) { FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(0, SectionIdx); Info.MaterialIndex = SectionIdx; Info.bEnableCollision = true; StaticMesh->GetSectionInfoMap().Set(0, SectionIdx, Info); StaticMesh->GetOriginalSectionInfoMap().Set(0, SectionIdx, Info); } // Set the Imported version before calling the build StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; StaticMesh->Build(); StaticMesh->MarkPackageDirty(); return StaticMesh; } /** *Constructor, setting all values to usable defaults */ FMergeStaticMeshParams::FMergeStaticMeshParams() : Offset(0.0f, 0.0f, 0.0f), Rotation(0, 0, 0), ScaleFactor(1.0f), ScaleFactor3D(1.0f, 1.0f, 1.0f), bDeferBuild(false), OverrideElement(INDEX_NONE), bUseUVChannelRemapping(false), bUseUVScaleBias(false) { // initialize some UV channel arrays for (int32 Channel = 0; Channel < UE_ARRAY_COUNT(UVChannelRemap); Channel++) { // we can't just map channel to channel by default, because we need to know when a UV channel is // actually being redirected in to, so that we can update Triangle.NumUVs UVChannelRemap[Channel] = INDEX_NONE; // default to a noop scale/bias UVScaleBias[Channel] = FVector4(1.0f, 1.0f, 0.0f, 0.0f); } } /** * Merges SourceMesh into DestMesh, applying transforms along the way * * @param DestMesh The static mesh that will have SourceMesh merged into * @param SourceMesh The static mesh to merge in to DestMesh * @param Params Settings for the merge */ void MergeStaticMesh(UStaticMesh* DestMesh, UStaticMesh* SourceMesh, const FMergeStaticMeshParams& Params) { // TODO_STATICMESH: Remove me. } // // FVerticesEqual // inline bool FVerticesEqual(FVector& V1, FVector& V2) { if (FMath::Abs(V1.X - V2.X) > THRESH_POINTS_ARE_SAME * 4.0f) { return 0; } if (FMath::Abs(V1.Y - V2.Y) > THRESH_POINTS_ARE_SAME * 4.0f) { return 0; } if (FMath::Abs(V1.Z - V2.Z) > THRESH_POINTS_ARE_SAME * 4.0f) { return 0; } return 1; } void GetBrushMesh(ABrush* Brush, UModel* Model, FMeshDescription& MeshDescription, TArray& OutMaterials) { TVertexAttributesRef VertexPositions = MeshDescription.VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); TVertexInstanceAttributesRef VertexInstanceNormals = MeshDescription.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); TVertexInstanceAttributesRef VertexInstanceTangents = MeshDescription.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); TVertexInstanceAttributesRef VertexInstanceBinormalSigns = MeshDescription.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); TVertexInstanceAttributesRef VertexInstanceColors = MeshDescription.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); TVertexInstanceAttributesRef VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); TEdgeAttributesRef EdgeHardnesses = MeshDescription.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::IsHard); TEdgeAttributesRef EdgeCreaseSharpnesses = MeshDescription.EdgeAttributes().GetAttributesRef(MeshAttribute::Edge::CreaseSharpness); TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = MeshDescription.PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); // Make sure we have one UVChannel VertexInstanceUVs.SetNumIndices(1); // Calculate the local to world transform for the source brush. FMatrix ActorToWorld = Brush ? Brush->ActorToWorld().ToMatrixWithScale() : FMatrix::Identity; bool ReverseVertices = 0; FVector4 PostSub = Brush ? FVector4(Brush->GetActorLocation()) : FVector4(0, 0, 0, 0); TMap RemapEdgeID; int32 NumPolys = Model->Polys->Element.Num(); // Create Fill the vertex position for (int32 PolygonIndex = 0; PolygonIndex < NumPolys; ++PolygonIndex) { FPoly& Polygon = Model->Polys->Element[PolygonIndex]; // Find a material index for this polygon. UMaterialInterface* Material = Polygon.Material; if (Material == nullptr) { Material = UMaterial::GetDefaultMaterial(MD_Surface); } int32 MaterialIndex = OutMaterials.AddUnique(FStaticMaterial(Material, Material->GetFName(), Material->GetFName())); FPolygonGroupID CurrentPolygonGroupID = FPolygonGroupID::Invalid; for (const FPolygonGroupID PolygonGroupID: MeshDescription.PolygonGroups().GetElementIDs()) { if (Material->GetFName() == PolygonGroupImportedMaterialSlotNames[PolygonGroupID]) { CurrentPolygonGroupID = PolygonGroupID; break; } } if (CurrentPolygonGroupID == FPolygonGroupID::Invalid) { CurrentPolygonGroupID = MeshDescription.CreatePolygonGroup(); PolygonGroupImportedMaterialSlotNames[CurrentPolygonGroupID] = Material->GetFName(); } // Cache the texture coordinate system for this polygon. FVector TextureBase = Polygon.Base - (Brush ? Brush->GetPivotOffset() : FVector::ZeroVector), TextureX = Polygon.TextureU / UModel::GetGlobalBSPTexelScale(), TextureY = Polygon.TextureV / UModel::GetGlobalBSPTexelScale(); // For each vertex after the first two vertices... for (int32 VertexIndex = 2; VertexIndex < Polygon.Vertices.Num(); VertexIndex++) { FVector Positions[3]; Positions[ReverseVertices ? 0 : 2] = ActorToWorld.TransformPosition(Polygon.Vertices[0]) - PostSub; Positions[1] = ActorToWorld.TransformPosition(Polygon.Vertices[VertexIndex - 1]) - PostSub; Positions[ReverseVertices ? 2 : 0] = ActorToWorld.TransformPosition(Polygon.Vertices[VertexIndex]) - PostSub; FVertexID VertexID[3] = {FVertexID::Invalid, FVertexID::Invalid, FVertexID::Invalid}; for (FVertexID IterVertexID: MeshDescription.Vertices().GetElementIDs()) { if (FVerticesEqual(Positions[0], VertexPositions[IterVertexID])) { VertexID[0] = IterVertexID; } if (FVerticesEqual(Positions[1], VertexPositions[IterVertexID])) { VertexID[1] = IterVertexID; } if (FVerticesEqual(Positions[2], VertexPositions[IterVertexID])) { VertexID[2] = IterVertexID; } } // Create the vertex instances TArray VertexInstanceIDs; VertexInstanceIDs.SetNum(3); for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) { if (VertexID[CornerIndex] == FVertexID::Invalid) { VertexID[CornerIndex] = MeshDescription.CreateVertex(); VertexPositions[VertexID[CornerIndex]] = Positions[CornerIndex]; } VertexInstanceIDs[CornerIndex] = MeshDescription.CreateVertexInstance(VertexID[CornerIndex]); VertexInstanceUVs.Set(VertexInstanceIDs[CornerIndex], 0, FVector2D((Positions[CornerIndex] - TextureBase) | TextureX, (Positions[CornerIndex] - TextureBase) | TextureY)); } // Create a polygon with the 3 vertex instances TArray NewEdgeIDs; const FPolygonID NewPolygonID = MeshDescription.CreatePolygon(CurrentPolygonGroupID, VertexInstanceIDs, &NewEdgeIDs); for (const FEdgeID& NewEdgeID: NewEdgeIDs) { // All edge are hard for BSP EdgeHardnesses[NewEdgeID] = true; } } } } // // CreateStaticMeshFromBrush - Creates a static mesh from the triangles in a model. // UStaticMesh* CreateStaticMeshFromBrush(UObject* Outer, FName Name, ABrush* Brush, UModel* Model) { FScopedSlowTask SlowTask(0.0f, NSLOCTEXT("UnrealEd", "CreatingStaticMeshE", "Creating static mesh...")); SlowTask.MakeDialog(); // Create the UStaticMesh object. FStaticMeshComponentRecreateRenderStateContext RecreateRenderStateContext(FindObject(Outer, *Name.ToString())); UStaticMesh* StaticMesh = NewObject(Outer, Name, RF_Public | RF_Standalone); // Add one LOD for the base mesh StaticMesh->AddSourceModel(); const int32 LodIndex = StaticMesh->GetNumSourceModels() - 1; FMeshDescription* MeshDescription = StaticMesh->CreateMeshDescription(LodIndex); // Fill out the mesh description and materials from the brush geometry TArray Materials; GetBrushMesh(Brush, Model, *MeshDescription, Materials); // Commit mesh description and materials list to static mesh StaticMesh->CommitMeshDescription(LodIndex); StaticMesh->SetStaticMaterials(Materials); // Set up the SectionInfoMap to enable collision const int32 NumSections = StaticMesh->GetStaticMaterials().Num(); for (int32 SectionIdx = 0; SectionIdx < NumSections; ++SectionIdx) { FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(0, SectionIdx); Info.MaterialIndex = SectionIdx; Info.bEnableCollision = true; StaticMesh->GetSectionInfoMap().Set(0, SectionIdx, Info); StaticMesh->GetOriginalSectionInfoMap().Set(0, SectionIdx, Info); } // Set the Imported version before calling the build StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; StaticMesh->Build(); StaticMesh->MarkPackageDirty(); return StaticMesh; } // Accepts a triangle (XYZ and UV values for each point) and returns a poly base and UV vectors // NOTE : the UV coords should be scaled by the texture size static inline void FTexCoordsToVectors(const FVector& V0, const FVector& UV0, const FVector& V1, const FVector& InUV1, const FVector& V2, const FVector& InUV2, FVector* InBaseResult, FVector* InUResult, FVector* InVResult) { // Create polygon normal. FVector PN = FVector((V0 - V1) ^ (V2 - V0)); PN = PN.GetSafeNormal(); FVector UV1(InUV1); FVector UV2(InUV2); // Fudge UV's to make sure no infinities creep into UV vector math, whenever we detect identical U or V's. if ((UV0.X == UV1.X) || (UV2.X == UV1.X) || (UV2.X == UV0.X) || (UV0.Y == UV1.Y) || (UV2.Y == UV1.Y) || (UV2.Y == UV0.Y)) { UV1 += FVector(0.004173f, 0.004123f, 0.0f); UV2 += FVector(0.003173f, 0.003123f, 0.0f); } // // Solve the equations to find our texture U/V vectors 'TU' and 'TV' by stacking them // into a 3x3 matrix , one for u(t) = TU dot (x(t)-x(o) + u(o) and one for v(t)= TV dot (.... , // then the third assumes we're perpendicular to the normal. // FMatrix TexEqu = FMatrix::Identity; TexEqu.SetAxis(0, FVector(V1.X - V0.X, V1.Y - V0.Y, V1.Z - V0.Z)); TexEqu.SetAxis(1, FVector(V2.X - V0.X, V2.Y - V0.Y, V2.Z - V0.Z)); TexEqu.SetAxis(2, FVector(PN.X, PN.Y, PN.Z)); TexEqu = TexEqu.InverseFast(); const FVector UResult(UV1.X - UV0.X, UV2.X - UV0.X, 0.0f); const FVector TUResult = TexEqu.TransformVector(UResult); const FVector VResult(UV1.Y - UV0.Y, UV2.Y - UV0.Y, 0.0f); const FVector TVResult = TexEqu.TransformVector(VResult); // // Adjust the BASE to account for U0 and V0 automatically, and force it into the same plane. // FMatrix BaseEqu = FMatrix::Identity; BaseEqu.SetAxis(0, TUResult); BaseEqu.SetAxis(1, TVResult); BaseEqu.SetAxis(2, FVector(PN.X, PN.Y, PN.Z)); BaseEqu = BaseEqu.InverseFast(); const FVector BResult = FVector(UV0.X - (TUResult | V0), UV0.Y - (TVResult | V0), 0.0f); *InBaseResult = -1.0f * BaseEqu.TransformVector(BResult); *InUResult = TUResult; *InVResult = TVResult; } /** * Creates a model from the triangles in a static mesh. */ void CreateModelFromStaticMesh(UModel* Model, AStaticMeshActor* StaticMeshActor) { #ifdef TODO_STATICMESH UStaticMesh* StaticMesh = StaticMeshActor->StaticMeshComponent->StaticMesh; FMatrix ActorToWorld = StaticMeshActor->ActorToWorld().ToMatrixWithScale(); Model->Polys->Element.Empty(); const FStaticMeshTriangle* RawTriangleData = (FStaticMeshTriangle*)StaticMesh->LODModels[0].RawTriangles.Lock(LOCK_READ_ONLY); if (StaticMesh->LODModels[0].RawTriangles.GetElementCount()) { for (int32 TriangleIndex = 0; TriangleIndex < StaticMesh->LODModels[0].RawTriangles.GetElementCount(); TriangleIndex++) { const FStaticMeshTriangle& Triangle = RawTriangleData[TriangleIndex]; FPoly* Polygon = new (Model->Polys->Element) FPoly; Polygon->Init(); Polygon->iLink = Polygon - Model->Polys->Element.GetData(); Polygon->Material = StaticMesh->LODModels[0].Elements[Triangle.MaterialIndex].Material; Polygon->PolyFlags = PF_DefaultFlags; Polygon->SmoothingMask = Triangle.SmoothingMask; new (Polygon->Vertices) FVector(ActorToWorld.TransformPosition(Triangle.Vertices[2])); new (Polygon->Vertices) FVector(ActorToWorld.TransformPosition(Triangle.Vertices[1])); new (Polygon->Vertices) FVector(ActorToWorld.TransformPosition(Triangle.Vertices[0])); Polygon->CalcNormal(1); Polygon->Finalize(NULL, 0); FTexCoordsToVectors(Polygon->Vertices[2], FVector(Triangle.UVs[0][0].X * UModel::GetGlobalBSPTexelScale(), Triangle.UVs[0][0].Y * UModel::GetGlobalBSPTexelScale(), 1), Polygon->Vertices[1], FVector(Triangle.UVs[1][0].X * UModel::GetGlobalBSPTexelScale(), Triangle.UVs[1][0].Y * UModel::GetGlobalBSPTexelScale(), 1), Polygon->Vertices[0], FVector(Triangle.UVs[2][0].X * UModel::GetGlobalBSPTexelScale(), Triangle.UVs[2][0].Y * UModel::GetGlobalBSPTexelScale(), 1), &Polygon->Base, &Polygon->TextureU, &Polygon->TextureV); } } StaticMesh->LODModels[0].RawTriangles.Unlock(); Model->Linked = 1; FBSPOps::bspValidateBrush(Model, 0, 1); Model->BuildBound(); #endif // #if TODO_STATICMESH } static void TransformPolys(UPolys* Polys, const FMatrix& Matrix) { for (int32 PolygonIndex = 0; PolygonIndex < Polys->Element.Num(); PolygonIndex++) { FPoly& Polygon = Polys->Element[PolygonIndex]; for (int32 VertexIndex = 0; VertexIndex < Polygon.Vertices.Num(); VertexIndex++) Polygon.Vertices[VertexIndex] = Matrix.TransformPosition(Polygon.Vertices[VertexIndex]); Polygon.Base = Matrix.TransformPosition(Polygon.Base); Polygon.TextureU = Matrix.TransformPosition(Polygon.TextureU); Polygon.TextureV = Matrix.TransformPosition(Polygon.TextureV); } } #undef LOCTEXT_NAMESPACE