소스 검색

Merge pull request #29 from chalonverse/master

Added Unreal engine exporter plugin
chalonverse 6 년 전
부모
커밋
504096ed69

+ 3 - 0
Errata.md

@@ -2,6 +2,9 @@
 Here are the known errors in the text of the book. If you notice any further errors,
 please create an issue on this GitHub repository.
 
+* Chapter 2
+  - Page 42: The loop over mActors is actually in Game::UnloadData, which is then called from Game::Shutdown
+    (found by Kevin Runge)
 * Chapter 3
   - Page 67: The return type of Actor::GetForward should be Vector2 (found by Takashi Imagire)
   - Page 70: When discussing the properties of the dot product, the text incorrectly states

+ 18 - 0
Exporter/GPMeshExporter/GPMeshExporter.uplugin

@@ -0,0 +1,18 @@
+{
+	"FileVersion": 3,
+	"FriendlyName": "Game Programming in C++ Mesh Exporter",
+	"Version": 1,
+	"VersionName": "1.0",
+	"CreatedBy": "",
+	"CreatedByURL": "",
+	"Category": "Other",
+	"Description": "Exports meshes, skeletons, and animations to Game Programming in C++ Formats",
+	"EnabledByDefault": false,
+	"Modules": [
+		{
+			"Name": "GPMeshExporter",
+			"Type": "Developer",
+			"LoadingPhase": "Default"
+		}
+	]
+}

+ 49 - 0
Exporter/GPMeshExporter/README.md

@@ -0,0 +1,49 @@
+# Game Programming in C++ Unreal Engine 4 Exporter
+This plugin allows you to export static meshes, skeletal meshes, skeletons, and animations
+from the Unreal Engine 4 format into the Game Programming in C++ format.
+
+This plugin has been verified to work in Unreal Engine 4.22.3. It likely will work in versions
+as early as Unreal Engine 4.19.x, though it has not been tested.
+
+This code is free to use, but the copyright of the code is maintained by Epic Games, and you
+must agree to the Unreal Engine license separately in order to use this plugin.
+
+# Using the plugin
+To use this plugin, follow these steps:
+1. Make sure the Unreal 4 project you want to use is setup to work with C++
+2. In the C++ Unreal 4 project directory, create a "Plugins" directory
+3. Place the "GPMeshExporter" directory inside "Plugins"
+4. Right click on your .uproject and regenerate the project files
+5. Now in the editor, you can right click on a static mesh and select
+   "Asset Actions>Export...". From the file dropdown, select
+   .gpmesh. Similarly, you can export skeletal meshes to .gpmesh and
+   animations to .gpanim.
+
+##A Note on Mesh Orientation
+The plug-in does not fix-up or transform the asset in any way. You
+should make sure your asset is facing down the X-axis with Z-up
+before you export it.
+
+##A Note on Textures
+Right now, the exporter will select the FIRST texture associated with
+the "BaseColor" of the FIRST material assigned to the mesh. Multiple
+materials/textures will be ignored.
+
+Also, the exporter assumes that you will follow the convention where the texture
+file is in Assets/NameOfTexture. So when it exports, you'll get both
+an .gpmesh and a .bmp file in the same directory, place both files in the
+Assets directory of the Game Programming in C++ project.
+
+## A Note on Shaders
+By default, static meshes will be exported and refer to the "BasicMesh"
+shader. If you want to change this, for now you have to just manually edit
+the .gpmesh file to change the shader. Similarly, SkeletalMeshes will
+export referring to the "Skinned" shader.
+
+##A Note on Skeletal Meshes/Skeletons and Animations
+Skeletal Meshes will export their textures as static meshes do, and will
+additionally export a .gpskel file in the same directory as the skeletal
+mesh exports.
+
+For animations, it is assumed that there is no scale applied -- bones
+only export their rotation and translation.

BIN
Exporter/GPMeshExporter/Resources/Icon128.png


+ 40 - 0
Exporter/GPMeshExporter/Source/GPMeshExporter/GPMeshExporter.Build.cs

@@ -0,0 +1,40 @@
+// Some copyright should be here...
+
+using UnrealBuildTool;
+
+public class GPMeshExporter : ModuleRules
+{
+	public GPMeshExporter(ReadOnlyTargetRules Target) : base(Target)
+    {
+        PrivatePCHHeaderFile = "Private/GPMeshExporterPrivatePCH.h";
+		PublicDependencyModuleNames.AddRange(
+			new string[]
+			{
+				"Core",
+				"CoreUObject",
+                "Engine",
+                "RenderCore",
+                "UnrealEd",
+                "ImageWrapper",
+				// ... add other public dependencies that you statically link with here ...
+			}
+			);
+			
+		
+		PrivateDependencyModuleNames.AddRange(
+			new string[]
+			{
+				"Slate", "SlateCore",
+				// ... add private dependencies that you statically link with here ...	
+			}
+			);
+		
+		
+		DynamicallyLoadedModuleNames.AddRange(
+			new string[]
+			{
+				// ... add any modules that your module loads dynamically here ...
+			}
+			);
+	}
+}

