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

#pragma once

#include "CoreMinimal.h"

#if WITH_EDITOR
#if PLATFORM_WINDOWS
THIRD_PARTY_INCLUDES_START
#include "Windows\AllowWindowsPlatformTypes.h"
#include "Simplygon.h"
#include "Windows\HideWindowsPlatformTypes.h"
THIRD_PARTY_INCLUDES_END
#endif // PLATFORM_WINDOWS
#endif
#include "SimplygonUE4UI.h"
#include "SimplygonMaterialChannelConverter.h"
#include "MeshDescription.h"
#include "MaterialBakingStructures.h"
#include "Engine/SkeletalMesh.h"
#include "Rendering/SkeletalMeshLODModel.h"

#define USE_DEPRECATED_CUSTOM_SKMESH_HARDEDGE 0

class USkeletalMesh;
class FSkeletalMeshLODModel;
struct FSimplygonFlattenMaterialSettings;

enum class ESimplygonSceneObjectTypes
{
	Actor = 0,
	StaticMeshComponent,
	StaticMesh,
	SkeletalMesh
};

struct FAddMeshMaterialInfoResult
{
	TMap<FName, int32> SlotNameToMaterialId{};
	TMap<int32, int32> SectionIndexToMaterialId{};
	TArray<FString> ComponentMeshMaterialKeys{};
	TMap<int32, bool> MaterialHasVertexData{};
	bool bComponentVertexData;

	FAddMeshMaterialInfoResult()
		:bComponentVertexData(false)
	{}
};

using FMeshMaterialSectionId = FString;
// Structure to collect mesh-material information to efficiently use BakeMaterial module
// by flattening all materials in one call
struct SIMPLYGONPLUGINMESHREDUCTION_API FSimplygonMeshMaterialInfo
{
public:
	explicit FSimplygonMeshMaterialInfo(bool bMakeSectionsUnique)
		: bUseUniqueSections(bMakeSectionsUnique)
	{}

	bool bBakeMaterials = false;
	// This will decide the order of Simplygon's material table
	TArray<FMeshMaterialSectionId> MaterialOutputOrder{};
	TMap<FMeshMaterialSectionId, UMaterialInterface*> SourceMaterials{};
	TMap<FMeshMaterialSectionId, FMeshData> BakeMaterialMeshSettings{};
	TMap<FMeshMaterialSectionId, FMaterialData> BakeMaterialMaterialSettings{};
	
	
	// In a mixed (baked and no baked materials) LOD chain we need a way to map
	// material indices to original sections.
	TMap<FMeshMaterialSectionId, TMap<int32, FName>> MaterialIndexToSlotName{};

	TArray<FString> MeshesToRelease{};

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

private:
	bool bUseUniqueSections;
};

/**
 * @brief An adapter to convert from SimplygonAPI types to Engine Types. 
*/
class SIMPLYGONPLUGINMESHREDUCTION_API FSimplygonDataConverter
{
public:

	/**
	 * @brief Add UStaticMesh to a Simplygon scene.
	 * @param SDK Pointer to Simplygon API
	 * @param Scene Target scene where the UStaticMesh should be added.
	 * @param FlattenMaterialSettings Input material flattening settings.
	 * @param StaticMesh Mesh that should be added to the Scene.
	 * @param OutMeshMaterialInfo Output mesh and material information used by material flattening.
	 *		  Note: This should be unique per Simplygon scene, see FLODRecipeManager::Build for an example usage.
	*/
	static void AddToScene(
		Simplygon::ISimplygon* SDK,
		Simplygon::spScene Scene,
		const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
		UStaticMesh* StaticMesh,
		FSimplygonMeshMaterialInfo& OutMeshMaterialInfo
	);

	/**
	 * @brief Add USkeletalMesh to a Simplygon scene.
	 * @param SDK Pointer to Simplygon API
	 * @param Scene Target scene where the USkeletalMesh should be added.
	 * @param FlattenMaterialSettings Input material flattening settings.
	 * @param SkeletalMesh Mesh that should be added to the Scene.
	 * @param OutMeshMaterialInfo Output mesh and material information used by material flattening.
	 *		  Note: This should be unique per Simplygon scene, see FLODRecipeManager::Build for an example usage.
	*/
	static void AddToScene(
		Simplygon::ISimplygon* SDK,
		Simplygon::spScene Scene,
		const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
		USkeletalMesh* SkeletalMesh,
		FSimplygonMeshMaterialInfo& OutMeshMaterialInfo
	);

	/**
	 * @brief Add UStaticMeshComponent to a Simplygon scene.
	 * @param SDK Pointer to Simplygon API
	 * @param Scene Target scene where the UStaticMeshComponent should be added.
	 * @param FlattenMaterialSettings Input material flattening settings.
	 * @param StaticMeshComponent Mesh component that should be added to the Scene.
	 * @param OutMeshMaterialInfo Output mesh and material information used by material flattening.
	 *		  Note: This should be unique per Simplygon scene, see FLODRecipeManager::Build for an example usage.
	*/
	static void AddToScene(
		Simplygon::ISimplygon* SDK,
		Simplygon::spScene Scene,
		const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
		UStaticMeshComponent* StaticMeshComponent,
		FSimplygonMeshMaterialInfo& OutMeshMaterialInfo
	);

