// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license.

#include "SimplygonDataConverter.h"
#include "SimplygonAssetUserData.h"
#include "SimplygonUI.h"

#include "MeshUtilities.h"
#include "MaterialUtilities.h"
#include "MeshAttributes.h"
#include "Engine/StaticMesh.h"
#include "StaticMeshAttributes.h"
#include "Rendering/SkeletalMeshModel.h"
#include "MeshMergeUtilities/Private/MeshMergeHelpers.h"
#include "SimplygonContent/Classes/SimplygonSettings.h"
#include "Materials/Material.h"
#include "IMaterialBakingModule.h"
#include "MaterialBakingStructures.h"
#include "Features/IModularFeatures.h"
#include "Engine/StaticMesh.h"
#include "Modules/ModuleManager.h"

#include "Async/ParallelFor.h"
#include "HAL/PlatformFilemanager.h"
#include "Misc/ScopedSlowTask.h"
#include "Modules/ModuleManager.h"
#include "Materials/Material.h"
#include "Async/ParallelFor.h"
#include "Components/InstancedStaticMeshComponent.h"

#include "Developer/MaterialUtilities/Public/MaterialUtilities.h"

DEFINE_LOG_CATEGORY_STATIC(LogSimplygonDataConverter, Verbose, All);

#define SIMPLYGON_COLOR_CHANNEL "CustomVertexColors"
#define LOCTEXT_NAMESPACE "SimplygonDataConverter"
#define SG_USER_TRIANGLE_FIELD_TRIANGLEIDS "UE4TriangleIds"
#define SG_USER_TRIANGLE_FIELD_MATERIALIDS "UE4TMaterialIds"
#define SG_USER_CORNER_FIELD_VERTEXIDS "UE4VertexIds"
#define SG_USER_CORNER_FIELD_CORNERIDS "UE4CornerIds"

struct FSkeletalMeshData
{
	TArray<SkeletalMeshImportData::FVertInfluence> Influences;
	TArray<SkeletalMeshImportData::FMeshWedge> Wedges;
	TArray<SkeletalMeshImportData::FMeshFace> Faces;
	TArray<FVector3f> Points;
	uint32 TexCoordCount;

	// List of vertex indices that should be ignored when re-applying morph targets to LOD
	TArray<int32> MorphTargetIgnoreList;
};

// Add to scene - UStaticMesh
void FSimplygonDataConverter::AddToScene(
	Simplygon::ISimplygon* SDK, 
	Simplygon::spScene Scene,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	UStaticMesh* StaticMesh,
	FSimplygonMeshMaterialInfo& OutMeshMaterialInfo)
{
	const int32 LODIndex = 0;

	FMeshDescription* MeshDescription = new FMeshDescription();
	FStaticMeshAttributes(*MeshDescription).Register();
	FMeshMergeHelpers::RetrieveMesh(StaticMesh, LODIndex, *MeshDescription);

	FString MeshDescriptionId = FString::Printf(TEXT("%d"), StaticMesh->GetUniqueID());
	OutMeshMaterialInfo.MeshesToRelease.Add(MeshDescriptionId);

	TArray<FSectionInfo> Sections;
	FMeshMergeHelpers::ExtractSections(StaticMesh, LODIndex, Sections);

	Simplygon::spGeometryData GeometryData = CreateGeometryData(SDK, FlattenMaterialSettings, MeshDescription, MeshDescriptionId, Sections, OutMeshMaterialInfo);

	// Create Simplygon mesh scene node
	Simplygon::spSceneMesh MeshNode = SDK->CreateSceneMesh();
	MeshNode->SetGeometry(GeometryData);
	MeshNode->SetName(TCHAR_TO_ANSI(*StaticMesh->GetFName().ToString()));
	Scene->GetRootNode()->AddChild(MeshNode);

	// Meta data?
	auto SimplygonUserData = StaticMesh->GetAssetUserData<USimplygonAssetUserData>();
}

// Add to scene - USkeletalMesh
void FSimplygonDataConverter::AddToScene(
	Simplygon::ISimplygon* SDK,
	Simplygon::spScene Scene,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	USkeletalMesh* SkeletalMesh,
	FSimplygonMeshMaterialInfo& OutMeshMaterialInfo)
{
	// Create Geometry Data from Skeletal Mesh
	Simplygon::spGeometryData GeometryData = CreateGeometryData(SDK, Scene, FlattenMaterialSettings, SkeletalMesh, OutMeshMaterialInfo);

	// Add Geometry Data to Simplygon scene
	AddToScene(SDK, Scene, GeometryData, SkeletalMesh->GetName());

	// Meta data?
	auto SimplygonUserData = SkeletalMesh->GetAssetUserData<USimplygonAssetUserData>();
}

// Add to scene - UStaticMeshComponent
void FSimplygonDataConverter::AddToScene(
	Simplygon::ISimplygon* SDK,
	Simplygon::spScene Scene,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	UStaticMeshComponent* StaticMeshComponent,
	FSimplygonMeshMaterialInfo& MeshMaterialInfo)
{
	FMeshDescription* MeshDescription = new FMeshDescription();
	FStaticMeshAttributes(*MeshDescription).Register();
	FMeshMergeHelpers::RetrieveMesh(StaticMeshComponent, 0, *MeshDescription, true);
	
	FString MeshDescriptionId = FString::Printf(TEXT("%d"), StaticMeshComponent->GetStaticMesh()->GetUniqueID());
	MeshMaterialInfo.MeshesToRelease.Add(MeshDescriptionId);

	TArray<FSectionInfo> Sections;
	// Extract sections for given LOD index from the mesh 
	FMeshMergeHelpers::ExtractSections(StaticMeshComponent, 0, Sections);

	// If the component is an ISMC then we need to duplicate the vertex data
	if (StaticMeshComponent->IsA<UInstancedStaticMeshComponent>())
	{
		const UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast<UInstancedStaticMeshComponent>(StaticMeshComponent);
		FMeshMergeHelpers::ExpandInstances(InstancedStaticMeshComponent, *MeshDescription);
	}

	Simplygon::spGeometryData GeometryData = CreateGeometryData(SDK, FlattenMaterialSettings, MeshDescription, MeshDescriptionId, Sections, MeshMaterialInfo);
	
	// Create Simplygon mesh scene node
	Simplygon::spSceneMesh MeshNode = SDK->CreateSceneMesh();
	MeshNode->SetGeometry(GeometryData);
	MeshNode->SetName(TCHAR_TO_ANSI(*StaticMeshComponent->GetFName().ToString()));
	Scene->GetRootNode()->AddChild(MeshNode);

	// Add geometry data to selection set
	auto SimplygonUserData = StaticMeshComponent->GetAssetUserData<USimplygonAssetUserData>();
	auto AddMeshNodeToSelectionSet = [&SimplygonUserData, &SDK, &Scene, &MeshNode]()
	{
		bool bIsClippingComponent = SimplygonUserData ? (SimplygonUserData->MetaTag == ESimplygonMetaTagType::ClippingGeometry) : false;
		bool bIsOccluderComponent = SimplygonUserData ? (SimplygonUserData->MetaTag == ESimplygonMetaTagType::Occluder) : false;
		bool bIsGeometryCameraPath = SimplygonUserData ? (SimplygonUserData->MetaTag == ESimplygonMetaTagType::GeometryCameraPath) : false;

		if (bIsClippingComponent)
		{
			Simplygon::spSelectionSet ClippingSet = FindOrCreateSelectionSet(SDK, Scene, SG_CLIPPING_GEOMETRY_SELECTION_SET_NAME);
			ClippingSet->AddItem(MeshNode->GetNodeGUID());
		}
		else if (bIsOccluderComponent)
		{
			Simplygon::spSelectionSet OccluderSet = FindOrCreateSelectionSet(SDK, Scene, SG_OCCLUDER_GEOMETRY_SELECTION_SET_NAME);
			OccluderSet->AddItem(MeshNode->GetNodeGUID());
		}
		else if (bIsGeometryCameraPath)
		{
			Simplygon::spSelectionSet CameraSet = FindOrCreateSelectionSet(SDK, Scene, SG_VISIBILITY_SELECTION_SET_NAME);
			CameraSet->AddItem(MeshNode->GetNodeGUID());
		}
		else
		{
			// With 9.1 ProcessSelectionSet is automatically created within the Simplygon API from any mesh that does not belong to any other set.
		}

		return true;
	}();
}

// Add to scene - Materials
void FSimplygonDataConverter::AddToScene(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, FSimplygonMeshMaterialInfo& MeshMaterialInfo)
{
	FScopedSlowTask Progress(1.0, LOCTEXT("AddMaterialsToScene", "Adding materials to Simplygon scene (flattening materials)..."), true);
	Progress.MakeDialog();

	IMaterialBakingModule& MaterialBakingModule = FModuleManager::Get().LoadModuleChecked<IMaterialBakingModule>("MaterialBaking");

	TArray<FMaterialData> GlobalMaterialSettings;
	TArray<FMeshData*> MeshSettingPtrs;
	TArray<FMaterialData*> MaterialSettingPtrs;

	for (const FMeshMaterialSectionId& Id : MeshMaterialInfo.MaterialOutputOrder)
	{
		MeshSettingPtrs.Add(&MeshMaterialInfo.BakeMaterialMeshSettings[Id]);

		GlobalMaterialSettings.Add(MeshMaterialInfo.BakeMaterialMaterialSettings[Id]);
		MaterialSettingPtrs.Add(&MeshMaterialInfo.BakeMaterialMaterialSettings[Id]);
	}

	Progress.EnterProgressFrame(1.0f);
	TArray<FBakeOutput> BakeOutputs;
	MaterialBakingModule.BakeMaterials(MaterialSettingPtrs, MeshSettingPtrs, BakeOutputs);

	TArray<FFlattenMaterial> FlattenedMaterialArray;
	ConvertMaterialBakeOutput(BakeOutputs, GlobalMaterialSettings, FlattenedMaterialArray);

	AddSimplygonMaterials(SDK, Scene, FlattenedMaterialArray, true);

	// Release data
	TArray<FString> ReleasedMeshes;
	for (auto& IdToMeshSettings : MeshMaterialInfo.BakeMaterialMeshSettings)
	{
		FString* MeshId = MeshMaterialInfo.MeshesToRelease.FindByPredicate([&IdToMeshSettings](const FString& MatchMeshId)
		{
				return IdToMeshSettings.Key.Contains(MatchMeshId);
		});

		if (!MeshId)
			continue;

		bool bMeshAlreadyReleased = ReleasedMeshes.Contains(*MeshId);
		if (bMeshAlreadyReleased)
		{
			IdToMeshSettings.Value.MeshDescription = nullptr;
		}
		else
		{
			if (IdToMeshSettings.Value.MeshDescription != nullptr)
			{
				delete IdToMeshSettings.Value.MeshDescription;
				IdToMeshSettings.Value.MeshDescription = nullptr;
				ReleasedMeshes.Add(*MeshId);
			}
		}
	}
}

void FSimplygonDataConverter::AddToScene(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, Simplygon::spPipeline Pipeline, const TArray<FPlane>& ClippingPlanes)
{
	bool bHasClippingPlanes = ClippingPlanes.Num() > 0 && FSimplygonDataConverter::IsClippingPlaneEnabledInPipeline(Pipeline);

	if (bHasClippingPlanes)
	{
		Simplygon::spSelectionSet ClippingPlaneSet = FindOrCreateSelectionSet(SDK, Scene, SG_CLIPPING_PLANES_SELECTION_SET_NAME);

		for (auto& Plane : ClippingPlanes)
		{
			Simplygon::spScenePlane TempPlane = SDK->CreateScenePlane();
			FVector Normal = Plane.GetSafeNormal();
			FVector Position(Plane.GetComponentForAxis(EAxis::X) + Plane.W, Plane.GetComponentForAxis(EAxis::Y) + Plane.W, Plane.GetComponentForAxis(EAxis::Z) + Plane.W);
			Normal = FSimplygonDataConverter::VectorToSimplygon(Normal);
			Position = FSimplygonDataConverter::VectorToSimplygon(Position);
			float Pos[3] = { Position.X,Position.Y,Position.Z };
			float Nor[3] = { Normal.X,Normal.Y,Normal.Z };

			TempPlane->SetPosition(&Pos[0]);
			TempPlane->SetNormal(&Nor[0]);

			Scene->GetRootNode()->AddChild(TempPlane);
			ClippingPlaneSet->AddItem(TempPlane->GetNodeGUID());
		}

		Scene->GetSelectionSetTable()->AddItem(ClippingPlaneSet);
		if (Pipeline->IsA("IRemeshingPipeline"))
		{
			FSimplygonDataConverter::SetupClippingPlanesFromPipelineSettings<Simplygon::spRemeshingPipeline>(Pipeline);

		}
		else if (Pipeline->IsA("IRemeshingLegacyPipeline"))
		{
			//FSimplygonDataConverter::SetupClippingPlanesFromPipelineSettings<Simplygon::spRemeshingLegacyPipeline>(Pipeline);
		}
		else if (Pipeline->IsA("IAggregationPipeline"))
		{
			FSimplygonDataConverter::SetupClippingPlanesFromPipelineSettings<Simplygon::spAggregationPipeline>(Pipeline);
		}
	}
	else
		FSimplygonDataConverter::DisableClippingPlaneInPipeline(Pipeline);
}

void FSimplygonDataConverter::AddToScene(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, Simplygon::spPipeline Pipeline, const TArray<FVector>& VisibilitySamples)
{
	bool bSampleNavmeshVisibility = VisibilitySamples.Num() > 0;

	if (bSampleNavmeshVisibility)
	{
		const int32 Resolution = 512;
		Simplygon::spSelectionSet VisibilitySet = FindOrCreateSelectionSet(SDK, Scene, SG_VISIBILITY_SELECTION_SET_NAME);

		Simplygon::spSceneCamera SceneCamera = SDK->CreateSceneCamera();
		SceneCamera->SetCameraType(Simplygon::ECameraType::Omnidirectional);
		SceneCamera->SetFieldOfView(FMath::DegreesToRadians(90));
		SceneCamera->SetPixelFieldOfView(FMath::DegreesToRadians(90) / Resolution);

		// Get the camera position
		Simplygon::spRealArray CameraPositions = SceneCamera->GetCameraPositions();

		// Set the tuple count to 1
		CameraPositions->SetTupleCount(VisibilitySamples.Num());

		FVector TransformedSample;
		for (auto SampleIndex = 0; SampleIndex < VisibilitySamples.Num(); ++SampleIndex)
		{
			TransformedSample = FSimplygonDataConverter::VectorToSimplygon(VisibilitySamples[SampleIndex]);
			float TempPoint[3] = { TransformedSample.X, TransformedSample.Y, TransformedSample.Z };
			CameraPositions->SetTuple(SampleIndex, &TempPoint[0]);
		}

		if (SceneCamera->ValidateCamera())
		{
			Scene->GetRootNode()->AddChild(SceneCamera);
			VisibilitySet->AddItem(SceneCamera->GetNodeGUID());
			Scene->GetSelectionSetTable()->AddItem(VisibilitySet);
		}

		if (Pipeline->IsA("IRemeshingPipeline"))
		{
			//does not support visibility for now
			FSimplygonDataConverter::SetupVisibilityCameraSetFromPipelineSettings<Simplygon::spRemeshingPipeline>(Pipeline);

		}
		else if (Pipeline->IsA("IRemeshingLegacyPipeline"))
		{
			FSimplygonDataConverter::SetupVisibilityCameraSetFromPipelineSettings<Simplygon::spRemeshingLegacyPipeline>(Pipeline);
		}
		else if (Pipeline->IsA("IAggregationPipeline"))
		{
			FSimplygonDataConverter::SetupVisibilityCameraSetFromPipelineSettings<Simplygon::spAggregationPipeline>(Pipeline);
		}
	}
}

void FSimplygonDataConverter::AddToScene(
	Simplygon::ISimplygon* SDK,
	Simplygon::spScene Scene,
	Simplygon::spPipeline Pipeline,
	const FMeshDescription* CullingGeometry)
{

	check(CullingGeometry != nullptr);
	TMap<FName, int32> MaterialMap;
	TArray<FBox2D> TexCoordsBounds; // Init empty array to skip bounds
	TArray<FVector2f> TexCoords; // Init empty array to skip bounds


	auto CullingGeoemtryData = CreateGeometryFromMeshDescription(SDK, *CullingGeometry, MaterialMap, TexCoordsBounds, TexCoords, false);

	Simplygon::spSceneMesh MeshNode = SDK->CreateSceneMesh();
	MeshNode->SetGeometry(CullingGeoemtryData);
	Scene->GetRootNode()->AddChild(MeshNode);
	
	auto ClippingGeoemtrySet = FindOrCreateSelectionSet(SDK, Scene, SG_CLIPPING_GEOMETRY_SELECTION_SET_NAME);
	ClippingGeoemtrySet->AddItem(MeshNode->GetNodeGUID().Data());

	
}

void FSimplygonDataConverter::AddToScene(
	Simplygon::ISimplygon* SDK, 
	Simplygon::spScene Scene, 
	Simplygon::spGeometryData GeometryData,
	const FString& NodeName)
{
	Simplygon::spSceneMesh MeshNode = SDK->CreateSceneMesh();
	MeshNode->SetGeometry(GeometryData);
	if(NodeName.IsEmpty())
		MeshNode->SetName(TCHAR_TO_ANSI(TEXT("MeshNode")));
	else
		MeshNode->SetName(TCHAR_TO_ANSI(*NodeName));
	Scene->GetRootNode()->AddChild(MeshNode);
}

Simplygon::spGeometryData FSimplygonDataConverter::CreateGeometryData(
	Simplygon::ISimplygon* API,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	const FMeshDescription* MeshDescription,
	const FString& MeshDescriptionId,
	const TArray<FSectionInfo>& Sections,
	FSimplygonMeshMaterialInfo& OutMeshMaterialInfo)
{
	auto MaterialInfo = OutMeshMaterialInfo.Add(FlattenMaterialSettings, Sections, MeshDescription, MeshDescriptionId);

	TArray<FBox2D> TexCoordsBounds; // Init empty array to skip bounds
	TArray<FVector2f> TexCoords; // Init empty array to skip bounds
	Simplygon::spGeometryData Geometry = FSimplygonDataConverter::CreateGeometryFromMeshDescription(API, *MeshDescription, MaterialInfo.SlotNameToMaterialId, TexCoordsBounds, TexCoords, false);
	
	if( OutMeshMaterialInfo.bBakeMaterials)
	{
		// Generate flatten material texture coordinates
		TArray<FVector2D> CustomTextureCoordinates;
		FSimplygonDataConverter::PackChartsUsingChartAggregator(
			API, Geometry, FlattenMaterialSettings, SG_MATERIALBAKING_INDEX, MaterialInfo,  CustomTextureCoordinates);

		// Copy custom texture coordinates to mesh settings
		for (const FString& MeshSectionId : MaterialInfo.ComponentMeshMaterialKeys)
		{
			FMeshData* MeshSettings = OutMeshMaterialInfo.BakeMaterialMeshSettings.Find(MeshSectionId);

			if (MeshSettings)
			{
				MeshSettings->CustomTextureCoordinates = CustomTextureCoordinates;
			}
		}
	
	}

	return Geometry;
}


Simplygon::spGeometryData FSimplygonDataConverter::CreateGeometryData(
	Simplygon::ISimplygon* API,
	Simplygon::spScene Scene,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	USkeletalMesh* SkeletalMesh,
	const TArray<FSectionInfo>& Sections,
	FSimplygonMeshMaterialInfo& OutMeshMaterialInfo)
{
	return CreateGeometryData(API, Scene, FlattenMaterialSettings, SkeletalMesh, OutMeshMaterialInfo);
}