+ 138 - 0
Exporter/GPMeshExporter/Source/GPMeshExporter/Private/AnimExporterGP.cpp

@@ -0,0 +1,138 @@
+// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
+#include "GPMeshExporterPrivatePCH.h"
+#include "AnimExporterGP.h"
+#include "Animation/AnimSequence.h"
+
+UAnimExporterGP::UAnimExporterGP(const FObjectInitializer& ObjectInitializer)
+	: Super(ObjectInitializer)
+{
+	SupportedClass = UAnimSequence::StaticClass();
+	bText = true;
+	PreferredFormatIndex = 0;
+	FormatExtension.Add(TEXT("gpanim"));
+	FormatDescription.Add(TEXT("GP Animation File"));
+}
+
+bool UAnimExporterGP::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags /*= 0*/)
+{
+	UAnimSequence* AnimSeq = CastChecked<UAnimSequence>(Object);
+
+	USkeleton* Skeleton = AnimSeq->GetSkeleton();
+	const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton();
+	USkeletalMesh* SkelMesh = Skeleton->GetPreviewMesh();
+	if (AnimSeq->SequenceLength == 0.f)
+	{
+		// something is wrong
+		return false;
+	}
+
+	int32 NumFrames = AnimSeq->GetRawNumberOfFrames();
+	const float FrameRate = NumFrames / AnimSeq->SequenceLength;
+
+	// Open another archive
+	FArchive* File = IFileManager::Get().CreateFileWriter(*UExporter::CurrentFilename);
+
+	// Let's try the header...
+	File->Logf(TEXT("{"));
+	File->Logf(TEXT("\t\"version\":1,"));
+
+	File->Logf(TEXT("\t\"sequence\":{"));
+	File->Logf(TEXT("\t\t\"frames\":%d,"), NumFrames);
+	File->Logf(TEXT("\t\t\"length\":%f,"), AnimSeq->SequenceLength);
+	File->Logf(TEXT("\t\t\"bonecount\":%d,"), RefSkeleton.GetNum());
+	File->Logf(TEXT("\t\t\"tracks\":["));
+
+	bool firstOutput = false;
+
+	for (int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetNum(); ++BoneIndex)
+	{
+		//int32 BoneTreeIndex = Skeleton->GetSkeletonBoneIndexFromMeshBoneIndex(SkelMesh, BoneIndex);
+		int32 BoneTrackIndex = Skeleton->GetAnimationTrackIndex(BoneIndex, AnimSeq, true);
+		
+		if (BoneTrackIndex == INDEX_NONE && BoneIndex != 0)
+		{
+			// If this sequence does not have a track for the current bone, then skip it
+			continue;
+		}
+	
+		if (firstOutput)
+		{
+			File->Logf(TEXT("\t\t\t},"));
+		}
+
+		firstOutput = true;
+
+		File->Logf(TEXT("\t\t\t{"));
+		File->Logf(TEXT("\t\t\t\t\"bone\":%d,"), BoneIndex);
+		File->Logf(TEXT("\t\t\t\t\"transforms\":["));
+		float AnimTime = 0.0f;
+		float AnimEndTime = AnimSeq->SequenceLength;
+		// Subtracts 1 because NumFrames includes an initial pose for 0.0 second
+		double TimePerKey = (AnimSeq->SequenceLength / (NumFrames - 1));
+		const float AnimTimeIncrement = TimePerKey;
+
+		bool bLastKey = false;
+		// Step through each frame and add the bone's transformation data
+		while (!bLastKey)
+		{
+			const TArray<FBoneNode>& BoneTree = Skeleton->GetBoneTree();
+
+			FTransform BoneAtom;
+			if (BoneTrackIndex != INDEX_NONE)
+			{
+				AnimSeq->GetBoneTransform(BoneAtom, BoneTrackIndex, AnimTime, true);
+			}
+			else
+			{
+				BoneAtom.SetIdentity();
+			}
+
+			bLastKey = AnimTime >= AnimEndTime;
+
+			File->Logf(TEXT("\t\t\t\t\t{"));
+
+			FQuat rot = BoneAtom.GetRotation();
+			// For the root bone, we need to fix-up the rotation because Unreal exports
+			// animations with Y-forward for some reason (maybe because Maya?)
+			if (BoneIndex == 0)
+			{
+				FQuat addRot(FVector(0.0f, 0.0f, 1.0f), -1.57f);
+				rot = addRot * rot;
+			}
+			File->Logf(TEXT("\t\t\t\t\t\t\"rot\":[%f,%f,%f,%f],"), rot.X, rot.Y, rot.Z, rot.W);
+			FVector trans = BoneAtom.GetTranslation();
+
+			// Sanjay: If it's skeleton retargeting, change the translation to be from the ref pose skeleton
+			if (BoneTree[BoneIndex].TranslationRetargetingMode == EBoneTranslationRetargetingMode::Skeleton)
+			{
+				const FTransform& BoneTransform = RefSkeleton.GetRefBonePose()[BoneIndex];
+				trans = BoneTransform.GetTranslation();
+			}
+
+			File->Logf(TEXT("\t\t\t\t\t\t\"trans\":[%f,%f,%f]"), trans.X, trans.Y, trans.Z);
+
+			if (!bLastKey)
+			{
+				File->Logf(TEXT("\t\t\t\t\t},"));
+			}
+			else
+			{
+				File->Logf(TEXT("\t\t\t\t\t}"));
+			}
+			
+
+			AnimTime += AnimTimeIncrement;
+		}
+
+		File->Logf(TEXT("\t\t\t\t]"), BoneIndex);
+	}
+
+	File->Logf(TEXT("\t\t\t}"));
+	File->Logf(TEXT("\t\t]"));
+	File->Logf(TEXT("\t}"));
+
+ 	File->Logf(TEXT("}"));
+ 	delete File;
+
+	return true;
+}