	/**
	 * @brief Add materials to a Simplygon scene.
	 *        NOTE: This is a slow running task due to flatten material.
	 * @param SDK Pointer to Simplygon API
	 * @param Scene Target scene where the MeshMaterialInfo should be added.
	 * @param MeshMaterialInfo Input mesh and material information used by material flattening.
	 *		  Note: This should be unique per Simplygon scene, see FLODRecipeManager::Build for an example usage.
	*/
	static void AddToScene(
		Simplygon::ISimplygon* SDK,
		Simplygon::spScene Scene,
		FSimplygonMeshMaterialInfo& MeshMaterialInfo
	);

	/**
	 * @brief Add clipping planes to Simplygon scene.
	 * @param SDK Pointer to Simplygon API
	 * @param Scene Target scene to where the clipping planes will be added.
	 * @param Pipeline Input pipeline for settings validation.
	 * @param ClippingPlanes Desired clipping planes that should be added to the scene.
	*/
	static void AddToScene(
		Simplygon::ISimplygon* SDK,
		Simplygon::spScene Scene,
		Simplygon::spPipeline Pipeline, 
		const TArray<FPlane>& ClippingPlanes);

	/**
	 * @brief Add visibility samples to Simplygon scene.
	 *        Ex: NavMesh visibility culling
	 * @param SDK Pointer to Simplygon API
	 * @param Scene Target scene to where the clipping planes will be added.
	 * @param Pipeline Input pipeline for settings validation.
	 * @param VisibilitySamples Desired visibility samples that should be added to the scene.
	*/
	static void AddToScene(
		Simplygon::ISimplygon* SDK,
		Simplygon::spScene Scene,
		Simplygon::spPipeline Pipeline,
		const TArray<FVector>& VisibilitySamples);

	/**
	 * @brief Add culling geometry to Simplygon scene.
	 *        Ex: Landscape and other arbitrary tagged clipping geometry
	 * @param SDK Pointer to Simplygon API
	 * @param Scene Target scene to where the clipping planes will be added.
	 * @param Pipeline Input pipeline for settings validation.
	 * @param CullingGeometry Desired geometry should be added to the scene as culling geometry.
	*/
	static void AddToScene(
		Simplygon::ISimplygon* SDK,
		Simplygon::spScene Scene,
		Simplygon::spPipeline Pipeline,
		const FMeshDescription* CullingGeometry);

	/**
	 * @brief Add GeometryData to Simplygon scene
	 * @param SDK Pointer to Simplygon API
	 * @param Scene Target scene to where the clipping planes will be added.
	 * @param GeometryData Input geometry that should be added to the Simplygon scene.
	 * @param NodeName Specify a name for the new scene node. Default: MeshNode
	*/
	static void AddToScene(
		Simplygon::ISimplygon* SDK,
		Simplygon::spScene Scene,
		Simplygon::spGeometryData GeometryData,
		const FString& NodeName = FString());

	/**
	 * @brief Create Simplygon GeometryData from FMeshDescription. 
	 *        Internally used by AddToScene methods.
	 * @param API Pointer to Simplygon API
	 * @param FlattenMaterialSettings Input flatten material settings.
	 * @param MeshDescription Input mesh to convert.
	 * @param MeshDescriptionId A unique input mesh description id.
	 * @param Sections Input mesh sections
	 * @param OutMeshMaterialInfo Output mesh and material information used by material flattening.
	*/
	static Simplygon::spGeometryData CreateGeometryData(
		Simplygon::ISimplygon* API,
		const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
		const FMeshDescription* MeshDescription,
		const FString& MeshDescriptionId,
		const TArray<FSectionInfo>& Sections,
		FSimplygonMeshMaterialInfo& OutMeshMaterialInfo);

	/**
	 * @brief Create Simplygon GeometryData from USkeletalMesh.
	 *        Internally used by AddToScene methods.
	 * @param API Pointer to Simplygon API
	 * @param Scene Target scene to where bones will be added.
	 * @param FlattenMaterialSettings Input flatten material settings.
	 * @param SkeletalMesh Input mesh to convert
	 * @param Sections Input mesh sections
	 * @param OutMeshMaterialInfo Output mesh and material information used by material flattening.
	*/
	UE_DEPRECATED(0, "(SkeletalMesh) CreateGeometryData with Sections has been deprecated. Please use the updated CreateGeometryData instead.")
	static Simplygon::spGeometryData CreateGeometryData(
		Simplygon::ISimplygon* API,
		Simplygon::spScene Scene,
		const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
		USkeletalMesh* SkeletalMesh,
		const TArray<FSectionInfo>& Sections,
		FSimplygonMeshMaterialInfo& OutMeshMaterialInfo);

