x

Search in
Sort by:

Question Status:

Search help

  • Simple searches use one or more words. Separate the words with spaces (cat dog) to search cat,dog or both. Separate the words with plus signs (cat +dog) to search for items that may contain cat but must contain dog.
  • You can further refine your search on the search results page, where you can search by keywords, author, topic. These can be combined with each other. Examples
    • cat dog --matches anything with cat,dog or both
    • cat +dog --searches for cat +dog where dog is a mandatory term
    • cat -dog -- searches for cat excluding any result containing dog
    • [cats] —will restrict your search to results with topic named "cats"
    • [cats] [dogs] —will restrict your search to results with both topics, "cats", and "dogs"

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 :)

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?).

Product Version: UE 4.13
Tags:
more ▼

asked Nov 01 '16 at 01:24 PM in C++ Programming

avatar image

mortmaire
137 12 10 22

avatar image mortmaire Nov 01 '16 at 01:25 PM

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.

avatar image mortmaire Nov 02 '16 at 01:46 PM

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 :)

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.

avatar image Doug E ♦♦ STAFF Nov 02 '16 at 01:54 PM

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.

avatar image mortmaire Nov 02 '16 at 02:19 PM

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 http://madebyevan.com/obscure-cpp-features/ page

(comments are locked)
10|2000 characters needed characters left
Viewable by all users

1 answer: sort voted first

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.

more ▼

answered Nov 02 '16 at 02:26 PM

avatar image

mortmaire
137 12 10 22

(comments are locked)
10|2000 characters needed characters left
Viewable by all users
Your answer
toggle preview:

Up to 5 attachments (including images) can be used with a maximum of 5.2 MB each and 5.2 MB total.

Follow this question

Once you sign in you will be able to subscribe for any updates here

Answers to this question