Simplygon::spGeometryData FSimplygonDataConverter::CreateGeometryData(
	Simplygon::ISimplygon* API,
	Simplygon::spScene Scene,
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
	USkeletalMesh* SkeletalMesh,
	FSimplygonMeshMaterialInfo& OutMeshMaterialInfo)
{
	TArray<FSectionInfo> Sections;
	ExtractSections(SkeletalMesh, 0, Sections);

	FMeshDescription* MeshDescriptionForFlatten = new FMeshDescription();
	FStaticMeshAttributes(*MeshDescriptionForFlatten).Register();
	ExtractMeshDescription(SkeletalMesh, 0, MeshDescriptionForFlatten, true);

	FString MeshId = FString::Printf(TEXT("%d"), SkeletalMesh->GetUniqueID());
	OutMeshMaterialInfo.MeshesToRelease.Add(MeshId);
	
	auto MaterialInfoResult = OutMeshMaterialInfo.Add(FlattenMaterialSettings, Sections, MeshDescriptionForFlatten, MeshId);

	FSkeletalMeshModel* SkeletalMeshResource = SkeletalMesh->GetImportedModel();
	check(SkeletalMeshResource);
	FSkeletalMeshLODModel* BaseMeshLODModel = &SkeletalMeshResource->LODModels[0];
	const TArray<FMatrix>& BoneMatrices = TArray<FMatrix>(); //BakePoseToLOD(LODIndex);

	// Create bone hierarchy for simplygon.
	TArray<Simplygon::rid> BoneTableIDs;
	FSimplygonDataConverter::CreateSkeletalHierarchy(API, Scene, SkeletalMesh->GetRefSkeleton(), BoneTableIDs);

	// Create a new scene mesh object
	Simplygon::spGeometryData Geometry = FSimplygonDataConverter::CreateGeometryFromSkeletalLODModel(
		API,
		*BaseMeshLODModel,
		BoneTableIDs,
		BoneMatrices,
		MaterialInfoResult.SectionIndexToMaterialId,
		TArray<FBox2D>(),
		TArray<FVector2f>());

	if (OutMeshMaterialInfo.bBakeMaterials)
	{
		//Generate flatten material texture coordinates
		TArray<FVector2D> CustomTextureCoordinates;

		FSimplygonDataConverter::PackChartsUsingChartAggregator(
			API, Geometry, FlattenMaterialSettings, SG_MATERIALBAKING_INDEX, MaterialInfoResult, CustomTextureCoordinates);

		// Copy custom texture coordinates to mesh settings
		for (const FString& MeshSectionId : MaterialInfoResult.ComponentMeshMaterialKeys)
		{
			FMeshData* MeshSettings = OutMeshMaterialInfo.BakeMaterialMeshSettings.Find(MeshSectionId);
			if (MeshSettings)
			{
				MeshSettings->CustomTextureCoordinates = CustomTextureCoordinates;
			}
		}
	}

	return Geometry;
}

void FSimplygonDataConverter::GetMaterialMaps(
	Simplygon::ISimplygon* sdk,
	const FMeshDescription& Mesh,
	TMap<FName, int32>& OutMaterialNameToIndex,
	TMap<int32, FName>& OutMaterialIndexToName,
	TArray<int32> MaterialIdRemap)
{
	TPolygonGroupAttributesConstRef<FName> PolygonGroupMaterialSlotName = Mesh.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);

	bool bRemapMaterialIds = MaterialIdRemap.Num() > 0;

	for (const FPolygonGroupID& PolygonGroupID : Mesh.PolygonGroups().GetElementIDs())
	{
		int32 MaterialId = bRemapMaterialIds ? MaterialIdRemap[PolygonGroupID.GetValue()] : PolygonGroupID.GetValue();
		
		FName MaterialName = PolygonGroupMaterialSlotName[PolygonGroupID];
		// If available, use MaterialId to fetch the material name
		if(MaterialId < PolygonGroupMaterialSlotName.GetNumElements())
			MaterialName = PolygonGroupMaterialSlotName[FPolygonGroupID(MaterialId)];

		OutMaterialNameToIndex.Add(MaterialName, MaterialId);
		OutMaterialIndexToName.Add(MaterialId, MaterialName);
	}
}

Simplygon::spGeometryData FSimplygonDataConverter::CreateGeometryFromMeshDescription(
	Simplygon::ISimplygon* SDK, 
	const FMeshDescription& Mesh,
	const TMap<FName, int32>& MaterialMap, 
	const TArray<FBox2D>& TextureBounds,
	const TArray<FVector2f>& InTexCoords, 
	bool SetVertexWeights)
{

	int32 NumTris = 0;
	for (const FPolygonID& PolygonID : Mesh.Polygons().GetElementIDs())
		NumTris += Mesh.GetPolygonTriangles(PolygonID).Num();

	int32 NumWedges = NumTris * 3;
	int32 NumVertices = Mesh.Vertices().Num();

	auto VertexPositions =
		Mesh.VertexAttributes().GetAttributesRef<FVector3f>(MeshAttribute::Vertex::Position);
	auto VertexInstanceNormals =
		Mesh.VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Normal);
	auto VertexInstanceTangents =
		Mesh.VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Tangent);
	auto VertexInstanceBinormalSigns = Mesh.VertexInstanceAttributes().GetAttributesRef<float>(
		MeshAttribute::VertexInstance::BinormalSign);

	auto VertexInstanceColors =
		Mesh.VertexInstanceAttributes().GetAttributesRef<FVector4f>(MeshAttribute::VertexInstance::Color);

	auto PolygonGroupMaterialSlotName = Mesh.PolygonGroupAttributes().GetAttributesRef<FName>(
		MeshAttribute::PolygonGroup::ImportedMaterialSlotName);

	auto VertexInstanceUVs = Mesh.VertexInstanceAttributes().GetAttributesRef<FVector2f>(
		MeshAttribute::VertexInstance::TextureCoordinate);
	int32 NumTextureCoordinates = VertexInstanceUVs.GetNumChannels();

	TArray<FLinearColor> VertexColors;
	TArray<int32> UniqueIndexCount;
	if (SetVertexWeights)
	{
		VertexColors.AddZeroed(NumVertices);
		UniqueIndexCount.AddZeroed(NumVertices);
	}

	Simplygon::spGeometryData OutGeometryData = SDK->CreateGeometryData();
	OutGeometryData->SetVertexCount(NumVertices);
	OutGeometryData->SetTriangleCount(NumTris);

	Simplygon::spRealArray Positions = OutGeometryData->GetCoords();
	Simplygon::spRidArray Indices = OutGeometryData->GetVertexIds();

	OutGeometryData->AddNormals();
	Simplygon::spRealArray Normals = OutGeometryData->GetNormals();

	OutGeometryData->AddTangents(0);
	Simplygon::spRealArray Tangents = OutGeometryData->GetTangents(0);
	Simplygon::spRealArray Bitangents = OutGeometryData->GetBitangents(0);

	OutGeometryData->AddColors(0);
	Simplygon::spRealArray LinearColors = OutGeometryData->GetColors(0);

	OutGeometryData->AddMaterialIds();
	Simplygon::spRidArray MaterialIndices = OutGeometryData->GetMaterialIds();

	OutGeometryData->AddGroupIds();
	Simplygon::spRidArray GroupIds = OutGeometryData->GetGroupIds();

	// Positions
	TArray<int32> RemapVerts;
	RemapVerts.AddZeroed(Mesh.Vertices().GetArraySize());
	int32 VertexIndex = 0, ComponentIndex = 0;
	Simplygon::real* TransformedPositions = new Simplygon::real[Mesh.Vertices().Num() * 3];
	for (const FVertexID& VertexID : Mesh.Vertices().GetElementIDs())
	{
		auto TempPos = VectorToSimplygon<FVector3f>(VertexPositions[VertexID]);
		TransformedPositions[ComponentIndex++] = TempPos.X;
		TransformedPositions[ComponentIndex++] = TempPos.Y;
		TransformedPositions[ComponentIndex++] = TempPos.Z;

		RemapVerts[VertexID.GetValue()] = VertexIndex++;
	}
	Positions->SetData(TransformedPositions, VertexIndex * 3);
	delete[] TransformedPositions;

	auto AddTexCoords = [&OutGeometryData, &Mesh, &MaterialMap, &PolygonGroupMaterialSlotName, &NumWedges, &VertexInstanceUVs](
		int32 TexCoordIndex, 
		const TArray<FBox2D>& TextureBounds,
		const TArray<FVector2f>& CustomUVs)
	{
		bool bUseCustomUVs = (TexCoordIndex == SG_MATERIALBAKING_INDEX && CustomUVs.Num() == NumWedges);

		OutGeometryData->AddTexCoords(TexCoordIndex);
		Simplygon::spRealArray TexCoords = OutGeometryData->GetTexCoords(TexCoordIndex);
		check(TexCoords->GetTupleSize() == 2);

		int32 TriangleIndex = 0;
		Simplygon::real* UVData = new Simplygon::real[TexCoords->GetTupleCount() * 2];
		Simplygon::real* UVComponent = UVData;
		for (const FPolygonID PolygonID : Mesh.Polygons().GetElementIDs())
		{
			const FPolygonGroupID& PolygonGroupID = Mesh.GetPolygonPolygonGroup(PolygonID);

			//Note : checking if material map contains any items. For culling/occluding geometries a material mapping might not be required. Default it to 0 (Assumption : -1 might not work with Simplygon API)
			const FName SlotName = PolygonGroupMaterialSlotName[PolygonGroupID];
			const int32* ValuePtr = MaterialMap.Find(SlotName);
			const int32 MaterialIndex = ValuePtr == nullptr ? 0 : *ValuePtr;

			if (!ValuePtr)
			{
				UE_LOG(LogSimplygonDataConverter, Warning, TEXT("Could not find material slot name %s, therefore defaulting to index 0"), *(SlotName.ToString()));
			}

			const TArrayView<const FTriangleID>& TriangleIDs = Mesh.GetPolygonTriangles(PolygonID);
			for (const FTriangleID& MeshTriangleID : TriangleIDs)
			{
				// Adjust UVs when baking materials
				float MinU = 0, ScaleU = 1;
				float MinV = 0, ScaleV = 1;
				if (TextureBounds.IsValidIndex(MaterialIndex) && TexCoordIndex == 0 &&
					CustomUVs.Num() == 0)
				{
					const FBox2D& Bounds = TextureBounds[MaterialIndex];
					if (Bounds.GetArea() > 0)
					{
						MinU = Bounds.Min.X;
						MinV = Bounds.Min.Y;
						ScaleU = 1.0f / (Bounds.Max.X - Bounds.Min.X);
						ScaleV = 1.0f / (Bounds.Max.Y - Bounds.Min.Y);
					}
				}

				for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
				{
					const FVertexInstanceID VertexInstanceID = Mesh.GetTriangleVertexInstance(MeshTriangleID, CornerIndex);

					const FVector2f& TexCoord =
						bUseCustomUVs ? CustomUVs[TriangleIndex * 3 + CornerIndex] : VertexInstanceUVs.Get(VertexInstanceID, TexCoordIndex);
					*UVComponent++ = (TexCoord.X - MinU) * ScaleU;
					*UVComponent++ = (TexCoord.Y - MinV) * ScaleV;
				}
				++TriangleIndex;
			}
		}
		TexCoords->SetData(UVData, TexCoords->GetTupleCount() * 2);
		delete[] UVData;
	};

	// Texture coordinates (Material baking)
	if(InTexCoords.Num() == NumWedges)
		AddTexCoords(SG_MATERIALBAKING_INDEX, TextureBounds, InTexCoords);

	// Texture coordinates (Original)
	for (int32 TexCoordIndex = 0; TexCoordIndex < NumTextureCoordinates; ++TexCoordIndex)
	{
		AddTexCoords(TexCoordIndex, TextureBounds, InTexCoords);
	}

	Simplygon::rid* MaterialIndexData = new Simplygon::rid[MaterialIndices->GetItemCount()];
	Simplygon::rid* MaterialIndexComponent = MaterialIndexData;
	Simplygon::rid* GroupIdData = new Simplygon::rid[GroupIds->GetItemCount()];
	Simplygon::rid* GroupIdComponent = GroupIdData;
	Simplygon::rid* IndexData = new Simplygon::rid[Indices->GetItemCount()];
	Simplygon::rid* IndexComponent = IndexData;
	Simplygon::real* NormalData = new Simplygon::real[Normals->GetTupleCount() * 3];
	Simplygon::real* NormalComponent = NormalData;
	Simplygon::real* TangentData = new Simplygon::real[Tangents->GetTupleCount() * 3];
	Simplygon::real* TangentComponent = TangentData;
	Simplygon::real* BitangentData = new Simplygon::real[Bitangents->GetTupleCount() * 3];
	Simplygon::real* BitangentComponent = BitangentData;
	Simplygon::real* ColorData = new Simplygon::real[LinearColors->GetTupleCount() * 4];
	Simplygon::real* ColorComponent = ColorData;
	for (const FPolygonID PolygonID : Mesh.Polygons().GetElementIDs())
	{
		const FPolygonGroupID& PolygonGroupID = Mesh.GetPolygonPolygonGroup(PolygonID);

		const TArrayView<const FTriangleID>& TriangleIDs = Mesh.GetPolygonTriangles(PolygonID);
		for (const FTriangleID& MeshTriangleID : TriangleIDs)
		{
			// Material Indices
			//Note : checking if material map contains any items. For culling/occluding geometries a material mapping might not be required. Default it to 0 (Assumption : -1 might not work with Simplygon API)
			const FName SlotName = PolygonGroupMaterialSlotName[PolygonGroupID];
			const int32* ValuePtr = MaterialMap.Find(SlotName);
			const int32 MaterialIndex = ValuePtr == nullptr ? 0 : *ValuePtr;

			if (!ValuePtr)
			{
				UE_LOG(LogSimplygonDataConverter, Warning, TEXT("Could not find material slot name %s, therefore defaulting to index 0"), *(SlotName.ToString()));
			}

			*MaterialIndexComponent++ = MaterialIndex;

			// Smoothing groups
			*GroupIdComponent++ = 0;

			for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
			{
				const FVertexInstanceID VertexInstanceID = Mesh.GetTriangleVertexInstance(MeshTriangleID, CornerIndex);

				// Vertex Indices
				*IndexComponent++ = RemapVerts[Mesh.GetVertexInstanceVertex(VertexInstanceID).GetValue()];

				// Normals
				auto Normal = VectorToSimplygon<FVector3f>(VertexInstanceNormals[VertexInstanceID]);
				*NormalComponent++ = Normal.X;
				*NormalComponent++ = Normal.Y;
				*NormalComponent++ = Normal.Z;

				// Tangents
				auto Tangent = VectorToSimplygon<FVector3f>(VertexInstanceTangents[VertexInstanceID]);
				*TangentComponent++ = Tangent.X;
				*TangentComponent++ = Tangent.Y;
				*TangentComponent++ = Tangent.Z;

				// Bitangents
				FVector3f Bitangent = FVector3f::CrossProduct(VertexInstanceNormals[VertexInstanceID],
					VertexInstanceTangents[VertexInstanceID])
					.GetSafeNormal() *
					VertexInstanceBinormalSigns[VertexInstanceID];
				Bitangent = VectorToSimplygon(Bitangent);
				*BitangentComponent++ = Bitangent.X;
				*BitangentComponent++ = Bitangent.Y;
				*BitangentComponent++ = Bitangent.Z;

				// Vertex Colors
				FLinearColor VertexColor =
					FLinearColor(VertexInstanceColors[VertexInstanceID]).ToFColor(true);
				*ColorComponent++ = VertexColor.R;
				*ColorComponent++ = VertexColor.G;
				*ColorComponent++ = VertexColor.B;
				*ColorComponent++ = VertexColor.A;

				// Vertex Weights (based on vertex colors)
				if (SetVertexWeights)
				{
					const int32 VertexInd =
						RemapVerts[Mesh.GetVertexInstanceVertex(VertexInstanceID).GetValue()];
					VertexColors[VertexInd] += VertexColor;
					UniqueIndexCount[VertexInd] += 1;
				}
			}
		}
	}
	MaterialIndices->SetData(MaterialIndexData, MaterialIndices->GetItemCount());
	GroupIds->SetData(GroupIdData, GroupIds->GetItemCount());
	Indices->SetData(IndexData, Indices->GetItemCount());
	Normals->SetData(NormalData, Normals->GetItemCount());
	Tangents->SetData(TangentData, Tangents->GetItemCount());
	Bitangents->SetData(BitangentData, Bitangents->GetItemCount());
	LinearColors->SetData(ColorData, LinearColors->GetItemCount());
	delete[] MaterialIndexData;
	delete[] GroupIdData;
	delete[] IndexData;
	delete[] NormalData;
	delete[] TangentData;
	delete[] BitangentData;
	delete[] ColorData;

	if (SetVertexWeights)
	{
		if (OutGeometryData->GetVertexWeights().IsNull())
		{
			OutGeometryData->AddVertexWeights();
		}
		Simplygon::spRealArray VertexWeights = OutGeometryData->GetVertexWeights();

		// Assign the average wedge color as vertex weights
		float VertexWeight = 1.0f;
		float Alpha = 0.0f;
		for (int32 VertexIdx = 0; VertexIdx < NumVertices; ++VertexIdx)
		{
			/*
			 * Alpha = 0,		 Reduce
			 * Alpha = 0.5,		 Neutral
			 * Alpha = 1.0,		 Keep
			 */
			VertexWeight = 1.0f;
			FLinearColor& Color = VertexColors[VertexIdx];
			Alpha = Color.A / UniqueIndexCount[VertexIdx];
			VertexWeight = ConvertFromColorToWeights(Alpha, 8.0f);
			VertexWeights->SetItem(VertexIdx, VertexWeight);
		}
	}

	return OutGeometryData;
}

