// Copyright Epic Games, Inc. All Rights Reserved.

#include "DatasmithMaxWriter.h"

#include "DatasmithMaxClassIDs.h"
#include "MaxMaterialsToUEPbr/DatasmithMaxMaterialsToUEPbr.h"
#include "DatasmithExportOptions.h"
#include "DatasmithSceneFactory.h"
#include "DatasmithMaxSceneParser.h"
#include "DatasmithMaxLogger.h"
#include "Misc/Paths.h"

#include "Windows/AllowWindowsPlatformTypes.h"
MAX_INCLUDES_START
	#include "bitmap.h"
	#include "xref/iXrefMaterial.h"
MAX_INCLUDES_END 
#include "Windows/HideWindowsPlatformTypes.h"

FString FDatasmithMaxMatWriter::TextureSuffix = TEXT("_Tex");
FString FDatasmithMaxMatWriter::TextureBakeFormat = TEXT(".tga");

bool FDatasmithMaxMatHelper::HasNonBakeableSubmap(Texmap* InTexmap)
{
	if (InTexmap == NULL)
	{
		return false;
	}

	// Skip any view dependent TexMap
	// Having a THIRDPARTYMULTITEXCLASS as an input for a color correction node crashes when calling RenderBitmap on the color correction texmap so skip it.
	if (InTexmap->ClassID() == VRAYDIRTCLASS || InTexmap->ClassID() == CORONAAOCLASS || InTexmap->ClassID() == FALLOFFCLASS || InTexmap->ClassID() == THIRDPARTYMULTITEXCLASS)
	{
		return true;
	}

	if (InTexmap->ClassID() == RBITMAPCLASS)
	{
		StdUVGen* UV = ((BitmapTex*)InTexmap)->GetUVGen();
		if (UV == NULL)
		{
			return true;
		}
		float U = UV->GetUScl(GetCOREInterface()->GetTime());
		float V = UV->GetVScl(GetCOREInterface()->GetTime());
		float IntegralPart;
		if (U > 10.f || V > 10.f || FMath::IsNearlyEqual(FMath::Modf(U, &IntegralPart), 0.f) == false || FMath::IsNearlyEqual(FMath::Modf(V, &IntegralPart), 0.f) == false)
		{
			return true;
		}
	}

	for (int SubMap = 0; SubMap < InTexmap->NumSubTexmaps(); SubMap++)
	{
		if (InTexmap->SubTexmapOn(SubMap) != 0 && HasNonBakeableSubmap(InTexmap->GetSubTexmap(SubMap)) == true)
		{
			return true;
		}
	}

	return false;
}