	/**
	 * @brief Create Simplygon GeometryData from USkeletalMesh.
	 *        Internally used by AddToScene methods.
	 * @param API Pointer to Simplygon API
	 * @param Scene Target scene to where bones will be added.
	 * @param FlattenMaterialSettings Input flatten material settings.
	 * @param SkeletalMesh Input mesh to convert
	 * @param Sections Input mesh sections
	 * @param OutMeshMaterialInfo Output mesh and material information used by material flattening.
	*/
	static Simplygon::spGeometryData CreateGeometryData(
		Simplygon::ISimplygon* API,
		Simplygon::spScene Scene,
		const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
		USkeletalMesh* SkeletalMesh,
		FSimplygonMeshMaterialInfo& OutMeshMaterialInfo);

	/**
	 * @brief Gets material mappings.
	 * @param SDK Pointer to ISimplygon factory.
	 * @param Mesh The MeshDescription to extract material mapping from.
	 * @param OutMaterialNameToIndex A mapping between material name to index.
	 * @param OutMaterialIndexToName A mapping between material index to name.
	*/
	static void GetMaterialMaps(
		Simplygon::ISimplygon* sdk,
		const FMeshDescription& Mesh,
		TMap<FName, int32>& OutMaterialNameToIndex,
		TMap<int32, FName>& OutMaterialIndexToName,
		TArray<int32> MaterialIdRemap = TArray<int32>());

	/**
	 * @brief Created Simplygon::spGeometryData from MeshDescription.
	 * @param SDK  Pointer to ISimplygon factory.
	 * @param Mesh MeshDecription to convert.
	 * @param MaterialMap Material mapping.
	 * @param TextureBounds UV bounds.
	 * @param InTexCoords UV Coords.
	 * @param SetVertexWeights If true will set vertex weights.
	 * @return 
	*/
	static Simplygon::spGeometryData CreateGeometryFromMeshDescription(
		Simplygon::ISimplygon* SDK,
		const FMeshDescription& Mesh,
		const TMap<FName, int32>& MaterialMap,
		const TArray<FBox2D>& TextureBounds,
		const TArray<FVector2D>& InTexCoords,
		bool SetVertexWeights);

	/**
	 * @brief Create MeshDescription from Simplygon::spGeometryData.
	 * @param GeometryData The Simplygon::spGeometryData to convert to MeshDescription
	 * @param MaterialIndexToSlotName Mapping between GeometryData's material indices and MeshDescription's section slot names
	 *								  See FSimplygonMeshMaterialInfo::Add for more info
	 * @param OutMesh The converted MeshDescription.
	 * @param bHasBakedMaterials If true will check for material lods on Simplygon::spGeometryData and perform required operations.
	*/
	static void CreateMeshDescriptionFromGeometry(
		const Simplygon::spGeometryData& GeometryData,
		const TMap<int32, FName>& MaterialIndexToSlotName,
		FMeshDescription& OutMesh,
		bool bHasBakedMaterials);

	/**
	 * Add Simplygon materials to a simplygon scene by converting from flattened materials.
	 * @param SDK - Simplygon API which you can get from the Simplygon module.
	 * @param Scene - The scene where you want to add materials.
	 * @param InputMaterials - Source flattened materials which will be converted to Simplygon materials and added to Scene.
	 * @param bReleaseInputMaterials - If true, will remove InputMaterials color data to free memory.
	 */
	static bool AddSimplygonMaterials(
		Simplygon::ISimplygon* SDK,
		Simplygon::spScene Scene,
		const TArray<struct FFlattenMaterial>& InputMaterials,
		bool bReleaseInputMaterials);

	/**
	 * Add default diffuse samples
	 * @param OutMaterial - Output material.
	 */
	static void AddDefaultDiffuseToMaterial( FFlattenMaterial& OutMaterial );

	/**
	 * Create Flatten material by converting from Simplygon materials contained in the scene.
	 * @param Scene - The scene which contains materials.
	 * @param OutMaterial - Output material.
	 * @param OutputMaterialIndex - Default 0, from what material output to convert from.
	 */
	static bool CreateFlattenMaterial(
		Simplygon::spScene Scene, 
		struct FFlattenMaterial& OutMaterial, 
		int32 OutputMaterialIndex = 0);

	/**
	 * @brief Create material casters in the given pipeline based on properties in the input materials.
	 * @param SDK Pointer to ISimplygon factory.
	 * @param Pipeline Pipeline to use.
	 * @param InputMaterials The input materials.
	*/
	static void CreateMaterialCasters( Simplygon::ISimplygon* SDK, Simplygon::spPipeline Pipeline, const TArray<FFlattenMaterial>& InputMaterials );