void FSimplygonDataConverter::CreateMeshDescriptionFromGeometry(
	const Simplygon::spGeometryData& GeometryData,
	const TMap<int32, FName>& MaterialIndexToSlotName,
	FMeshDescription& OutMesh,
	bool bHasBakedMaterials)
{
	check(GeometryData);

	Simplygon::spRealArray Positions = GeometryData->GetCoords();
	Simplygon::spRidArray Indices = GeometryData->GetVertexIds();
	Simplygon::spRidArray MaterialIndices = GeometryData->GetMaterialIds();
	Simplygon::spRidArray GroupIds = GeometryData->GetGroupIds();
	Simplygon::spRealArray LinearColors = GeometryData->GetColors(0);
	Simplygon::spRealArray Normals = GeometryData->GetNormals();
	Simplygon::spRealArray Tangents = bHasBakedMaterials ? GeometryData->GetTangents(SG_MATERIALBAKING_INDEX) : GeometryData->GetTangents(0);
	Simplygon::spRealArray Bitangents = bHasBakedMaterials ? GeometryData->GetBitangents(SG_MATERIALBAKING_INDEX) : GeometryData->GetBitangents(0);
	Simplygon::spRealArray CustomTexCoordsFromFlatten = GeometryData->GetTexCoords(SG_MATERIALBAKING_INDEX);

	bool bHasTangnets = Tangents && !Tangents->IsEmpty();
	bool bHasBitangents = Bitangents && !Bitangents->IsEmpty();
	bool bHasLinearColors = LinearColors && !LinearColors->IsEmpty();

	bool bHasCustomUVs = bHasBakedMaterials && CustomTexCoordsFromFlatten != nullptr;
	auto GetTexCoords = [&bHasCustomUVs, &CustomTexCoordsFromFlatten, &GeometryData](int32 TexCoordIndex)
	{
		if (bHasCustomUVs && TexCoordIndex == 0)
			return CustomTexCoordsFromFlatten;
		else
			return GeometryData->GetTexCoords(TexCoordIndex);
	};

	check(Positions);
	check(Indices);
	

	int32 NumTriangles = GeometryData->GetTriangleCount();
	int32 NumCorners = NumTriangles * 3;
	int32 NumVertices = GeometryData->GetVertexCount();

	OutMesh.Empty();
	OutMesh.ReserveNewVertexInstances(NumCorners);
	OutMesh.ReserveNewPolygons(NumTriangles);
	// Approximately 2.5 edges per polygons
	OutMesh.ReserveNewEdges(NumCorners * 2.5f / 3);
	OutMesh.ReserveNewVertices(NumVertices);

	// Gather all array data
	TVertexAttributesRef<FVector3f> VertexPositions =
		OutMesh.VertexAttributes().GetAttributesRef<FVector3f>(MeshAttribute::Vertex::Position);

	TVertexInstanceAttributesRef<FVector3f> VertexInstanceNormals =
		OutMesh.VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Normal);
	TVertexInstanceAttributesRef<FVector3f> VertexInstanceTangents =
		OutMesh.VertexInstanceAttributes().GetAttributesRef<FVector3f>(
			MeshAttribute::VertexInstance::Tangent);
	TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns =
		OutMesh.VertexInstanceAttributes().GetAttributesRef<float>(
			MeshAttribute::VertexInstance::BinormalSign);
	TVertexInstanceAttributesRef<FVector4f> VertexInstanceColors =
		OutMesh.VertexInstanceAttributes().GetAttributesRef<FVector4f>(MeshAttribute::VertexInstance::Color);
	TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs =
		OutMesh.VertexInstanceAttributes().GetAttributesRef<FVector2f>(
			MeshAttribute::VertexInstance::TextureCoordinate);

	TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames =
		OutMesh.PolygonGroupAttributes().GetAttributesRef<FName>(
			MeshAttribute::PolygonGroup::ImportedMaterialSlotName);

	// Pre-setup texture coordinates
	int32 NumTexCoords = 0;
	int32 MaxTexCoords = MAX_MESH_TEXTURE_COORDS_MD;
	TArray<int32> TextureCoordinateRemapIndex;
	TextureCoordinateRemapIndex.AddZeroed(MaxTexCoords);
	for (int32 TextureCoordinateIndex = 0; TextureCoordinateIndex < MaxTexCoords; ++TextureCoordinateIndex)
	{
		TextureCoordinateRemapIndex[TextureCoordinateIndex] = INDEX_NONE;

		Simplygon::spRealArray TexCoords = GetTexCoords(TextureCoordinateIndex);
		if (TexCoords)
		{
			TextureCoordinateRemapIndex[TextureCoordinateIndex] = NumTexCoords;
			NumTexCoords++;
		}
	}

	verify(NumTexCoords > 0);
	VertexInstanceUVs.SetNumChannels(NumTexCoords);

	// Positions
	TArray<FVertexID> VertexIndexToVertexID;
	VertexIndexToVertexID.AddUninitialized(NumVertices);
	for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
	{
		Simplygon::spRealData tuple = Positions->GetTuple(VertexIndex);
		const Simplygon::real* vertexPos = tuple.Data();

		FVertexID VertexID = OutMesh.CreateVertex();
		VertexPositions[VertexID] = VectorFromSimplygon<FVector3f>(
			FVector3f(vertexPos[0], vertexPos[1], vertexPos[2]));
		VertexIndexToVertexID[VertexIndex] = VertexID;
	}

	// Polygon Groups
	TArray<FPolygonGroupID> PolygonGroups;
	TMap<int32, FPolygonGroupID> MaterialIndexToPolygonGroup;
	for (int32 TriIndex = 0; TriIndex < NumTriangles; ++TriIndex)
	{
		const int32 MaterialIndex = MaterialIndices->GetItem(TriIndex);

		if (!MaterialIndexToPolygonGroup.Contains(MaterialIndex))
		{
			FPolygonGroupID PolygonGroupID = OutMesh.CreatePolygonGroup();
			PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(*FString::Printf(TEXT("MaterialSlot_%d"), MaterialIndex));

			// A material slot name should always exist for meshes w/o material baking
			if (MaterialIndexToSlotName.Contains(MaterialIndex))
			{
				PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = MaterialIndexToSlotName[MaterialIndex];
			}

			PolygonGroups.Add(PolygonGroupID);
			MaterialIndexToPolygonGroup.Add(MaterialIndex, PolygonGroupID);
		}
	}

	// Triangles
	for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
	{
		int32 VerticeIndexBase = TriangleIndex * 3;

		// PolygonGroup
		FPolygonGroupID PolygonGroupID = INDEX_NONE;

		int32 MaterialIndex = MaterialIndices->GetItem(TriangleIndex);
		if (MaterialIndexToPolygonGroup.Contains(MaterialIndex))
		{
			PolygonGroupID = MaterialIndexToPolygonGroup[MaterialIndex];
		}

		if (PolygonGroupID == INDEX_NONE)
		{
			PolygonGroupID = OutMesh.CreatePolygonGroup();
			PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(*FString::Printf(TEXT("MaterialSlot_%d"), MaterialIndex));
			PolygonGroups.Add(PolygonGroupID);
			MaterialIndexToPolygonGroup.Add(MaterialIndex, PolygonGroupID);
		}

		TArray<FVertexInstanceID> TriangleVertexInstanceIDs;
		TriangleVertexInstanceIDs.SetNum(3);
		for (int32 Corner = 0; Corner < 3; ++Corner)
		{
			int32 VerticeIndex = VerticeIndexBase + Corner;
			FVertexID VertexID = VertexIndexToVertexID[Indices->GetItem(VerticeIndex)];

			FVertexInstanceID VertexInstanceID = OutMesh.CreateVertexInstance(VertexID);
			TriangleVertexInstanceIDs[Corner] = VertexInstanceID;

			// Vertex colors
			if (bHasLinearColors)
			{
				Simplygon::spRealData tuple = LinearColors->GetTuple(VerticeIndex);
				const Simplygon::real* sgVertexColor = tuple.Data();
				FLinearColor LinearColor(sgVertexColor[0], sgVertexColor[1], sgVertexColor[2],
					sgVertexColor[3]);
				VertexInstanceColors[VertexInstanceID] =
					FLinearColor::FromSRGBColor(LinearColor.ToFColor(true));
			}
			else
			{
				VertexInstanceColors[VertexInstanceID] = FLinearColor::White;
			}

			// Normal
			Simplygon::spRealData tuple = Normals->GetTuple(VerticeIndex);
			const Simplygon::real* sgNormal = tuple.Data();
			FVector3f Normal = VectorFromSimplygon(FVector3f(sgNormal[0], sgNormal[1], sgNormal[2]));
			VertexInstanceNormals[VertexInstanceID] = Normal;

			// Tangent
			if (bHasTangnets)
			{
				tuple = Tangents->GetTuple(VerticeIndex);
				const Simplygon::real* sgTangents = tuple.Data();
				FVector3f Tangent = VectorFromSimplygon(FVector3f(sgTangents[0], sgTangents[1], sgTangents[2]));
				VertexInstanceTangents[VertexInstanceID] = Tangent;

				// Bitangent
				if (bHasBitangents)
				{
					tuple = Bitangents->GetTuple(VerticeIndex);
					const Simplygon::real* sgBitangents = tuple.Data();
					FVector3f Bitangent = VectorFromSimplygon(FVector3f(sgBitangents[0], sgBitangents[1], sgBitangents[2]));

					//basis determinant sign computation using scalar triple product instead
					auto sign = Tangent.GetSafeNormal().Cross(Bitangent.GetSafeNormal()).Dot(Normal.GetSafeNormal());
					VertexInstanceBinormalSigns[VertexInstanceID] = sign;
					//GetBasisDeterminantSign(
					//	Tangent.GetSafeNormal(), Bitangent.GetSafeNormal(), Normal.GetSafeNormal());
				}
			}
	
			// Texture Coordinates
			for (int32 TextureCoordinateIndex = 0; TextureCoordinateIndex < NumTexCoords;
				++TextureCoordinateIndex)
			{
				int32 TextureCoordIndex = TextureCoordinateRemapIndex[TextureCoordinateIndex];
				if (TextureCoordIndex == INDEX_NONE)
				{
					continue;
				}
				
				Simplygon::spRealArray TexCoords = GetTexCoords(TextureCoordinateIndex);

				tuple = TexCoords->GetTuple(VerticeIndex);
				const Simplygon::real* sgTextCoords = tuple.Data();

				VertexInstanceUVs.Set(VertexInstanceID, TextureCoordIndex,
					FVector2f(sgTextCoords[0], sgTextCoords[1]));
			}
		}

		OutMesh.CreatePolygon(PolygonGroupID, TriangleVertexInstanceIDs);
		/*const FPolygonID NewPolygonID = OutMesh.CreatePolygon(PolygonGroupID, TriangleVertexInstanceIDs);
		int32 NewTriangleIndex = OutMesh.GetPolygonTriangles(NewPolygonID).AddDefaulted();
		FMeshTriangle& NewTriangle = OutMesh.GetPolygonTriangles(NewPolygonID)[NewTriangleIndex];
		for (int32 Corner = 0; Corner < 3; ++Corner)
		{
			FVertexInstanceID VertexInstanceID = TriangleVertexInstanceIDs[Corner];
			NewTriangle.SetVertexInstanceID(Corner, VertexInstanceID);
		}*/
	}
}

bool FSimplygonDataConverter::AddSimplygonMaterials(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, const TArray<FFlattenMaterial>& InputMaterials, bool bReleaseInputMaterials)
{
	if (InputMaterials.Num() == 0)
	{
		UE_LOG(LogSimplygonDataConverter, Log, TEXT("Input meshes do not contain any materials. A proxy without material will be generated."));
		return false;
	}

	FCriticalSection TableMutex;
	Simplygon::spTextureTable TextureTable = Scene->GetTextureTable();
	Simplygon::spMaterialTable MaterialTable = Scene->GetMaterialTable();

	TArray<Simplygon::spMaterial> MaterialArray;
	MaterialArray.SetNum(InputMaterials.Num());
	//for(int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++)
	ParallelFor(InputMaterials.Num(), [&](int32 MaterialIndex)
	{
		const FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex];
		Simplygon::spMaterial SgMaterial = SDK->CreateMaterial();
		SgMaterial->SetName(TCHAR_TO_ANSI(*FString::Printf(TEXT("FlattenMaterial%d"), MaterialIndex)));
		
		// Make sure we don't have any pre-added channels
		RemoveAllMaterialChannels(SgMaterial);

		for (const auto& ChannelToProperty : FSimplygonMaterialChannelConverter::GetChannelToPropertyMap())
		{
			EFlattenMaterialProperties MaterialProperty = ChannelToProperty.Value;

			bool sRGB = true;
			if (MaterialProperty == EFlattenMaterialProperties::Normal 
				|| MaterialProperty == EFlattenMaterialProperties::Opacity 
				|| MaterialProperty == EFlattenMaterialProperties::OpacityMask)
				sRGB = false;

			bool bHasPropertyData = FlattenMaterial.DoesPropertyContainData(MaterialProperty);
			if (bHasPropertyData)
			{
				SetMaterialChannelData(
					SDK, 
					FlattenMaterial.GetPropertySamples(MaterialProperty), 
					FlattenMaterial.GetPropertySize(MaterialProperty), 
					SgMaterial, 
					TextureTable,
					TableMutex,
					TCHAR_TO_ANSI(*ChannelToProperty.Key), 
					sRGB,
					FlattenMaterial.EmissiveScale);
			}
			//SG-TODO: Do we need this special treatment?
			else if (!bHasPropertyData && (MaterialProperty == EFlattenMaterialProperties::Emissive))
			{
				TArray<FColor> BlackEmissive;
				BlackEmissive.AddZeroed(1);
				SetMaterialChannelData(SDK, BlackEmissive, FIntPoint(1, 1), SgMaterial, TextureTable, TableMutex, TCHAR_TO_ANSI(*ChannelToProperty.Key), sRGB, FlattenMaterial.EmissiveScale);
			}

			if (MaterialProperty == EFlattenMaterialProperties::OpacityMask)
			{
				SgMaterial->SetBlendMode(Simplygon::EMaterialBlendMode::Mask);
				SgMaterial->SetOpacityCutoff(1.0);
				SgMaterial->SetOpacityType(Simplygon::EOpacityType::Opacity);
			}
			if (MaterialProperty == EFlattenMaterialProperties::Opacity)
			{
				SgMaterial->SetBlendMode(Simplygon::EMaterialBlendMode::Blend);
				SgMaterial->SetOpacityType(Simplygon::EOpacityType::Opacity);
			}
		}

		MaterialArray[MaterialIndex] = SgMaterial;
	//}
	});

	for(int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++)
	{
		const FFlattenMaterial& FlattenMaterial = InputMaterials[MaterialIndex];

		MaterialTable->AddMaterial(MaterialArray[MaterialIndex]);

		if (bReleaseInputMaterials)
		{
			// Release FlattenMaterial. Using const_cast here to avoid removal of "const" from input data here
			// and above the call chain.
			const_cast<FFlattenMaterial*>(&FlattenMaterial)->ReleaseData();
		}
	}

	return true;
}

void FSimplygonDataConverter::AddDefaultDiffuseToMaterial(FFlattenMaterial& OutMaterial)
{
	const FIntPoint ConstantSize(1, 1);
	OutMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, ConstantSize);

	TArray<FColor>& BaseColorSamples = OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse);
	BaseColorSamples.Empty();
	BaseColorSamples.Add(FColor::Black);
}

bool FSimplygonDataConverter::CreateFlattenMaterial(Simplygon::spScene Scene, FFlattenMaterial& OutMaterial, int32 OutputMaterialIndex)
{
	bool bHasBakedMaterials = false;


	UE_LOG( LogSimplygonDataConverter, Warning, TEXT( "NumMaterials : %d" ), Scene->GetMaterialTable()->GetMaterialsCount() );

	const FString SimplygonCastMaterial = "SimplygonCastMaterial";
	for( auto i = 0U; i < Scene->GetMaterialTable()->GetMaterialsCount(); ++i ) {
		auto Material = Scene->GetMaterialTable()->GetMaterial( i );
		FString MaterialName = ANSI_TO_TCHAR( Material->GetName().c_str() );
		if( MaterialName.Equals(SimplygonCastMaterial) )
			OutputMaterialIndex = i;
	}

	Simplygon::spMaterial Material = Scene->GetMaterialTable()->GetMaterial(OutputMaterialIndex);
	if (Material.IsNull())
		return false;

	Simplygon::spTextureTable TextureTable = Scene->GetTextureTable();

	int32 NumMaterialChannels = Material->GetMaterialChannelCount();
	if (NumMaterialChannels <= 0)
		return false;

	bool bHasBaseColor = false;

	for (int32 ChannelIndex = 0; ChannelIndex < NumMaterialChannels; ++ChannelIndex)
	{
		Simplygon::spString MaterialChannelName = Material->GetMaterialChannelFromIndex(ChannelIndex);
		FString ChannelName = ANSI_TO_TCHAR(MaterialChannelName.c_str());

		EFlattenMaterialProperties MaterialProperty;
		if (!FSimplygonMaterialChannelConverter::MaterialChannelToProperty(ChannelName, MaterialProperty))
			continue;

		if (MaterialProperty == EFlattenMaterialProperties::Diffuse)
			bHasBaseColor = true;

		FIntPoint Size = FIntPoint::ZeroValue;

		GetMaterialChannelData(Material, TextureTable, MaterialChannelName, OutMaterial.GetPropertySamples(MaterialProperty), Size, OutMaterial.EmissiveScale);
		OutMaterial.SetPropertySize(MaterialProperty, Size);

		bHasBakedMaterials = true;
	}
	
	// Setup constant value if there is no texture data.
	// NOTE: When we convert to Unreal materials we rely on ProxyMaterialUtilities::CreateProxyMaterialInstance, 
	//       which expects BaseColor to have either texture data or constant value.
	if (!bHasBaseColor)
	{
		FIntPoint ConstantSize(1, 1);
		OutMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, ConstantSize);
		
		TArray<FColor>& BaseColorSamples = OutMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse);
		BaseColorSamples.Empty();
		BaseColorSamples.Add(FColor::Black);
	}

	return bHasBakedMaterials;
}

bool FSimplygonDataConverter::CreateGeometryDataTextures(Simplygon::spScene Scene, Simplygon::spPipeline Pipeline, const FString& BaseTextureName, TArray<UObject*>& AssetsToSync)
{
	bool bHasDataCasters = false;

	if(Scene->GetMaterialTable().GetMaterialsCount() <= 0)
		return false;

	Simplygon::spMaterial Material = Scene->GetMaterialTable()->GetMaterial(0);
	if (Material.IsNull())
		return false;

	Simplygon::spTextureTable TextureTable = Scene->GetTextureTable();

	int32 NumMaterialChannels = Material->GetMaterialChannelCount();
	if (NumMaterialChannels <= 0)
		return false;

	// Get casted geometry data channels from pipeline and use it for lookup below.
	const TArray<FString> GeometryCasterChannels = [&Pipeline]()
	{
		TArray<FString> MaterialChannels;

		Simplygon::spObjectCollection MaterialCasters = Pipeline.GetMaterialCasters();
		for (Simplygon::rhandle CasterHandle = MaterialCasters->GetFirstItem();
			 CasterHandle;
			 CasterHandle = MaterialCasters->GetNextItem(CasterHandle))
		{
			auto GeometryCaster = Simplygon::spGeometryDataCaster::SafeCast(MaterialCasters.GetItemsObject(CasterHandle));
			if (GeometryCaster.IsNull())
				continue;

			auto GeometryDataCasterSettings = GeometryCaster.GetGeometryDataCasterSettings();
			FString ChannelName = ANSI_TO_TCHAR(GeometryDataCasterSettings.GetMaterialChannel().c_str());
			MaterialChannels.Add((ChannelName));
		}
		return MaterialChannels;
	}();

	if (GeometryCasterChannels.Num() <= 0)
		return false;

	for(const FString& GeometryCasterChannel : GeometryCasterChannels)
	{
		FIntPoint Size = FIntPoint::ZeroValue;

		TArray<FColor> Samples8;
		TArray<FFloat16Color> Samples16;
		float DummyEmissiveScale;
		GetMaterialChannelData(Material, TextureTable, TCHAR_TO_ANSI(*GeometryCasterChannel), Samples8, Size, DummyEmissiveScale, &Samples16);
		
		const bool bHas16bitSamples = Samples16.Num() > 0;

		const FString SimplygonGeomDataTextureDir = FPaths::Combine(TEXT("/Game"), TEXT("Simplygon"), TEXT("LODRecipe"), TEXT("Materials"));
		const FString AbsoluteTextureName = FPaths::Combine(SimplygonGeomDataTextureDir, TEXT("T_") + BaseTextureName + TEXT("_") + GeometryCasterChannel);
		const FString TextureName = FPackageName::GetShortName(AbsoluteTextureName);

		UPackage* TexturePackage = CreatePackage(*AbsoluteTextureName);
		TexturePackage->FullyLoad();
		TexturePackage->Modify();

		ETextureSourceFormat TexSrcFormat = bHas16bitSamples ? TSF_RGBA16F : TSF_BGRA8;
		UTexture2D* GeometryDataTexture = NewObject<UTexture2D>(TexturePackage, *TextureName, RF_Public | RF_Standalone);
		GeometryDataTexture->Source.Init(Size.X, Size.Y, /*NumSlices=*/ 1, /*NumMips=*/ 1, TexSrcFormat);
		GeometryDataTexture->MipGenSettings = TMGS_NoMipmaps;
		GeometryDataTexture->SRGB = false;
		GeometryDataTexture->CompressionNone = true;
		GeometryDataTexture->LODGroup =  bHas16bitSamples ? TEXTUREGROUP_16BitData : TEXTUREGROUP_8BitData;		

		// Copy data to texture
		const int32 TextureDataSize = GeometryDataTexture->Source.CalcMipSize(0);
		if (bHas16bitSamples)
		{
			check(TextureDataSize == Samples16.Num() * sizeof(FFloat16Color));
			FFloat16Color* TextureData = (FFloat16Color*)GeometryDataTexture->Source.LockMip(0);
			FMemory::Memcpy(TextureData, Samples16.GetData(), TextureDataSize);
		}
		else
		{
			check(TextureDataSize == Samples8.Num() * sizeof(FColor));
			FColor* TextureData = (FColor*)GeometryDataTexture->Source.LockMip(0);
			FMemory::Memcpy(TextureData, Samples8.GetData(), Samples8.Num() * sizeof(FColor));
		}

		GeometryDataTexture->Source.UnlockMip(0);
		GeometryDataTexture->PostEditChange();
		
		AssetsToSync.Add(GeometryDataTexture);

		bHasDataCasters = true;
	}

	return bHasDataCasters;
}