EDSBitmapType FDatasmithMaxMatHelper::GetTextureClass(Texmap* InTexMap)
{
	EDSBitmapType Type = EDSBitmapType::NotSupported;

	if (InTexMap != nullptr)
	{
		MSTR ClassName;
		InTexMap->GetClassName(ClassName);

		if (InTexMap->ClassID() == RBITMAPCLASS)
		{
			Type = EDSBitmapType::RegularBitmap;
		}
		//Somehow, there are multiple autodesk map classes using the same ClassID, we only support Autodesk Bitmap.
		else if (InTexMap->ClassID() == AUTODESKBITMAPCLASS && FCString::Stricmp(ClassName, TEXT("Autodesk Bitmap")) == 0)
		{
			Type = EDSBitmapType::AutodeskBitmap;
		}
		else if (InTexMap->ClassID() == THEABITMAPCLASS)
		{
			Type = EDSBitmapType::TheaBitmap;
		}
		else if (InTexMap->ClassID() == REGULARNORMALCLASS)
		{
			Type = EDSBitmapType::NormalMap;
		}
		else if (InTexMap->ClassID() == VRAYNORMALCLASS)
		{
			Type = EDSBitmapType::NormalMap;
		}
		else if (InTexMap->ClassID() == CORONANORMALCLASS)
		{
			Type = EDSBitmapType::NormalMap;
		}
		else if (InTexMap->ClassID() == COLORCORRECTCLASS)
		{
			if (FDatasmithMaxMatHelper::HasNonBakeableSubmap(InTexMap) == false)
			{
				Type = EDSBitmapType::BakeableMap;
			}
			else
			{
				Type = EDSBitmapType::ColorCorrector;
			}
		}
		else if (InTexMap->ClassID() == FALLOFFCLASS)
		{
			Type = EDSBitmapType::FallOff;
		}
		else if (InTexMap->ClassID() == MIXCLASS)
		{
			if (FDatasmithMaxMatHelper::HasNonBakeableSubmap(InTexMap) == false)
			{
				Type = EDSBitmapType::BakeableMap;
			}
			else
			{
				Type = EDSBitmapType::Mix;
			}
		}
		else if (InTexMap->ClassID() == NOISECLASS)
		{
			Type = EDSBitmapType::Noise;
		}
		else if (InTexMap->ClassID() == GRADIENTCLASS)
		{
			if (FDatasmithMaxMatHelper::HasNonBakeableSubmap(InTexMap) == false)
			{
				Type = EDSBitmapType::BakeableMap;
			}
			else
			{
				Type = EDSBitmapType::Gradient;
			}
		}
		else if (InTexMap->ClassID() == GRADIENTRAMPCLASS)
		{
			if (FDatasmithMaxMatHelper::HasNonBakeableSubmap(InTexMap) == false)
			{
				Type = EDSBitmapType::BakeableMap;
			}
		}
		else if (InTexMap->ClassID() == CHECKERCLASS)
		{
			if (FDatasmithMaxMatHelper::HasNonBakeableSubmap(InTexMap) == false)
			{
				Type = EDSBitmapType::BakeableMap;
			}
			else
			{
				Type = EDSBitmapType::Checker;
			}
		}
		else if (InTexMap->ClassID() == CELLULARCLASS)
		{
			Type = EDSBitmapType::Cellular;
		}
		else if (InTexMap->ClassID() == VRAYDIRTCLASS)
		{
			Type = EDSBitmapType::VRayDirt;
		}
		else if (InTexMap->ClassID() == COMPOSITETEXCLASS)
		{
			Type = EDSBitmapType::CompositeTex;
		}
		else if (InTexMap->ClassID() == CORONABITMAPCLASS)
		{
			Type = EDSBitmapType::CoronaBitmap;
		}
		else if (InTexMap->ClassID() == VRAYHDRICLASS)
		{
			Type = EDSBitmapType::VRayHRDI;
		}
		else if (InTexMap->ClassID() == CORONACOLORCLASS)
		{
			Type = EDSBitmapType::CoronaColor;
		}
		else if (InTexMap->ClassID() == CORONAMIXCLASS)
		{
			Type = EDSBitmapType::CoronaMix;
		}
		else if (InTexMap->ClassID() == CORONAMULTITEXCLASS)
		{
			Type = EDSBitmapType::CoronaMultiTex;
		}
		else if (InTexMap->ClassID() == CORONAAOCLASS)
		{
			Type = EDSBitmapType::CoronaAO;
		}
		else if (InTexMap->ClassID() == VRAYCOLORCLASS)
		{
			Type = EDSBitmapType::VRayColor;
		}
		else if (InTexMap->ClassID() == THIRDPARTYMULTITEXCLASS)
		{
			Type = EDSBitmapType::ThirdPartyMultiTex;
		}
		else if (InTexMap->ClassID() == CORONAPHYSICALSKYCLASS)
		{
			Type = EDSBitmapType::PhysicalSky;
		}
		else if (InTexMap->ClassID() == VRAYPHYSICALSKYCLASS)
		{
			Type = EDSBitmapType::PhysicalSky;
		}
		else if (InTexMap->ClassID() == MRPHYSICALSKYCLASS)
		{
			Type = EDSBitmapType::PhysicalSky;
		}
		else if (InTexMap->ClassID() == MRPHYSICALSKYBCLASS)
		{
			Type = EDSBitmapType::PhysicalSky;
		}
		else if (InTexMap->ClassID() == TILESMAPCLASS)
		{
			if (FDatasmithMaxMatHelper::HasNonBakeableSubmap(InTexMap) == false)
			{
				Type = EDSBitmapType::BakeableMap;
			}
		}
	}

	return Type;
}

EDSMaterialType FDatasmithMaxMatHelper::GetMaterialClass(Mtl* Material)
{
	EDSMaterialType Type = EDSMaterialType::NotSupported;
	if (Material != nullptr)
	{
		if (Material->ClassID() == THEAMATOLDCLASS)
		{
			Type = EDSMaterialType::TheaMaterialDeprecated;
		}
		else if (Material->ClassID() == THEARANDOMCLASS)
		{
			Type = EDSMaterialType::TheaRandom;
		}
		else if (Material->ClassID() == THEAMATERIALCLASS)
		{
			Type = EDSMaterialType::TheaMaterial;
		}
		else if (Material->ClassID() == THEABASICCLASS)
		{
			Type = EDSMaterialType::TheaBasic;
		}
		else if (Material->ClassID() == THEAGLOSSYCLASS)
		{
			Type = EDSMaterialType::TheaGlossy;
		}
		else if (Material->ClassID() == THEASSSCLASS)
		{
			Type = EDSMaterialType::TheaSSS;
		}
		else if (Material->ClassID() == THEAFILMCLASS)
		{
			Type = EDSMaterialType::TheaFilm;
		}
		else if (Material->ClassID() == THEACOATINGCLASS)
		{
			Type = EDSMaterialType::TheaCoating;
		}
		else if (Material->ClassID() == MULTIMAT_CLASS_ID)
		{
			Type = EDSMaterialType::MultiMat;
		}
		else if (Material->ClassID() == STANDARDMATCLASS)
		{
			Type = EDSMaterialType::StandardMat;
		}
		else if (Material->ClassID() == SHELLCLASS)
		{
			Type = EDSMaterialType::ShellMat;
		}
		else if (Material->ClassID() == BLENDMATCLASS)
		{
			Type = EDSMaterialType::BlendMat;
		}
		else if (Material->ClassID() == XREFMATCLASS)
		{
			Type = EDSMaterialType::XRefMat;
		}
		else if (Material->ClassID() == ARCHDESIGNMATCLASS)
		{
			Type = EDSMaterialType::ArchDesignMat;
		}
		else if (Material->ClassID() == VRAYMATCLASS)
		{
			Type = EDSMaterialType::VRayMat;
		}
		else if (Material->ClassID() == VRAYBLENDMATCLASS)
		{
			Type = EDSMaterialType::VRayBlendMat;
		}
		else if (Material->ClassID() == VRAYLIGHTMATCLASS)
		{
			Type = EDSMaterialType::VRayLightMat;
		}
		else if (Material->ClassID() == VRAYFASTSSSCLASS)
		{
			Type = EDSMaterialType::VRayFastSSSMat;
		}
		else if (Material->ClassID() == CORONAMATCLASS)
		{
			Type = EDSMaterialType::CoronaMat;
		}
		else if (Material->ClassID() == CORONALAYERMATCLASS)
		{
			Type = EDSMaterialType::CoronaLayerMat;
		}
		else if (Material->ClassID() == CORONALIGHTMATCLASS)
		{
			Type = EDSMaterialType::CoronaLightMat;
		}
		else if (Material->ClassID() == PHYSICALMATCLASS)
		{
			Type = EDSMaterialType::PhysicalMat;
		}
	}

	return Type;
}