	/**
	 * @brief Create hierarchy of bones from the 
	 * @param SDK Pointer to ISimplygon factory.
	 * @param Scene Simplygon::spScene use to setup bone hierarchy.
	 * @param Skeleton The reference skeleton.
	 * @param OutBoneTableIDs List of bone indices.
	*/
	static void CreateSkeletalHierarchy(
		Simplygon::ISimplygon* SDK, 
		Simplygon::spScene& Scene,
		const struct FReferenceSkeleton& Skeleton,
		TArray<Simplygon::rid>& OutBoneTableIDs);

	/**
	 * @brief Creates Simplygon::spGeometryData from FSkeletalMeshLODmodel
	 * @param SDK Pointer to ISimplygon factory.
	 * @param Scene The Simplygon::spScene where data will be setup.
	 * @param LODModel FSkeletalMeshLODModel to use.
	 * @param BoneIDs List of bone indices.
	 * @param BoneMatrices List of bone transforms.
	 * @param LODIndex LOD index.
	 * @return The newly created Simplygon::spGeometryData
	*/
	static Simplygon::spGeometryData CreateGeometryFromSkeletalLODModel(
		Simplygon::ISimplygon* SDK, 
		Simplygon::spScene Scene, 
		const FSkeletalMeshLODModel& LODModel,
		const TArray<Simplygon::rid>& BoneIDs, 
		const TArray<FMatrix>& BoneMatrices, 
		const int32 LODIndex);