+ 22 - 0
Exporter/GPMeshExporter/Source/GPMeshExporter/Private/GPMeshExporter.cpp

@@ -0,0 +1,22 @@
+// Some copyright should be here...
+
+#include "GPMeshExporterPrivatePCH.h"
+
+
+
+#define LOCTEXT_NAMESPACE "FGPMeshExporterModule"
+
+void FGPMeshExporterModule::StartupModule()
+{
+	// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
+}
+
+void FGPMeshExporterModule::ShutdownModule()
+{
+	// This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
+	// we call this function before unloading the module.
+}
+
+#undef LOCTEXT_NAMESPACE
+	
+IMPLEMENT_MODULE(FGPMeshExporterModule, GPMeshExporter)

+ 13 - 0
Exporter/GPMeshExporter/Source/GPMeshExporter/Private/GPMeshExporterPrivatePCH.h

@@ -0,0 +1,13 @@
+// Some copyright should be here...
+#include "CoreUObject.h"
+#include "GPMeshExporter.h"
+#include "Exporters/Exporter.h"
+#include "UnrealEd.h"
+#include "StaticMeshResources.h"
+#include "Engine/StaticMesh.h"
+#include "ImageUtils.h"
+#include "EngineModule.h"
+#include "RendererInterface.h"
+
+// You should place include statements to your module's private header files here.  You only need to
+// add includes for headers that are used in most of your module's source files though.

+ 96 - 0
Exporter/GPMeshExporter/Source/GPMeshExporter/Private/GPTextureExporterBMP.cpp

@@ -0,0 +1,96 @@
+#include "GPMeshExporterPrivatePCH.h"
+#include "GPTextureExporterBMP.h"
+#include "Logging/MessageLog.h"
+#include "Runtime/ImageWrapper/Public/BmpImageSupport.h"
+
+/*------------------------------------------------------------------------------
+UTextureExporterBMP implementation.
+------------------------------------------------------------------------------*/
+UGPTextureExporterBMP::UGPTextureExporterBMP(const FObjectInitializer& ObjectInitializer)
+	: Super(ObjectInitializer)
+{
+	SupportedClass = UTexture2D::StaticClass();
+	PreferredFormatIndex = 0;
+	FormatExtension.Add(TEXT("BMP"));
+	FormatDescription.Add(TEXT("Windows Bitmap"));
+
+}
+
+bool UGPTextureExporterBMP::ExportBinary(UObject* Object, const TCHAR* Type, FArchive& Ar, FFeedbackContext* Warn, int32 FileIndex, uint32 PortFlags)
+{
+	UTexture2D* Texture = CastChecked<UTexture2D>(Object);
+
+	if (!Texture->Source.IsValid() || (Texture->Source.GetFormat() != TSF_BGRA8 && Texture->Source.GetFormat() != TSF_RGBA16))
+	{
+		return false;
+	}
+
+	const bool bIsRGBA16 = Texture->Source.GetFormat() == TSF_RGBA16;
+	const int32 SourceBytesPerPixel = bIsRGBA16 ? 8 : 4;
+
+// 	if (bIsRGBA16)
+// 	{
+// 		FMessageLog ExportWarning("EditorErrors");
+// 		FFormatNamedArguments Arguments;
+// 		Arguments.Add(TEXT("Name"), FText::FromString(Texture->GetName()));
+// 		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));
+// 		ExportWarning.Open(EMessageSeverity::Warning);
+// 	}
+
+	int32 SizeX = Texture->Source.GetSizeX();
+	int32 SizeY = Texture->Source.GetSizeY();
+	TArray<uint8> RawData;
+	Texture->Source.GetMipData(RawData, 0);
+
+	FBitmapFileHeader bmf;
+	FBitmapInfoHeader bmhdr;
+
+	// File header.
+	bmf.bfType = 'B' + (256 * (int32)'M');
+	bmf.bfReserved1 = 0;
+	bmf.bfReserved2 = 0;
+	int32 biSizeImage = SizeX * SizeY * 3;
+	bmf.bfOffBits = sizeof(FBitmapFileHeader) + sizeof(FBitmapInfoHeader);
+	bmhdr.biBitCount = 24;
+
+	bmf.bfSize = bmf.bfOffBits + biSizeImage;
+	Ar << bmf;
+
+	// Info header.
+	bmhdr.biSize = sizeof(FBitmapInfoHeader);
+	bmhdr.biWidth = SizeX;
+	bmhdr.biHeight = SizeY;
+	bmhdr.biPlanes = 1;
+	bmhdr.biCompression = BCBI_RGB;
+	bmhdr.biSizeImage = biSizeImage;
+	bmhdr.biXPelsPerMeter = 0;
+	bmhdr.biYPelsPerMeter = 0;
+	bmhdr.biClrUsed = 0;
+	bmhdr.biClrImportant = 0;
+	Ar << bmhdr;
+
+
+	// Upside-down scanlines.
+	for (int32 i = SizeY - 1; i >= 0; i--)
+	{
+		uint8* ScreenPtr = &RawData[i*SizeX*SourceBytesPerPixel];
+		for (int32 j = SizeX; j>0; j--)
+		{
+			if (bIsRGBA16)
+			{
+				Ar << ScreenPtr[1];
+				Ar << ScreenPtr[3];
+				Ar << ScreenPtr[5];
+				ScreenPtr += 8;
+			}
+			else
+			{
+				Ar << ScreenPtr[0];
+				Ar << ScreenPtr[1];
+				Ar << ScreenPtr[2];
+				ScreenPtr += 4;
+			}
+		}
+	}
+	return true;
+}