Mtl* FDatasmithMaxMatHelper::GetRenderedXRefMaterial(Mtl* Material)
{
	const TimeValue CurrentTime = GetCOREInterface()->GetTime();
	const int NumParamBlocks = Material->NumParamBlocks();

	bool bSourceMaterialOverridden = false;
	Mtl* MaterialOverride = nullptr;
	for (int j = 0; j < NumParamBlocks; j++)
	{
		IParamBlock2* ParamBlock2 = Material->GetParamBlockByID((short)j);
		ParamBlockDesc2* ParamBlockDesc = ParamBlock2->GetDesc();

		for (int i = 0; i < ParamBlockDesc->count; i++)
		{
			const ParamDef& ParamDefinition = ParamBlockDesc->paramdefs[i];

			// Diffuse
			if (FCString::Stricmp(ParamDefinition.int_name, TEXT("enableOverride")) == 0)
			{
				bSourceMaterialOverridden = ParamBlock2->GetInt(ParamDefinition.ID, CurrentTime) != 0;
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("overrideMaterial")) == 0)
			{
				MaterialOverride = ParamBlock2->GetMtl(ParamDefinition.ID, CurrentTime);
			}
		}
		ParamBlock2->ReleaseDesc();
	}

	Mtl* RenderedMaterial = nullptr;
	if (bSourceMaterialOverridden)
	{
		RenderedMaterial = MaterialOverride;
	}
	else
	{
		IXRefMaterial* XRefMaterial = static_cast<IXRefMaterial*>(Material);
		RenderedMaterial = XRefMaterial->GetSourceMaterial(true);
	}

	return RenderedMaterial;
}

BMM_Color_fl FDatasmithMaxMatHelper::TemperatureToColor(float Kelvin)
{
	float Temp = Kelvin / 100.0f;
	float Red;
	float Green;
	float Blue;

	if (Temp <= 66.0f)
	{
		Red = 255.0f;
		Green = Temp;
		Green = 99.4708025861f * log(Green) - 161.1195681661f;
		if (Temp <= 19.0f)
		{
			Blue = 0.0f;
		}
		else
		{
			Blue = Temp - 10.0f;
			Blue = 138.5177312231f * log(Blue) - 305.0447927307f;
		}
	}
	else
	{
		Red = Temp - 60.0f;
		Red = 329.698727446f * FMath::Pow(Red, -0.1332047592f);
		Green = Temp - 60;
		Green = 288.1221695283f * FMath::Pow(Green, -0.0755148492f);
		Blue = 255.0f;
	}
	BMM_Color_fl Color;
	Color.r = Red / 255.0f;
	Color.g = Green / 255.0f;
	Color.b = Blue / 255.0f;
	return Color;
}

FLinearColor FDatasmithMaxMatHelper::MaxColorToFLinearColor(BMM_Color_fl Color, float Multiplier /*= 1.0f*/)
{
	Color.r = FMath::Pow(Color.r, 1.0f / FDatasmithExportOptions::ColorGamma);
	Color.g = FMath::Pow(Color.g, 1.0f / FDatasmithExportOptions::ColorGamma);
	Color.b = FMath::Pow(Color.b, 1.0f / FDatasmithExportOptions::ColorGamma);
	float Red = Color.r * Multiplier;
	float Green = Color.g * Multiplier;
	float Blue = Color.b * Multiplier;

	return FLinearColor(Red, Green, Blue);
}

FLinearColor FDatasmithMaxMatHelper::MaxLinearColorToFLinearColor(BMM_Color_fl Color, float Multiplier /*= 1.0f*/)
{
	return FLinearColor( Color.r, Color.g, Color.b );
}

float FDatasmithMaxMatHelper::GetBitmapGamma(BitmapTex* InBitmapTex)
{
	if (InBitmapTex)
	{
		if (Bitmap* ActualBitmap = InBitmapTex->GetBitmap(GetCOREInterface()->GetTime()))
		{
			return ActualBitmap->Gamma();
		}
	}
	return 2.2f;
}

float FDatasmithMaxMatHelper::GetBitmapGamma(BitmapInfo* InBitmap)
{
	if (InBitmap)
	{
		return InBitmap->Gamma();
	}
	return 2.2f;
}

bool FDatasmithMaxMatHelper::IsSRGB(BitmapTex& InBitmapTex)
{
	if (Bitmap* ActualBitmap = InBitmapTex.GetBitmap(GetCOREInterface()->GetTime()))
	{
		return IsSRGB(*ActualBitmap);
	}
	return true;
}