void FSimplygonDataConverter::CreateMaterialCasters( Simplygon::ISimplygon* SDK, Simplygon::spPipeline Pipeline, const TArray<FFlattenMaterial>& InputMaterials )
{
	bool bHasDiffuseCaster = false;
	bool bHasMetallicCaster = false;
	bool bHasSpecularCaster = false;
	bool bHasRoughnessCaster = false;
	bool bHasAOCaster = false;
	bool bHasNormalCaster = false;
	bool bHasOpacityCaster = false;
	bool bHasOpacityMaskCaster = false;
	bool bHasEmissiveCaster = false;
	bool bHasSubsurfaceCaster = false;
	for (int32 MaterialIndex = 0; MaterialIndex < InputMaterials.Num(); MaterialIndex++)
	{
		auto& InputMaterial = InputMaterials[MaterialIndex];
		if (!bHasDiffuseCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Diffuse))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_BASECOLOR;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel( EFlattenMaterialProperties::Diffuse, ChannelName );
			Simplygon::spColorCaster diffuseCaster = SDK->CreateColorCaster();
			diffuseCaster->GetColorCasterSettings()->SetOutputSRGB( true );
			diffuseCaster->GetColorCasterSettings()->SetOutputPixelFormat( Simplygon::EPixelFormat::R8G8B8A8 );
			diffuseCaster->GetColorCasterSettings()->SetMaterialChannel( TCHAR_TO_ANSI(*ChannelName) );
			Pipeline->AddMaterialCaster( diffuseCaster, 0 );
			bHasDiffuseCaster = true;
		}
		if (!bHasMetallicCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Metallic))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_METALNESS;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel( EFlattenMaterialProperties::Metallic, ChannelName );
			Simplygon::spColorCaster metallicCaster = SDK->CreateColorCaster();
			metallicCaster->GetColorCasterSettings()->SetOutputSRGB( true );
			metallicCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8 );
			metallicCaster->GetColorCasterSettings()->SetMaterialChannel( TCHAR_TO_ANSI(*ChannelName) );
			Pipeline->AddMaterialCaster( metallicCaster, 0 );
			bHasMetallicCaster = true;
		}
		if (!bHasSpecularCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Specular))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_SPECULAR;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel( EFlattenMaterialProperties::Metallic, ChannelName );
			Simplygon::spColorCaster specularCaster = SDK->CreateColorCaster();
			specularCaster->GetColorCasterSettings()->SetOutputSRGB( true );
			specularCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8G8B8A8);
			specularCaster->GetColorCasterSettings()->SetMaterialChannel( TCHAR_TO_ANSI(*ChannelName) );
			Pipeline->AddMaterialCaster( specularCaster, 0 );
			bHasSpecularCaster = true;
		}
		if (!bHasRoughnessCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Roughness))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_ROUGHNESS;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel( EFlattenMaterialProperties::Roughness, ChannelName );
			Simplygon::spColorCaster roughnessCaster = SDK->CreateColorCaster();
			roughnessCaster->GetColorCasterSettings()->SetOutputSRGB( true );
			roughnessCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8);
			roughnessCaster->GetColorCasterSettings()->SetMaterialChannel( TCHAR_TO_ANSI(*ChannelName) );
			Pipeline->AddMaterialCaster( roughnessCaster, 0 );
			bHasRoughnessCaster = true;
		}
		if (!bHasAOCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::AmbientOcclusion))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_OCCLUSION;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel( EFlattenMaterialProperties::AmbientOcclusion, ChannelName );
			Simplygon::spColorCaster aoCaster = SDK->CreateColorCaster();
			aoCaster->GetColorCasterSettings()->SetOutputSRGB( true );
			aoCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8);
			aoCaster->GetColorCasterSettings()->SetMaterialChannel( TCHAR_TO_ANSI(*ChannelName) );
			Pipeline->AddMaterialCaster( aoCaster, 0 );
			bHasAOCaster = true;
		}
		if (!bHasNormalCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Normal))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_NORMALS;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel( EFlattenMaterialProperties::Normal, ChannelName );
			Simplygon::spNormalCaster normalCaster = SDK->CreateNormalCaster();
			normalCaster->GetNormalCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8G8B8);
			normalCaster->GetNormalCasterSettings()->SetMaterialChannel( TCHAR_TO_ANSI(*ChannelName) );
			normalCaster->GetNormalCasterSettings()->SetFlipBackfacingNormals( false );
			normalCaster->GetNormalCasterSettings()->SetGenerateTangentSpaceNormals( true );
			Pipeline->AddMaterialCaster( normalCaster, 0 );
			bHasNormalCaster = true;
		}
		if (!bHasOpacityCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Opacity))
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_OPACITY;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel( EFlattenMaterialProperties::Opacity, ChannelName );
			Simplygon::spColorCaster opacityCaster = SDK->CreateColorCaster();
			opacityCaster->GetColorCasterSettings()->SetOutputSRGB( false );
			opacityCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8G8B8A8);
			opacityCaster->GetColorCasterSettings()->SetMaterialChannel( TCHAR_TO_ANSI(*ChannelName) );
			Pipeline->AddMaterialCaster( opacityCaster, 0 );
			bHasOpacityCaster = true;
		}
		if (!bHasOpacityMaskCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::OpacityMask))
		{
			FString ChannelName = "OpacityMask";
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel( EFlattenMaterialProperties::OpacityMask, ChannelName );
			Simplygon::spColorCaster opacityCaster = SDK->CreateColorCaster();
			opacityCaster->GetColorCasterSettings()->SetOutputSRGB( false );
			opacityCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8);
			opacityCaster->GetColorCasterSettings()->SetMaterialChannel( TCHAR_TO_ANSI(*ChannelName) );
			Pipeline->AddMaterialCaster( opacityCaster, 0 );
			bHasOpacityMaskCaster = true;
		}
		if (!bHasEmissiveCaster && InputMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Num())
		{
			FString ChannelName = Simplygon::SG_MATERIAL_CHANNEL_EMISSIVE;
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel( EFlattenMaterialProperties::Emissive, ChannelName );
			Simplygon::spColorCaster emissiveCaster = SDK->CreateColorCaster();
			emissiveCaster->GetColorCasterSettings()->SetOutputSRGB( true );
			emissiveCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8G8B8A8);
			emissiveCaster->GetColorCasterSettings()->SetMaterialChannel( TCHAR_TO_ANSI(*ChannelName) );
			Pipeline->AddMaterialCaster( emissiveCaster, 0 );
			bHasEmissiveCaster = true;
		}
		if (!bHasSubsurfaceCaster && InputMaterial.DoesPropertyContainData(EFlattenMaterialProperties::SubSurface))
		{
			FString ChannelName = "SubSurface";
			FSimplygonMaterialChannelConverter::MaterialPropertyToChannel( EFlattenMaterialProperties::SubSurface, ChannelName );
			Simplygon::spColorCaster subsurfaceCaster = SDK->CreateColorCaster();
			subsurfaceCaster->GetColorCasterSettings()->SetOutputSRGB( true );
			subsurfaceCaster->GetColorCasterSettings()->SetOutputPixelFormat(Simplygon::EPixelFormat::R8G8B8A8);
			subsurfaceCaster->GetColorCasterSettings()->SetMaterialChannel( TCHAR_TO_ANSI(*ChannelName) );
			Pipeline->AddMaterialCaster( subsurfaceCaster, 0 );
			bHasSubsurfaceCaster = true;
		}
	}
}

void FSimplygonDataConverter::CreateSkeletalHierarchy(Simplygon::ISimplygon* SDK, Simplygon::spScene& Scene, const FReferenceSkeleton& Skeleton, TArray<Simplygon::rid>& OutBoneTableIDs)
	{
		TArray<Simplygon::spSceneBone> BoneArray;

		// Create Simplygon scene nodes for each bone in our Skeletal Hierarchy
		for (int32 BoneIndex = 0; BoneIndex < Skeleton.GetRawBoneNum(); ++BoneIndex)
		{
			Simplygon::spSceneBone SceneBone = SDK->CreateSceneBone();
			BoneArray.Add(SceneBone);
		}

		Simplygon::spSceneBoneTable BoneTable = Scene->GetBoneTable();
		for (int32 BoneIndex = 0; BoneIndex < Skeleton.GetRawBoneNum(); ++BoneIndex)
		{
			int32 ParentIndex = Skeleton.GetParentIndex(BoneIndex);
			Simplygon::spSceneBone CurrentBone = BoneArray[BoneIndex];

			/*if(InBoneTree[BoneIndex].bLockBone)
			{
			CurrentBone->SetLockFromBoneLOD(true);
			}

			if(InBoneTree[BoneIndex].bRemoveBone)
			{
			CurrentBone->SetForceBoneRemoval(true);
			}*/

			// We add the bone to the scene's bone table. This returns a uid that we must apply to the geometry
			// data.
			Simplygon::rid BoneID = BoneTable->AddBone(CurrentBone);
			OutBoneTableIDs.Add(BoneID);

			// Handle root bone. Add to Scene root.
			if (BoneIndex == 0)
			{
				Scene->GetRootNode()->AddChild(CurrentBone);
			}
			else
			{
				Simplygon::spSceneBone ParentBone = BoneArray[ParentIndex];
				ParentBone->AddChild(CurrentBone);
			}
		}
	}