+ 294 - 0
Exporter/GPMeshExporter/Source/GPMeshExporter/Private/SkeletalMeshExporterGP.cpp

@@ -0,0 +1,294 @@
+// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
+#include "GPMeshExporterPrivatePCH.h"
+#include "SkeletalMeshExporterGP.h"
+#include "GPTextureExporterBMP.h"
+#include "SkeletalMeshModel.h"
+
+
+USkeletalMeshExporterGP::USkeletalMeshExporterGP(const FObjectInitializer& ObjectInitializer)
+	: Super(ObjectInitializer)
+{
+	SupportedClass = USkeletalMesh::StaticClass();
+	bText = true;
+	PreferredFormatIndex = 0;
+	FormatExtension.Add(TEXT("gpmesh"));
+	FormatDescription.Add(TEXT("GP Mesh File"));
+
+	BMPExporter = CreateDefaultSubobject<UGPTextureExporterBMP>(TEXT("BMPExporter"));
+}
+
+bool USkeletalMeshExporterGP::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags /*= 0*/)
+{
+	USkeletalMesh* SkeletalMesh = CastChecked<USkeletalMesh>(Object);
+	FString currFileName = UExporter::CurrentFilename;
+	// Open another archive
+	FArchive* File = IFileManager::Get().CreateFileWriter(*currFileName);
+
+	// Let's try the header...
+	File->Logf(TEXT("{"));
+	File->Logf(TEXT("\t\"version\":1,"));
+	File->Logf(TEXT("\t\"vertexformat\":\"PosNormSkinTex\","));
+	File->Logf(TEXT("\t\"shader\":\"Skinned\","));
+
+	File->Logf(TEXT("\t\"textures\":["));
+	
+	// See if there are any textures
+	FString texturePath;
+	TArray<UTexture*> textures;
+	if (SkeletalMesh->Materials.Num() > 0 && SkeletalMesh->Materials[0].MaterialInterface &&
+		SkeletalMesh->Materials[0].MaterialInterface->GetTexturesInPropertyChain(MP_BaseColor, textures, nullptr, nullptr))
+	{
+		if (textures.Num() > 0)
+		{
+			int32 startIdx;
+			int32 endIdx;
+			if (currFileName.FindLastChar('/', startIdx) &&
+				currFileName.FindLastChar('.', endIdx))
+			{
+				FString textureName = currFileName.Mid(startIdx + 1, endIdx - startIdx - 1);
+				texturePath = "Assets/";
+				texturePath += textureName;
+				texturePath += ".bmp";
+
+				FString outputPath = currFileName.Mid(0, startIdx + 1);
+				outputPath += textureName;
+				outputPath += ".bmp";
+
+				UExporter::ExportToFile(textures[0], BMPExporter, *outputPath, false);
+			}
+		}
+	}
+
+	if (texturePath.IsEmpty())
+	{
+		File->Logf(TEXT("\t\t\"Assets/Default.png\""));
+	}
+	else
+	{
+		File->Logf(TEXT("\t\t\"%s\""), *texturePath);
+	}
+
+	File->Logf(TEXT("\t],"));
+
+	File->Logf(TEXT("\t\"specularPower\":100.0,"));
+
+	const FSkeletalMeshModel* SkelMeshResource = SkeletalMesh->GetImportedModel();
+	const FSkeletalMeshLODModel& SourceModel = SkelMeshResource->LODModels[0];
+	const int32 VertexCount = SourceModel.GetNumNonClothingVertices();
+	TArray<FSoftSkinVertex> Vertices;
+	GetVertices(SourceModel, Vertices);
+	//SourceModel.GetNonClothVertices(Vertices);
+
+	// Write the vertices
+	File->Logf(TEXT("\t\"vertices\":["));
+	for (int32 i = 0; i < VertexCount; i++)
+	{
+		FSoftSkinVertex& v = Vertices[i];
+		FVector Norm(v.TangentZ);
+		uint8* b = Vertices[i].InfluenceBones;
+		uint8* w = Vertices[i].InfluenceWeights;
+		if (i < VertexCount - 1)
+		{
+			File->Logf(TEXT("\t\t[%f,%f,%f,%f,%f,%f,%u,%u,%u,%u,%u,%u,%u,%u,%f,%f],"),
+				v.Position.X, v.Position.Y, v.Position.Z,
+				Norm.X, Norm.Y, Norm.Z,
+				b[0], b[1], b[2], b[3],
+				w[0], w[1], w[2], w[3],
+				v.UVs[0].X, v.UVs[0].Y);
+		}
+		else
+		{
+			File->Logf(TEXT("\t\t[%f,%f,%f,%f,%f,%f,%u,%u,%u,%u,%u,%u,%u,%u,%f,%f]"),
+				v.Position.X, v.Position.Y, v.Position.Z,
+				Norm.X, Norm.Y, Norm.Z,
+				b[0], b[1], b[2], b[3],
+				w[0], w[1], w[2], w[3],
+				v.UVs[0].X, v.UVs[0].Y);
+		}
+	}
+
+	File->Logf(TEXT("\t],"));
+
+ 
+ 	// Write the indices
+ 	File->Logf(TEXT("\t\"indices\":["));
+ 
+	uint32 NumIndices = SourceModel.IndexBuffer.Num();
+	uint32 CurrIdx = 0;
+ 
+ 	check(NumIndices % 3 == 0);
+
+	int32 SectionCount = SourceModel.Sections.Num();
+	int32 ClothSectionVertexRemoveOffset = 0;
+
+	for (int32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
+	{
+		const FSkelMeshSection& Section = SourceModel.Sections[SectionIndex];
+
+		// Static meshes contain one triangle list per element.
+		int32 TriangleCount = Section.NumTriangles;
+
+		// Copy over the index buffer into the FBX polygons set.
+		for (int32 TriangleIndex = 0; TriangleIndex < TriangleCount; ++TriangleIndex)
+		{
+			uint32 a = SourceModel.IndexBuffer[Section.BaseIndex + ((TriangleIndex * 3) + 0)] - ClothSectionVertexRemoveOffset;
+			uint32 b = SourceModel.IndexBuffer[Section.BaseIndex + ((TriangleIndex * 3) + 1)] - ClothSectionVertexRemoveOffset;
+			uint32 c = SourceModel.IndexBuffer[Section.BaseIndex + ((TriangleIndex * 3) + 2)] - ClothSectionVertexRemoveOffset;
+			// Is this the last one?
+			if (SectionIndex == (SectionCount - 1) && TriangleIndex == (TriangleCount - 1))
+			{
+				File->Logf(TEXT("\t\t[%u,%u,%u]"), a, b, c);
+			}
+			else
+			{
+				File->Logf(TEXT("\t\t[%u,%u,%u],"), a, b, c);
+			}
+		}
+	}
+
+	File->Logf(TEXT("\t]"));
+ 	File->Logf(TEXT("}"));
+ 	delete File;
+
+	// Now export the skeleton
+	return ExportSkeleton(SkeletalMesh->RefSkeleton, currFileName);
+}
+
+bool USkeletalMeshExporterGP::ExportSkeleton(const FReferenceSkeleton& RefSkeleton, const FString& currFileName)
+{
+	if (RefSkeleton.GetNum() == 0)
+	{
+		return false;
+	}
+
+	FString FileName;
+	int32 startIdx;
+	int32 endIdx;
+	if (currFileName.FindLastChar('/', startIdx) &&
+		currFileName.FindLastChar('.', endIdx))
+	{
+		FString skeletonName = currFileName.Mid(startIdx + 1, endIdx - startIdx - 1);
+
+		FileName = currFileName.Mid(0, startIdx + 1);
+		FileName += skeletonName;
+		FileName += ".gpskel";
+	}
+
+	// Open file for output
+	FArchive* File = IFileManager::Get().CreateFileWriter(*FileName);
+
+	File->Logf(TEXT("{"));
+	File->Logf(TEXT("\t\"version\":1,"));
+
+	File->Logf(TEXT("\t\"bonecount\":%d,"), RefSkeleton.GetNum());
+
+	File->Logf(TEXT("\t\"bones\":["));
+
+	for (int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetNum(); ++BoneIndex)
+	{
+		const FMeshBoneInfo& CurrentBone = RefSkeleton.GetRefBoneInfo()[BoneIndex];
+		const FTransform& BoneTransform = RefSkeleton.GetRefBonePose()[BoneIndex];
+		File->Logf(TEXT("\t\t{"));
+
+		File->Logf(TEXT("\t\t\t\"name\":\"%s\","), *CurrentBone.ExportName);
+
+		if (BoneIndex != 0)
+		{
+			File->Logf(TEXT("\t\t\t\"parent\":%d,"), CurrentBone.ParentIndex);
+		}
+		else
+		{
+			// Root node
+			File->Logf(TEXT("\t\t\t\"parent\":-1,"));
+		}
+
+		File->Logf(TEXT("\t\t\t\"bindpose\":{"));
+		// I'm assuming we don't need scale, because the FBX export doesn't seem to export scale, either...
+		FQuat rot = BoneTransform.GetRotation();
+		File->Logf(TEXT("\t\t\t\t\"rot\":[%f,%f,%f,%f],"), rot.X, rot.Y, rot.Z, rot.W);
+		FVector trans = BoneTransform.GetTranslation();
+		File->Logf(TEXT("\t\t\t\t\"trans\":[%f,%f,%f]"), trans.X, trans.Y, trans.Z);
+		File->Logf(TEXT("\t\t\t}"));
+
+		if (BoneIndex == RefSkeleton.GetNum() - 1)
+		{
+			File->Logf(TEXT("\t\t}"));
+		}
+		else
+		{
+			File->Logf(TEXT("\t\t},"));
+		}
+	}
+
+	File->Logf(TEXT("\t]"));
+
+	File->Logf(TEXT("}"));
+	delete File;
+
+	return true;
+}
+
+void USkeletalMeshExporterGP::GetVertices(const class FSkeletalMeshLODModel& Model, TArray<FSoftSkinVertex>& Vertices) const
+{
+	Vertices.Empty(Model.GetNumNonClothingVertices());
+	Vertices.AddUninitialized(Model.GetNumNonClothingVertices());
+
+	// Initialize the vertex data
+	// All chunks are combined into one (rigid first, soft next)
+	FSoftSkinVertex* DestVertex = (FSoftSkinVertex*)Vertices.GetData();
+	for (int32 SectionIndex = 0; SectionIndex < Model.Sections.Num(); SectionIndex++)
+	{
+		const FSkelMeshSection& Section = Model.Sections[SectionIndex];
+		//check(Chunk.NumRigidVertices == Chunk.RigidVertices.Num());
+		//check(Chunk.NumSoftVertices == Chunk.SoftVertices.Num());
+		// Sanjay:: I guess there aren't rigid vertices anymore?
+		//for (int32 VertexIndex = 0; VertexIndex < Chunk.RigidVertices.Num(); VertexIndex++)
+		//{
+		//	const FRigidSkinVertex& SourceVertex = Chunk.RigidVertices[VertexIndex];
+		//	DestVertex->Position = SourceVertex.Position;
+		//	DestVertex->TangentX = SourceVertex.TangentX;
+		//	DestVertex->TangentY = SourceVertex.TangentY;
+		//	DestVertex->TangentZ = SourceVertex.TangentZ;
+		//	// store the sign of the determinant in TangentZ.W
+		//	DestVertex->TangentZ.Vector.W = GetBasisDeterminantSignByte(SourceVertex.TangentX, SourceVertex.TangentY, SourceVertex.TangentZ);
+
+		//	// copy all texture coordinate sets
+		//	FMemory::Memcpy(DestVertex->UVs, SourceVertex.UVs, sizeof(FVector2D)*MAX_TEXCOORDS);
+
+		//	DestVertex->Color = SourceVertex.Color;
+		//	// Sanjay: Changed this to lookup in the bone map for the chunk
+		//	DestVertex->InfluenceBones[0] = Chunk.BoneMap[SourceVertex.Bone];
+		//	DestVertex->InfluenceWeights[0] = 255;
+		//	for (int32 InfluenceIndex = 1; InfluenceIndex < MAX_TOTAL_INFLUENCES; InfluenceIndex++)
+		//	{
+		//		DestVertex->InfluenceBones[InfluenceIndex] = 0;
+		//		DestVertex->InfluenceWeights[InfluenceIndex] = 0;
+		//	}
+		//	DestVertex++;
+		//}
+		//FMemory::Memcpy(DestVertex, Chunk.SoftVertices.GetData(), Chunk.SoftVertices.Num() * sizeof(FSoftSkinVertex));
+		//DestVertex += Chunk.SoftVertices.Num();
+		
+		// Sanjay: Manually copy the soft vertex data as well, so we can use the bone map
+		for (int32 VertexIndex = 0; VertexIndex < Section.SoftVertices.Num(); VertexIndex++)
+		{
+			const FSoftSkinVertex& SourceVertex = Section.SoftVertices[VertexIndex];
+			DestVertex->Position = SourceVertex.Position;
+			DestVertex->TangentX = SourceVertex.TangentX;
+			DestVertex->TangentY = SourceVertex.TangentY;
+			DestVertex->TangentZ = SourceVertex.TangentZ;
+
+			// copy all texture coordinate sets
+			FMemory::Memcpy(DestVertex->UVs, SourceVertex.UVs, sizeof(FVector2D)*MAX_TEXCOORDS);
+
+			DestVertex->Color = SourceVertex.Color;
+			// Sanjay: Changed this to lookup in the bone map for the chunk
+			for (int32 InfluenceIndex = 0; InfluenceIndex < MAX_TOTAL_INFLUENCES; InfluenceIndex++)
+			{
+				DestVertex->InfluenceBones[InfluenceIndex] = Section.BoneMap[SourceVertex.InfluenceBones[InfluenceIndex]];
+				DestVertex->InfluenceWeights[InfluenceIndex] = SourceVertex.InfluenceWeights[InfluenceIndex];
+			}
+			DestVertex++;
+		}
+	}
+}

+ 132 - 0
Exporter/GPMeshExporter/Source/GPMeshExporter/Private/StaticMeshExporterGP.cpp

@@ -0,0 +1,132 @@
+// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
+#include "GPMeshExporterPrivatePCH.h"
+#include "StaticMeshExporterGP.h"
+#include "GPTextureExporterBMP.h"
+
+
+UStaticMeshExporterGP::UStaticMeshExporterGP(const FObjectInitializer& ObjectInitializer)
+	: Super(ObjectInitializer)
+{
+	SupportedClass = UStaticMesh::StaticClass();
+	bText = true;
+	PreferredFormatIndex = 0;
+	FormatExtension.Add(TEXT("gpmesh"));
+	FormatDescription.Add(TEXT("GP Mesh File"));
+
+	BMPExporter = CreateDefaultSubobject<UGPTextureExporterBMP>(TEXT("BMPExporter"));
+}
+
+bool UStaticMeshExporterGP::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags /*= 0*/)
+{
+	UStaticMesh* StaticMesh = CastChecked<UStaticMesh>(Object);
+
+	// Open another archive
+	FArchive* File = IFileManager::Get().CreateFileWriter(*UExporter::CurrentFilename);
+
+	// Let's try the header...
+	File->Logf(TEXT("{"));
+	File->Logf(TEXT("\t\"version\":1,"));
+	File->Logf(TEXT("\t\"vertexformat\":\"PosNormTex\","));
+	File->Logf(TEXT("\t\"shader\":\"BasicMesh\","));
+
+	File->Logf(TEXT("\t\"textures\":["));
+	
+	// See if there are any textures
+	FString texturePath;
+	TArray<UTexture*> textures;
+	if (StaticMesh->GetMaterial(0) &&
+		StaticMesh->GetMaterial(0)->GetTexturesInPropertyChain(MP_BaseColor, textures, nullptr, nullptr))
+	{
+		if (textures.Num() > 0)
+		{
+			int32 startIdx;
+			int32 endIdx;
+			if (UExporter::CurrentFilename.FindLastChar('/', startIdx) &&
+				UExporter::CurrentFilename.FindLastChar('.', endIdx))
+			{
+				FString textureName = UExporter::CurrentFilename.Mid(startIdx + 1, endIdx - startIdx - 1);
+				texturePath = "Assets/";
+				texturePath += textureName;
+				texturePath += ".bmp";
+
+				FString outputPath = UExporter::CurrentFilename.Mid(0, startIdx + 1);
+				outputPath += textureName;
+				outputPath += ".bmp";
+
+				UExporter::ExportToFile(textures[0], BMPExporter, *outputPath, false);
+			}
+		}
+	}
+
+	if (texturePath.IsEmpty())
+	{
+		File->Logf(TEXT("\t\t\"Assets/Default.png\""));
+	}
+	else
+	{
+		File->Logf(TEXT("\t\t\"%s\""), *texturePath);
+	}
+
+	File->Logf(TEXT("\t],"));
+	File->Logf(TEXT("\t\"specularPower\":100.0,"));
+
+	// Currently, we only export LOD 0 of the static mesh. In the future, we could potentially export all available LODs
+	const FStaticMeshLODResources& RenderData = StaticMesh->GetLODForExport(0);
+	uint32 VertexCount = RenderData.GetNumVertices();
+
+	// Write the vertices
+	File->Logf(TEXT("\t\"vertices\":["));
+	for (uint32 i = 0; i < VertexCount; i++)
+	{
+		const FVector& Pos = RenderData.VertexBuffers.PositionVertexBuffer.VertexPosition(i);
+		const FVector& Normal = RenderData.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(i);
+		const FVector2D UV = RenderData.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(i, 0);
+
+		if (i < VertexCount - 1)
+		{
+			File->Logf(TEXT("\t\t[%f,%f,%f,%f,%f,%f,%f,%f],"),
+				Pos.X, Pos.Y, Pos.Z,
+				Normal.X, Normal.Y, Normal.Z,
+				UV.X, UV.Y);
+		}
+		else
+		{
+			File->Logf(TEXT("\t\t[%f,%f,%f,%f,%f,%f,%f,%f]"),
+				Pos.X, Pos.Y, Pos.Z,
+				Normal.X, Normal.Y, Normal.Z,
+				UV.X, UV.Y);
+		}
+	}
+
+	File->Logf(TEXT("\t],"));
+
+	// Write the indices
+	File->Logf(TEXT("\t\"indices\":["));
+
+	FIndexArrayView Indices = RenderData.IndexBuffer.GetArrayView();
+	uint32 NumIndices = Indices.Num();
+
+	check(NumIndices % 3 == 0);
+	for (uint32 i = 0; i < NumIndices / 3; i++)
+	{
+		// Wavefront indices are 1 based
+		uint32 a = Indices[3 * i + 0];
+		uint32 b = Indices[3 * i + 1];
+		uint32 c = Indices[3 * i + 2];
+
+		if (i < (NumIndices / 3 - 1))
+		{
+			File->Logf(TEXT("\t\t[%u,%u,%u],"), a, b, c);
+		}
+		else
+		{
+			File->Logf(TEXT("\t\t[%u,%u,%u]"), a, b, c);
+		}
+	}
+
+	File->Logf(TEXT("\t]"));
+	File->Logf(TEXT("}"));
+	delete File;
+
+	return true;
+}