bool FDatasmithMaxMatHelper::IsSRGB(Bitmap& InBitmap)
{
	if (InBitmap.IsHighDynamicRange() )
	{
		return false;
	}

#ifdef MAX_RELEASE_R19 
	BitmapInfo BitmapInformation = InBitmap.GetBitmapInfo();

	switch ( BitmapInformation.Type() )
	{
	// UE considers that all bit depth >= 16 are linear
	case BMM_GRAY_16:
	case BMM_FLOAT_RGBA_32:
	case BMM_FLOAT_GRAY_32:
	case BMM_REALPIX_32:
	case BMM_TRUE_48:
	case BMM_TRUE_64:
		return false; // Linear
		break;
	default:
		return true; // sRGB
		break;
	}
#endif // MAX_RELEASE_R19

	return true;
}

void FDatasmithMaxMatExport::GetXMLTexture(TSharedRef< IDatasmithScene > DatasmithScene, Texmap* InTexMap, const TCHAR* AssetsPath)
{
	switch (FDatasmithMaxMatHelper::GetTextureClass(InTexMap))
	{
	case EDSBitmapType::NotSupported:
		return;
		break;
	case EDSBitmapType::RegularBitmap:
		FDatasmithMaxMatWriter::GetRegularTexmap(DatasmithScene, (BitmapTex*)InTexMap);
		break;
	case EDSBitmapType::AutodeskBitmap:
		FDatasmithMaxMatWriter::GetAutodeskTexmap(DatasmithScene, InTexMap);
		break; 
	case EDSBitmapType::TheaBitmap:
		FDatasmithMaxMatWriter::GetTheaTexmap(DatasmithScene, (BitmapTex*)InTexMap);
		break;
	case EDSBitmapType::CoronaBitmap:
		FDatasmithMaxMatWriter::GetCoronaTexmap(DatasmithScene, (BitmapTex*)InTexMap);
		break;
	case EDSBitmapType::VRayHRDI:
		FDatasmithMaxMatWriter::GetVrayHdri(DatasmithScene, (BitmapTex*)InTexMap);
		break;
	case EDSBitmapType::ColorCorrector:
		GetXMLTexture(DatasmithScene, InTexMap->GetSubTexmap(0), AssetsPath);
		break;
	case EDSBitmapType::BakeableMap:
		FDatasmithMaxMatWriter::AddBakeable(DatasmithScene, (BitmapTex*)InTexMap, AssetsPath);
		break;
	case EDSBitmapType::NormalMap:
	case EDSBitmapType::FallOff:
	case EDSBitmapType::Gradient:
	case EDSBitmapType::GradientRamp:
	case EDSBitmapType::Checker:
	case EDSBitmapType::Cellular:
	case EDSBitmapType::Mix:
	case EDSBitmapType::Noise:
	case EDSBitmapType::CompositeTex:
		for (int i = 0; i < InTexMap->NumSubTexmaps(); i++)
		{
			GetXMLTexture(DatasmithScene, InTexMap->GetSubTexmap(i), AssetsPath);
		}
		break;
	default:
		break;
	}
}

bool FDatasmithMaxMatExport::UseFirstSubMapOnly(EDSMaterialType MaterialType, Mtl* Material)
{
	switch (MaterialType)
	{
	case EDSMaterialType::TheaMaterial:
	case EDSMaterialType::MultiMat:
	case EDSMaterialType::StandardMat:
	case EDSMaterialType::ArchDesignMat:
	case EDSMaterialType::VRayMat:
	case EDSMaterialType::VRayLightMat:
	case EDSMaterialType::CoronaMat:
	case EDSMaterialType::CoronaLightMat:
	case EDSMaterialType::BlendMat:
	case EDSMaterialType::XRefMat:
	case EDSMaterialType::VRayBlendMat:
	case EDSMaterialType::CoronaLayerMat:
		return false;
	default:
		if (Material->NumSubMtls() < 1)
		{
			return false;
		}
		else
		{
			return true;
		}
	}
}

TSharedPtr< IDatasmithBaseMaterialElement > FDatasmithMaxMatExport::ExportUniqueMaterial(TSharedRef< IDatasmithScene > DatasmithScene, Mtl* Material, const TCHAR* AssetsPath)
{
	// Names should be unique prior to this
	FString MaterialName(FDatasmithUtils::SanitizeObjectName(Material->GetName().data()));
	for (int i = 0; i < DatasmithScene->GetMaterialsCount(); i++)
	{
		if (FString(DatasmithScene->GetMaterial(i)->GetName()) == MaterialName)
		{
			return DatasmithScene->GetMaterial(i);
		}
	}

	if ( FDatasmithMaxMaterialsToUEPbr* MaterialConverter = FDatasmithMaxMaterialsToUEPbrManager::GetMaterialConverter( Material ) )
	{
		TSharedPtr< IDatasmithBaseMaterialElement > UEPbrMaterial;

		MaterialConverter->Convert( DatasmithScene, UEPbrMaterial, Material, AssetsPath );

		if ( UEPbrMaterial )
		{
			DatasmithScene->AddMaterial( UEPbrMaterial );
		}

		return UEPbrMaterial;
	}
	else
	{
		TSharedRef< IDatasmithMaterialElement > MaterialElement = FDatasmithSceneFactory::CreateMaterial( (TCHAR*)Material->GetName().data() );
		DatasmithScene->AddMaterial( MaterialElement );

		WriteXMLMaterial( DatasmithScene, MaterialElement, Material );

		return MaterialElement;
	}
}