Simplygon::spGeometryData FSimplygonDataConverter::CreateGeometryFromSkeletalLODModel(
	Simplygon::ISimplygon* SDK,
	Simplygon::spScene Scene,
	const FSkeletalMeshLODModel& LODModel,
	const TArray<Simplygon::rid>& BoneIDs,
	const TArray<FMatrix>& BoneMatrices,
	const int32 LODIndex)
{
	Simplygon::spGeometryData GeometryData = CreateGeometryFromSkeletalLODModel(
		SDK,
		LODModel,
		BoneIDs,
		BoneMatrices);

	// Create a new simplygon scene mesh object
	Simplygon::spSceneMesh Mesh = SDK->CreateSceneMesh();

	// Assign the geometry data to the mesh
	Mesh->SetGeometry(GeometryData);

	// Add Mesh to the scene
	Scene->GetRootNode()->AddChild(Mesh);

	return GeometryData;
}

	Simplygon::spGeometryData FSimplygonDataConverter::CreateGeometryFromSkeletalLODModel(
		Simplygon::ISimplygon* SDK,
		const FSkeletalMeshLODModel& LODModel,
		const TArray<Simplygon::rid>& BoneIDs,
		const TArray<FMatrix>& BoneMatrices,
		const TMap<int32, int32>& SectionIndexToMaterialIndex,
		const TArray<FBox2D>& TextureBounds,
		const TArray<FVector2f>& InTexCoords)
	{
		TArray<FSoftSkinVertex> Vertices;
		LODModel.GetVertices(Vertices);

		const uint32 VertexCount = LODModel.NumVertices;
		const uint32 TexCoordCount = LODModel.NumTexCoords;
		check(TexCoordCount <= MAX_TEXCOORDS);

		uint32 TriCount = 0;

		const uint32 SectionCount = (uint32)LODModel.Sections.Num();

		for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
		{
			TriCount += LODModel.Sections[SectionIndex].NumTriangles;
		}

		Simplygon::spGeometryData GeometryData = SDK->CreateGeometryData();
		GeometryData->SetVertexCount(VertexCount);
		GeometryData->SetTriangleCount(TriCount);

		// Per-vertex data.
		GeometryData->AddBoneWeights(MAX_TOTAL_INFLUENCES);
		Simplygon::spRealArray Positions = GeometryData->GetCoords();
		Simplygon::spRidArray BoneIds = GeometryData->GetBoneIds();
		Simplygon::spRealArray BoneWeights = GeometryData->GetBoneWeights();

		// Per-corner per-triangle data.
		Simplygon::spRidArray Indices = GeometryData->GetVertexIds();
		GeometryData->AddNormals();
		GeometryData->AddTangents(0);
		Simplygon::spRealArray Normals = GeometryData->GetNormals();
		Simplygon::spRealArray Tangents = GeometryData->GetTangents(0);
		Simplygon::spRealArray Bitangents = GeometryData->GetBitangents(0);

		Simplygon::spValueArray ColorValues = GeometryData->AddBaseTypeUserCornerField(Simplygon::EBaseTypes::TYPES_ID_UCHAR, SIMPLYGON_COLOR_CHANNEL, 4);
		check(ColorValues);
		Simplygon::spUnsignedCharArray Colors = Simplygon::spUnsignedCharArray::SafeCast(ColorValues);
		check(Colors);

		// Per-triangle data.
		GeometryData->AddMaterialIds();
		Simplygon::spRidArray MaterialIndices = GeometryData->GetMaterialIds();

		// Add per-vertex data. This data needs to be added per-chunk so that we can properly map bones.
		int32 PositionComponentIndex = 0;
		Simplygon::real* TransformedPositions = new Simplygon::real[VertexCount * 3];
		uint32 FirstVertex = 0;
		for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
		{
			const FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
			const uint32 LastVertex = FirstVertex + (uint32)Section.SoftVertices.Num();
			for (uint32 VertexIndex = FirstVertex; VertexIndex < LastVertex; ++VertexIndex)
			{
				FSoftSkinVertex& Vertex = Vertices[VertexIndex];
				Simplygon::rid VertexBoneIds[MAX_TOTAL_INFLUENCES];
				Simplygon::real VertexBoneWeights[MAX_TOTAL_INFLUENCES];
				// get blended matrix
				FMatrix BlendedMatrix = FMatrix::Identity;

				uint32 TotalInfluence = 0;
				for (uint32 InfluenceIndex = 0; InfluenceIndex < MAX_TOTAL_INFLUENCES; ++InfluenceIndex)
				{
					int32 BoneIndex = Vertex.InfluenceBones[InfluenceIndex];
					const uint8 BoneInfluence = Vertex.InfluenceWeights[InfluenceIndex];
					TotalInfluence += BoneInfluence;

					if (BoneInfluence > 0)
					{
						check(BoneIndex < Section.BoneMap.Num());
						uint32 BoneID = BoneIDs[Section.BoneMap[BoneIndex]];
						VertexBoneIds[InfluenceIndex] = BoneID;
						VertexBoneWeights[InfluenceIndex] = BoneInfluence / 255.0f;

						if (BoneMatrices.IsValidIndex(Section.BoneMap[BoneIndex]))
						{
							const FMatrix Matrix = BoneMatrices[Section.BoneMap[BoneIndex]];
							// calculate blended matrix
							if (InfluenceIndex == 0)
							{
								BlendedMatrix = (Matrix * VertexBoneWeights[InfluenceIndex]);
							}
							else
							{
								BlendedMatrix += (Matrix * VertexBoneWeights[InfluenceIndex]);
							}
						}
					}
					else
					{
						//Set BoneID and BoneWeight to -1 and 0 respectively as required by simplygon.
						VertexBoneIds[InfluenceIndex] = -1;
						VertexBoneWeights[InfluenceIndex] = 0;
					}
				}

				// transform position
				FVector WeightedVertex = BlendedMatrix.TransformVector(FVector(Vertex.Position.X, Vertex.Position.Y, Vertex.Position.Z));
				FVector WeightedTangentX = BlendedMatrix.TransformVector(FVector(Vertex.TangentX.X, Vertex.TangentX.Y, Vertex.TangentX.Z));
				FVector WeightedTangentY = BlendedMatrix.TransformVector(FVector(Vertex.TangentY.X, Vertex.TangentY.Y, Vertex.TangentY.Z));
				FVector4f TangentTemp = Vertex.TangentZ;
				FVector WeightedTangentZ = BlendedMatrix.TransformVector({
					TangentTemp.X,
					TangentTemp.Y,
					TangentTemp.Z});

				check(TotalInfluence == 255);
				Vertex.TangentX = FVector3f(WeightedTangentX.GetSafeNormal());
				Vertex.TangentY = FVector3f(WeightedTangentY.GetSafeNormal());
				uint8 WComponent = Vertex.TangentZ.W;
				Vertex.TangentZ = FVector3f(WeightedTangentZ.GetSafeNormal());
				Vertex.TangentZ.W = WComponent;

				FVector FinalVert = VectorToSimplygon(WeightedVertex);
				TransformedPositions[PositionComponentIndex++] = FinalVert.X;
				TransformedPositions[PositionComponentIndex++] = FinalVert.Y;
				TransformedPositions[PositionComponentIndex++] = FinalVert.Z;

				BoneIds->SetTuple(VertexIndex, VertexBoneIds);
				BoneWeights->SetTuple(VertexIndex, VertexBoneWeights);
			}
			FirstVertex = LastVertex;
		}
		Positions.SetData(TransformedPositions, VertexCount * 3);
		delete[] TransformedPositions;

		// Add per-vertex per-triangle data.
		Simplygon::rid* IndexData = new Simplygon::rid[Indices->GetItemCount()];
		Simplygon::rid* IndexComponent = IndexData;
		Simplygon::real* NormalData = new Simplygon::real[Normals->GetTupleCount() * 3];
		Simplygon::real* NormalComponent = NormalData;
		Simplygon::real* TangentData = new Simplygon::real[Tangents->GetTupleCount() * 3];
		Simplygon::real* TangentComponent = TangentData;
		Simplygon::real* BitangentData = new Simplygon::real[Bitangents->GetTupleCount() * 3];
		Simplygon::real* BitangentComponent = BitangentData;
		unsigned char* ColorData = new unsigned char[Colors->GetTupleCount() * 4];
		unsigned char* ColorComponent = ColorData;
		for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
		{
			const FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
			const uint32 FirstIndex = Section.BaseIndex;
			const uint32 LastIndex = FirstIndex + Section.NumTriangles * 3;

			for (uint32 Index = FirstIndex; Index < LastIndex; ++Index)
			{
				// Vertex Indices
				uint32 VertexIndex = LODModel.IndexBuffer[Index];
				*IndexComponent++ = VertexIndex;

				FSoftSkinVertex& Vertex = Vertices[VertexIndex];
				FVector4f TangentTemp = Vertex.TangentZ;

				// Normals
				FVector3f Normal = VectorToSimplygon<FVector3f>(FVector3f{TangentTemp.X, TangentTemp.Y, TangentTemp.Z});
				*NormalComponent++ = Normal.X;
				*NormalComponent++ = Normal.Y;
				*NormalComponent++ = Normal.Z;

				// Tangents
				FVector3f Tangent = VectorToSimplygon(Vertex.TangentX);
				*TangentComponent++ = Tangent.X;
				*TangentComponent++ = Tangent.Y;
				*TangentComponent++ = Tangent.Z;

				// Bitangents
				FVector3f Bitangent = VectorToSimplygon(Vertex.TangentY);
				*BitangentComponent++ = Bitangent.X;
				*BitangentComponent++ = Bitangent.Y;
				*BitangentComponent++ = Bitangent.Z;
				
				// Vertex Colors
				*ColorComponent++ = Vertex.Color.R;
				*ColorComponent++ = Vertex.Color.G;
				*ColorComponent++ = Vertex.Color.B;
				*ColorComponent++ = Vertex.Color.A;
			}
		}
		Indices->SetData(IndexData, Indices->GetItemCount());
		Normals->SetData(NormalData, Normals->GetItemCount());
		Tangents->SetData(TangentData, Tangents->GetItemCount());
		Bitangents->SetData(BitangentData, Bitangents->GetItemCount());
		Colors->SetData(ColorData, Colors->GetItemCount());
		delete[] IndexData;
		delete[] NormalData;
		delete[] TangentData;
		delete[] BitangentData;
		delete[] ColorData;

		auto AddTexCoords = [&GeometryData, &SectionCount, &LODModel, &Vertices](
			int32 TexCoordIndex,
			const TArray<FBox2D>& TextureBounds,
			const TArray<FVector2f>& CustomUVs)
		{
			bool bUseCustomUVs = (TexCoordIndex == SG_MATERIALBAKING_INDEX && CustomUVs.Num() > 0);

			GeometryData->AddTexCoords(TexCoordIndex);
			Simplygon::spRealArray TexCoords = GeometryData->GetTexCoords(TexCoordIndex);
			check(TexCoords->GetTupleSize() == 2);

			Simplygon::real* UVData = new Simplygon::real[TexCoords->GetTupleCount() * 2];
			Simplygon::real* UVComponent = UVData;
			for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
			{
				const FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
				const uint32 FirstIndex = Section.BaseIndex;
				const uint32 LastIndex = FirstIndex + Section.NumTriangles * 3;

				// Compute texture bounds for current material.
				float MinU = 0, ScaleU = 1;
				float MinV = 0, ScaleV = 1;
				if (TextureBounds.IsValidIndex(Section.MaterialIndex) && TexCoordIndex == 0 &&
					CustomUVs.Num() == 0)
				{
					const FBox2D& Bounds = TextureBounds[Section.MaterialIndex];
					if (Bounds.GetArea() > 0)
					{
						MinU = Bounds.Min.X;
						MinV = Bounds.Min.Y;
						ScaleU = 1.0f / (Bounds.Max.X - Bounds.Min.X);
						ScaleV = 1.0f / (Bounds.Max.Y - Bounds.Min.Y);
					}
				}

				for (uint32 Index = FirstIndex; Index < LastIndex; ++Index)
				{
					uint32 VertexIndex = LODModel.IndexBuffer[Index];
					FSoftSkinVertex& Vertex = Vertices[VertexIndex];

					const FVector2f& TexCoord = bUseCustomUVs ? CustomUVs[Index] : Vertex.UVs[TexCoordIndex];
					*UVComponent++ = (TexCoord.X - MinU) * ScaleU;
					*UVComponent++ = (TexCoord.Y - MinV) * ScaleV;
				}
			}
			TexCoords.SetData(UVData, TexCoords.GetTupleCount() * 2);
			delete[] UVData;
		};

		// Add texture coordinates (Material baking)
		if (InTexCoords.Num() > 0)
			AddTexCoords(SG_MATERIALBAKING_INDEX, TextureBounds, InTexCoords);

		// Add texture coordinates (Original)
		for (uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex)
		{
			AddTexCoords(TexCoordIndex, TextureBounds, InTexCoords);
		}

		// Add per-triangle data.
		uint32 TriangleCount = 0;
		for (uint32 SectionIndex = 0; SectionIndex < SectionCount; ++SectionIndex)
		{
			const FSkelMeshSection& Section = LODModel.Sections[SectionIndex];

			if (Section.NumTriangles == 0)
			{
				UE_LOG(LogSimplygonDataConverter, Warning, TEXT("Skipping section with zero triangles"));
				continue;
			}

			const uint32 FirstTriIndex = Section.BaseIndex / 3;
			const uint32 LastTriIndex = FirstTriIndex + Section.NumTriangles - 1;
			const uint16 MaterialIndex = SectionIndexToMaterialIndex.Contains(SectionIndex) ? SectionIndexToMaterialIndex[SectionIndex] : Section.MaterialIndex;
			for (uint32 TriIndex = FirstTriIndex; TriIndex <= LastTriIndex; ++TriIndex)
			{
				MaterialIndices->SetItem(TriangleCount, MaterialIndex);
				check(MaterialIndices->GetItem(TriangleCount) == MaterialIndex);
				TriangleCount++;
			}
		}

#if 0
		if (GetMutableDefault<USimplygonSettings>()->bPreserveOriginalSectionCount)
		{
			TMap<int32, int32> MatIdToTriId;

			Simplygon::spRidArray MaterialIds = GeometryData->GetMaterialIds();
			for (uint32 TriangleId = 0; TriangleId < MaterialIds->GetItemCount(); ++TriangleId)
			{
				int32 MatId = MaterialIds->GetItem(TriangleId);
				if (!MatIdToTriId.Contains(MatId))
				{
					MatIdToTriId.Add(MatId, TriangleId);
				}
			}

			GeometryData->AddVertexLocks();
			Simplygon::spBoolArray VertexLocks = GeometryData->GetVertexLocks();
			Simplygon::spRidArray VertexIds = GeometryData->GetVertexIds();
			for (int32 i = 0; i < MatIdToTriId.Num(); ++i)
			{
				//Use triangle id as an offset into vertex ids.
				int32 Offset = MatIdToTriId[i];
				for (int32 j = 0; j < 3; ++j)
				{
					int32 VertexId = VertexIds->GetItem(Offset * 3 + j);
					VertexLocks->SetItem(VertexId, true);
				}
			}
		}
#endif
		return GeometryData;
	}

	void FSimplygonDataConverter::CreateSkeletalLODModelFromGeometry(
		FSkeletalMeshLODModel* NewModel,
		const Simplygon::spGeometryData& GeometryData, 
		USkeletalMesh* SkeletalMesh,
		const int32 LODIndex,
		const bool bHasBakedMaterials,
		const bool bCreateMorphTargets,
		const TSet<TSoftObjectPtr<UMorphTarget>>* MorphTargetsToRemove)
	{
		check(GeometryData);

		FSkeletalMeshData MeshData;
		ExtractSkeletalDataFromGeometry(GeometryData, MeshData, bHasBakedMaterials);

		TArray<FVector3f>& Vertices = MeshData.Points;
		for (int32 VertexIndex = 0; VertexIndex < MeshData.Points.Num(); ++VertexIndex)
		{
			Vertices[VertexIndex] = VectorFromSimplygon(Vertices[VertexIndex]);
		}

		for (int32 FaceIndex = 0; FaceIndex < MeshData.Faces.Num(); ++FaceIndex)
		{
			SkeletalMeshImportData::FMeshFace& Face = MeshData.Faces[FaceIndex];
			for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
			{
				Face.TangentX[CornerIndex] = FSimplygonDataConverter::VectorFromSimplygon(Face.TangentX[CornerIndex]);
				Face.TangentY[CornerIndex] = FSimplygonDataConverter::VectorFromSimplygon(Face.TangentY[CornerIndex]);
				Face.TangentZ[CornerIndex] = FSimplygonDataConverter::VectorFromSimplygon(Face.TangentZ[CornerIndex]);
			}
		}

		// Create a map of 'point to original'
		// Needed in order to create morph targets from GeometryData
		TArray<int32> VertexToOriginalVertex;
		VertexToOriginalVertex.AddUninitialized(MeshData.Points.Num());
		for (int32 PointIdx = 0; PointIdx < MeshData.Points.Num(); PointIdx++)
		{
			VertexToOriginalVertex[PointIdx] = PointIdx;
		}

		// Make sure we do not recalculate normals
		IMeshUtilities::MeshBuildOptions Options;
		Options.bComputeNormals = false;
		Options.bComputeTangents = false;
		IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities");

		// Create skinning streams for NewModel.
		MeshUtilities.BuildSkeletalMesh(*NewModel, SkeletalMesh->GetName(), SkeletalMesh->GetRefSkeleton(), MeshData.Influences, MeshData.Wedges, MeshData.Faces, MeshData.Points, VertexToOriginalVertex, Options);

		NewModel->SyncronizeUserSectionsDataArray();

		// Set texture coordinate count on the new model.
		NewModel->NumTexCoords = MeshData.TexCoordCount;

		if (bCreateMorphTargets)
		{
			CreateMorphTargetsFromGeometry(SkeletalMesh->GetMorphTargets(), GeometryData, LODIndex, NewModel, MeshData.MorphTargetIgnoreList, MorphTargetsToRemove);
		}
	}

	static Simplygon::spGeometryData ExtractGeometryForMaterial(Simplygon::ISimplygon* API, Simplygon::spGeometryData Source, int32 MaterialIndex)
	{
		auto MaterialsIds = Source->GetMaterialIds();
		const int32 TrinagleCount = Source->GetTriangleCount();
		auto Triangles = Source->GetTriangles();
		Simplygon::spGeometryData SubSectionGeometry = API->CreateGeometryData();

		Simplygon::spRidArray TriangleIds = API->CreateRidArray();
		for (auto TriangleIndex = 0; TriangleIndex < TrinagleCount; ++TriangleIndex)
		{
			if (MaterialsIds->GetItem(TriangleIndex) == MaterialIndex)
				TriangleIds->AddItem(TriangleIndex);
		}

		Source->ExtractTriangles(SubSectionGeometry, TriangleIds);
	    SubSectionGeometry->Compact( true );

		for (auto Index = 0U; Index < SubSectionGeometry->GetMaterialIds()->GetItemCount(); ++Index)
			SubSectionGeometry->GetMaterialIds()->SetItem(Index, 0);

		return SubSectionGeometry;
	}

	void FSimplygonDataConverter::PackChartsUsingChartAggregator(
		Simplygon::ISimplygon* SDK,
		Simplygon::spGeometryData GeometryData,
		const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
		uint32 TexCoordIndex,
		FAddMeshMaterialInfoResult& MaterialInfo,
		TArray<FVector2D>& OutTexCoords)
	{
		// Find empty texcoord level
		if (GeometryData->GetTexCoords(TexCoordIndex).IsNull())
		{
			GeometryData->AddTexCoords(TexCoordIndex);
		}

		TArray<int32> UniqueMaterialIds;
		Simplygon::spRidArray MaterialIds = GeometryData->GetMaterialIds();
		int32 NumMaterialIds = MaterialIds->GetItemCount();
		for (int32 MatIndex = 0; MatIndex < NumMaterialIds; ++MatIndex)
		{
			UniqueMaterialIds.AddUnique(MaterialIds->GetItem(MatIndex));
		}

		bool bSuccess = false;
		int32 NumDummyMaterialToAdd = FMath::Max(UniqueMaterialIds) + 1;
		Simplygon::spMaterialTable MaterialTable = SDK->CreateMaterialTable();

		Simplygon::spChartAggregator ChartAggregator = SDK->CreateChartAggregator();
	    
		
		ChartAggregator->SetChartAggregatorMode((Simplygon::EChartAggregatorMode)FlattenMaterialSettings.ChartAggregatorSettings.ChartAggregatorMode);
		ChartAggregator->SetSurfaceAreaScale((Simplygon::ESurfaceAreaScale)FlattenMaterialSettings.ChartAggregatorSettings.SurfaceAreaScale);
		ChartAggregator->SetLockUVRotation(FlattenMaterialSettings.ChartAggregatorSettings.LockUVRotation);
		ChartAggregator->SetTexCoordLevel(0);
		ChartAggregator->SetUseVertexWeights(false);
		ChartAggregator->SetTextureWidth(FlattenMaterialSettings.TextureSize.X);
		ChartAggregator->SetTextureHeight(FlattenMaterialSettings.TextureSize.Y);
		ChartAggregator->SetGutterSpace(FlattenMaterialSettings.GutterSpace);

		// setup user fields for tracking and remapping
		Simplygon::spRidArray OriginalCornerIds = Simplygon::spRidArray::SafeCast(
		GeometryData->AddBaseTypeUserCornerField( Simplygon::EBaseTypes::TYPES_ID_RID, SG_USER_CORNER_FIELD_CORNERIDS, 1 ) );

	    for( auto TriId = 0U; TriId < GeometryData->GetTriangleCount(); ++TriId )
	    {
		    OriginalCornerIds->SetItem( TriId * 3 + 0, TriId * 3 + 0 );
		    OriginalCornerIds->SetItem( TriId * 3 + 1, TriId * 3 + 1 );
		    OriginalCornerIds->SetItem( TriId * 3 + 2, TriId * 3 + 2 );
	    }

	    Simplygon::spGeometryData MergedGeometry = SDK->CreateGeometryData();

	    // Note: We split the geometry and repack the charts to get better texture usage.
	    // As the flatten material process only takes the sub geometry into account.
	    for( auto MaterialId : UniqueMaterialIds )
	    {
		    MaterialTable->Clear();
		    Simplygon::spMaterial material = SDK->CreateMaterial();
		    MaterialTable->AddMaterial( material );
		    auto SubGeometry = ExtractGeometryForMaterial( SDK, GeometryData, MaterialId );

		    ChartAggregator->SetSeparateOverlappingCharts( MaterialInfo.MaterialHasVertexData[ MaterialId ] );
		    ChartAggregator->SetMaterialTable( MaterialTable );
		    bSuccess = ChartAggregator->Parameterize( SubGeometry, SubGeometry->GetTexCoords( TexCoordIndex ) );

		    if( !bSuccess )
		    {
			    UE_LOG( LogSimplygonDataConverter, Error, TEXT( "Failed to create a valid Simplygon TexCoordGenerator." ) );
			    return;
		    }

		    MergedGeometry->AppendGeometry( SubGeometry );
	    }

		//fetch the cornerids for remapping
		OriginalCornerIds = Simplygon::spRidArray::SafeCast( MergedGeometry->GetUserCornerField( SG_USER_CORNER_FIELD_CORNERIDS ) );

	    if( OriginalCornerIds.IsNull() )
	    {
		    UE_LOG( LogSimplygonDataConverter, Error, TEXT( "Remapping UVs to original geometry requires original corner ids." ) );
		    return;
	    }

	    GeometryData->GetTexCoords( TexCoordIndex )->IndexedCopy( MergedGeometry->GetTexCoords( TexCoordIndex ), OriginalCornerIds, 0 );
	   			
		int32 NumCorners = GeometryData->GetTriangleCount() * 3;

		OutTexCoords.Empty(NumCorners);
		OutTexCoords.AddUninitialized(NumCorners);
		Simplygon::spRealArray NewTexcoords = GeometryData->GetTexCoords(TexCoordIndex);
		for (int32 WedgeIndex = 0; WedgeIndex < NumCorners; ++WedgeIndex)
		{
			Simplygon::spRealData DestTexCoord = NewTexcoords->GetTuple(WedgeIndex);
			const Simplygon::real* sgTexCoords = DestTexCoord.Data();

			OutTexCoords[WedgeIndex].X = sgTexCoords[0];
			OutTexCoords[WedgeIndex].Y = sgTexCoords[1];
		}
	}


	void FSimplygonDataConverter::GenerateTextureCoordinates(
        Simplygon::ISimplygon* SDK,
        Simplygon::spGeometryData GeometryData,
        uint32 TexCoordIndex,
        Simplygon::spObject TextureCoordinateGenerator,
        TArray<FVector2D>& OutTexCoords)
    {
	    
	    // Find empty texcoord level
	    if( GeometryData->GetTexCoords( TexCoordIndex ).IsNull() )
	    {
		    GeometryData->AddTexCoords( TexCoordIndex );
	    }

	    TArray<int32> UniqueMaterialIds;
	    Simplygon::spRidArray MaterialIds = GeometryData->GetMaterialIds();
	    int32 NumMaterialIds = MaterialIds->GetItemCount();
	    for( int32 MatIndex = 0; MatIndex < NumMaterialIds; ++MatIndex )
	    {
		    UniqueMaterialIds.AddUnique( MaterialIds->GetItem( MatIndex ) );
	    }


		bool bSuccess = false;
	    int32 NumDummyMaterialToAdd = FMath::Max( UniqueMaterialIds ) + 1;
	    Simplygon::spMaterialTable MaterialTable = SDK->CreateMaterialTable();

		
		for (int32 MaterialIndex = 0; MaterialIndex < NumDummyMaterialToAdd; ++MaterialIndex)
		{
			Simplygon::spMaterial material = SDK->CreateMaterial();
			MaterialTable->AddMaterial(material);
		}

		Simplygon::spChartAggregator ChartAggregator = Simplygon::spChartAggregator::SafeCast(TextureCoordinateGenerator);
		Simplygon::spParameterizer Paramaterizer = Simplygon::spParameterizer::SafeCast(TextureCoordinateGenerator);
		check(ChartAggregator || Paramaterizer)

		if (ChartAggregator)
		{
			ChartAggregator->SetMaterialTable(MaterialTable);
			bSuccess = ChartAggregator->Parameterize(GeometryData, GeometryData->GetTexCoords(TexCoordIndex));
		}
		else if (Paramaterizer) { bSuccess = Paramaterizer->Parameterize(GeometryData, GeometryData->GetTexCoords(TexCoordIndex)); }

		if (!bSuccess)
		{
			return;
		}

		int32 NumCorners = GeometryData->GetTriangleCount() * 3;

		OutTexCoords.Empty(NumCorners);
		OutTexCoords.AddUninitialized(NumCorners);
		Simplygon::spRealArray NewTexcoords = GeometryData->GetTexCoords(TexCoordIndex);
		for (int32 WedgeIndex = 0; WedgeIndex < NumCorners; ++WedgeIndex)
		{
			Simplygon::spRealData DestTexCoord = NewTexcoords->GetTuple(WedgeIndex);
			const Simplygon::real* sgTexCoords = DestTexCoord.Data();

			OutTexCoords[WedgeIndex].X = sgTexCoords[0];
			OutTexCoords[WedgeIndex].Y = sgTexCoords[1];
		}
	   
	    
	    
	}


static Simplygon::spImageData GetImageData(
	const Simplygon::spMaterial& InSGMaterial, 
	const char* SGMaterialChannelName,
	const Simplygon::spTextureTable& TextureTable)
{
	if (!InSGMaterial->HasMaterialChannel(SGMaterialChannelName))
	{
		return Simplygon::NullPtr;
	}

	Simplygon::spShadingNode ExitNode = InSGMaterial->GetShadingNetwork(SGMaterialChannelName);
	if (ExitNode.IsNull())
	{
		// Current channel has not been baked
		return Simplygon::NullPtr;
	}

	Simplygon::spShadingColorNode ColorNode = Simplygon::spShadingColorNode::SafeCast( ExitNode );
	if( !ColorNode.IsNull() )
	{
		UE_LOG( LogSimplygonDataConverter, Warning, TEXT( "Baked Material has ColorNode on %s channel." ), ANSI_TO_TCHAR(SGMaterialChannelName) );
		return Simplygon::NullPtr;
	}

	Simplygon::spShadingTextureNode TextureNode = Simplygon::spShadingTextureNode::SafeCast(ExitNode);
	if (TextureNode.IsNull())
	{
		// Current channel is not a texture
		UE_LOG( LogSimplygonDataConverter, Warning, TEXT( "Baked Material has no texture for %s channel." ), ANSI_TO_TCHAR(SGMaterialChannelName) );
		return Simplygon::NullPtr;
	}
	// Names are based on guid, so this should be safe...
	Simplygon::rid TextureId = TextureTable->FindTextureId(TextureNode->GetTextureName()); 
	Simplygon::spTexture Texture = TextureTable->GetTexture(TextureId);
	if (!Texture)
	{
		// Did not find texture
		return Simplygon::NullPtr;
	}

	Simplygon::spImageData TextureImageData = Texture->GetImageData();
	if (TextureImageData.IsNull())
	{
		Simplygon::NullPtr;
	}

	return TextureImageData;
}