+ 22 - 0
Exporter/GPMeshExporter/Source/GPMeshExporter/Public/AnimExporterGP.h

@@ -0,0 +1,22 @@
+// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
+
+//=============================================================================
+// StaticMeshExporterOBJ
+//=============================================================================
+
+#pragma once
+#include "Exporters/Exporter.h"
+#include "AnimExporterGP.generated.h"
+
+UCLASS()
+class UAnimExporterGP : public UExporter
+{
+	GENERATED_UCLASS_BODY()
+
+
+	// Begin UExporter Interface
+	virtual bool ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags = 0) override;
+};
+
+
+

+ 16 - 0
Exporter/GPMeshExporter/Source/GPMeshExporter/Public/GPMeshExporter.h

@@ -0,0 +1,16 @@
+// Some copyright should be here...
+
+#pragma once
+
+#include "ModuleManager.h"
+
+
+
+class FGPMeshExporterModule : public IModuleInterface
+{
+public:
+
+	/** IModuleInterface implementation */
+	virtual void StartupModule() override;
+	virtual void ShutdownModule() override;
+};

+ 21 - 0
Exporter/GPMeshExporter/Source/GPMeshExporter/Public/GPTextureExporterBMP.h

@@ -0,0 +1,21 @@
+// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
+
+//=============================================================================
+// TextureExporterBMP
+//=============================================================================
+
+#pragma once
+#include "Exporters/Exporter.h"
+#include "GPTextureExporterBMP.generated.h"
+
+UCLASS()
+class UGPTextureExporterBMP : public UExporter
+{
+	GENERATED_UCLASS_BODY()
+
+
+	bool ExportBinary(UObject* Object, const TCHAR* Type, FArchive& Ar, FFeedbackContext* Warn, int32 FileIndex = 0, uint32 PortFlags = 0) override;
+};
+
+
+