void FDatasmithMaxMatExport::WriteXMLMaterial(TSharedRef< IDatasmithScene > DatasmithScene, TSharedPtr< IDatasmithMaterialElement > MaterialElement, Mtl* Material)
{
	if (Material == nullptr)
	{
		return;
	}

	EDSMaterialType MaterialType = FDatasmithMaxMatHelper::GetMaterialClass(Material);

	switch (FDatasmithMaxMatHelper::GetMaterialClass(Material))
	{
	case EDSMaterialType::StandardMat:
		FDatasmithMaxMatWriter::ExportStandardMaterial(DatasmithScene, MaterialElement, Material);
		break;

	case EDSMaterialType::TheaMaterial:
		FDatasmithMaxMatWriter::ExportTheaMaterial(DatasmithScene, MaterialElement, Material);
		break;

	case EDSMaterialType::VRayMat:
		FDatasmithMaxMatWriter::ExportVRayMaterial(DatasmithScene, MaterialElement, Material);
		break;

	case EDSMaterialType::VRayLightMat:
		FDatasmithMaxMatWriter::ExportVRayLightMaterial(DatasmithScene, MaterialElement, Material);
		break;

	case EDSMaterialType::ArchDesignMat:
		FDatasmithMaxMatWriter::ExportArchDesignMaterial(DatasmithScene, MaterialElement, Material);
		break;

	case EDSMaterialType::CoronaMat:
		FDatasmithMaxMatWriter::ExportCoronaMaterial(DatasmithScene, MaterialElement, Material);
		break;

	case EDSMaterialType::CoronaLightMat:
		FDatasmithMaxMatWriter::ExportCoronaLightMaterial(DatasmithScene, MaterialElement, Material);
		break;

	case EDSMaterialType::BlendMat:
		FDatasmithMaxMatWriter::ExportBlendMaterial(DatasmithScene, MaterialElement, Material);
		break;

	case EDSMaterialType::XRefMat:
		FDatasmithMaxMatExport::WriteXMLMaterial(DatasmithScene, MaterialElement, FDatasmithMaxMatHelper::GetRenderedXRefMaterial(Material));
		break;

	case EDSMaterialType::VRayBlendMat:
		FDatasmithMaxMatWriter::ExportVrayBlendMaterial(DatasmithScene, MaterialElement, Material);
		break;

	case EDSMaterialType::CoronaLayerMat:
		FDatasmithMaxMatWriter::ExportCoronaBlendMaterial(DatasmithScene, MaterialElement, Material);
		break;

	case EDSMaterialType::PhysicalMat:
		FDatasmithMaxMatWriter::ExportPhysicalMaterial(DatasmithScene, MaterialElement, Material);
		break;

	case EDSMaterialType::TheaRandom:
		return; // already added!
		break;

	default:
		if (UseFirstSubMapOnly(MaterialType, Material) == false)
		{
			DatasmithMaxLogger::Get().AddUnsupportedMat(Material);
		}
		else
		{
			DatasmithMaxLogger::Get().AddPartialSupportedMat(Material);
			FDatasmithMaxMatExport::WriteXMLMaterial(DatasmithScene, MaterialElement, Material->GetSubMtl(0));
		}
		break;
	}
}

