Ok, my way to deal with this problem: I created auxiliary class in Unreal (called Externals), which gave me Externals.cpp and Externals.h files. From Externals.h you can erase everything, but my Externals.cpp looks like this (it’s from UE4.13 version):
#include "MyProject.h"
#include "Editor/UnrealEd/Public/ScopedTransaction.h"
#include "UnrealEd.h"
#include "StaticMeshResources.h"
#include "Factories.h"
#include "TextureLayout.h"
#include "BSPOps.h"
#include "Developer/RawMesh/Public/RawMesh.h"
#include "MeshUtilities.h"
#include "Engine/Polys.h"
#include "PhysicsEngine/BodySetup.h"
#include "Editor/UnrealEd/Public/AssetSelection.h"
#include "Editor/UnrealEd/Public/SnappingUtils.h"
#include "Editor/UnrealEd/Private/Editor/ActorPositioning.h"
#include "Runtime/Engine/Public/LevelUtils.h"
#include "Runtime/Slate/Public/Widgets/Notifications/SNotificationList.h"
#include "Runtime/Slate/Public/Framework/Notifications/NotificationManager.h"
#include "Externals.h"
UStaticMesh* CreateStaticMeshFromBrush(UObject* Outer, FName Name, ABrush* Brush, UModel* Model)
{
GWarn->BeginSlowTask(NSLOCTEXT("UnrealEd", "CreatingStaticMeshE", "Creating static mesh..."), true);
FRawMesh RawMesh;
TArray<UMaterialInterface*> Materials;
GetBrushMesh(Brush, Model, RawMesh, Materials);
UStaticMesh* StaticMesh = CreateStaticMesh(RawMesh, Materials, Outer, Name);
GWarn->EndSlowTask();
return StaticMesh;
}
UStaticMesh* CreateStaticMesh(struct FRawMesh& RawMesh, TArray<UMaterialInterface*>& 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
FStaticMeshSourceModel* SrcModel = new(StaticMesh->SourceModels) FStaticMeshSourceModel();
SrcModel->RawMeshBulkData->SaveRawMesh(RawMesh);
StaticMesh->Materials = Materials;
int32 NumSections = StaticMesh->Materials.Num();
// Set up the SectionInfoMap to enable collision
for (int32 SectionIdx = 0; SectionIdx < NumSections; ++SectionIdx)
{
FMeshSectionInfo Info = StaticMesh->SectionInfoMap.Get(0, SectionIdx);
Info.MaterialIndex = SectionIdx;
Info.bEnableCollision = true;
StaticMesh->SectionInfoMap.Set(0, SectionIdx, Info);
}
StaticMesh->Build();
StaticMesh->MarkPackageDirty();
return StaticMesh;
}
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,struct FRawMesh& OutMesh,TArray<UMaterialInterface*>& OutMaterials)
{
// 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);
int32 NumPolys = Model->Polys->Element.Num();
// For each polygon in the model...
TArray<FVector> TempPositions;
for( int32 PolygonIndex = 0; PolygonIndex < NumPolys; ++PolygonIndex )
{
FPoly& Polygon = Model->Polys->Element[PolygonIndex];
UMaterialInterface* Material = Polygon.Material;
// Find a material index for this polygon.
int32 MaterialIndex = OutMaterials.AddUnique(Material);
// 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++)
{
// Create a triangle for the vertex.
OutMesh.FaceMaterialIndices.Add(MaterialIndex);
// Generate different smoothing mask for each poly to give the mesh hard edges. Note: only 32 smoothing masks supported.
OutMesh.FaceSmoothingMasks.Add(1<<(PolygonIndex%32));
FVector Positions[3];
FVector2D UVs[3];
Positions[ReverseVertices ? 0 : 2] = ActorToWorld.TransformPosition(Polygon.Vertices[0]) - PostSub;
UVs[ReverseVertices ? 0 : 2].X = (Positions[ReverseVertices ? 0 : 2] - TextureBase) | TextureX;
UVs[ReverseVertices ? 0 : 2].Y = (Positions[ReverseVertices ? 0 : 2] - TextureBase) | TextureY;
Positions[1] = ActorToWorld.TransformPosition(Polygon.Vertices[VertexIndex - 1]) - PostSub;
UVs[1].X = (Positions[1] - TextureBase) | TextureX;
UVs[1].Y = (Positions[1] - TextureBase) | TextureY;
Positions[ReverseVertices ? 2 : 0] = ActorToWorld.TransformPosition(Polygon.Vertices[VertexIndex]) - PostSub;
UVs[ReverseVertices ? 2 : 0].X = (Positions[ReverseVertices ? 2 : 0] - TextureBase) | TextureX;
UVs[ReverseVertices ? 2 : 0].Y = (Positions[ReverseVertices ? 2 : 0] - TextureBase) | TextureY;
for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
{
TempPositions.Add(Positions[CornerIndex]);
OutMesh.WedgeTexCoords[0].Add(UVs[CornerIndex]);
}
}
}
// Merge vertices within a certain distance of each other.
for (int32 i = 0; i < TempPositions.Num(); ++i)
{
FVector Position = TempPositions[i];
int32 FinalIndex = INDEX_NONE;
for (int32 VertexIndex = 0; VertexIndex < OutMesh.VertexPositions.Num(); ++VertexIndex)
{
if (FVerticesEqual(Position, OutMesh.VertexPositions[VertexIndex]))
{
FinalIndex = VertexIndex;
break;
}
}
if (FinalIndex == INDEX_NONE)
{
FinalIndex = OutMesh.VertexPositions.Add(Position);
}
OutMesh.WedgeIndices.Add(FinalIndex);
}
}
static AActor* PrivateAddActor(UObject* Asset, UActorFactory* Factory, bool SelectActor = true, EObjectFlags ObjectFlags = RF_Transactional, const FName Name = NAME_None)
{
if (!Factory)
{
return nullptr;
}
AActor* Actor = NULL;
AActor* NewActorTemplate = Factory->GetDefaultActor(Asset);
if (!NewActorTemplate)
{
return nullptr;
}
UWorld* OldWorld = nullptr;
// The play world needs to be selected if it exists
if (GIsEditor && GEditor->PlayWorld && !GIsPlayInEditorWorld)
{
OldWorld = SetPlayInEditorWorld(GEditor->PlayWorld);
}
// For Brushes/Volumes, use the default brush as the template rather than the factory default actor
if (NewActorTemplate->IsA(ABrush::StaticClass()) && GWorld->GetDefaultBrush() != nullptr)
{
NewActorTemplate = GWorld->GetDefaultBrush();
}
const FSnappedPositioningData PositioningData = FSnappedPositioningData(GCurrentLevelEditingViewportClient, GEditor->ClickLocation, GEditor->ClickPlane)
.UseFactory(Factory)
.UsePlacementExtent(NewActorTemplate->GetPlacementExtent());
FTransform ActorTransform = FActorPositioning::GetSnappedSurfaceAlignedTransform(PositioningData);
if (GetDefault<ULevelEditorViewportSettings>()->SnapToSurface.bEnabled)
{
// HACK: If we are aligning rotation to surfaces, we have to factor in the inverse of the actor transform so that the resulting transform after SpawnActor is correct.
if (auto* RootComponent = NewActorTemplate->GetRootComponent())
{
RootComponent->UpdateComponentToWorld();
}
ActorTransform = NewActorTemplate->GetTransform().Inverse() * ActorTransform;
}
// Do not fade snapping indicators over time if the viewport is not realtime
bool bClearImmediately = !GCurrentLevelEditingViewportClient || !GCurrentLevelEditingViewportClient->IsRealtime();
FSnappingUtils::ClearSnappingHelpers(bClearImmediately);
ULevel* DesiredLevel = GWorld->GetCurrentLevel();
// Don't spawn the actor if the current level is locked.
if (FLevelUtils::IsLevelLocked(DesiredLevel))
{
FNotificationInfo Info(NSLOCTEXT("UnrealEd", "Error_OperationDisallowedOnLockedLevel", "The requested operation could not be completed because the level is locked."));
Info.ExpireDuration = 3.0f;
FSlateNotificationManager::Get().AddNotification(Info);
}
else
{
FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "CreateActor", "Create Actor"), (ObjectFlags & RF_Transactional) != 0);
// Create the actor.
Actor = Factory->CreateActor(Asset, DesiredLevel, ActorTransform, ObjectFlags, Name);
if (Actor)
{
if (SelectActor)
{
GEditor->SelectNone(false, true);
GEditor->SelectActor(Actor, true, true);
}
Actor->InvalidateLightingCache();
Actor->PostEditChange();
}
GEditor->RedrawLevelEditingViewports();
}
if (Actor)
{
Actor->MarkPackageDirty();
ULevel::LevelDirtiedEvent.Broadcast();
}
// Restore the old world if there was one
if (OldWorld)
{
RestoreEditorWorld(OldWorld);
}
return Actor;
}
AActor* FActorFactoryAssetProxy::AddActorForAsset(UObject* AssetObj, bool SelectActor, EObjectFlags ObjectFlags, UActorFactory* FactoryToUse /*= NULL*/, const FName Name)
{
AActor* Result = NULL;
const FAssetData AssetData(AssetObj);
FText UnusedErrorMessage;
if (AssetObj != NULL)
{
// If a specific factory has been provided, verify its validity and then use it to create the actor
if (FactoryToUse)
{
if (FactoryToUse->CanCreateActorFrom(AssetData, UnusedErrorMessage))
{
Result = PrivateAddActor(AssetObj, FactoryToUse, SelectActor, ObjectFlags, Name);
}
}
// If no specific factory has been provided, find the highest priority one that is valid for the asset and use
// it to create the actor
else
{
const TArray<UActorFactory*>& ActorFactories = GEditor->ActorFactories;
for (int32 FactoryIdx = 0; FactoryIdx < ActorFactories.Num(); FactoryIdx++)
{
UActorFactory* ActorFactory = ActorFactories[FactoryIdx];
// Check if the actor can be created using this factory, making sure to check for an asset to be assigned from the selector
if (ActorFactory->CanCreateActorFrom(AssetData, UnusedErrorMessage))
{
Result = PrivateAddActor(AssetObj, ActorFactory, SelectActor, ObjectFlags, Name);
if (Result != NULL)
{
break;
}
}
}
}
}
return Result;
}
Basically I was copying all problematic functions and structures with unresolved externals to my file. Also, I had to include libraries they were using in MyProject.Build.cs:
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UnrealEd", "RawMesh", "RenderCore", "Slate", "SlateCore" });
This way I was able to copy whole body of ConvertBrushesToStaticMesh into my function and analyse it from this point. Everything works perfectly well.