+ 29 - 0
Exporter/GPMeshExporter/Source/GPMeshExporter/Public/SkeletalMeshExporterGP.h

@@ -0,0 +1,29 @@
+// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
+
+//=============================================================================
+// StaticMeshExporterOBJ
+//=============================================================================
+
+#pragma once
+#include "Exporters/Exporter.h"
+#include "Rendering/SkeletalMeshLODModel.h"
+#include "SkeletalMeshExporterGP.generated.h"
+
+UCLASS()
+class USkeletalMeshExporterGP : public UExporter
+{
+	GENERATED_UCLASS_BODY()
+
+
+	// Begin UExporter Interface
+	virtual bool ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags = 0) override;
+	// End UExporter Interface
+	virtual bool ExportSkeleton(const struct FReferenceSkeleton& RefSkeleton, const FString& currFileName);
+
+	void GetVertices(const class FSkeletalMeshLODModel& Model, TArray<FSoftSkinVertex>& Vertices) const;
+
+	class UGPTextureExporterBMP* BMPExporter;
+};
+
+
+

+ 25 - 0
Exporter/GPMeshExporter/Source/GPMeshExporter/Public/StaticMeshExporterGP.h

@@ -0,0 +1,25 @@
+// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
+
+//=============================================================================
+// StaticMeshExporterOBJ
+//=============================================================================
+
+#pragma once
+#include "Exporters/Exporter.h"
+#include "StaticMeshExporterGP.generated.h"
+
+UCLASS()
+class UStaticMeshExporterGP : public UExporter
+{
+	GENERATED_UCLASS_BODY()
+
+
+	// Begin UExporter Interface
+	virtual bool ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags = 0) override;
+	// End UExporter Interface
+
+	class UGPTextureExporterBMP* BMPExporter;
+};
+
+
+