FString FDatasmithMaxMatWriter::DumpTexture(TSharedRef< IDatasmithScene > DatasmithScene, TSharedPtr<IDatasmithCompositeTexture>& CompTex, Texmap* InTexmap, const TCHAR* Prefix, const TCHAR* ColorPrefix, bool bForceInvert, bool bIsGrayscale)
{
	if (InTexmap == NULL)
	{
		return TEXT("");
	}

	// if it is not a regular one it should be nested.
	if (CompTex->GetMode() != EDatasmithCompMode::Regular)
	{
		TSharedPtr<IDatasmithCompositeTexture> ChildCompTex = FDatasmithSceneFactory::CreateCompositeTexture();
		FString Result = DumpTexture(DatasmithScene, ChildCompTex, InTexmap, Prefix,ColorPrefix, bForceInvert, bIsGrayscale);
		CompTex->AddSurface(ChildCompTex);
		return Result;
	}

	EDSBitmapType TextureClass = FDatasmithMaxMatHelper::GetTextureClass(InTexmap);
	if (FString(Prefix) == DATASMITH_BUMPTEXNAME && TextureClass != EDSBitmapType::NormalMap)
	{
		DatasmithMaxLogger::Get().AddTextureError(TEXT("Bump maps are automatically converted to normal maps, as Unreal Engine does not handle bump maps directly."));
	}

	switch( TextureClass )
	{
		case EDSBitmapType::RegularBitmap:
		{
			return DumpBitmap(CompTex, (BitmapTex*)InTexmap, Prefix, bForceInvert, bIsGrayscale);
		}
		case EDSBitmapType::AutodeskBitmap:
		{
			return DumpAutodeskBitmap(CompTex, InTexmap, Prefix, bForceInvert, bIsGrayscale);
		}
		case EDSBitmapType::TheaBitmap:
		{
			return DumpBitmapThea(CompTex, (BitmapTex*)InTexmap, Prefix, bForceInvert, bIsGrayscale);
		}
		case EDSBitmapType::CoronaBitmap:
		{
			return DumpBitmapCorona(CompTex, (BitmapTex*)InTexmap, Prefix, bForceInvert, bIsGrayscale);
		}
		case EDSBitmapType::VRayHRDI:
		{
			return DumpVrayHdri(CompTex, (BitmapTex*)InTexmap, Prefix, bForceInvert);
		}
		case EDSBitmapType::ColorCorrector:
		{
			if(FDatasmithMaxMatHelper::HasNonBakeableSubmap(InTexmap) == false)
			{
				return DumpBakeable(CompTex, (BitmapTex*)InTexmap, Prefix, bForceInvert, bIsGrayscale);
			}
			else
			{
				return DumpColorCorrect(DatasmithScene, CompTex, InTexmap, Prefix, bForceInvert, bIsGrayscale);
			}
		}
		case EDSBitmapType::Gradient:
		{
			DatasmithMaxLogger::Get().AddPartialSupportedMap(InTexmap);
			if (InTexmap->GetSubTexmap(0) != NULL)
			{
				return DumpTexture(DatasmithScene, CompTex, InTexmap->GetSubTexmap(0), Prefix, ColorPrefix, bForceInvert, bIsGrayscale);
			}
			else
			{
				return DumpColorOfTexmap(CompTex, InTexmap, ColorPrefix, TEXT("color1"));
			}
		}
		case EDSBitmapType::GradientRamp:
		{
			DatasmithMaxLogger::Get().AddPartialSupportedMap(InTexmap);
			if (InTexmap->GetSubTexmap(0) != NULL)
			{
				return DumpTexture(DatasmithScene, CompTex, InTexmap->GetSubTexmap(0), Prefix, ColorPrefix, bForceInvert, bIsGrayscale);
			}
			else
			{
				return DumpColorOfTexmap(CompTex, InTexmap, ColorPrefix, TEXT("color1"));
			}
		}
		case EDSBitmapType::Checker:
		{
			DatasmithMaxLogger::Get().AddPartialSupportedMap(InTexmap);
			if (InTexmap->GetSubTexmap(0) != NULL)
			{
				return DumpTexture(DatasmithScene, CompTex, InTexmap->GetSubTexmap(0), Prefix, ColorPrefix, bForceInvert, bIsGrayscale);
			}
			else
			{
				return DumpColorOfTexmap(CompTex, InTexmap, ColorPrefix, TEXT("color1"));
			}
		}
		case EDSBitmapType::Cellular:
		{
			DatasmithMaxLogger::Get().AddPartialSupportedMap(InTexmap);
			if (InTexmap->GetSubTexmap(0) != NULL)
			{
				return DumpTexture(DatasmithScene, CompTex, InTexmap->GetSubTexmap(0), Prefix, ColorPrefix, bForceInvert, bIsGrayscale);
			}
			else
			{
				return DumpColorOfTexmap(CompTex, InTexmap, ColorPrefix, TEXT("cellColor"));
			}
		}
		case EDSBitmapType::FallOff:
		{
			return DumpFalloff(DatasmithScene, CompTex, InTexmap, Prefix, bForceInvert, bIsGrayscale);
		}
		case EDSBitmapType::Mix:
		{
			return DumpMix(DatasmithScene, CompTex, InTexmap, Prefix, bForceInvert, bIsGrayscale);
		}
		case EDSBitmapType::CompositeTex:
		{
			return DumpCompositetex(DatasmithScene, CompTex, InTexmap, Prefix, bForceInvert, bIsGrayscale);
		}
		case EDSBitmapType::NormalMap:
		{
			return DumpNormalTexture(DatasmithScene, CompTex, InTexmap, Prefix, ColorPrefix, bForceInvert, bIsGrayscale);
		}
		case EDSBitmapType::Noise:
		{
			DatasmithMaxLogger::Get().AddPartialSupportedMap(InTexmap);
			if (InTexmap->GetSubTexmap(0) != NULL)
			{
				return DumpTexture(DatasmithScene, CompTex, InTexmap->GetSubTexmap(0), Prefix, ColorPrefix, bForceInvert, bIsGrayscale);
			}
			else
			{
				return DumpColorOfTexmap(CompTex, InTexmap, ColorPrefix, TEXT("color1"));
			}
		}
		case EDSBitmapType::VRayDirt:
		{
			DatasmithMaxLogger::Get().AddTextureError(TEXT("V-Ray dirt texture is not supported and only the unoccluded texture/color will be used"));

			if (InTexmap->GetSubTexmap(0) != NULL)
			{
				return DumpTexture(DatasmithScene, CompTex, InTexmap->GetSubTexmap(0), Prefix, ColorPrefix, bForceInvert, bIsGrayscale);
			}
			else
			{
				return DumpColorOfTexmap(CompTex, InTexmap, ColorPrefix, TEXT("unoccluded_color"));
			}
		}
		case EDSBitmapType::CoronaAO:
		{
			DatasmithMaxLogger::Get().AddGeneralError(TEXT("CoronaAO texture is not supported and only the unoccluded texture/color will be used"));

			if (InTexmap->GetSubTexmap(1) != NULL)
			{
				return DumpTexture(DatasmithScene, CompTex, InTexmap->GetSubTexmap(1), Prefix, ColorPrefix, bForceInvert, bIsGrayscale);
			}
			else
			{
				return DumpColorOfTexmap(CompTex, InTexmap, ColorPrefix, TEXT("colorUnoccluded"));
			}
		}
		case EDSBitmapType::VRayColor:
		{
			return DumpVrayColor(CompTex, InTexmap, Prefix, bForceInvert);
		}
		case EDSBitmapType::CoronaColor:
		{
			return DumpCoronaColor(CompTex, InTexmap, Prefix, bForceInvert);
		}
		case EDSBitmapType::CoronaMix:
		{
			return DumpCoronaMix(DatasmithScene, CompTex, InTexmap, Prefix, bForceInvert, bIsGrayscale);
		}
		case EDSBitmapType::CoronaMultiTex:
		{
			DatasmithMaxLogger::Get().AddPartialSupportedMap(InTexmap);
			return DumpCoronaMultitex(DatasmithScene, CompTex, InTexmap, Prefix, ColorPrefix, bForceInvert, bIsGrayscale);
		}
		case EDSBitmapType::PhysicalSky:
		{
			DatasmithScene->SetUsePhysicalSky(true);
			return TEXT("");
		}
		case EDSBitmapType::ThirdPartyMultiTex:
		{
			DatasmithMaxLogger::Get().AddPartialSupportedMap(InTexmap);
			if (InTexmap->GetSubTexmap(0) != NULL)
			{
				return DumpTexture(DatasmithScene, CompTex, InTexmap->GetSubTexmap(0), Prefix, ColorPrefix, bForceInvert, bIsGrayscale);
			}
			else
			{
				return DumpColorOfTexmap(CompTex, InTexmap, ColorPrefix, TEXT("sub_color"));
			}
		}
		case EDSBitmapType::BakeableMap:
		{
			if (FDatasmithMaxMatHelper::HasNonBakeableSubmap(InTexmap) == false)
			{
				return DumpBakeable(CompTex, (BitmapTex*)InTexmap, Prefix, bForceInvert, bIsGrayscale);
			}
		}
		default:
		{

			if (InTexmap->NumSubTexmaps() > 0)
			{
				DatasmithMaxLogger::Get().AddUnsupportedMap(InTexmap);
				// just for try!
				if (InTexmap->GetSubTexmap(0) != NULL)
				{
					return DumpTexture(DatasmithScene, CompTex, InTexmap->GetSubTexmap(0), Prefix, ColorPrefix, bForceInvert, bIsGrayscale);
				}
				else
				{
					return DumpColorOfTexmap(CompTex, InTexmap, ColorPrefix, TEXT("color1"));
				}
			}
			else
			{
				DatasmithMaxLogger::Get().AddUnsupportedMap(InTexmap);
				return DumpColorOfTexmap(CompTex, InTexmap, ColorPrefix, TEXT("color1"));
			}
		}
	}
}

