Unresolved external symbols in CreateStaticMeshFromBrush

TL;DR - I get unresolved externals after creating this class, despite the fact I have included UnrealEd in library list:

#include "GameFramework/Actor.h"
#include "Editor/UnrealEd/Public/Editor.h"
#include "BrushAddition.generated.h"

UCLASS()
class MYPROJECT_API AMyActor : public AActor
{
	GENERATED_BODY()
	
public:	
	ABrushAddition(){
        UStaticMesh* NewMesh = CreateStaticMeshFromBrush(NULL,TEXT(""),NULL,NULL);
        //just dummy arguments, they should be ok for compiling
    }
	virtual void BeginPlay() override;
	virtual void Tick( float DeltaSeconds ) override;
};

and from MyProject.Build.cs:

    PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "RenderCore", "InputCore", "UnrealEd", "GeometryMode", "RawMesh" });

    PrivateDependencyModuleNames.AddRange(new string[] { "UnrealEd" });

Now the long version of my post:

At the beginning I’ll say that UE4 works perfectly well on my PC as long, as I use documented functions in C++. But there is a problem - when I dig somewhere deeper, I get sometimes in crucial moment “Unresolved external symbol” when calling a function. For example, I want to look into OnCreateStaticMesh function from BrushDetails.h - it’s core function inside is ConvertActors from EditorEngine.h, which uses DoConvertActors from the same header. Function DoConvertActors uses ConvertBrushesToStaticMesh (same header), which also works perfectly well when implemented properly - for example brushes there is created temporary static mesh file inside Temporary folder.

But after this step there are first difficulties.

Of course I had to link UnrealEd library in my build.cs file to use these functions. If I implement body of ConvertBrushesToStaticMesh function instead of simply calling the function, I get the information, that functions CreateStaticMeshFromBrush (from Editor.h, body inside StaticMeshEdit.cpp file) and FActorFactoryAssetProxy::AddActorForAsset, static function described in AssetSelection.h, with body included in AssetSelection.cpp, simply are unresolved externals - probably because I don’t have included libraries witch their compiled versions. On API webpage I have simple information, that those both functions are inside UnrealEd library.

If I could just copy bodies of those functions inside my files, that wouldn’t be a problem - but of course those functions use more unresolved externals, and probably this tree will grow up until I’ll copy every single definition from library. I don’t feel like being compiling program today, so I ask for help :slight_smile:

What should I do to get those functions included in my project? I use them in body of AMyActor class, so there is no way to change API to ENGINE_API (or is there?).

If the only way is to recompile Unreal Engine source code with few changes, I’m okay with that, only please tell me what exactly should I change here.

I kinda found a solution - I have created new simple class file and I was copying every unresolved function into it’s cpp file. It did not take me that long, however it needed some tricky linking. I’ll copy here this file once I’m done with my problem :slight_smile:

Still it’s more like patching the problem than finding a real solution. I also tried to compile little modified engine by myself (I added UNREALED_API before CreateStaticMeshFromBrush declaration), but… well, compiling time is awfully long and full of problems.

Hey mortmaire-

What are the exact error messages you receive when you get the unresolved external errors? Additionally, you mentioned including UnrealEd in your Build.cs file, did you also add include statements for the headers where the functions you’re trying to use are located?

Also, upon looking at OnCreateStaticMesh() in BrushDetails.h, this function is marked as private meaning that it cannot be called outside of the BrushDetails class. If you are using a source version of the engine, you can edit the class to make the function public instead which should allow you to call it from your code.

Hey Doug. Answering your question - I’ve got following error message:

Error 	LNK2019	unresolved external symbol "class UStaticMesh * __cdecl CreateStaticMeshFromBrush(class UObject *,class FName,class ABrush *,class UModel *)" (?CreateStaticMeshFromBrush@@YAPEAVUStaticMesh@@PEAVUObject@@VFName@@PEAVABrush@@PEAVUModel@@@Z) referenced in function "public: virtual void __cdecl AMyActor::PostEditChangeProperty(struct FPropertyChangedEvent &)" (?PostEditChangeProperty@AMyActor@@UEAAXAEAUFPropertyChangedEvent@@@Z)

Also, I have little trick for using private methods:

#define class struct
#define private public
#define protected public
#include "ProblematicHeader.h"
#undef class
#undef private
#undef protected

It’s from Obscure C++ Features - Made by Evan page

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.