	/**
	 * @brief Creates Simplygon::spGeometryData from FSkeletalMeshLODmodel
	 * @param SDK Pointer to ISimplygon factory.
	 * @param Scene The Simplygon::spScene where data will be setup.
	 * @param LODModel FSkeletalMeshLODModel to use.
	 * @param BoneIDs List of bone indices.
	 * @param BoneMatrices List of bone transforms.
	 * @param TextureBounds UV bounds.
	 * @param InTexCoords UV Coordinates.
	 * @return The newly created Simplygon::spGeometryData
	*/
	static Simplygon::spGeometryData 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<FVector2D>& InTexCoords);

	/**
	 * @brief Creates a FSkeletalMeshLODmodel from Simplygon::spGeometryData
	 * @param NewModel The newly created FSkeletalMeshLODModel to populate.
	 * @param GeometryData Simplygon::spGeometryData to convert from.
	 * @param SkeletalMesh The owning skeletal mesh.
	 * @param LODIndex Index to the LOD we are creating.
	 * @param bHasBakedMaterials If true will do necessary step to handle material lods from Simplygon::spGeometryData
	 * @param bCreateMorphTargets If true, will create morph targets for the LOD if the data exists in GeometryData
	 * @param MorphTargetsToRemove Morph targets that should be removed for this LOD 
	*/
	static void CreateSkeletalLODModelFromGeometry(
		FSkeletalMeshLODModel* NewModel,
		const Simplygon::spGeometryData& GeometryData,
		USkeletalMesh* SkeletalMesh,
		const int32 LODIndex,
		const bool bHasBakedMaterials,
		const bool bCreateMorphTargets = false,
		const TSet<TSoftObjectPtr<class UMorphTarget>>* MorphTargetsToRemove = nullptr);

	/**
	 * @brief Writes out scene to disk.
	 * @param SDK Pointer to ISimplygon factory.
	 * @param Scene The Simplygon::spScene to export.
	 * @param outputFileName The file path where the file will be exported.
	*/
	static void OutputDebugGeometry( Simplygon::ISimplygon* SDK, Simplygon::spScene& Scene, const FString& OutputFileName );

	/**
	 * @brief Writes out scene to disk.
	 * @param SDK Pointer to ISimplygon factory.
	 * @param Geometry The Simplygon::spGeoemtryData to export.
	 * @param outputFileName The file path where the file will be exported.
	 */
	static void OutputDebugGeometry( Simplygon::ISimplygon* SDK, Simplygon::spGeometryData& Geometry, const FString& OutputFileName );

	/**
	 * @brief Writes out scene to disk.
	 * @param SDK Pointer to ISimplygon factory.
	 * @param Scene The Simplygon::spScene to export.
	 * @param outputFileName The file path where the file will be exported.
	*/
	static void SaveSimplygonSceneToIntermediate(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, const FString& Prefix, FGuid Id = FGuid::NewGuid());

	/**
	 * @brief Writes out pipeline
	 * @param SDK Pointer to ISimplygon factory.
	 * @param Scene The Simplygon::spScene to export.
	 * @param outputFileName The file path where the file will be exported.
	*/
	static void SaveSimplygonPipelineToIntermediate(Simplygon::ISimplygon* SDK, Simplygon::spPipeline Pipeline, const FString& Prefix, FGuid Id = FGuid::NewGuid());

	/**
	 * @brief Converts colors to weights.
	 * @param ColorIntensity Color intensity to convert.
	 * @param Multiplier color multiplier.
	 * @return Converted weight value for color.
	*/
	static float ConvertFromColorToWeights(float ColorIntensity, float Multiplier);

	/**
	 * @brief Swizzle vector from UE -> Simplygon coordinate system (axis swap Y/Z)
	*/
	static FVector VectorToSimplygon(const FVector& vec);

	/**
	 * @brief Swizzle vector from Simplygon -> UE coordinate system (axis swap Y/Z)
	*/
	static FVector VectorFromSimplygon(const FVector& vec);

	/**
	 * @brief Get selection set from scene. The selection set will be created if it does not exist.
	 * @param SDK Pointer to Simplygon API.
	 * @param Scene  where to find or create the selection set.
	 * @param SelectionSetName Name of the selection set to find or create.
	*/
	static Simplygon::spSelectionSet FindOrCreateSelectionSet(Simplygon::ISimplygon* SDK, Simplygon::spScene Scene, const char* SelectionSetName);

	/**
	 * @brief Method to setup camera set from pipelines settings.
	 * @tparam Pipeline Template parameter using a subclass of spPipeline (IPipeline)
	 * @param InPipeline The pipeline to use.
	 * @param bOverride If true will override use to visibility weights. The flag exist for cases where visibility is not exposed to the user i.e current HLOD system.
	*/
	template<typename Pipeline>
	static void SetupVisibilityCameraSetFromPipelineSettings(Simplygon::spPipeline InPipeline, bool bOverride = false);

	template<typename Pipeline>
	static void SetupClippingPlanesFromPipelineSettings(Simplygon::spPipeline InPipeline, bool bOverride = false);

	static bool IsClippingPlaneEnabledInPipeline(Simplygon::spPipeline InPipeline);

	static void DisableClippingPlaneInPipeline(Simplygon::spPipeline InPipeline);

	template<typename Pipeline>
	static bool GetUseClippingPlaneSetting(Simplygon::spPipeline InPipeline);

	template<typename Pipeline>
	static void SetUseClippingPlaneSetting(Simplygon::spPipeline InPipeline, bool bNewValue);

	static void GenerateTextureCoordinates(
		Simplygon::ISimplygon* SDK,
		Simplygon::spGeometryData GeometryData,
		uint32 TexCoordIndex,
		Simplygon::spObject TextureCoordinateGenerator,
	                                        TArray<FVector2D>& OutTexCoords);

	static void PackChartsUsingChartAggregator(
		Simplygon::ISimplygon* SDK,
		Simplygon::spGeometryData GeometryData,
		const FSimplygonFlattenMaterialSettings& FlattenMaterialSettings,
		uint32 TexCoordIndex,
		FAddMeshMaterialInfoResult& MaterialInfo,
		TArray<FVector2D>& OutTexCoords);
	

	static void PopulateLandscapeCullingGeometry(Simplygon::ISimplygon* SDK, 
		Simplygon::spScene Scene, 
		Simplygon::spPipeline InPipeline, 
		UWorld* World, 
		const FBoxSphereBounds& EstimatedBounds);

	static void CreateMorphFieldsFromSkeletalMesh(
		Simplygon::spGeometryData& GeometryData,
		const USkeletalMesh* SkeletalMesh,
		int32 BaseLODModel);

	static void CreateMorphTargetsFromGeometry(
		TArray<class UMorphTarget*>& MorphTargets,
		const Simplygon::spGeometryData GeometryData,
		const int32 LODIndex,
		const FSkeletalMeshLODModel* LODModel,
		const TArray<int32> VerticesToSkip,
		const TSet<TSoftObjectPtr<UMorphTarget>>* MorphTargetsToRemove = nullptr);


	/**
	 * @brief Method tries to cast cascaded pipeline at index to the given type. Returns true on success.
	 * @tparam InPipeline Template parameter using a subclass of spPipeline (IPipeline)
	 * @param InPipeline The pipeline to use.
	 * @param InIndex Index of the cascaded Pipeline (LODIndex -1)
	 * @param OutPipeline the casted pipeline
	 */
	template <typename Pipeline> static bool TryGetCascadedPipeline( const Simplygon::spPipeline InPipeline, uint32 InIndex, Pipeline& OutPipeline )
	{
		if( InPipeline->GetCascadedPipelineCount() == 0 || InIndex >= InPipeline->GetCascadedPipelineCount() )
			return false;

		OutPipeline = Pipeline::SafeCast( InPipeline->GetCascadedPipelineByIndex( InIndex ) );

		return !OutPipeline.IsNull();
	}

	
	template <typename Pipeline>
	static bool IsPipelineOfType(const Simplygon::spPipeline InPipeline) {

		if( InPipeline.IsNull() )
			return false;

		auto TestPipeline = Pipeline::SafeCast( InPipeline );
		return !TestPipeline.IsNull();
	}

	/**
	 * @brief Method check if the cascaded pipeline is of specified type.
	 * @tparam InPipeline Template parameter using a subclass of spPipeline (IPipeline)
	 * @param InPipeline The pipeline to use.
	 * @param InIndex Index of the cascaded Pipeline (LODIndex -1)
	 * system.
	 */
	template <typename Pipeline> 
	static bool IsCascadedPipelineOfType( const Simplygon::spPipeline InPipeline, uint32 InIndex = 0 )
	{

		if( InPipeline->GetCascadedPipelineCount() == 0 || InIndex >= InPipeline->GetCascadedPipelineCount() )
			return false;

		auto TestPipeline = Pipeline::SafeCast( InPipeline->GetCascadedPipelineByIndex( InIndex ) );
		return !TestPipeline.IsNull();

	}

	static FString GetPipelineBakedMaterialBaseTemplatePath( const Simplygon::spPipeline InPipeline, uint32 InIndex = 0 );

	static void UpdateVectorsInPipeline( Simplygon::spPipeline InPipeline );