FString FDatasmithMaxMatWriter::DumpNormalTexture(TSharedRef< IDatasmithScene > DatasmithScene, TSharedPtr<IDatasmithCompositeTexture>& CompTex, Texmap* InTexmap, const TCHAR* Prefix, const TCHAR* ColorPrefix, bool bForceInvert, bool bIsGrayscale)
{
	int NumParamBlocks = InTexmap->NumParamBlocks();

	bool bInvertGreen = false;
	for (int j = 0; j < NumParamBlocks; j++)
	{
		IParamBlock2* ParamBlock2 = InTexmap->GetParamBlockByID((short)j);
		// The the descriptor to 'decode'
		ParamBlockDesc2* ParamBlockDesc = ParamBlock2->GetDesc();
		// Loop through all the defined parameters therein
		for (int i = 0; i < ParamBlockDesc->count; i++)
		{
			const ParamDef& ParamDefinition = ParamBlockDesc->paramdefs[i];
			if (FCString::Stricmp(ParamDefinition.int_name, TEXT("flip_green")) == 0 || FCString::Stricmp(ParamDefinition.int_name, TEXT("flipgreen")) == 0)
			{
				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) == 1)
				{
					bInvertGreen = true;
				}
				continue;
			}

			if (FCString::Stricmp(ParamDefinition.int_name, TEXT("complement_ddl")) == 0)
			{
				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) == 4)
				{
					bInvertGreen = false;
				}
				else
				{
					bInvertGreen = true;
				}
				continue;
			}
		}
	}

	EDSBitmapType TextureClass = FDatasmithMaxMatHelper::GetTextureClass(InTexmap);
	if (TextureClass == EDSBitmapType::NormalMap)
	{
		if (FString(Prefix) != DATASMITH_BUMPTEXNAME)
		{
			FString DumpName = DumpTexture(DatasmithScene, CompTex, InTexmap->GetSubTexmap(0), Prefix, ColorPrefix, bForceInvert || bInvertGreen, bIsGrayscale);

			TSharedPtr< IDatasmithTextureElement > TextureElement;
			for (int TextureIndex = 0; TextureIndex < DatasmithScene->GetTexturesCount(); TextureIndex++)
			{
				if (DumpName == DatasmithScene->GetTexture(TextureIndex)->GetName())
				{
					TextureElement = DatasmithScene->GetTexture(TextureIndex);
				}
			}
		
			if (TextureElement)
			{
				// Only process the TextureElement once (we assume that the "Other" TextureMode means that it hasn't been processed yet)
				if ( TextureElement->GetTextureMode() == EDatasmithTextureMode::Other )
				{
					if ( bInvertGreen )
					{
						TextureElement->SetTextureMode( EDatasmithTextureMode::NormalGreenInv );
					}
					else
					{
						TextureElement->SetTextureMode( EDatasmithTextureMode::Normal );
					}

					float RgbCurve = TextureElement->GetRGBCurve();
					// if RgbCurve <= 0 it has been set to default mode so we can forget about that
					// this happens mostly when no advanced gamma correction is enabled in 3dsmax preferences
					if (RgbCurve > 0.f)
					{
						if (FDatasmithMaxMatWriter::GetCoronaFixNormal(InTexmap) == true)
						{
							// corona has a checkbox to work in automatic mode
							RgbCurve = -1.f;
						}
						else
						{
							// Bitmaps are by default using Gamma/2.2f RGB curve but the reference for normalmaps
							// should be Gamma/1.0f 
							// if the user has set the proper value (1.0) the result will be 2.2/2.2 = 1.0
							RgbCurve *= 2.2f;
						}
						TextureElement->SetRGBCurve(RgbCurve);

						if (FMath::IsNearlyEqual(RgbCurve, 1.0f, 0.001f) == false && RgbCurve > 0.0f)
						{
							FString Error = FString("Potential wrong input gamma on normalmap: ") + FPaths::GetCleanFilename(TextureElement->GetFile());
							DatasmithMaxLogger::Get().AddTextureError(*Error);
						}
					}
				}
			}
			return DumpName;
		}
		else
		{
			return DumpTexture(DatasmithScene, CompTex, InTexmap->GetSubTexmap(1), TEXT(""), TEXT(""), bForceInvert, bIsGrayscale);
		}
	}

	if (TextureClass == EDSBitmapType::TheaBitmap)
	{
		return DumpBitmapThea(CompTex, (BitmapTex*)InTexmap, Prefix, bForceInvert || bInvertGreen, bIsGrayscale);
	}

	return TEXT("");
}

