Skip to content

Commit 504096e

Browse files
authored
Merge pull request gameprogcpp#29 from chalonverse/master
Added Unreal engine exporter plugin
2 parents 73eb550 + c784409 commit 504096e

16 files changed

Lines changed: 918 additions & 0 deletions

Errata.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
Here are the known errors in the text of the book. If you notice any further errors,
33
please create an issue on this GitHub repository.
44

5+
* Chapter 2
6+
- Page 42: The loop over mActors is actually in Game::UnloadData, which is then called from Game::Shutdown
7+
(found by Kevin Runge)
58
* Chapter 3
69
- Page 67: The return type of Actor::GetForward should be Vector2 (found by Takashi Imagire)
710
- Page 70: When discussing the properties of the dot product, the text incorrectly states
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"FileVersion": 3,
3+
"FriendlyName": "Game Programming in C++ Mesh Exporter",
4+
"Version": 1,
5+
"VersionName": "1.0",
6+
"CreatedBy": "",
7+
"CreatedByURL": "",
8+
"Category": "Other",
9+
"Description": "Exports meshes, skeletons, and animations to Game Programming in C++ Formats",
10+
"EnabledByDefault": false,
11+
"Modules": [
12+
{
13+
"Name": "GPMeshExporter",
14+
"Type": "Developer",
15+
"LoadingPhase": "Default"
16+
}
17+
]
18+
}