void FSimplygonDataConverter::GetMaterialChannelData(
	const Simplygon::spMaterial& InSGMaterial, 
	const Simplygon::spTextureTable& TextureTable, 
	const char* SGMaterialChannelName, 
	TArray<FColor>& OutSamples, 
	FIntPoint& OutTextureSize,
	float& OutEmissiveScale,
	TArray<FFloat16Color>* OutSamples16)
{
	Simplygon::spImageData TextureImageData = GetImageData(InSGMaterial, SGMaterialChannelName, TextureTable);
	if (TextureImageData.IsNull())
		return;

	EFlattenMaterialProperties FlattenProperty;
	FSimplygonMaterialChannelConverter::MaterialChannelToProperty(SGMaterialChannelName, FlattenProperty);

	OutTextureSize.X = TextureImageData->GetXSize();
	OutTextureSize.Y = TextureImageData->GetYSize();

	int32 TexelsCount = OutTextureSize.X * OutTextureSize.Y;
	OutSamples.Empty(TexelsCount);
	OutSamples.AddUninitialized(TexelsCount);
	
	if (OutSamples16)
	{
		OutSamples16->Empty(TexelsCount);
	}

	auto HasAlphaChannel = [&SGMaterialChannelName, &FlattenProperty]()
	{
		return SGMaterialChannelName == Simplygon::SG_MATERIAL_CHANNEL_DIFFUSE ||
			SGMaterialChannelName == Simplygon::SG_MATERIAL_CHANNEL_BASECOLOR ||
			FlattenProperty == EFlattenMaterialProperties::Diffuse;
	};

	if (FlattenProperty == EFlattenMaterialProperties::Emissive)
	{
		Simplygon::spUnsignedShortArray ImageColors = Simplygon::spUnsignedShortArray::SafeCast(TextureImageData->GetColors());

		Simplygon::spUnsignedShortData ImageData = ImageColors->GetData();
		const uint16* ColorData = ImageData.Data();
		const unsigned int TupleSize = ImageColors->GetTupleSize();
		float MaxValue = 0.0f;
		for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex, ColorData += TupleSize)
			MaxValue = FMath::Max(MaxValue, FMath::Max3((float)ColorData[0], (float)ColorData[1], (float)ColorData[2]));
		OutEmissiveScale = MaxValue / 255.0f;
			
		float Scale = 255.0f / MaxValue;
		ColorData = ImageData.Data();
		for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex, ColorData += TupleSize)
		{
			OutSamples[TexelIndex].R = (uint8)FMath::RoundToInt((float)ColorData[0] * Scale);
			OutSamples[TexelIndex].G = (uint8)FMath::RoundToInt((float)ColorData[1] * Scale);
			OutSamples[TexelIndex].B = (uint8)FMath::RoundToInt((float)ColorData[2] * Scale);
		}
	}
	else
	{
		switch (TextureImageData->GetColorsValueType())
		{
			// 16-bit
			case Simplygon::EBaseTypes::TYPES_ID_USHORT:
			{
				OutSamples16->AddUninitialized(TexelsCount);
				Simplygon::spUnsignedShortArray ImageColors16 = Simplygon::spUnsignedShortArray::SafeCast(TextureImageData->GetColors());
				Simplygon::spUnsignedShortData ImageData16 = ImageColors16->GetData();
				const uint16* ColorData16 = ImageData16.Data();
				const unsigned int TupleSize = ImageColors16->GetTupleSize();
				
				for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex, ColorData16 += TupleSize)
				{
					(*OutSamples16)[TexelIndex].R = (float)ColorData16[0] / (float)UINT16_MAX;
					(*OutSamples16)[TexelIndex].G = (float)ColorData16[1] / (float)UINT16_MAX;
					(*OutSamples16)[TexelIndex].B = (float)ColorData16[2] / (float)UINT16_MAX;
					(*OutSamples16)[TexelIndex].A = HasAlphaChannel() ? ((float)ColorData16[3] / (float)UINT16_MAX) : FLinearColor::White.A;
				}
				break;
			}
			// 32-bit
			case Simplygon::EBaseTypes::TYPES_ID_FLOAT:
			{
				OutSamples16->AddUninitialized(TexelsCount);
				Simplygon::spFloatArray ImageColors32 = Simplygon::spFloatArray::SafeCast(TextureImageData->GetColors());
				Simplygon::spFloatData ImageData32 = ImageColors32->GetData();
				const float* ColorData32 = ImageData32.Data();
				const unsigned int TupleSize = ImageColors32->GetTupleSize();

				for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex, ColorData32 += TupleSize)
				{
					(*OutSamples16)[TexelIndex].R = ColorData32[0];
					(*OutSamples16)[TexelIndex].G = ColorData32[1];
					(*OutSamples16)[TexelIndex].B = ColorData32[2];
					(*OutSamples16)[TexelIndex].A = HasAlphaChannel() ? ColorData32[3]: FLinearColor::White.A;
				}
				break;
			}
			// 8-bit
			default:
			{
				Simplygon::spUnsignedCharArray ImageColors8 = Simplygon::spUnsignedCharArray::SafeCast(TextureImageData->GetColors());
				Simplygon::spUnsignedCharData ImageData8 = ImageColors8->GetData();
				const uint8* ColorData8 = ImageData8.Data();
				const unsigned int TupleSize = ImageColors8->GetTupleSize();
				for (int32 TexelIndex = 0; TexelIndex < TexelsCount; ++TexelIndex, ColorData8 += TupleSize)
				{
					OutSamples[TexelIndex].R = ColorData8[0];
					OutSamples[TexelIndex].G = ColorData8[1];
					OutSamples[TexelIndex].B = ColorData8[2];
					OutSamples[TexelIndex].A = HasAlphaChannel() ? ColorData8[3] : FColor::White.A;
				}
				break;
			}
		}
	}
}