void FDatasmithMaxMatWriter::DumpWeightedTexture(TSharedRef< IDatasmithScene > DatasmithScene, TSharedPtr<IDatasmithCompositeTexture>& CompTex, Texmap* InTexmap, BMM_Color_fl Color, float Weight, const TCHAR* Prefix, const TCHAR* ColorPrefix, bool bForceInvert, bool bIsGrayscale)
{
	FString ActualPrefix = FString(Prefix) + FString(TEXT("comp"));
	CompTex->SetMode(EDatasmithCompMode::Mix);
	DumpTexture(DatasmithScene, CompTex, InTexmap, DATASMITH_TEXTURENAME, DATASMITH_COLORNAME, bForceInvert, bIsGrayscale);

	CompTex->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(Color));
	CompTex->AddParamVal1(IDatasmithCompositeTexture::ParamVal(1.0f-(float)Weight, TEXT("Weight")));
}
void FDatasmithMaxMatWriter::DumpWeightedColor(TSharedPtr<IDatasmithCompositeTexture>& CompTex, BMM_Color_fl ColorA, BMM_Color_fl ColorB, float Weight, const TCHAR* Prefix)
{
	if (Weight == 1)
	{
		CompTex->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(ColorA));
		return;
	}
	if (Weight == 0)
	{
		CompTex->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(ColorB));
		return;
	}
	FString ActualPrefix = FString(Prefix) + FString(TEXT("comp"));
	CompTex->SetMode(EDatasmithCompMode::Mix);

	CompTex->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(ColorA));
	CompTex->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(ColorB));
	CompTex->AddParamVal1(IDatasmithCompositeTexture::ParamVal(1.0f-(float)Weight, TEXT("Weight")));
}

FString FDatasmithMaxMatWriter::DumpColorOfTexmap(TSharedPtr<IDatasmithCompositeTexture>& CompTex, Texmap* InTexmap, const TCHAR* ColorPrefix, TCHAR* Property)
{
	int NumParamBlocks = InTexmap->NumParamBlocks();

	for (int j = 0; j < NumParamBlocks; j++)
	{
		IParamBlock2* ParamBlock2 = InTexmap->GetParamBlockByID((short)j);
		if (ParamBlock2 == NULL)
		{
			return TEXT("");
		}

		// The the descriptor to 'decode'
		ParamBlockDesc2* ParamBlockDesc = ParamBlock2->GetDesc();
		if (ParamBlockDesc == NULL)
		{
			return TEXT("");
		}

		// Loop through all the defined parameters therein
		for (int i = 0; i < ParamBlockDesc->count; i++)
		{
			const ParamDef& ParamDefinition = ParamBlockDesc->paramdefs[i];

			if (FCString::Stricmp(ParamDefinition.int_name, Property) == 0)
			{
				BMM_Color_fl Color = (BMM_Color_fl)ParamBlock2->GetColor(ParamDefinition.ID, GetCOREInterface()->GetTime());
				CompTex->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(Color));
				return TEXT("");
			}
		}
		ParamBlock2->ReleaseDesc();
	}

	// not found using 'property', choose first color
	for (int j = 0; j < NumParamBlocks; j++)
	{
		IParamBlock2* ParamBlock2 = InTexmap->GetParamBlockByID((short)j);
		ParamBlockDesc2* ParamBlockDesc = ParamBlock2->GetDesc();
		for (int i = 0; i < ParamBlockDesc->count; i++)
		{
			const ParamDef& ParamDefinition = ParamBlockDesc->paramdefs[i];
			BMM_Color_fl Color = (BMM_Color_fl)ParamBlock2->GetColor(ParamDefinition.ID, GetCOREInterface()->GetTime());

			if (Color.r != 0 || Color.g != 0 || Color.b != 0)
			{
				CompTex->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(Color));
				return TEXT("");
			}
		}
		ParamBlock2->ReleaseDesc();
	}

	return TEXT("");
}