Exporter/GPMeshExporter/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Game Programming in C++ Unreal Engine 4 Exporter
2+
This plugin allows you to export static meshes, skeletal meshes, skeletons, and animations
3+
from the Unreal Engine 4 format into the Game Programming in C++ format.
4+
5+
This plugin has been verified to work in Unreal Engine 4.22.3. It likely will work in versions
6+
as early as Unreal Engine 4.19.x, though it has not been tested.
7+
8+
This code is free to use, but the copyright of the code is maintained by Epic Games, and you
9+
must agree to the Unreal Engine license separately in order to use this plugin.
10+
11+
# Using the plugin
12+
To use this plugin, follow these steps:
13+
1. Make sure the Unreal 4 project you want to use is setup to work with C++
14+
2. In the C++ Unreal 4 project directory, create a "Plugins" directory
15+
3. Place the "GPMeshExporter" directory inside "Plugins"
16+
4. Right click on your .uproject and regenerate the project files
17+
5. Now in the editor, you can right click on a static mesh and select
18+
"Asset Actions>Export...". From the file dropdown, select
19+
.gpmesh. Similarly, you can export skeletal meshes to .gpmesh and
20+
animations to .gpanim.
21+
22+
##A Note on Mesh Orientation
23+
The plug-in does not fix-up or transform the asset in any way. You
24+
should make sure your asset is facing down the X-axis with Z-up
25+
before you export it.
26+
27+
##A Note on Textures
28+
Right now, the exporter will select the FIRST texture associated with
29+
the "BaseColor" of the FIRST material assigned to the mesh. Multiple
30+
materials/textures will be ignored.
31+
32+
Also, the exporter assumes that you will follow the convention where the texture
33+
file is in Assets/NameOfTexture. So when it exports, you'll get both
34+
an .gpmesh and a .bmp file in the same directory, place both files in the
35+
Assets directory of the Game Programming in C++ project.
36+
37+
## A Note on Shaders
38+
By default, static meshes will be exported and refer to the "BasicMesh"
39+
shader. If you want to change this, for now you have to just manually edit
40+
the .gpmesh file to change the shader. Similarly, SkeletalMeshes will
41+
export referring to the "Skinned" shader.
42+
43+
##A Note on Skeletal Meshes/Skeletons and Animations
44+
Skeletal Meshes will export their textures as static meshes do, and will
45+
additionally export a .gpskel file in the same directory as the skeletal
46+
mesh exports.
47+
48+
For animations, it is assumed that there is no scale applied -- bones
49+
only export their rotation and translation.
12.4 KB
Loading
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Some copyright should be here...
2+
3+
using UnrealBuildTool;
4+
5+
public class GPMeshExporter : ModuleRules
6+
{
7+
public GPMeshExporter(ReadOnlyTargetRules Target) : base(Target)
8+
{
9+
PrivatePCHHeaderFile = "Private/GPMeshExporterPrivatePCH.h";
10+
PublicDependencyModuleNames.AddRange(
11+
new string[]
12+
{
13+
"Core",
14+
"CoreUObject",
15+
"Engine",
16+
"RenderCore",
17+
"UnrealEd",
18+
"ImageWrapper",
19+
// ... add other public dependencies that you statically link with here ...
20+
}
21+
);
22+
23+
24+
PrivateDependencyModuleNames.AddRange(
25+
new string[]
26+
{
27+
"Slate", "SlateCore",
28+
// ... add private dependencies that you statically link with here ...
29+
}
30+
);
31+
32+
33+
DynamicallyLoadedModuleNames.AddRange(
34+
new string[]
35+
{
36+
// ... add any modules that your module loads dynamically here ...
37+
}
38+
);
39+
}
40+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2+
#include "GPMeshExporterPrivatePCH.h"
3+
#include "AnimExporterGP.h"
4+
#include "Animation/AnimSequence.h"
5+
6+
UAnimExporterGP::UAnimExporterGP(const FObjectInitializer& ObjectInitializer)
7+
: Super(ObjectInitializer)
8+
{
9+
SupportedClass = UAnimSequence::StaticClass();
10+
bText = true;
11+
PreferredFormatIndex = 0;
12+
FormatExtension.Add(TEXT("gpanim"));
13+
FormatDescription.Add(TEXT("GP Animation File"));
14+
}
15+
16+
bool UAnimExporterGP::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags /*= 0*/)
17+
{
18+
UAnimSequence* AnimSeq = CastChecked<UAnimSequence>(Object);
19+
20+
USkeleton* Skeleton = AnimSeq->GetSkeleton();
21+
const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton();
22+
USkeletalMesh* SkelMesh = Skeleton->GetPreviewMesh();
23+
if (AnimSeq->SequenceLength == 0.f)
24+
{
25+
// something is wrong
26+
return false;
27+
}
28+
29+
int32 NumFrames = AnimSeq->GetRawNumberOfFrames();
30+
const float FrameRate = NumFrames / AnimSeq->SequenceLength;
31+
32+
// Open another archive
33+
FArchive* File = IFileManager::Get().CreateFileWriter(*UExporter::CurrentFilename);
34+
35+
// Let's try the header...
36+
File->Logf(TEXT("{"));
37+
File->Logf(TEXT("\t\"version\":1,"));
38+
39+
File->Logf(TEXT("\t\"sequence\":{"));
40+
File->Logf(TEXT("\t\t\"frames\":%d,"), NumFrames);
41+
File->Logf(TEXT("\t\t\"length\":%f,"), AnimSeq->SequenceLength);
42+
File->Logf(TEXT("\t\t\"bonecount\":%d,"), RefSkeleton.GetNum());
43+
File->Logf(TEXT("\t\t\"tracks\":["));
44+
45+
bool firstOutput = false;
46+
47+
for (int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetNum(); ++BoneIndex)
48+
{
49+
//int32 BoneTreeIndex = Skeleton->GetSkeletonBoneIndexFromMeshBoneIndex(SkelMesh, BoneIndex);
50+
int32 BoneTrackIndex = Skeleton->GetAnimationTrackIndex(BoneIndex, AnimSeq, true);
51+
52+
if (BoneTrackIndex == INDEX_NONE && BoneIndex != 0)
53+
{
54+
// If this sequence does not have a track for the current bone, then skip it
55+
continue;
56+
}
57+
58+
if (firstOutput)
59+
{
60+
File->Logf(TEXT("\t\t\t},"));
61+
}
62+
63+
firstOutput = true;
64+
65+
File->Logf(TEXT("\t\t\t{"));
66+
File->Logf(TEXT("\t\t\t\t\"bone\":%d,"), BoneIndex);
67+
File->Logf(TEXT("\t\t\t\t\"transforms\":["));
68+
float AnimTime = 0.0f;
69+
float AnimEndTime = AnimSeq->SequenceLength;
70+
// Subtracts 1 because NumFrames includes an initial pose for 0.0 second
71+
double TimePerKey = (AnimSeq->SequenceLength / (NumFrames - 1));
72+
const float AnimTimeIncrement = TimePerKey;
73+
74+
bool bLastKey = false;
75+
// Step through each frame and add the bone's transformation data
76+
while (!bLastKey)
77+
{
78+
const TArray<FBoneNode>& BoneTree = Skeleton->GetBoneTree();
79+
80+
FTransform BoneAtom;
81+
if (BoneTrackIndex != INDEX_NONE)
82+
{
83+
AnimSeq->GetBoneTransform(BoneAtom, BoneTrackIndex, AnimTime, true);
84+
}
85+
else
86+
{
87+
BoneAtom.SetIdentity();
88+
}
89+
90+
bLastKey = AnimTime >= AnimEndTime;
91+
92+
File->Logf(TEXT("\t\t\t\t\t{"));
93+
94+
FQuat rot = BoneAtom.GetRotation();
95+
// For the root bone, we need to fix-up the rotation because Unreal exports
96+
// animations with Y-forward for some reason (maybe because Maya?)
97+
if (BoneIndex == 0)
98+
{
99+
FQuat addRot(FVector(0.0f, 0.0f, 1.0f), -1.57f);
100+
rot = addRot * rot;
101+
}
102+
File->Logf(TEXT("\t\t\t\t\t\t\"rot\":[%f,%f,%f,%f],"), rot.X, rot.Y, rot.Z, rot.W);
103+
FVector trans = BoneAtom.GetTranslation();
104+
105+
// Sanjay: If it's skeleton retargeting, change the translation to be from the ref pose skeleton
106+
if (BoneTree[BoneIndex].TranslationRetargetingMode == EBoneTranslationRetargetingMode::Skeleton)
107+
{
108+
const FTransform& BoneTransform = RefSkeleton.GetRefBonePose()[BoneIndex];
109+
trans = BoneTransform.GetTranslation();
110+
}
111+
112+
File->Logf(TEXT("\t\t\t\t\t\t\"trans\":[%f,%f,%f]"), trans.X, trans.Y, trans.Z);
113+
114+
if (!bLastKey)
115+
{
116+
File->Logf(TEXT("\t\t\t\t\t},"));
117+
}
118+
else
119+
{
120+
File->Logf(TEXT("\t\t\t\t\t}"));
121+
}
122+
123+
124+
AnimTime += AnimTimeIncrement;
125+
}
126+
127+
File->Logf(TEXT("\t\t\t\t]"), BoneIndex);
128+
}
129+
130+
File->Logf(TEXT("\t\t\t}"));
131+
File->Logf(TEXT("\t\t]"));
132+
File->Logf(TEXT("\t}"));
133+
134+
File->Logf(TEXT("}"));
135+
delete File;
136+
137+
return true;
138+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Some copyright should be here...
2+
3+
#include "GPMeshExporterPrivatePCH.h"
4+
5+
6+
7+
#define LOCTEXT_NAMESPACE "FGPMeshExporterModule"
8+
9+
void FGPMeshExporterModule::StartupModule()
10+
{
11+
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
12+
}
13+
14+
void FGPMeshExporterModule::ShutdownModule()
15+
{
16+
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
17+
// we call this function before unloading the module.
18+
}
19+
20+
#undef LOCTEXT_NAMESPACE
21+
22+
IMPLEMENT_MODULE(FGPMeshExporterModule, GPMeshExporter)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Some copyright should be here...
2+
#include "CoreUObject.h"
3+
#include "GPMeshExporter.h"
4+
#include "Exporters/Exporter.h"
5+
#include "UnrealEd.h"
6+
#include "StaticMeshResources.h"
7+
#include "Engine/StaticMesh.h"
8+
#include "ImageUtils.h"
9+
#include "EngineModule.h"
10+
#include "RendererInterface.h"
11+
12+
// You should place include statements to your module's private header files here. You only need to
13+
// add includes for headers that are used in most of your module's source files though.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#include "GPMeshExporterPrivatePCH.h"
2+
#include "GPTextureExporterBMP.h"
3+
#include "Logging/MessageLog.h"
4+
#include "Runtime/ImageWrapper/Public/BmpImageSupport.h"
5+
6+
/*------------------------------------------------------------------------------
7+
UTextureExporterBMP implementation.
8+
------------------------------------------------------------------------------*/
9+
UGPTextureExporterBMP::UGPTextureExporterBMP(const FObjectInitializer& ObjectInitializer)
10+
: Super(ObjectInitializer)
11+
{
12+
SupportedClass = UTexture2D::StaticClass();
13+
PreferredFormatIndex = 0;
14+
FormatExtension.Add(TEXT("BMP"));
15+
FormatDescription.Add(TEXT("Windows Bitmap"));
16+
17+
}
18+
19+
bool UGPTextureExporterBMP::ExportBinary(UObject* Object, const TCHAR* Type, FArchive& Ar, FFeedbackContext* Warn, int32 FileIndex, uint32 PortFlags)
20+
{
21+
UTexture2D* Texture = CastChecked<UTexture2D>(Object);
22+
23+
if (!Texture->Source.IsValid() || (Texture->Source.GetFormat() != TSF_BGRA8 && Texture->Source.GetFormat() != TSF_RGBA16))
24+
{
25+
return false;
26+
}
27+
28+
const bool bIsRGBA16 = Texture->Source.GetFormat() == TSF_RGBA16;
29+
const int32 SourceBytesPerPixel = bIsRGBA16 ? 8 : 4;
30+
31+
// if (bIsRGBA16)
32+
// {
33+
// FMessageLog ExportWarning("EditorErrors");
34+
// FFormatNamedArguments Arguments;
35+
// Arguments.Add(TEXT("Name"), FText::FromString(Texture->GetName()));
36+
// ExportWarning.Warning(FText::Format(FText("{Name}: Texture is RGBA16 and cannot be represented at such high bit depth in .bmp. Color will be scaled to RGBA8."), Arguments));
37+
// ExportWarning.Open(EMessageSeverity::Warning);
38+
// }
39+
40+
int32 SizeX = Texture->Source.GetSizeX();
41+
int32 SizeY = Texture->Source.GetSizeY();
42+
TArray<uint8> RawData;
43+
Texture->Source.GetMipData(RawData, 0);
44+
45+
FBitmapFileHeader bmf;
46+
FBitmapInfoHeader bmhdr;
47+
48+
// File header.
49+
bmf.bfType = 'B' + (256 * (int32)'M');
50+
bmf.bfReserved1 = 0;
51+
bmf.bfReserved2 = 0;
52+
int32 biSizeImage = SizeX * SizeY * 3;
53+
bmf.bfOffBits = sizeof(FBitmapFileHeader) + sizeof(FBitmapInfoHeader);
54+
bmhdr.biBitCount = 24;
55+
56+
bmf.bfSize = bmf.bfOffBits + biSizeImage;
57+
Ar << bmf;
58+
59+
// Info header.
60+
bmhdr.biSize = sizeof(FBitmapInfoHeader);
61+
bmhdr.biWidth = SizeX;
62+
bmhdr.biHeight = SizeY;
63+
bmhdr.biPlanes = 1;
64+
bmhdr.biCompression = BCBI_RGB;
65+
bmhdr.biSizeImage = biSizeImage;
66+
bmhdr.biXPelsPerMeter = 0;
67+
bmhdr.biYPelsPerMeter = 0;
68+
bmhdr.biClrUsed = 0;
69+
bmhdr.biClrImportant = 0;
70+
Ar << bmhdr;
71+
72+
73+
// Upside-down scanlines.
74+
for (int32 i = SizeY - 1; i >= 0; i--)
75+
{
76+
uint8* ScreenPtr = &RawData[i*SizeX*SourceBytesPerPixel];
77+
for (int32 j = SizeX; j>0; j--)
78+
{
79+
if (bIsRGBA16)
80+
{
81+
Ar << ScreenPtr[1];
82+
Ar << ScreenPtr[3];
83+
Ar << ScreenPtr[5];
84+
ScreenPtr += 8;
85+
}
86+
else
87+
{
88+
Ar << ScreenPtr[0];
89+
Ar << ScreenPtr[1];
90+
Ar << ScreenPtr[2];
91+
ScreenPtr += 4;
92+
}
93+
}
94+
}
95+
return true;
96+
}

0 commit comments

Comments
 (0)