EM_Task/UnrealEd/Private/StaticMeshEdit.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

405 lines
18 KiB
C++

// 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<FStaticMaterial>& Materials, UObject* InOuter, FName InName)
{
// Create the UStaticMesh object.
FStaticMeshComponentRecreateRenderStateContext RecreateRenderStateContext(FindObject<UStaticMesh>(InOuter, *InName.ToString()));
auto StaticMesh = NewObject<UStaticMesh>(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<FStaticMaterial>& OutMaterials)
{
TVertexAttributesRef<FVector> VertexPositions = MeshDescription.VertexAttributes().GetAttributesRef<FVector>(MeshAttribute::Vertex::Position);
TVertexInstanceAttributesRef<FVector> VertexInstanceNormals = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Normal);
TVertexInstanceAttributesRef<FVector> VertexInstanceTangents = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector>(MeshAttribute::VertexInstance::Tangent);
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = MeshDescription.VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
TVertexInstanceAttributesRef<FVector4> VertexInstanceColors = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector4>(MeshAttribute::VertexInstance::Color);
TVertexInstanceAttributesRef<FVector2D> VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().GetAttributesRef<FVector2D>(MeshAttribute::VertexInstance::TextureCoordinate);
TEdgeAttributesRef<bool> EdgeHardnesses = MeshDescription.EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
TEdgeAttributesRef<float> EdgeCreaseSharpnesses = MeshDescription.EdgeAttributes().GetAttributesRef<float>(MeshAttribute::Edge::CreaseSharpness);
TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = MeshDescription.PolygonGroupAttributes().GetAttributesRef<FName>(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<uint32, FEdgeID> 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<FVertexInstanceID> 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<FEdgeID> 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<UStaticMesh>(Outer, *Name.ToString()));
UStaticMesh* StaticMesh = NewObject<UStaticMesh>(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<FStaticMaterial> 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