private:

	/**
	 * @brief Gets material channel texture data from Simplygon::spMaterial
	 * @param InSGMaterial The Simplygon::spMaterial to get data form.
	 * @param TextureTable THe Simplygon::spTextureTable to lookup for texture data.
	 * @param SGMaterialChannelName The material channel name.
	 * @param OutSamples The resulting pixel data.
	 * @param OutTextureSize The texture size.
	 * @param OutEmissiveScale The emissive scale.
	*/
	static void GetMaterialChannelData(const Simplygon::spMaterial& InSGMaterial,
		const Simplygon::spTextureTable& TextureTable,
		const char* SGMaterialChannelName, TArray<FColor>& OutSamples,
		FIntPoint& OutTextureSize,
		float& OutEmissiveScale);

	/**
	 * @brief Setup material channel textures for use with Simplygon
	 * @param SDK Pointer to ISimplygon factory.
	 * @param InSamples The material channel texture data to map to Simplygon.
	 * @param InTextureSize The texture size.
	 * @param InSGMaterial The Simplygon::spMaterial where the channel is setup.
	 * @param TextureTable The texture table where texture data is saved.
	 * @param SGMaterialChannelName The material channel name.
	 * @param sRGB If true the texture data is sRGB.
	 * @param EmissiveScale The emissive scale value.
	*/
	static void SetMaterialChannelData(
		Simplygon::ISimplygon* SDK, 
		const TArray<FColor>& InSamples,
		FIntPoint InTextureSize, 
		Simplygon::spMaterial& InSGMaterial,
		Simplygon::spTextureTable& TextureTable,
		FCriticalSection& TableMutex,
		const char* SGMaterialChannelName, 
		bool sRGB,
		float EmissiveScale);

	/**
	 * @brief Setup FSkeletalMeshData from Simplygon::spGeometryData.
	 * @param GeometryData Simplygon::spGeometryData from which to setup FSkeletalMeshData.
	 * @param MeshData The resulting FSkeletalMeshData.
	 * @param bHasBakedMaterials If set to true will take steps to extract material LOD from Simplygon::spGeometryData.
	*/
	static void ExtractSkeletalDataFromGeometry(
		const Simplygon::spGeometryData& GeometryData,
		struct FSkeletalMeshData& MeshData,
		bool bHasBakedMaterials);

	/**
	 * @brief Remove all material channels.
	 * @param Material The Simplygon::spMaterial from which to remove channels.
	*/
	static void RemoveAllMaterialChannels(Simplygon::spMaterial Material);

	static void ConvertMaterialBakeOutput(
		const TArray<FBakeOutput>& BakeOutputs,
		const TArray<FMaterialData>& MaterialData,
		TArray<FFlattenMaterial>& FlattenedMaterials);

	static EFlattenMaterialProperties MaterialPropertyToFlattenMaterialProperty(int32 NewProperty);

	static void ExtractSections(const USkeletalMesh* SkeletalMesh, int32 LODIndex, TArray<FSectionInfo>& OutSections);
	static void ExtractMeshDescription(const USkeletalMesh* SkeletalMesh, int32 LODIndex, FMeshDescription* OutMeshDescription, bool bPropagateVertexColours);

	
};

/**
 * @brief Flatten Material Settings.
*/
struct FSimplygonFlattenMaterialSettings : public FMaterialProxySettings
{
public:
	FChartAggregatorSettings ChartAggregatorSettings{};

	/**
	 * @brief Constructor.
	*/
	FSimplygonFlattenMaterialSettings() : FMaterialProxySettings() 
	{
		Reset();
	}