void FSimplygonDataConverter::SetMaterialChannelData(
	Simplygon::ISimplygon* SDK, 
	const TArray<FColor>& InSamples, 
	FIntPoint InTextureSize, 
	Simplygon::spMaterial& SgMaterial, 
	Simplygon::spTextureTable& TextureTable,
	FCriticalSection& TableMutex,
	const char* MaterialChannelName,
	bool sRGB,
	float EmissiveScale)
{
	// Add material channel to material
	if (!SgMaterial->HasMaterialChannel(MaterialChannelName))
		SgMaterial->AddMaterialChannel(MaterialChannelName);

	if (InSamples.Num() > 1)
	{
		EFlattenMaterialProperties FlattenProperty;
		FSimplygonMaterialChannelConverter::MaterialChannelToProperty(MaterialChannelName, FlattenProperty);

		int32 NumTexels = InTextureSize.X * InTextureSize.Y;
		Simplygon::spImageData ImageData = SDK->CreateImageData();

		auto HasAlphaChannel = [&MaterialChannelName, &FlattenProperty]()
		{
			return MaterialChannelName == Simplygon::SG_MATERIAL_CHANNEL_DIFFUSE ||
				MaterialChannelName == Simplygon::SG_MATERIAL_CHANNEL_BASECOLOR ||
				FlattenProperty == EFlattenMaterialProperties::Diffuse;
		};

		// 16bit color channel for Emissive so that we can recalculate emissive scale after casting
		if (FlattenProperty == EFlattenMaterialProperties::Emissive)
		{
			ImageData->AddColors(Simplygon::EBaseTypes::TYPES_ID_USHORT, Simplygon::SG_IMAGEDATA_FORMAT_RGB);
			ImageData->Set2DSize(InTextureSize.X, InTextureSize.Y);
			Simplygon::spUnsignedShortArray ImageColors = Simplygon::spUnsignedShortArray::SafeCast(ImageData->GetColors());

			ImageColors->SetTupleCount(NumTexels);
			check(NumTexels - 1 <= ImageColors->GetMaxTupleId())

			uint16* SwizzledBuffer = new uint16[NumTexels * 3];
			uint16* Texel = SwizzledBuffer;
			for (int32 TexelIndex = 0; TexelIndex < NumTexels; TexelIndex++)
			{
				*Texel++ = (uint16)FMath::RoundToInt((float)InSamples[TexelIndex].R * EmissiveScale);
				*Texel++ = (uint16)FMath::RoundToInt((float)InSamples[TexelIndex].G * EmissiveScale);
				*Texel++ = (uint16)FMath::RoundToInt((float)InSamples[TexelIndex].B * EmissiveScale);
			}

			ImageColors->SetData(SwizzledBuffer, NumTexels * 3);
			delete[] SwizzledBuffer;
		}
		else if (HasAlphaChannel())
		{
			ImageData->AddColors(Simplygon::EBaseTypes::TYPES_ID_UCHAR, Simplygon::SG_IMAGEDATA_FORMAT_RGBA);
			ImageData->Set2DSize(InTextureSize.X, InTextureSize.Y);
			Simplygon::spUnsignedCharArray ImageColors = Simplygon::spUnsignedCharArray::SafeCast(ImageData->GetColors());

			ImageColors->SetTupleCount(NumTexels);
			check(NumTexels - 1 <= ImageColors->GetMaxTupleId())

			uint8* SwizzledBuffer = new uint8[NumTexels * 4];
			uint8* Texel = SwizzledBuffer;
			for (int32 TexelIndex = 0; TexelIndex < NumTexels; TexelIndex++)
			{
				*Texel++ = InSamples[TexelIndex].R;
				*Texel++ = InSamples[TexelIndex].G;
				*Texel++ = InSamples[TexelIndex].B;
				*Texel++ = InSamples[TexelIndex].A;
			}

			ImageColors->SetData(SwizzledBuffer, NumTexels * 4);
			delete[] SwizzledBuffer;
		}
		else
		{
			ImageData->AddColors(Simplygon::EBaseTypes::TYPES_ID_UCHAR, Simplygon::SG_IMAGEDATA_FORMAT_RGB);
			ImageData->Set2DSize(InTextureSize.X, InTextureSize.Y);
			Simplygon::spUnsignedCharArray ImageColors = Simplygon::spUnsignedCharArray::SafeCast(ImageData->GetColors());

			ImageColors->SetTupleCount(NumTexels);
			check(NumTexels - 1 <= ImageColors->GetMaxTupleId())

			uint8* SwizzledBuffer = new uint8[NumTexels * 3];
			uint8* Texel = SwizzledBuffer;
			for (int32 TexelIndex = 0; TexelIndex < NumTexels; TexelIndex++)
			{
				*Texel++ = InSamples[TexelIndex].R;
				*Texel++ = InSamples[TexelIndex].G;
				*Texel++ = InSamples[TexelIndex].B;
			}

			ImageColors->SetData(SwizzledBuffer, NumTexels * 3);
			delete[] SwizzledBuffer;
		}

		/*
		{
			auto exporter = SDK->CreateImageDataExporter();
			exporter->SetExportFilePath( "D:\\flattened.bmp" );
			exporter->SetImage( ImageData );
			exporter->RunExport();
		}
		*/
		// Need to add a texture to the texture table in order to be able
		// to reference it in the shading network.
		FGuid TextureGuid = FGuid::NewGuid();
		Simplygon::spTexture Texture = SDK->CreateTexture();
#ifndef DEBUG_PROXY_MESH
		Texture->SetImageData(ImageData);
#else
		// Texture->SetFilePath(TCHAR_TO_ANSI(*TextureOutputPath));
#endif
		Texture->SetName(TCHAR_TO_ANSI(*TextureGuid.ToString()));
		TableMutex.Lock();
		TextureTable->AddTexture(Texture);
		TableMutex.Unlock();

		// Create shading network with one texture node
		Simplygon::spShadingTextureNode TextureNode = SDK->CreateShadingTextureNode();
		TextureNode->SetTexCoordLevel(SG_MATERIALBAKING_INDEX);
		TextureNode->SetTextureName(TCHAR_TO_ANSI(*TextureGuid.ToString()));
		TextureNode->SetUseSRGB(sRGB);
		SgMaterial->SetShadingNetwork(MaterialChannelName, TextureNode);
	}
	else if (InSamples.Num() == 1)
	{
		Simplygon::spShadingColorNode ColorNode = SDK->CreateShadingColorNode();
		// Handle uniform value
		if (sRGB)
		{
			const FLinearColor& LinearColor = FLinearColor::FromSRGBColor(InSamples[0]);
			
			ColorNode->SetColor(LinearColor.R, LinearColor.G, LinearColor.B, 1.0f);
		}
		else
		{
			ColorNode->SetColor(InSamples[0].R / 255.f, InSamples[0].G / 255.f, InSamples[0].B/255.f, 1.0f);
		}
	
		SgMaterial->SetShadingNetwork(MaterialChannelName, ColorNode);
	}
	else
	{
		Simplygon::spShadingColorNode ColorNode = SDK->CreateShadingColorNode();
		ColorNode->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
		SgMaterial->SetShadingNetwork(MaterialChannelName, ColorNode);
	}
}

	void FSimplygonDataConverter::ExtractSkeletalDataFromGeometry(const Simplygon::spGeometryData& GeometryData, FSkeletalMeshData& MeshData, bool bHasBakedMaterials)
	{
		TArray<FVector> PointNormals;
		TArray<uint32> PointList;
		TArray<uint32> PointInfluenceMap;

		check(GeometryData);

		Simplygon::spRealArray Positions = GeometryData->GetCoords();
		Simplygon::spRidArray Indices = GeometryData->GetVertexIds();
		Simplygon::spRidArray MaterialIndices = GeometryData->GetMaterialIds();
		Simplygon::spValueArray VertexColorValues = GeometryData->GetUserCornerField(SIMPLYGON_COLOR_CHANNEL);
		Simplygon::spUnsignedCharArray VertexColors = Simplygon::spUnsignedCharArray::SafeCast(VertexColorValues);
		Simplygon::spRealArray Normals = GeometryData->GetNormals();
		Simplygon::spRealArray Tangents = bHasBakedMaterials ? GeometryData->GetTangents(SG_MATERIALBAKING_INDEX) : GeometryData->GetTangents(0);
		Simplygon::spRealArray Bitangents = bHasBakedMaterials ? GeometryData->GetBitangents(SG_MATERIALBAKING_INDEX) : GeometryData->GetBitangents(0);
		Simplygon::spRidArray BoneIds = GeometryData->GetBoneIds();
		Simplygon::spRealArray BoneWeights = GeometryData->GetBoneWeights();
		Simplygon::spRealArray TexCoords[MAX_TEXCOORDS];
		Simplygon::spRealArray CustomTexCoordsFromFlatten = GeometryData->GetTexCoords(SG_MATERIALBAKING_INDEX);
		bool bHasCustomUVs = bHasBakedMaterials && CustomTexCoordsFromFlatten != nullptr;

		uint32 TexCoordCount = 0;
		for (uint32 TexCoordIndex = 0; TexCoordIndex < MAX_TEXCOORDS; ++TexCoordIndex)
		{
			if (bHasCustomUVs && TexCoordIndex == 0)
				TexCoords[TexCoordIndex] = CustomTexCoordsFromFlatten;
			else
				TexCoords[TexCoordIndex] = GeometryData->GetTexCoords(TexCoordIndex);

			if (TexCoords[TexCoordIndex].IsNull())
				break;
			TexCoordCount++;
		}
		MeshData.TexCoordCount = TexCoordCount;

		check(Positions);
		check(Indices);
		check(Normals);
		check(Tangents);
		check(Bitangents);
		check(BoneIds);
		check(BoneWeights);

		const bool bHaveColors = (!VertexColors.IsNull());
		const uint32 VertexCount = GeometryData->GetVertexCount();
		const uint32 TriCount = GeometryData->GetTriangleCount();

		// Initialize the lists of points, wedges, and faces.
		MeshData.Points.AddZeroed(VertexCount);
		PointNormals.AddZeroed(VertexCount);
		PointList.Reserve(VertexCount);
		PointInfluenceMap.Reserve(VertexCount);
		for (uint32 PointIndex = 0; PointIndex < VertexCount; ++PointIndex)
		{
			PointList.Add(INDEX_NONE);
			PointInfluenceMap.Add(INDEX_NONE);
		}
		MeshData.Wedges.AddZeroed(TriCount * 3);
		MeshData.Faces.AddZeroed(TriCount);

		// The number of influences may have changed if we have specified a max number of bones per vertex.
		uint32 NumOfInfluences = FMath::Min<uint32>(BoneIds->GetTupleSize(), MAX_TOTAL_INFLUENCES);

		check(BoneWeights->GetTupleCount() == VertexCount);

		// Per-vertex data.
		for (uint32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex)
		{
			FVector3f& Point = MeshData.Points[VertexIndex];

			Simplygon::spRealData sgPositionData = Positions->GetTuple(VertexIndex);
			const Simplygon::real* sgPosition = sgPositionData.Data();
			Point = FVector3f(sgPosition[0], sgPosition[1], sgPosition[2]);

			Simplygon::spRidData sgBoneIdsData = BoneIds->GetTuple(VertexIndex);
			const Simplygon::rid* sgBoneId = sgBoneIdsData.Data();

			Simplygon::spRealData sgBoneWeightsData = BoneWeights->GetTuple(VertexIndex);
			const Simplygon::real* sgBoneWeights = sgBoneWeightsData.Data();

			PointInfluenceMap[VertexIndex] = (uint32)MeshData.Influences.Num();
			for (uint32 InfluenceIndex = 0; InfluenceIndex < NumOfInfluences; ++InfluenceIndex)
			{
				const uint16 BoneIndex = sgBoneId[InfluenceIndex];
				const float BoneWeight = sgBoneWeights[InfluenceIndex];

				if (BoneIndex >= 0 && BoneWeight > 0.0f)
				{
					SkeletalMeshImportData::FVertInfluence* VertInfluence =
						new(MeshData.Influences) SkeletalMeshImportData::FVertInfluence;
					VertInfluence->BoneIndex = BoneIndex;
					VertInfluence->Weight = BoneWeight;
					VertInfluence->VertIndex = VertexIndex;
				}
			}
		}

		// Per-triangle and per-corner data.
		for (uint32 TriIndex = 0; TriIndex < TriCount; ++TriIndex)
		{
			// Per-triangle data.
			SkeletalMeshImportData::FMeshFace& Face = MeshData.Faces[TriIndex];

			Face.MeshMaterialIndex = MaterialIndices ? MaterialIndices->GetItem(TriIndex) : 0;

			// Per-corner data.
			for (uint32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
			{
				const uint32 WedgeIndex = TriIndex * 3 + CornerIndex;
				const uint32 BasePointIndex = (uint32)Indices->GetItem(WedgeIndex);
				uint32 PointIndex = BasePointIndex;

				check(BasePointIndex < (uint32)PointList.Num());

				// Duplicate points where needed to create hard edges.
				FVector WedgeNormal;
				Simplygon::spRealData sgNormalData = Normals->GetTuple(WedgeIndex);
				const Simplygon::real* sgNormal = sgNormalData.Data();
				WedgeNormal = FVector(sgNormal[0], sgNormal[1], sgNormal[2]);
				WedgeNormal.Normalize();

				FVector WedgeTangent, WedgeBitangent;
				Simplygon::spRealData sgTangentData = Tangents->GetTuple(WedgeIndex);
				const Simplygon::real* sgTangent = sgTangentData.Data();
				WedgeTangent = FVector(sgTangent[0], sgTangent[1], sgTangent[2]);

				Simplygon::spRealData sgBitangentData = Bitangents->GetTuple(WedgeIndex);
				const Simplygon::real* sgBitangent = sgBitangentData.Data();
				WedgeBitangent = FVector(sgBitangent[0], sgBitangent[1], sgBitangent[2]);

				Face.TangentX[CornerIndex] = FVector3f(WedgeTangent);
				Face.TangentY[CornerIndex] = FVector3f(WedgeBitangent);
				Face.TangentZ[CornerIndex] = FVector3f(WedgeNormal);

#if USE_DEPRECATED_CUSTOM_SKMESH_HARDEDGE
				FVector PointNormal = PointNormals[PointIndex];
				if (PointNormal.SizeSquared() < KINDA_SMALL_NUMBER)
				{
					PointNormals[PointIndex] = WedgeNormal;
				}
				else
				{
					while ((PointNormal | WedgeNormal) - 1.0f < -KINDA_SMALL_NUMBER)
					{
						PointIndex = PointList[PointIndex];
						if (PointIndex == INDEX_NONE)
						{
							break;
						}
						check(PointIndex < (uint32)PointList.Num());
						PointNormal = PointNormals[PointIndex];
					}

					if (PointIndex == INDEX_NONE)
					{
						FVector Point = MeshData.Points[BasePointIndex];
						PointIndex = MeshData.Points.Add(Point);
						check(PointIndex == (uint32)PointList.Num());
						check(PointIndex == (uint32)PointInfluenceMap.Num());
						check(PointIndex == (uint32)PointNormals.Num());

						// This point should not be taking into account when re-applying morph targets to LOD,
						// since now the MeshData point count will missmatch GeometryData vertex count.
						MeshData.MorphTargetIgnoreList.Add(PointIndex);

						PointNormals.Add(WedgeNormal);
						uint32 NextPointIndex = PointList[BasePointIndex];
						check(NextPointIndex < (uint32)PointList.Num() || NextPointIndex == INDEX_NONE);
						PointList[BasePointIndex] = PointIndex;
						PointList.Add(NextPointIndex);
						PointInfluenceMap.Add((uint32)MeshData.Influences.Num());

						int32 InfluenceIndex = PointInfluenceMap[BasePointIndex];
						while (MeshData.Influences[InfluenceIndex].VertIndex == BasePointIndex)
						{
							SkeletalMeshImportData::FVertInfluence* NewVertInfluence =
								new(MeshData.Influences) SkeletalMeshImportData::FVertInfluence;
							NewVertInfluence->BoneIndex = MeshData.Influences[InfluenceIndex].BoneIndex;
							NewVertInfluence->Weight = MeshData.Influences[InfluenceIndex].Weight;
							NewVertInfluence->VertIndex = PointIndex;
							InfluenceIndex++;
						}

						check(PointNormals.Num() == MeshData.Points.Num());
						check(PointList.Num() == MeshData.Points.Num());
						check(PointInfluenceMap.Num() == MeshData.Points.Num());
					}
				}
#endif

				check(PointIndex != INDEX_NONE);
				check((MeshData.Points[PointIndex] - MeshData.Points[BasePointIndex]).SizeSquared() ==
					0.0f);

				SkeletalMeshImportData::FMeshWedge& Wedge = MeshData.Wedges[WedgeIndex];
				Wedge.iVertex = PointIndex;
				for (uint32 TexCoordIndex = 0; TexCoordIndex < TexCoordCount; ++TexCoordIndex)
				{
					Simplygon::spRealData sgTexCoordData = TexCoords[TexCoordIndex]->GetTuple(WedgeIndex);
					const Simplygon::real* sgTexCoord = sgTexCoordData.Data();
					Wedge.UVs[TexCoordIndex] = FVector2f(sgTexCoordData[0], sgTexCoordData[1]);
				}

				if (bHaveColors)
				{
					Simplygon::spUnsignedCharData sgVertexColorData = VertexColors->GetTuple(WedgeIndex);
					const uint8* sgColors = sgVertexColorData.Data();
					Wedge.Color = FColor(sgColors[0], sgColors[1], sgColors[2], sgColors[3]);
				}
				else
				{
					Wedge.Color = FColor(255, 255, 255, 255);
				}

				Face.iWedge[CornerIndex] = WedgeIndex;
			}
		}
	}

void FSimplygonDataConverter::CreateMorphFieldsFromSkeletalMesh(Simplygon::spGeometryData& GeometryData, const USkeletalMesh* SkeletalMesh, int32 BaseLODModel)
{
	if (SkeletalMesh->GetMorphTargets().Num() == 0)
	{
		return;
	}

	// Obtain vertices of original LODModel
	FSkeletalMeshModel* SkeletalMeshResource = SkeletalMesh->GetImportedModel();
	const FSkeletalMeshLODModel& BaseSourceLODModel = SkeletalMeshResource->LODModels[BaseLODModel];

	int32 NumVertices = BaseSourceLODModel.NumVertices;

	// Compute maximal morph distance for each vertex
	TArray<float> MaxMorphWeight;
	MaxMorphWeight.AddZeroed(NumVertices);
	for (int32 MorphIndex = 0; MorphIndex < SkeletalMesh->GetMorphTargets().Num(); MorphIndex++)
	{
		UMorphTarget* MorphTarget = SkeletalMesh->GetMorphTargets()[MorphIndex];
		if (MorphTarget)
		{

			int32 NumDeltas;
			const FMorphTargetDelta* Deltas = MorphTarget->GetMorphTargetDelta(BaseLODModel, NumDeltas);

			for (int32 MorphVertIdx = 0; MorphVertIdx < NumDeltas; MorphVertIdx++)
			{
				const FMorphTargetDelta& MorphVertex = Deltas[MorphVertIdx];
				float Distance = MorphVertex.PositionDelta.Size();
				int32 WedgeIndex = MorphVertex.SourceIdx;
				if (Distance > MaxMorphWeight[WedgeIndex])
				{
					MaxMorphWeight[WedgeIndex] = Distance;
				}
			}
		}
	}

	// Add vertex weights taking into account morph data
	GeometryData->AddVertexWeights();
	Simplygon::spRealArray VertexWeights = GeometryData->GetVertexWeights();
	for (int32 Index = 0; Index < NumVertices; Index++)
	{
		// Add vertex weight. 1.0 is default (normal) weight.
		VertexWeights->SetItem(Index, 1.0f + MaxMorphWeight[Index]);
	}

	for (int32 MorphIndex = 0; MorphIndex < SkeletalMesh->GetMorphTargets().Num(); MorphIndex++)
	{
		UMorphTarget* MorphTarget = SkeletalMesh->GetMorphTargets()[MorphIndex];
		if (MorphTarget)
		{
			// Get morph geometry
			int32 NumDeltas;
			const FMorphTargetDelta* Deltas = MorphTarget->GetMorphTargetDelta(BaseLODModel, NumDeltas);

			// Add vertex streams to GeometryData
			FString MorphVertexFieldName = FString::Printf(TEXT("MorphVertexField%d"), MorphIndex);
			FString MorphNormalFieldName = FString::Printf(TEXT("MorphNormalField%d"), MorphIndex);

			Simplygon::spRealArray MorphVertexField = Simplygon::spRealArray::SafeCast(GeometryData->AddBaseTypeUserVertexField(Simplygon::EBaseTypes::TYPES_ID_REAL, TCHAR_TO_ANSI(*MorphVertexFieldName), 3));
			Simplygon::spRealArray MorphNormalField = Simplygon::spRealArray::SafeCast(GeometryData->AddBaseTypeUserVertexField(Simplygon::EBaseTypes::TYPES_ID_REAL, TCHAR_TO_ANSI(*MorphNormalFieldName), 3));

			MorphVertexField->SetTupleCount(NumVertices);
			MorphNormalField->SetTupleCount(NumVertices);

			// Prepare wedge to morph vertex map for fast look up
			TArray<int32> VertexToMorphVert;
			VertexToMorphVert.AddUninitialized(NumVertices);
			for (int32 Index = 0; Index < NumVertices; Index++)
			{
				VertexToMorphVert[Index] = -1;
			}
			for (int32 MorphVertIdx = 0; MorphVertIdx < NumDeltas; MorphVertIdx++)
			{
				const FMorphTargetDelta& MorphVertex = Deltas[MorphVertIdx];
				VertexToMorphVert[MorphVertex.SourceIdx] = MorphVertIdx;
			}

			// FVertexAnimDelta is sparse array structure holding information about vertices which are
			// changed by morph.
			for (int32 VertexIndex = 0; VertexIndex < NumVertices; VertexIndex++)
			{
				int32 MorphVertIdx = VertexToMorphVert[VertexIndex];
				if (MorphVertIdx >= 0)
				{
					const FMorphTargetDelta& MorphVertex = Deltas[MorphVertIdx];
					check(MorphVertex.SourceIdx == VertexIndex);

					// Verify vertex index, just in case
					if (MorphVertex.SourceIdx < (uint32)NumVertices)
					{
						// Store delta information into custom fields
						MorphVertexField->SetTuple(VertexIndex, (float*)&MorphVertex.PositionDelta);
						MorphNormalField->SetTuple(VertexIndex, (float*)&MorphVertex.TangentZDelta);
					}
				}
			}
		}
	}
}

void FSimplygonDataConverter::CreateMorphTargetsFromGeometry(
	TArray<UMorphTarget*>& MorphTargets,
	const Simplygon::spGeometryData GeometryData,
	const int32 LODIndex,
	const FSkeletalMeshLODModel* LODModel,
	const TArray<int32> VerticesToSkip,
	const TSet<TSoftObjectPtr<UMorphTarget>>* MorphTargetsToRemove)
{
	int32 NumVertices = LODModel->NumVertices;

	for (int32 MorphIndex = 0; MorphIndex < MorphTargets.Num(); MorphIndex++)
	{
		UMorphTarget* MorphTarget = MorphTargets[MorphIndex];

		if (MorphTarget)
		{
			if (MorphTargetsToRemove != nullptr && MorphTargetsToRemove->Contains(MorphTarget))
				continue;

			// Get vertex streams from GeometryData
			FString MorphVertexFieldName = FString::Printf(TEXT("MorphVertexField%d"), MorphIndex);
			FString MorphNormalFieldName = FString::Printf(TEXT("MorphNormalField%d"), MorphIndex);

			Simplygon::spRealArray MorphVertexField = Simplygon::spRealArray::SafeCast(GeometryData->GetUserVertexField(TCHAR_TO_ANSI(*MorphVertexFieldName)));
			Simplygon::spRealArray MorphNormalField = Simplygon::spRealArray::SafeCast(GeometryData->GetUserVertexField(TCHAR_TO_ANSI(*MorphNormalFieldName)));

			if (MorphVertexField.IsNull() || MorphNormalField.IsNull())
			{
				UE_LOG(LogSimplygonDataConverter, Warning, TEXT("Cound not find following user vertex fields[%s,%s]. Currently morph targets are not taken into account when using the Aggregation/Remeshing pipelines."), *MorphVertexFieldName, *MorphNormalFieldName);
				continue;
			}

			uint32 MorphTargetVertexCount = MorphVertexField->GetTupleCount();
			uint32 MorphTargetNormalCount = MorphNormalField->GetTupleCount();
			
			TArray<FMorphTargetDelta> Deltas;
			Deltas.Reserve(NumVertices);

			// Read morph data from geometry.
			for (int32 VertexIndex = 0; VertexIndex < NumVertices; VertexIndex++)
			{
				// OriginalVertexIndex aka GeometryData vertex index
				int32 OriginalVertexIndex = LODModel->MeshToImportVertexMap[VertexIndex];
				if (VerticesToSkip.Contains(OriginalVertexIndex))
					continue;

				Deltas.Add(FMorphTargetDelta());
				FMorphTargetDelta& Delta = Deltas.Last();
				Delta.SourceIdx = VertexIndex;

				//Position delta
				Simplygon::spRealData MorphVertex = MorphVertexField->GetTuple(OriginalVertexIndex);
				const float* sgPositionDelta = MorphVertex.Data();
				Delta.PositionDelta = FVector3f(sgPositionDelta[0], sgPositionDelta[1], sgPositionDelta[2]);

				//Normal DeltaZ
				Simplygon::spRealData MorphNormal = MorphNormalField->GetTuple(OriginalVertexIndex);
				const float* sgNormalDeltaZ = MorphNormal.Data();
				Delta.TangentZDelta = FVector3f(sgNormalDeltaZ[0], sgNormalDeltaZ[1], sgNormalDeltaZ[2]);
			}

			MorphTarget->PopulateDeltas(Deltas, LODIndex, LODModel->Sections);
		}
	}
}

	void FSimplygonDataConverter::RemoveAllMaterialChannels(Simplygon::spMaterial Material)
	{
		int32 NumMaterialChannels = Material->GetMaterialChannelCount();
		TArray<Simplygon::spString> ChannelsToRemove;

		for (int32 ChannelIndex = 0; ChannelIndex < NumMaterialChannels; ++ChannelIndex)
		{
			ChannelsToRemove.Add(Material->GetMaterialChannelFromIndex(ChannelIndex));
		}

		for (Simplygon::spString ChannelName : ChannelsToRemove)
		{
			Material->RemoveMaterialChannel(ChannelName);
		}
	}

	void FSimplygonDataConverter::ConvertMaterialBakeOutput(const TArray<FBakeOutput>& BakeOutputs, const TArray<FMaterialData>& MaterialData, TArray<FFlattenMaterial>& FlattenedMaterials)
	{
		for (int32 OutputIndex = 0; OutputIndex < BakeOutputs.Num(); ++OutputIndex)
		{
			const FBakeOutput& Output = BakeOutputs[OutputIndex];
			const FMaterialData& MaterialInfo = MaterialData[OutputIndex];

			FFlattenMaterial Material;

			for (TPair<EMaterialProperty, FIntPoint> SizePair : Output.PropertySizes)
			{
				EFlattenMaterialProperties FlattenProperty = MaterialPropertyToFlattenMaterialProperty(SizePair.Key);
				check(FlattenProperty != EFlattenMaterialProperties::NumFlattenMaterialProperties)

					Material.SetPropertySize(FlattenProperty, SizePair.Value);
				Material.GetPropertySamples(FlattenProperty).Append(Output.PropertyData[SizePair.Key]);
			}

			Material.bDitheredLODTransition = MaterialInfo.Material->IsDitheredLODTransition();
			Material.BlendMode = BLEND_Opaque;
			Material.bTwoSided = MaterialInfo.Material->IsTwoSided();
			Material.EmissiveScale = Output.EmissiveScale;

			FlattenedMaterials.Add(Material);
		}
	}


	EFlattenMaterialProperties FSimplygonDataConverter::MaterialPropertyToFlattenMaterialProperty(int32 NewProperty)
	{
		const EFlattenMaterialProperties Remap[MP_Refraction] =
		{
			EFlattenMaterialProperties::Emissive,
			EFlattenMaterialProperties::Opacity,
			EFlattenMaterialProperties::OpacityMask,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::Diffuse,
			EFlattenMaterialProperties::Metallic,
			EFlattenMaterialProperties::Specular,
			EFlattenMaterialProperties::Roughness,
			EFlattenMaterialProperties::Anisotropy,
			EFlattenMaterialProperties::Normal,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::NumFlattenMaterialProperties,
			EFlattenMaterialProperties::AmbientOcclusion
		};

		return Remap[NewProperty];
	}

	void FSimplygonDataConverter::ExtractSections(const USkeletalMesh* SkeletalMesh, int32 LODIndex, TArray<FSectionInfo>& OutSections)
	{
		static UMaterialInterface* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface);
		FSkeletalMeshModel* Resource = SkeletalMesh->GetImportedModel();

		checkf(Resource->LODModels.IsValidIndex(LODIndex), TEXT("Invalid LOD Index"));

		/*TArray<FName> MaterialSlotNames = Component->GetMaterialSlotNames();*/

		const FSkeletalMeshLODInfo* LODInfo = SkeletalMesh->GetLODInfo(LODIndex);

		const FSkeletalMeshLODModel& Model = Resource->LODModels[LODIndex];
		for (const FSkelMeshSection& MeshSection : Model.Sections)
		{
			// Retrieve material for this section
			int32 MaterialIndex = MeshSection.MaterialIndex;
			if (LODInfo->LODMaterialMap.IsValidIndex(MaterialIndex) && SkeletalMesh->GetMaterials().IsValidIndex(LODInfo->LODMaterialMap[MaterialIndex]))
			{
				MaterialIndex = LODInfo->LODMaterialMap[MaterialIndex];
			}

			UMaterialInterface* StoredMaterial = SkeletalMesh->GetMaterials()[MaterialIndex].MaterialInterface;;
			// Make sure the resource actual exists, otherwise use default material
			StoredMaterial = (StoredMaterial != nullptr) && StoredMaterial->GetMaterialResource(GMaxRHIFeatureLevel) ? StoredMaterial : DefaultMaterial;

			FSectionInfo SectionInfo;
			SectionInfo.Material = StoredMaterial;
			SectionInfo.MaterialSlotName = /*MaterialSlotNames.IsValidIndex(MeshSection.MaterialIndex) ? MaterialSlotNames[MeshSection.MaterialIndex] :*/ NAME_None;
			SectionInfo.MaterialIndex = MeshSection.MaterialIndex;

			if (MeshSection.bCastShadow /*&& Component->CastShadow*/)
			{
				SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FSkelMeshSection, bCastShadow));
			}

			if (MeshSection.bRecomputeTangent)
			{
				SectionInfo.EnabledProperties.Add(GET_MEMBER_NAME_CHECKED(FSkelMeshSection, bRecomputeTangent));
			}

			OutSections.Add(SectionInfo);
		}
	}

void FSimplygonDataConverter::ExtractMeshDescription(const USkeletalMesh* SkeletalMesh, int32 LODIndex, FMeshDescription* OutMeshDescription, bool bPropagateVertexColours)
{
	FSkeletalMeshModel* Resource = SkeletalMesh->GetImportedModel();
	if (Resource->LODModels.IsValidIndex(LODIndex))
	{
		const FSkeletalMeshLODInfo& SrcLODInfo = *(SkeletalMesh->GetLODInfo(LODIndex));

		FSkeletalMeshLODModel& LODModel = Resource->LODModels[LODIndex];

		TArray<FSoftSkinVertex> Vertices;
		LODModel.GetVertices(Vertices);

		const uint32 VertexCount = LODModel.NumVertices;
		const uint32 TexCoordCount = LODModel.NumTexCoords;
		check(TexCoordCount <= MAX_TEXCOORDS);

		const int32 NumSections = LODModel.Sections.Num();

		OutMeshDescription->Empty();

		int32 TotalTriangles = 0;
		int32 TotalCorners = 0;
		for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
		{
			const FSkelMeshSection& SkelMeshSection = LODModel.Sections[SectionIndex];
			TotalTriangles += SkelMeshSection.NumTriangles;
		}
		TotalCorners = TotalTriangles * 3;
		OutMeshDescription->ReserveNewVertices(VertexCount);
		OutMeshDescription->ReserveNewPolygons(TotalTriangles);
		OutMeshDescription->ReserveNewVertexInstances(TotalCorners);
		OutMeshDescription->ReserveNewEdges(TotalCorners);

		TVertexAttributesRef<FVector3f> VertexPositions = OutMeshDescription->VertexAttributes().GetAttributesRef<FVector3f>(MeshAttribute::Vertex::Position);
		TEdgeAttributesRef<bool> EdgeHardnesses = OutMeshDescription->EdgeAttributes().GetAttributesRef<bool>(MeshAttribute::Edge::IsHard);
		TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = OutMeshDescription->PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
		TVertexInstanceAttributesRef<FVector3f> VertexInstanceNormals = OutMeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Normal);
		TVertexInstanceAttributesRef<FVector3f> VertexInstanceTangents = OutMeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector3f>(MeshAttribute::VertexInstance::Tangent);
		TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = OutMeshDescription->VertexInstanceAttributes().GetAttributesRef<float>(MeshAttribute::VertexInstance::BinormalSign);
		TVertexInstanceAttributesRef<FVector4f> VertexInstanceColors = OutMeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector4f>(MeshAttribute::VertexInstance::Color);
		TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = OutMeshDescription->VertexInstanceAttributes().GetAttributesRef<FVector2f>(MeshAttribute::VertexInstance::TextureCoordinate);

		// Copy skinned vertex positions
		uint32 FirstVertex = 0;
		for (int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex)
		{
			const FSkelMeshSection& Section = LODModel.Sections[SectionIndex];
			const uint32 LastVertex = FirstVertex + (uint32)Section.SoftVertices.Num();
			for (uint32 VertexIndex = FirstVertex; VertexIndex < LastVertex; ++VertexIndex)
			{
				FSoftSkinVertex& Vertex = Vertices[VertexIndex];
				const FVertexID VertexID = OutMeshDescription->CreateVertex();
				VertexPositions[VertexID] = Vertex.Position;
			}
			FirstVertex = LastVertex;
		}

		VertexInstanceUVs.SetNumChannels(MAX_TEXCOORDS);


		for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
		{
			const FSkelMeshSection& SkelMeshSection = LODModel.Sections[SectionIndex];
			const int32 NumWedges = SkelMeshSection.NumTriangles * 3;

			//Create the polygon group ID
			int32 SectionMaterialIndex = SkelMeshSection.MaterialIndex;
			int32 MaterialIndex = SectionMaterialIndex;
			// use the remapping of material indices for all LODs besides the base LOD 
			if (LODIndex > 0 && SrcLODInfo.LODMaterialMap.IsValidIndex(SkelMeshSection.MaterialIndex))
			{
				MaterialIndex = FMath::Clamp<int32>(SrcLODInfo.LODMaterialMap[SkelMeshSection.MaterialIndex], 0, SkeletalMesh->GetMaterials().Num());
			}

			FName ImportedMaterialSlotName = SkeletalMesh->GetMaterials()[MaterialIndex].ImportedMaterialSlotName;
			const FPolygonGroupID SectionPolygonGroupID(SectionMaterialIndex);
			if (!OutMeshDescription->IsPolygonGroupValid(SectionPolygonGroupID))
			{
				OutMeshDescription->CreatePolygonGroupWithID(SectionPolygonGroupID);
				PolygonGroupImportedMaterialSlotNames[SectionPolygonGroupID] = ImportedMaterialSlotName;
			}
			int32 WedgeIndex = 0;
			for (uint32 SectionTriangleIndex = 0; SectionTriangleIndex < SkelMeshSection.NumTriangles; ++SectionTriangleIndex)
			{
				FVertexID VertexIndexes[3];
				TArray<FVertexInstanceID> VertexInstanceIDs;
				VertexInstanceIDs.SetNum(3);
				for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex, ++WedgeIndex)
				{
					const int32 VertexIndexForWedge = LODModel.IndexBuffer[SkelMeshSection.BaseIndex + WedgeIndex];
					VertexIndexes[CornerIndex] = FVertexID(VertexIndexForWedge);
					FVertexInstanceID VertexInstanceID = OutMeshDescription->CreateVertexInstance(VertexIndexes[CornerIndex]);
					VertexInstanceIDs[CornerIndex] = VertexInstanceID;

					const FSoftSkinVertex& SoftVertex = SkelMeshSection.SoftVertices[VertexIndexForWedge - SkelMeshSection.BaseVertexIndex];

					const FSoftSkinVertex& SkinnedVertex = Vertices[VertexIndexForWedge];
					const FVector3f TangentX = SkinnedVertex.TangentX;
					const FVector4f TangentZ = SkinnedVertex.TangentZ;
					const FVector4f UnpackedTangentZ = SkinnedVertex.TangentZ;
					const float TangentYSign = UnpackedTangentZ.W;

					VertexInstanceTangents[VertexInstanceID] = TangentX;
					VertexInstanceBinormalSigns[VertexInstanceID] = TangentYSign;
					VertexInstanceNormals[VertexInstanceID] = TangentZ;

					for (uint32 TexCoordIndex = 0; TexCoordIndex < MAX_TEXCOORDS; TexCoordIndex++)
					{
						//Add this vertex instance tex coord
						VertexInstanceUVs.Set(VertexInstanceID, TexCoordIndex, SoftVertex.UVs[TexCoordIndex]);
					}

					//Add this vertex instance color
					VertexInstanceColors[VertexInstanceID] = bPropagateVertexColours ? FVector4f(FLinearColor(SoftVertex.Color)) : FVector4f(1.0f, 1.0f, 1.0f);
				}
				//Create a polygon from this triangle
				const FPolygonID NewPolygonID = OutMeshDescription->CreatePolygon(SectionPolygonGroupID, VertexInstanceIDs);
				//Triangulate the polygon
				OutMeshDescription->ComputePolygonTriangulation(NewPolygonID);
			}
		}
	}
}