	/**
	 * Construct flatten material settings using mapping image settings and caster settings
	 * from a pipeline.
	 * @param Pipeline - Input pipeline holding all data in order to create a flatten material setting.
	 */
	FSimplygonFlattenMaterialSettings(Simplygon::spPipeline Pipeline) : FMaterialProxySettings()
	{
		Reset();
		Simplygon::spObjectCollection Casters = Pipeline->GetMaterialCasters();
		int32 NumCasters = Casters->GetItemCount();

		if (NumCasters <= 0)
			return;

		TextureSize.X = Pipeline->GetMappingImageSettings()->GetOutputMaterialSettings(0)->GetTextureWidth();
		TextureSize.Y = Pipeline->GetMappingImageSettings()->GetOutputMaterialSettings(0)->GetTextureHeight();
		GutterSpace = Pipeline->GetMappingImageSettings()->GetOutputMaterialSettings(0)->GetGutterSpace();

		for (int32 CasterIndex = 0; CasterIndex < NumCasters; ++CasterIndex)
		{
			Simplygon::spObject CasterObject = Casters->GetItemAsObject(CasterIndex);
			Simplygon::spMaterialCaster MaterialCaster = Simplygon::spMaterialCaster::SafeCast(CasterObject);
			if (MaterialCaster == nullptr)
				continue;

			FString ChannelName = ANSI_TO_TCHAR(MaterialCaster->GetMaterialCasterSettings()->GetMaterialChannel().c_str());
			EFlattenMaterialProperties Property;
			if (!FSimplygonMaterialChannelConverter::MaterialChannelToProperty(ChannelName, Property))
				continue;

			switch (Property)
			{
			case EFlattenMaterialProperties::Diffuse:
				//Material baking API always flattens BaseColor/Diffuse
				break;
			case EFlattenMaterialProperties::Metallic:
				bMetallicMap = true;
				break;
			case EFlattenMaterialProperties::Specular:
				bSpecularMap = true;
				break;
			case EFlattenMaterialProperties::Roughness:
				bRoughnessMap = true;
				break;
			case EFlattenMaterialProperties::Normal:
				bNormalMap = true;
				break;
			case EFlattenMaterialProperties::Opacity:
				bOpacityMap = true;
				break;
			case EFlattenMaterialProperties::Emissive:
				bEmissiveMap = true;
				break;
			case EFlattenMaterialProperties::SubSurface:
				//Subsurface is missing in FFlattenMaterialSettings
				break;
			case EFlattenMaterialProperties::OpacityMask:
				bOpacityMaskMap = true;
				break;
			case EFlattenMaterialProperties::AmbientOcclusion:
				bAmbientOcclusionMap = true;
				break;
			case EFlattenMaterialProperties::NumFlattenMaterialProperties:
				break;
			default:
				break;
			}
		}
	}

	/**
	 * Concatenate settings by merging settings from Source. 
	 * NOTE: The larges texture sizes will be chosen.
	 * @param Source - Source settings which should be concatenated.
	 */
	void Concatenate(const FSimplygonFlattenMaterialSettings& Source)
	{
		auto AppendSize = [](const FIntPoint& Src, FIntPoint& Dest)
		{
			if (Src.X > Dest.X)
				Dest.X = Src.X;

			if (Src.Y > Dest.Y)
				Dest.Y = Src.Y;
		};

		bAmbientOcclusionMap |= Source.bAmbientOcclusionMap;
		bEmissiveMap |= Source.bEmissiveMap;
		bMetallicMap |= Source.bMetallicMap;
		bNormalMap |= Source.bNormalMap;
		bOpacityMap |= Source.bOpacityMap;
		bOpacityMaskMap |= Source.bOpacityMaskMap;
		bRoughnessMap |= Source.bRoughnessMap;
		bSpecularMap |= Source.bSpecularMap;

		AppendSize(Source.TextureSize, TextureSize);
		AppendSize(Source.AmbientOcclusionTextureSize, AmbientOcclusionTextureSize);
		AppendSize(Source.DiffuseTextureSize, DiffuseTextureSize);
		AppendSize(Source.EmissiveTextureSize, EmissiveTextureSize);
		AppendSize(Source.MetallicTextureSize, MetallicTextureSize);
		AppendSize(Source.NormalTextureSize, NormalTextureSize);
		AppendSize(Source.OpacityTextureSize, OpacityTextureSize);
		AppendSize(Source.OpacityMaskTextureSize, OpacityMaskTextureSize);
		AppendSize(Source.RoughnessTextureSize, RoughnessTextureSize);
		AppendSize(Source.SpecularTextureSize, SpecularTextureSize);

		bAllowTwoSidedMaterial |= Source.bAllowTwoSidedMaterial;
		if (Source.GutterSpace > GutterSpace)
			GutterSpace = Source.GutterSpace;
	}

	/**
	 * Test if the settings is valid for flattening. Use it to decide whether it is needed.
	 * to flatten or not.
	 */
	bool IsValid() const
	{
		bool bValidTextureSize = TextureSize.X > 0 && TextureSize.Y > 0;

		bool bValidPropertyBaking = bAmbientOcclusionMap
			|| bEmissiveMap
			|| bMetallicMap
			|| bNormalMap
			|| bOpacityMap
			|| bOpacityMaskMap
			|| bRoughnessMap
			|| bSpecularMap;

		return bValidTextureSize || bValidPropertyBaking;
	}

	/**
	 * @brief Reset Material settings.
	*/
	void Reset()
	{
		TextureSizingType = ETextureSizingType::TextureSizingType_UseSingleTextureSize;
		TextureSize = FIntPoint(0, 0);

		bAmbientOcclusionMap = false;
		bEmissiveMap = false;
		bMetallicMap = false;
		bNormalMap = false; 
		bOpacityMap = false; ;
		bOpacityMaskMap = false;
		bRoughnessMap = false;
		bSpecularMap = false;
	}

	/**
	 * @brief Get proxy material settings.
	 * @return 
	*/
	FMaterialProxySettings* GetMaterialProxySettings()
	{
		return dynamic_cast<FMaterialProxySettings*>(this);
	}

	TMap<EMaterialProperty, FIntPoint> GetMaterialPropertySizes() const
	{
		TMap<EMaterialProperty, FIntPoint> PropertySizes;
		PropertySizes.Add(EMaterialProperty::MP_BaseColor, TextureSize);

		if (bAmbientOcclusionMap)
			PropertySizes.Add(EMaterialProperty::MP_AmbientOcclusion, TextureSize);
		if (bEmissiveMap)
			PropertySizes.Add(EMaterialProperty::MP_EmissiveColor, TextureSize);
		if (bMetallicMap)
			PropertySizes.Add(EMaterialProperty::MP_Metallic, TextureSize);
		if (bNormalMap)
			PropertySizes.Add(EMaterialProperty::MP_Normal, TextureSize);
		if (bOpacityMap)
			PropertySizes.Add(EMaterialProperty::MP_Opacity, TextureSize);
		if (bOpacityMaskMap)
			PropertySizes.Add(EMaterialProperty::MP_OpacityMask, TextureSize);
		if (bRoughnessMap)
			PropertySizes.Add(EMaterialProperty::MP_Roughness, TextureSize);
		if (bSpecularMap)
			PropertySizes.Add(EMaterialProperty::MP_Specular, TextureSize);

		return PropertySizes;
	}
};

template<typename Pipeline>
void FSimplygonDataConverter::SetupVisibilityCameraSetFromPipelineSettings(Simplygon::spPipeline InPipeline, bool bOverride)
{
	auto ConcretePipeline = Pipeline::SafeCast(InPipeline);
	if (ConcretePipeline)
	{
		//NOTE: Override is currently used where the visibility setting is not expose to the user. i.e HLOD and standard Proxy Interface
		if (bOverride)
		{
			ConcretePipeline->GetVisibilitySettings()->SetUseVisibilityWeightsInTexcoordGenerator(true);
			ConcretePipeline->GetVisibilitySettings()->SetUseVisibilityWeightsInReducer(true);
			ConcretePipeline->GetVisibilitySettings()->SetCullOccludedGeometry(true);
		}

		bool SetupCameraSet = ConcretePipeline->GetVisibilitySettings()->GetUseVisibilityWeightsInTexcoordGenerator()
			|| ConcretePipeline->GetVisibilitySettings()->GetUseVisibilityWeightsInReducer();

		if (SetupCameraSet)
			ConcretePipeline->GetVisibilitySettings()->SetCameraSelectionSetName(SG_VISIBILITY_SELECTION_SET_NAME);
	}
}

template<typename Pipeline>
void FSimplygonDataConverter::SetupClippingPlanesFromPipelineSettings(Simplygon::spPipeline InPipeline, bool bOverride)
{
	auto ConcretePipeline = Pipeline::SafeCast(InPipeline);
	if (ConcretePipeline)
	{
		//NOTE: Override is currently used where the visibility setting is not expose to the user. i.e HLOD and standard Proxy Interface
		if (bOverride)
		{
			ConcretePipeline->GetGeometryCullingSettings()->SetUseClippingPlanes(true);
		}

		bool SetupClippingPlaneSet = ConcretePipeline->GetGeometryCullingSettings()->GetUseClippingPlanes();

		if (SetupClippingPlaneSet)
			ConcretePipeline->GetGeometryCullingSettings()->SetClippingPlaneSelectionSetName(SG_CLIPPING_PLANES_SELECTION_SET_NAME);
	}
}

template<typename Pipeline>
bool FSimplygonDataConverter::GetUseClippingPlaneSetting(Simplygon::spPipeline InPipeline)
{
	auto ConcretePipeline = Pipeline::SafeCast(InPipeline);
	if constexpr (!TAreTypesEqual<Pipeline, Simplygon::spRemeshingLegacyPipeline>::Value)
	{
		if (ConcretePipeline)
			return ConcretePipeline->GetGeometryCullingSettings()->GetUseClippingPlanes();
	}

	return false;
}

template<typename Pipeline>
void FSimplygonDataConverter::SetUseClippingPlaneSetting(Simplygon::spPipeline InPipeline, bool bNewValue)
{
	auto ConcretePipeline = Pipeline::SafeCast(InPipeline);
	if constexpr (!TAreTypesEqual<Pipeline, Simplygon::spRemeshingLegacyPipeline>::Value)
	{
		if (ConcretePipeline)
			ConcretePipeline->GetGeometryCullingSettings()->SetUseClippingPlanes(bNewValue);
	}

}