UMaterialInstanceConstant* FSimplygonDataConverter::CreateProxyMaterialInstance(UPackage* InOuter, const FMaterialProxySettings& InMaterialProxySettings, UMaterialInterface* InBaseMaterial, const FFlattenMaterial& FlattenMaterial, const FString& AssetBasePath, const FString& AssetBaseName, TArray<UObject*>& OutAssetsToSync, FMaterialUpdateContext* MaterialUpdateContext)
{
	return FMaterialUtilities::CreateFlattenMaterialInstance(InOuter, InMaterialProxySettings, InBaseMaterial, FlattenMaterial, AssetBasePath, AssetBaseName, OutAssetsToSync, MaterialUpdateContext);
	//return CreateProxyMaterialInstance(InOuter, InMaterialProxySettings, GEngine->DefaultFlattenMaterial, FlattenMaterial, AssetBasePath, AssetBasePath, OutAssetsToSync, MaterialUpdateContext);
}

void FSimplygonDataConverter::OutputDebugGeometry( Simplygon::ISimplygon* SDK, Simplygon::spScene& Scene, const FString& OutputFileName )
{

	auto objexp = SDK->CreateSceneExporter();
	FString SimplygonPath = FPaths::ProjectIntermediateDir() + TEXT("Simplygon/");

	if (!FPaths::DirectoryExists(SimplygonPath))
	{
		IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
		PlatformFile.CreateDirectory(*SimplygonPath);
	}

	FString outputPath = FPaths::ConvertRelativePathToFull( SimplygonPath + OutputFileName );
	objexp->SetExportFilePath(TCHAR_TO_ANSI(*outputPath));
	//objexp->AddObserver(&EventHandler, Simplygon::Simplygon::EEventCodes::Progress);
	objexp->SetScene(Scene);
	objexp->RunExport();
}

void FSimplygonDataConverter::OutputDebugGeometry( Simplygon::ISimplygon* SDK, Simplygon::spGeometryData& Geometry, const FString& OutputFileName )
{

	auto objexp = SDK->CreateSceneExporter();
	auto Scene = SDK->CreateScene();
	auto SceneMesh = SDK->CreateSceneMesh();
	SceneMesh->SetGeometry( Geometry );
	Scene->GetRootNode()->AddChild( SceneMesh );
	FString SimplygonPath = FPaths::ProjectIntermediateDir() + TEXT( "Simplygon/" );

	if( !FPaths::DirectoryExists( SimplygonPath ) )
	{
		IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
		PlatformFile.CreateDirectory( *SimplygonPath );
	}

	FString outputPath = FPaths::ConvertRelativePathToFull( SimplygonPath + OutputFileName );
	objexp->SetExportFilePath( TCHAR_TO_ANSI( *outputPath ) );
	// objexp->AddObserver(&EventHandler, Simplygon::Simplygon::EEventCodes::Progress);
	objexp->SetScene( Scene );
	objexp->RunExport();
}

void  FSimplygonDataConverter::SaveSimplygonSceneToIntermediate(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, const FString& Prefix, FGuid Id)
{
	check(SDK != nullptr);
	check(!Scene.IsNull());
	const USimplygonSettings* EditorSettings = GetDefault< USimplygonSettings>();
	const FString OutputDir = EditorSettings->DebugPath.FilePath;
	const FString OutputFileNameWithExtension = FString::Printf(TEXT("%s%s%s"), *Prefix, *Id.ToString().Left(8), TEXT(".sgscene"));
	const FString OutputFilePath = FPaths::Combine(OutputDir, *OutputFileNameWithExtension);
	if (FPlatformFileManager::Get().GetPlatformFile().CreateDirectoryTree(*OutputDir))
	{
		auto FileNameAnsi = StringCast<ANSICHAR>(*OutputFilePath);
		Scene->SaveToFile(FileNameAnsi.Get());
	}
}

void  FSimplygonDataConverter::SaveSimplygonPipelineToIntermediate(Simplygon::ISimplygon* SDK, Simplygon::spPipeline Pipeline, const FString& Prefix, FGuid Id)
{
	check(SDK != nullptr);
	check(Pipeline != nullptr);
	const USimplygonSettings* EditorSettings = GetDefault< USimplygonSettings>();
	const FString OutputDir = EditorSettings->DebugPath.FilePath;
	const FString OutputFilePath = FPaths::Combine(OutputDir, *FString::Printf(TEXT("%s%s%s"), *Prefix, *Id.ToString().Left(8), TEXT(".json")));
	if (FPlatformFileManager::Get().GetPlatformFile().CreateDirectoryTree(*OutputDir))
	{
		Simplygon::spPipelineSerializer PipelineSerializer = SDK->CreatePipelineSerializer();
		auto FileNameAnsi = StringCast<ANSICHAR>(*OutputFilePath);
		PipelineSerializer->SavePipelineToFile(FileNameAnsi.Get(), Pipeline);
	}
}

float FSimplygonDataConverter::ConvertFromColorToWeights(float ColorIntensity, float Multiplier)
{
	// what we need to raise 2 to, to get multiplier squared
	float Exponent = FMath::Log2(Multiplier) * 2.0f;
	// get a value 1/multiplier to multiplier
	return FMath::Pow(2.0f, (ColorIntensity * Exponent)) / Multiplier;
}

template <typename T>
T FSimplygonDataConverter::VectorToSimplygon(const T& vec)
{
	return T(vec.X, vec.Z, vec.Y);
}

template <typename T>
T FSimplygonDataConverter::VectorFromSimplygon(const T& vec)
{
	return T(vec.X, vec.Z, vec.Y);
}

Simplygon::spSelectionSet FSimplygonDataConverter::FindOrCreateSelectionSet(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, const char* SelectionSetName)
{
	Simplygon::spSelectionSet SelectionSet = Scene->GetSelectionSetTable()->FindSelectionSet(SelectionSetName);
	if (SelectionSet.IsNull())
	{
		SelectionSet = SDK->CreateSelectionSet();
		SelectionSet->SetName(SelectionSetName);
		Scene->GetSelectionSetTable()->AddItem(SelectionSet);
	}
	return SelectionSet;
}

bool FSimplygonDataConverter::IsClippingPlaneEnabledInPipeline(Simplygon::spPipeline InPipeline)
{
	if (InPipeline->IsA("IRemeshingPipeline"))
	{
		return FSimplygonDataConverter::GetUseClippingPlaneSetting<Simplygon::spRemeshingPipeline>(InPipeline);

	}
	else if (InPipeline->IsA("IRemeshingLegacyPipeline"))
	{
		return FSimplygonDataConverter::GetUseClippingPlaneSetting<Simplygon::spRemeshingLegacyPipeline>(InPipeline);
	}
	else if (InPipeline->IsA("IAggregationPipeline"))
	{
		return FSimplygonDataConverter::GetUseClippingPlaneSetting<Simplygon::spAggregationPipeline>(InPipeline);
	}
	return false;
}

void FSimplygonDataConverter::DisableClippingPlaneInPipeline(Simplygon::spPipeline InPipeline)
{
	if (InPipeline->IsA("IRemeshingPipeline"))
	{
		FSimplygonDataConverter::SetUseClippingPlaneSetting<Simplygon::spRemeshingPipeline>(InPipeline, false);

	}
	else if (InPipeline->IsA("IRemeshingLegacyPipeline"))
	{
		FSimplygonDataConverter::SetUseClippingPlaneSetting<Simplygon::spRemeshingLegacyPipeline>(InPipeline, false);
	}
	else if (InPipeline->IsA("IAggregationPipeline"))
	{
		FSimplygonDataConverter::SetUseClippingPlaneSetting<Simplygon::spAggregationPipeline>(InPipeline, false);
	}
}

void FSimplygonDataConverter::PopulateLandscapeCullingGeometry(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, Simplygon::spPipeline InPipeline, UWorld* World, const FBoxSphereBounds& EstimatedBounds)
{
	TArray<FMeshDescription*> CullingGeometries;

	FMeshMergeHelpers::RetrieveCullingLandscapeAndVolumes(World, EstimatedBounds, ELandscapeCullingPrecision::Medium, CullingGeometries);
	for (auto CullingGeoemtry : CullingGeometries)
	{
		FSimplygonDataConverter::AddToScene(SDK, Scene, InPipeline, CullingGeoemtry);
	}

	ParallelFor(CullingGeometries.Num(),
		[&CullingGeometries](int32 Index)
		{
			delete CullingGeometries[Index];
		}
	);
}

///////////////////////////////////////////////////////////
//FSimplygonMeshMaterialInfo

inline FAddMeshMaterialInfoResult FSimplygonMeshMaterialInfo::Add(
	const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings, 
	const TArray<FSectionInfo>& Sections, 
	const FMeshDescription* MeshDescription, 
	const FString& MeshId)
{
	FAddMeshMaterialInfoResult Result;

	bBakeMaterials |= FlattenMaterialSettings.IsValid();

	for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); ++SectionIndex)
	{
		const FSectionInfo& Section = Sections[SectionIndex];

		if (bBakeMaterials)
		{
			FString MeshMaterialKey = FString::Printf(TEXT("%s_%d"), *MeshId, Section.Material->GetUniqueID());

			bool bMaterialVertexData = false;
			int32 NumTexCoords = 0;
			FMaterialUtilities::AnalyzeMaterial(Section.Material, FlattenMaterialSettings, NumTexCoords, bMaterialVertexData);
			
			if (bMaterialVertexData)
			{
				MeshMaterialKey += TEXT("_");
				MeshMaterialKey += FGuid::NewGuid().ToString();
			}

			// Store local id for later use to map custom texture coordinates to correct mesh settings
			Result.ComponentMeshMaterialKeys.AddUnique(MeshMaterialKey);

			int32 MaterialIndex = bUseUniqueSections ? MaterialOutputOrder.AddUnique(MeshMaterialKey) : MaterialOutputOrder.Add(MeshMaterialKey);
			Result.SectionIndexToMaterialId.Add(SectionIndex, MaterialIndex);
			Result.SlotNameToMaterialId.Add(Section.MaterialSlotName, MaterialIndex);
			Result.MaterialHasVertexData.Add( MaterialIndex, bMaterialVertexData );
			
			// In a mixed (baked and no baked materials) LOD chain we need a way to map
			// material indices to original sections.
			MaterialIndexToSlotName.FindOrAdd(MeshMaterialKey).Add(MaterialIndex, Section.MaterialSlotName);

			// Create mesh settings, needed for material baking (flatten material)
			if (!BakeMaterialMeshSettings.Contains(MeshMaterialKey))
			{
				FMeshData MeshSettings;
				// This material indices array corresponds to PolygonGroupID in the MeshDescription.
				// See how it is being used in FMeshMaterialRenderItem::PopulateWithMeshData()
				// We assume SectionIndex == PolygonGroupID
				MeshSettings.MaterialIndices.Add(SectionIndex);
				MeshSettings.MeshDescription = const_cast<FMeshDescription*>(MeshDescription);
				BakeMaterialMeshSettings.Add(MeshMaterialKey, MeshSettings);
			}

			// Create material settings, needed for material baking (flatten material)
			if (!BakeMaterialMaterialSettings.Contains(MeshMaterialKey))
			{
				FMaterialData MaterialSettings;
				MaterialSettings.Material = Section.Material;
				MaterialSettings.bPerformBorderSmear = true;
				MaterialSettings.PropertySizes = FlattenMaterialSettings.GetMaterialPropertySizes();
				BakeMaterialMaterialSettings.Add(MeshMaterialKey, MaterialSettings);
			}
		}
		else
		{
			FString MaterialKey = FString::Printf(TEXT("%d"), Section.Material->GetUniqueID());
			int32 MaterialIndex = bUseUniqueSections ? MaterialOutputOrder.AddUnique(MaterialKey) : MaterialOutputOrder.Add(MaterialKey);
			Result.SectionIndexToMaterialId.Add(SectionIndex, MaterialIndex);
			Result.SlotNameToMaterialId.Add(Section.MaterialSlotName, MaterialIndex);
			MaterialIndexToSlotName.FindOrAdd(MaterialKey).Add(MaterialIndex, Section.MaterialSlotName);

			if (!SourceMaterials.Contains(MaterialKey))
			{
				SourceMaterials.Add(MaterialKey, Section.Material);
			}
		}
	}
	return Result;
}

inline void FSimplygonDataConverter::UpdateVectorsInPipeline( Simplygon::spPipeline InPipeline )
{
	auto ImposterPipeline = Simplygon::spImpostorFromSingleViewPipeline::SafeCast( InPipeline );
	if( !ImposterPipeline.IsNull() )
	{
		// NOTE: We need to chagne the frame for view vector before running processing as we change the frame for the geometry.
		auto ViewDirection = ImposterPipeline->GetImpostorFromSingleViewSettings()->GetViewDirection();
		FVector3f ViewDirectionCorretedFrame =
			FSimplygonDataConverter::VectorToSimplygon(FVector3f(ViewDirection.Data()[0], ViewDirection.Data()[1], ViewDirection.Data()[2]));
		ImposterPipeline->GetImpostorFromSingleViewSettings()->SetViewDirection( (float*)&ViewDirectionCorretedFrame );
	}

	auto FlipbookPipeline = Simplygon::spFlipbookPipeline::SafeCast( InPipeline );
	if( !FlipbookPipeline.IsNull() )
	{
		// We need to chagne the frame for up and view vectors before running processing as we change the frame for the geometry.
		auto ViewDirection = FlipbookPipeline->GetFlipbookSettings()->GetViewDirection();
		FVector3f ViewDirectionCorretedFrame =
			FSimplygonDataConverter::VectorToSimplygon(FVector3f(ViewDirection.Data()[0], ViewDirection.Data()[1], ViewDirection.Data()[2]));
		FlipbookPipeline->GetFlipbookSettings()->SetViewDirection( (float*)&ViewDirectionCorretedFrame );

		auto UpVector = FlipbookPipeline->GetFlipbookSettings()->GetUpVector();
		FVector3f UpVectorCorretedFrame =
			FSimplygonDataConverter::VectorToSimplygon(FVector3f(UpVector.Data()[0], UpVector.Data()[1], UpVector.Data()[2]));
		FlipbookPipeline->GetFlipbookSettings()->SetUpVector((float*)&UpVectorCorretedFrame);
	}

	auto BillboardCloudOuterShell = Simplygon::spBillboardCloudPipeline::SafeCast(InPipeline);
	if (!BillboardCloudOuterShell.IsNull())
	{
		auto UpVector = BillboardCloudOuterShell->GetBillboardCloudSettings()->GetUpVector();
		FVector3f UpVectorCorretedFrame =
			FSimplygonDataConverter::VectorToSimplygon(FVector3f(UpVector.Data()[0], UpVector.Data()[1], UpVector.Data()[2]));
		BillboardCloudOuterShell->GetBillboardCloudSettings()->SetUpVector((float*)&UpVectorCorretedFrame);
	}

	auto BillboardCloudFoliage = Simplygon::spBillboardCloudVegetationPipeline::SafeCast(InPipeline);
	if (!BillboardCloudFoliage.IsNull())
	{
		auto UpVector = BillboardCloudFoliage->GetBillboardCloudSettings()->GetUpVector();
		FVector3f UpVectorCorretedFrame =
			FSimplygonDataConverter::VectorToSimplygon(FVector3f(UpVector.Data()[0], UpVector.Data()[1], UpVector.Data()[2]));
		BillboardCloudFoliage->GetBillboardCloudSettings()->SetUpVector((float*)&UpVectorCorretedFrame);
	}

}

FString FSimplygonDataConverter::GetPipelineBakedMaterialBaseTemplatePath( const Simplygon::spPipeline InPipeline, uint32 InIndex  )
{
	bool IsCascaded = InPipeline->GetCascadedPipelineCount() > 0;
	
	if (IsCascaded) {
		if( IsCascadedPipelineOfType<Simplygon::spFlipbookPipeline>( InPipeline, InIndex ) )
			return TEXT( "/Simplygon/Materials/M_SG_BaseFlipbook.M_SG_BaseFlipbook" );

		if( IsCascadedPipelineOfType<Simplygon::spBillboardCloudVegetationPipeline>( InPipeline, InIndex ) ||
		    IsCascadedPipelineOfType<Simplygon::spBillboardCloudPipeline>( InPipeline, InIndex ) )
			return TEXT( "/Simplygon/Materials/M_SG_BaseImposter.M_SG_BaseImposter" );
	}
	else
	{
		if( IsPipelineOfType<Simplygon::spFlipbookPipeline>( InPipeline ) )
			return TEXT( "/Simplygon/Materials/M_SG_BaseFlipbook.M_SG_BaseFlipbook" );

		if( IsPipelineOfType<Simplygon::spBillboardCloudVegetationPipeline>( InPipeline ) ||
		    IsPipelineOfType<Simplygon::spBillboardCloudPipeline>( InPipeline ) )
			return TEXT( "/Simplygon/Materials/M_SG_BaseImposter.M_SG_BaseImposter" );
	}
	

	return TEXT( "/Engine/EngineMaterials/BaseFlattenMaterial.BaseFlattenMaterial" );
}

#undef LOCTEXT_NAMESPACE

