// Copyright Epic Games, Inc. All Rights Reserved.
#include "DatasmithMaxWriter.h"

#include "DatasmithSceneFactory.h"
#include "DatasmithSceneExporter.h"
#include "DatasmithMaxSceneParser.h"
#include "DatasmithMaxLogger.h"
#include "DatasmithExportOptions.h"
#include "DatasmithMaxSceneExporter.h"
#include "DatasmithMaxTexmapParser.h"
#include "DatasmithMaxHelper.h"

#include "Misc/Paths.h"

#include "Windows/AllowWindowsPlatformTypes.h"
MAX_INCLUDES_START
#include "bitmap.h"
#include "pbbitmap.h"
#include "plugapi.h"
MAX_INCLUDES_END 
#include "Windows/HideWindowsPlatformTypes.h"


struct CropParameters
{
	bool bApplyCropping = false;
	float HorizontalStart = 0.0f;
	float VerticalStart = 0.0f;
	float Width = 1.0f;
	float Height = 1.0f;
};



void GetBitmapCroppingInfo(BitmapTex* InBitmapTex, CropParameters &Cropping)
{
	bool bCropEnabled = false;
	int CropPlace = 0;
	float ClipU = 0.0f;
	float ClipV = 0.0f;
	float ClipW = 1.0f;
	float ClipH = 1.0f;

	int NumParamBlocks = InBitmapTex->NumParamBlocks();
	for (int j = 0; j < NumParamBlocks; j++)
	{
		IParamBlock2* ParamBlock2 = InBitmapTex->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("apply")) == 0)
			{
				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) != 0)
				{
					bCropEnabled = true;
				}
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("cropPlace")) == 0)
			{
				CropPlace = ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("clipu")) == 0)
			{
				ClipU = ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("clipv")) == 0)
			{
				ClipV = ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("clipw")) == 0)
			{
				ClipW = ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("cliph")) == 0)
			{
				ClipH = ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
		}
		ParamBlock2->ReleaseDesc();
	}

	if (bCropEnabled == true && CropPlace == 0 && (ClipU != 0 || ClipV != 0 || ClipW != 0 || ClipH != 0))
	{
		Cropping.bApplyCropping = true;
		Cropping.HorizontalStart = ClipU;
		Cropping.VerticalStart = ClipV;
		Cropping.Width = ClipW;
		Cropping.Height = ClipH;
	}
	else
	{
		Cropping.bApplyCropping = false;
	}
}

FString FDatasmithMaxMatWriter::GetActualBitmapPath(BitmapTex* InBitmapTex)
{
	if ( !InBitmapTex )
	{
		return FString();
	}

	FString ActualFilePath = FDatasmithMaxSceneExporter::GetActualPath(InBitmapTex->GetMap().GetFullFilePath().data());	

	// if path is empty 3dsmax was not able to resolve the file path but we'll get it in other way just to force logging it
	if (ActualFilePath.IsEmpty())
	{
		ActualFilePath = InBitmapTex->GetMap().GetFileName();
	}

	if (FPaths::FileExists(ActualFilePath) == false)
	{
		return ActualFilePath;
	}

	CropParameters Cropping;
	GetBitmapCroppingInfo(InBitmapTex, Cropping);

	if ( Cropping.bApplyCropping )
	{
		FString Base = FPaths::GetBaseFilename(ActualFilePath);
		FString NewBasename = Base + FString("_3dsmaxcrop");
		FString NewFilename = FPaths::GetPath(ActualFilePath) + FString("\\") + NewBasename;
		NewFilename = NewFilename + FString(".") + FPaths::GetExtension(ActualFilePath);
		ActualFilePath = NewFilename;
	}

	return ActualFilePath;
}

FString FDatasmithMaxMatWriter::GetActualBitmapPath(BitmapInfo* InBitmapInfo)
{
	if (!InBitmapInfo)
	{
		return FString();
	}

	FString ActualBitmapPath = FDatasmithMaxSceneExporter::GetActualPath(InBitmapInfo->GetPathEx().GetString());
	
	if (ActualBitmapPath.IsEmpty())
	{
		ActualBitmapPath = InBitmapInfo->Name();
	}

	return ActualBitmapPath;
}

template<typename T>
FString GetActualBitmapNameImpl(T* InBitmap)
{
	FString ActualBitmapPath = FDatasmithMaxMatWriter::GetActualBitmapPath(InBitmap);

	if (ActualBitmapPath.IsEmpty())
	{
		return FString();
	}

	float Gamma = FDatasmithMaxMatHelper::GetBitmapGamma(InBitmap);
	FString ActualBitmapName = FDatasmithUtils::SanitizeObjectName(FPaths::GetBaseFilename(ActualBitmapPath)) + TEXT("_") + FString::SanitizeFloat(Gamma).Replace(TEXT("."), TEXT("_")) + FDatasmithMaxMatWriter::TextureSuffix;

	return ActualBitmapName;
}

FString FDatasmithMaxMatWriter::GetActualBitmapName(BitmapTex* InBitmapTex)
{
	return GetActualBitmapNameImpl(InBitmapTex);
}

FString FDatasmithMaxMatWriter::GetActualBitmapName(BitmapInfo* InBitmapInfo)
{
	return GetActualBitmapNameImpl(InBitmapInfo);
}

FString FDatasmithMaxMatWriter::CropBitmap(BitmapTex* InBitmapTex)
{
	const FString ActualFilePath = GetActualBitmapPath(InBitmapTex);

	Bitmap* ActualBitmap = InBitmapTex->GetBitmap(GetCOREInterface()->GetTime());

	// No need to check on cropping info if actual bitmap is invalid 
	if (ActualBitmap != nullptr)
	{
		CropParameters Cropping;
		GetBitmapCroppingInfo(InBitmapTex, Cropping);

		if (Cropping.bApplyCropping == true)
		{
			BitmapInfo BitmapInformation;

			// 3dsmax v2017 and newer allows the developers to read the current bitmap info 
			// so our output will be even more similar to the input
#ifdef MAX_RELEASE_R19 
			BitmapInformation = ActualBitmap->GetBitmapInfo();
			switch (ActualBitmap->GetBitmapInfo().Type())
			{
			case BMM_TRUE_24:
				BitmapInformation.SetType(BMM_TRUE_32);
				break;
			case BMM_TRUE_48:
				BitmapInformation.SetType(BMM_TRUE_64);
				break;
			case BMM_YUV_422:
				BitmapInformation.SetType(BMM_TRUE_16);
				break;
			case BMM_BMP_4:
				BitmapInformation.SetType(BMM_TRUE_16);
				break;
			case BMM_PAD_24:
				BitmapInformation.SetType(BMM_TRUE_32);
				break;
			default:
				BitmapInformation.SetType(ActualBitmap->GetBitmapInfo().Type());
				break;
			}
#else
			BitmapInformation.SetGamma(ActualBitmap->Gamma());
			if (ActualBitmap->IsHighDynamicRange())
			{
				BitmapInformation.SetType(BMM_FLOAT_RGBA_32);
			}
			else
			{
				BitmapInformation.SetType(BMM_TRUE_32);
			}
#endif


			BitmapInformation.SetWidth(ActualBitmap->Width() * Cropping.Width);
			BitmapInformation.SetHeight(ActualBitmap->Height() * Cropping.Height);
			BitmapInformation.SetName(*ActualFilePath);

			if ( Bitmap* NewBitmap = TheManager->Create(&BitmapInformation) )
			{
				BMM_Color_fl *Line;
				for (int y = 0; y < NewBitmap->Height(); y++)
				{
					Line = (BMM_Color_fl *)calloc(NewBitmap->Width(), sizeof(BMM_Color_fl));
					int OffsetX = ActualBitmap->Width() * Cropping.HorizontalStart;
					int OffsetY = y + ActualBitmap->Height() * Cropping.VerticalStart;
					ActualBitmap->GetLinearPixels(OffsetX, OffsetY, NewBitmap->Width(), Line);
					NewBitmap->PutPixels(0, y, NewBitmap->Width(), Line);
					free(Line);
				}

				NewBitmap->OpenOutput(&BitmapInformation);
				NewBitmap->Write(&BitmapInformation);
				NewBitmap->Close(&BitmapInformation);
				NewBitmap->DeleteThis();
			}
			else
			{
				FString Error = FString( TEXT("Unable to crop texture ") ) + InBitmapTex->GetName();
				DatasmithMaxLogger::Get().AddTextureError(*Error);
			}
		}
	}

	return ActualFilePath;
}

FDatasmithTextureSampler SetupTextureSampler(Texmap* Texture, TimeValue CurrentTime, float Multiplier, bool bForceInvert)
{
	StdUVGen* UV = nullptr;
	if (Texture)
	{
		UVGen* BaseUV = Texture->GetTheUVGen();
		if (BaseUV && BaseUV->IsStdUVGen())
		{
			UV = static_cast<StdUVGen*>(BaseUV);
		}
	}

	if (!UV)
	{
		return 	FDatasmithTextureSampler(0, 1, 1, 0, 0, 0, 1, bForceInvert, 0, false, 0, 0);
	}

	bool bIsUsingRealWorldScale = !!UV->GetUseRealWorldScale();
	float UScale = UV->GetUScl(CurrentTime);
	float VScale = UV->GetVScl(CurrentTime);
	bool bCroppedTexture = false;

	// Determine the sub-class of Texmap
	Class_ID ClassID = Texture->ClassID();
	bool bIsBitmapTex = ClassID.PartA() == BMTEX_CLASS_ID;
	if (!bIsBitmapTex)
	{
		// Only procedural textures requires some processing of the UV scale values
		if (!bIsUsingRealWorldScale)
		{
			// If RealWorldScale is not used, the UV values smaller than 1 are treated as 1
			UScale = FMath::Max(UScale, 1.0f);
			VScale = FMath::Max(VScale, 1.0f);

			// When a scale is greater than 1, the tiling is baked into the texture (does not apply when using RealWorldScale because the scales are inverted)
			if (UScale > 1.0f || VScale > 1.0f)
			{
				UScale = 1.0f;
				VScale = 1.0f;
			}
		}
		else
		{
			// Determine if texture has some fraction of the repeating pattern
			// If so, UV cropping will be needed to have seamless tiling
			bool bHasFractionalUV = false;
			if (UScale != VScale)
			{
				// Check how many times the smallest value fits within the greater value and look if there's a fractional part
				float Numerator = FMath::Max(UScale, VScale);
				float Denominator = FMath::Min(UScale, VScale);

				float Quotient = Numerator / Denominator;
				bHasFractionalUV = FMath::Frac(Quotient) > THRESH_UVS_ARE_SAME;
			}
			bCroppedTexture = bHasFractionalUV;
		}
	}

	float IntegerPart;
	float UOffset = (UV->GetUOffs(CurrentTime)) * UScale;
	if (!bIsUsingRealWorldScale)
	{
		UOffset += (-0.5f + 0.5f * UScale);
	}

	UOffset = 1.0f - UOffset;
	UOffset = FMath::Modf(UOffset, &IntegerPart);

	float VOffset = (UV->GetVOffs(CurrentTime)) * VScale;
	if (!bIsUsingRealWorldScale)
	{
		VOffset += (0.5f - 0.5f * VScale);
	}
	else
	{
		VOffset -= VScale;
	}

	VOffset = FMath::Modf(VOffset, &IntegerPart);

	int TextureTilingDirection = UV->GetTextureTiling();
	int MirrorU = 0;
	int MirrorV = 0;
	if (TextureTilingDirection & U_MIRROR)
	{
		MirrorU = 2;
	}
	if (TextureTilingDirection & V_MIRROR)
	{
		MirrorV = 2;
	}

	int Slot = 0;

	int CoordinateIndex = 0;
	if (UV->GetSlotType() == MAPSLOT_TEXTURE)
	{
		if (UV->GetUVWSource() == UVWSRC_EXPLICIT)
		{
			CoordinateIndex = Texture->GetMapChannel() - 1;
		}
	}

	return FDatasmithTextureSampler(CoordinateIndex, UScale, VScale, UOffset, VOffset, -(UV->GetWAng(CurrentTime)) / (2 * PI), Multiplier, bForceInvert, Slot, bCroppedTexture, MirrorU, MirrorV);
}

FString FDatasmithMaxMatWriter::DumpBitmap(TSharedPtr<IDatasmithCompositeTexture>& CompTex, BitmapTex* InBitmapTex, const TCHAR* Prefix, bool bForceInvert, bool bIsGrayscale)
{
	CropBitmap(InBitmapTex); // Crop if necessary

	int CurrentFrame = GetCOREInterface()->GetTime() / GetTicksPerFrame();
	TimeValue CurrentTime = TimeValue(TIME_TICKSPERSEC * ((float)CurrentFrame / GetFrameRate()));

	bool bInvertTexture = false;
	if (InBitmapTex->GetTexout()->GetInvert() == (BOOL)true)
	{
		bInvertTexture = true;
	}

	if (bInvertTexture)
	{
		bForceInvert = !bForceInvert;
	}

	float Multiplier = InBitmapTex->GetTexout()->GetOutputLevel(CurrentTime);

	FDatasmithTextureSampler TextureSampler(SetupTextureSampler(InBitmapTex, CurrentTime, Multiplier, bForceInvert));

	if(bIsGrayscale)
	{
		if (InBitmapTex->GetAlphaAsMono(true))
		{
			TextureSampler.OutputChannel = 4;
		}
	}
	else
	{
		if (InBitmapTex->GetAlphaAsRGB(true))
		{
			TextureSampler.OutputChannel = 4;
		}
	}

	const FString ActualBitmapName = GetActualBitmapName(InBitmapTex);
	CompTex->AddSurface( *ActualBitmapName, TextureSampler );
	
	return ActualBitmapName;
}

FString FDatasmithMaxMatWriter::DumpAutodeskBitmap(TSharedPtr<IDatasmithCompositeTexture>& CompTex, Texmap* InTexmap, const TCHAR* Prefix, bool bForceInvert, bool bIsGrayscale)
{
	DatasmithMaxTexmapParser::FAutodeskBitmapParameters AutodeskBitmapParameters = DatasmithMaxTexmapParser::ParseAutodeskBitmap(InTexmap);
	if (AutodeskBitmapParameters.SourceFile == nullptr) 
	{
		return FString();
	}

	FScopedBitMapPtr ActualBitmap(AutodeskBitmapParameters.SourceFile->bi, AutodeskBitmapParameters.SourceFile->bm);
	if (ActualBitmap.Map == nullptr)
	{
		return FString();
	}

	bool bInvertTexture = false;
	if (AutodeskBitmapParameters.bInvertImage)
	{
		bInvertTexture = true;
	}

	if (bInvertTexture)
	{
		bForceInvert = !bForceInvert;
	}

	int CurrentFrame = GetCOREInterface()->GetTime() / GetTicksPerFrame();
	TimeValue CurrentTime = TimeValue(TIME_TICKSPERSEC * ((float)CurrentFrame / GetFrameRate()));
	FDatasmithTextureSampler TextureSampler(SetupTextureSampler(InTexmap, CurrentTime, 1, bForceInvert));

	if (!bIsGrayscale && ActualBitmap.Map->HasAlpha())
	{
		TextureSampler.OutputChannel = 4;
	}

	const FString ActualBitmapName = GetActualBitmapName(&ActualBitmap.MapInfo);
	CompTex->AddSurface(*ActualBitmapName, TextureSampler);

	return ActualBitmapName;
}

void FDatasmithMaxMatWriter::GetRegularTexmap(TSharedRef< IDatasmithScene > DatasmithScene, BitmapTex* InBitmapTex)
{
	if (!InBitmapTex)
	{
		return;
	}

	FString Path = FDatasmithMaxMatWriter::GetActualBitmapPath(InBitmapTex);
	FString ActualBitmapName = FDatasmithMaxMatWriter::GetActualBitmapName(InBitmapTex);

	for (int i = 0; i < DatasmithScene->GetTexturesCount(); i++)
	{
		if (DatasmithScene->GetTexture(i)->GetFile() == Path && DatasmithScene->GetTexture(i)->GetName() == ActualBitmapName)
		{
			return;
		}
	}

	TSharedPtr< IDatasmithTextureElement > TextureElement = FDatasmithSceneFactory::CreateTexture(*ActualBitmapName);
	if (gammaMgr.IsEnabled())
	{
		const float Gamma = FDatasmithMaxMatHelper::GetBitmapGamma(InBitmapTex);

		if (FDatasmithMaxMatHelper::IsSRGB(*InBitmapTex))
		{
			TextureElement->SetRGBCurve(Gamma / 2.2f);
		}
		else
		{
			TextureElement->SetRGBCurve(Gamma);
		}
	}

	TextureElement->SetFile(*Path);
	DatasmithScene->AddTexture(TextureElement);
}

void FDatasmithMaxMatWriter::GetAutodeskTexmap(TSharedRef< IDatasmithScene > DatasmithScene, Texmap* InTexMap)
{
	if (PBBitmap* BitmapSourceFile = DatasmithMaxTexmapParser::ParseAutodeskBitmap(InTexMap).SourceFile)
	{
		FScopedBitMapPtr ActualBitmap(BitmapSourceFile->bi, BitmapSourceFile->bm);
		if (!ActualBitmap.Map)
		{
			return;
		}

		FString Path = FDatasmithMaxMatWriter::GetActualBitmapPath(&ActualBitmap.MapInfo);
		FString ActualBitmapName = FDatasmithMaxMatWriter::GetActualBitmapName(&ActualBitmap.MapInfo);

		for (int i = 0; i < DatasmithScene->GetTexturesCount(); i++)
		{
			if (DatasmithScene->GetTexture(i)->GetFile() == Path && DatasmithScene->GetTexture(i)->GetName() == ActualBitmapName)
			{
				return;
			}
		}

		TSharedPtr< IDatasmithTextureElement > TextureElement = FDatasmithSceneFactory::CreateTexture(*ActualBitmapName);
		if (gammaMgr.IsEnabled())
		{
			const float Gamma = FDatasmithMaxMatHelper::GetBitmapGamma(&ActualBitmap.MapInfo);

			if (FDatasmithMaxMatHelper::IsSRGB(*ActualBitmap.Map))
			{
				TextureElement->SetRGBCurve(Gamma / 2.2f);
			}
			else
			{
				TextureElement->SetRGBCurve(Gamma);
			}
		}

		TextureElement->SetFile(*Path);
		DatasmithScene->AddTexture(TextureElement);
	}
}


enum class EScanlineMaterialMaps
{
	Ambient,
	Diffuse,
	SpecularColor,
	SpecularLevel,
	Glossiness,
	SelfIllumination,
	Opacity,
	FilterColor,
	Bump,
	Reflection,
	Refraction,
	Displacement
};

void FDatasmithMaxMatWriter::ExportStandardMaterial(TSharedRef< IDatasmithScene > DatasmithScene, TSharedPtr< IDatasmithMaterialElement >& MaterialElement, Mtl* Material)
{
	TSharedPtr< IDatasmithShaderElement > MaterialShader = FDatasmithSceneFactory::CreateShader((TCHAR*)Material->GetName().data());

	int NumParamBlocks = Material->NumParamBlocks();

	bool bDiffuseTexEnable = true;
	bool bReflectanceTexEnable = true;
	bool bMaskTexEnable = true;
	bool bGlossyTexEnable = true;
	bool bBumpTexEnable = true;
	bool bDisplaceTexEnable = true;

	float DiffuseTexAmount = 0.f;
	float ReflectanceTexAmount = 0.f;
	float MaskTexAmount = 0.f;
	float GlossyTexAmount = 0.f;
	float BumpTexAmount = 0.f;
	float DisplaceTexAmount = 0.f;

	bool bUseSelfIllumColor = true;

	Texmap* SelfIllumTex = nullptr;
	float SpecularLevel = 0.f;

	float Glossiness = 0.1f;

	for (int j = 0; j < NumParamBlocks; j++)
	{
		IParamBlock2* ParamBlock2 = Material->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("maps")) == 0)
			{
				if (ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Diffuse) == nullptr)
				{
					bDiffuseTexEnable = false;
				}

				if (ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::SpecularColor) == nullptr)
				{
					bReflectanceTexEnable = false;
				}

				if (ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Glossiness) == nullptr)
				{
					bGlossyTexEnable = false;
				}

				if (ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Opacity) == nullptr)
				{
					bMaskTexEnable = false;
				}

				if (ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Bump) == nullptr)
				{
					bBumpTexEnable = false;
				}

				if (ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Displacement) == nullptr)
				{
					bDisplaceTexEnable = false;
				}
			}

			if (FCString::Stricmp(ParamDefinition.int_name, TEXT("mapEnables")) == 0)
			{
				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Diffuse) == 0)
				{
					bDiffuseTexEnable = false;
				}

				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::SpecularColor) == 0)
				{
					bReflectanceTexEnable = false;
				}

				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Glossiness) == 0)
				{
					bGlossyTexEnable = false;
				}

				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Opacity) == 0)
				{
					bMaskTexEnable = false;
				}

				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Bump) == 0)
				{
					bBumpTexEnable = false;
				}

				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Displacement) == 0)
				{
					bDisplaceTexEnable = false;
				}
			}

			if (FCString::Stricmp(ParamDefinition.int_name, TEXT("mapAmounts")) == 0)
			{
				DiffuseTexAmount = ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Diffuse);
				ReflectanceTexAmount = ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::SpecularColor);
				GlossyTexAmount = ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Glossiness);
				MaskTexAmount = ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Opacity);
				BumpTexAmount = ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Bump);
				DisplaceTexAmount = ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Displacement);
			}

			if (FCString::Stricmp(ParamDefinition.int_name, TEXT("SpecularLevel")) == 0)
			{
				SpecularLevel = (float)ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) / 200.0f;
			}
			
			if (FCString::Stricmp(ParamDefinition.int_name, TEXT("SpecularLevel")) == 0)
			{
				Glossiness = (float)ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) / 100.0f;
			}
			
			if (FCString::Stricmp(ParamDefinition.int_name, TEXT("bUseSelfIllumColor")) == 0)
			{
				bUseSelfIllumColor = ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) != 0;
			}
			
			if (FCString::Stricmp(ParamDefinition.int_name, TEXT("selfillumMap")) == 0)
			{
				SelfIllumTex = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), 0);
			}

		}
		ParamBlock2->ReleaseDesc();
	}

	for (int j = 0; j < NumParamBlocks; j++)
	{
		IParamBlock2* ParamBlock2 = Material->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("mapAmounts")) == 0)
			{
				if (bBumpTexEnable == true)
				{
					MaterialShader->SetBumpAmount( ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime(), 8) );
				}
				if (bDisplaceTexEnable == true)
				{
					MaterialShader->SetDisplace( 0.1f * ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime(), 11) );
				}
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("ior")) == 0)
			{
				MaterialShader->SetIOR( 0.0 ); // ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime());
				MaterialShader->SetIORRefra( ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime()) );
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("twoSided")) == 0)
			{
				MaterialShader->SetTwoSided( ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) != 0 );
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("maps")) == 0)
			{
				BMM_Color_fl Color = (BMM_Color_fl)Material->GetDiffuse();
				if (bDiffuseTexEnable == true && DiffuseTexAmount > 0)
				{
					Texmap* LocalTex = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Diffuse);
					if (DiffuseTexAmount == 1)
					{
						DumpTexture(DatasmithScene, MaterialShader->GetDiffuseComp(), LocalTex, DATASMITH_DIFFUSETEXNAME, DATASMITH_DIFFUSECOLNAME, false, false);
					}
					else
					{
						DumpWeightedTexture(DatasmithScene, MaterialShader->GetDiffuseComp(), LocalTex, Color, DiffuseTexAmount, DATASMITH_DIFFUSETEXNAME, DATASMITH_DIFFUSECOLNAME, false, false);
					}
				}
				else
				{
					MaterialShader->GetDiffuseComp()->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(Color));
				}

				float RValue = Material->GetShinStr();
				Color = BMM_Color_fl(0.0f, 0.0f, 0.0f, 0.0f);
				if (RValue > 0.05f)
				{
					Color = (BMM_Color_fl)Material->GetSpecular();
				}
				if (bReflectanceTexEnable == true && ReflectanceTexAmount > 0)
				{
					Texmap* LocalTex = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::SpecularColor);
					if (ReflectanceTexAmount == 1)
					{
						DumpTexture(DatasmithScene, MaterialShader->GetRefleComp(), LocalTex, DATASMITH_REFLETEXNAME, DATASMITH_REFLECOLNAME, false, false);
					}
					else
					{
						DumpWeightedTexture(DatasmithScene, MaterialShader->GetRefleComp(), LocalTex, Color, ReflectanceTexAmount, DATASMITH_REFLETEXNAME, DATASMITH_REFLECOLNAME, false, false);
					}
				}
				else
				{
					if (RValue > 0.05f)
					{
						MaterialShader->GetRefleComp()->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(Color));
					}
				}

				float Rough = 1.0f - Glossiness;
				if (bGlossyTexEnable == true && GlossyTexAmount > 0)
				{
					Texmap* LocalTex = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Glossiness);
					if (GlossyTexAmount == 1)
					{
						DumpTexture(DatasmithScene, MaterialShader->GetRoughnessComp(), LocalTex, DATASMITH_ROUGHNESSTEXNAME, DATASMITH_ROUGHNESSVALUENAME, true, true);
					}
					else
					{
						Color = BMM_Color_fl(Rough, Rough, Rough, Rough);
						DumpWeightedTexture(DatasmithScene, MaterialShader->GetRoughnessComp(), LocalTex, Color, GlossyTexAmount, DATASMITH_REFLETEXNAME, DATASMITH_REFLECOLNAME, true, true);
					}
				}
				else
				{
					MaterialShader->GetRoughnessComp()->AddParamVal1( IDatasmithCompositeTexture::ParamVal(Rough, TEXT("Roughness")) );
				}

				if (bMaskTexEnable == true && MaskTexAmount > 0)
				{
					Texmap* LocalTex = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Opacity);
					DumpTexture(DatasmithScene, MaterialShader->GetMaskComp(), LocalTex, DATASMITH_CLIPTEXNAME, DATASMITH_CLIPTEXNAME, false, true);
				}

				if (bBumpTexEnable == true && BumpTexAmount > 0)
				{
					Texmap* LocalTex = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Bump);
					if (FDatasmithMaxMatHelper::GetTextureClass(LocalTex) == EDSBitmapType::NormalMap)
					{
						DumpTexture(DatasmithScene, MaterialShader->GetNormalComp(), LocalTex, DATASMITH_NORMALTEXNAME, DATASMITH_NORMALTEXNAME, false, false);
						DumpTexture(DatasmithScene, MaterialShader->GetBumpComp(), LocalTex, DATASMITH_BUMPTEXNAME, DATASMITH_BUMPTEXNAME, false, true);
					}
					else
					{
						DumpTexture(DatasmithScene, MaterialShader->GetBumpComp(), LocalTex, DATASMITH_BUMPTEXNAME, DATASMITH_BUMPTEXNAME, false, true);
					}
				}
				if (bDisplaceTexEnable == true && DisplaceTexAmount > 0)
				{
					Texmap* LocalTex = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), (int)EScanlineMaterialMaps::Displacement);
					DumpTexture(DatasmithScene, MaterialShader->GetDisplaceComp(), LocalTex, DATASMITH_DISPLACETEXNAME, DATASMITH_DISPLACETEXNAME, false, true);
					MaterialShader->SetDisplaceSubDivision(4.0);
				}
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("selfillumMap")) == 0 && bUseSelfIllumColor == true && SelfIllumTex != nullptr)
			{
				Texmap* LocalTex = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime(), 0);
				DumpTexture(DatasmithScene, MaterialShader->GetEmitComp(), LocalTex, DATASMITH_EMITTEXNAME, DATASMITH_EMITTEXNAME, false, false);
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("selfIllumColor")) == 0 && bUseSelfIllumColor == true && SelfIllumTex == nullptr)
			{
				BMM_Color_fl Color = (BMM_Color_fl)Material->GetSelfIllumColor();
				MaterialShader->GetEmitComp()->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(Color));
				MaterialShader->SetEmitPower(100.0);
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("opacity")) == 0 && bMaskTexEnable == false)
			{
				float ColorVal = 1.0f - ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime());
				if (ColorVal != 0)
				{
					BMM_Color_fl Color(ColorVal, ColorVal, ColorVal, 1.0);
					MaterialShader->GetTransComp()->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(Color));
					MaterialShader->SetEmitPower(100.0);
				}
			}
		}
		ParamBlock2->ReleaseDesc();
	}
	MaterialElement->AddShader(MaterialShader);
}

void FDatasmithMaxMatWriter::ExportBlendMaterial(TSharedRef< IDatasmithScene > DatasmithScene, TSharedPtr< IDatasmithMaterialElement >& MaterialElement, Mtl* Material)
{
	FDatasmithMaxMatExport::WriteXMLMaterial(DatasmithScene, MaterialElement, Material->GetSubMtl(0));
	FDatasmithMaxMatExport::WriteXMLMaterial(DatasmithScene, MaterialElement, Material->GetSubMtl(1));

	int NumParamBlocks = Material->NumParamBlocks();

	bool bMaskEnabled = true;
	Texmap* Mask = nullptr;
	float MixAmount = 0.5f;

	for (int j = 0; j < NumParamBlocks; j++)
	{
		IParamBlock2* ParamBlock2 = Material->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("Mask")) == 0)
			{
				Mask = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}

			if (FCString::Stricmp(ParamDefinition.int_name, TEXT("bMaskEnabled")) == 0)
			{
				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) == 0)
				{
					bMaskEnabled = false;
				}
			}

			if (FCString::Stricmp(ParamDefinition.int_name, TEXT("MixAmount")) == 0)
			{
				MixAmount = (float)ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
		}
		ParamBlock2->ReleaseDesc();
	}
	
	if (MaterialElement->GetShadersCount() != 0)
	{
		TSharedPtr< IDatasmithShaderElement >& Shader = MaterialElement->GetShader( MaterialElement->GetShadersCount() - 1 );
		Shader->SetBlendMode(EDatasmithBlendMode::Alpha);
		Shader->SetIsStackedLayer(true);

		if (Mask && bMaskEnabled)
		{
			DumpTexture(DatasmithScene, Shader->GetWeightComp(), Mask, DATASMITH_DIFFUSETEXNAME, DATASMITH_DIFFUSECOLNAME, false, false);
		}
		else
		{
			Shader->GetWeightComp()->AddParamVal1( IDatasmithCompositeTexture::ParamVal(MixAmount, TEXT("MixAmount")) );
		}
	}
	else 
	{
		DatasmithMaxLogger::Get().AddGeneralError(*FString::Printf(TEXT("The material %s won't be exported."),  MaterialElement->GetLabel()));
	}
}

// output order:
// slot0: input texture color
// slot1: color filter
// value list 1: hue sat lift/Brightness gamma/Contrast tintamount
// value list 2: RewireR RewireG RewireB
FString FDatasmithMaxMatWriter::DumpColorCorrect(TSharedRef< IDatasmithScene > DatasmithScene, TSharedPtr<IDatasmithCompositeTexture>& CompTex, Texmap* InTexmap, const TCHAR* Prefix, bool bForceInvert, bool bIsGrayscale)
{
	DatasmithMaxTexmapParser::FColorCorrectionParameters ColorCorrectionParameters = DatasmithMaxTexmapParser::ParseColorCorrection(InTexmap);

	FString ActualPrefix = FString(Prefix) + FString(TEXT("comp"));
	if (ColorCorrectionParameters.bAdvancedLightnessMode == false)
	{
		CompTex->SetMode( EDatasmithCompMode::ColorCorrectContrast );
	}
	else
	{
		CompTex->SetMode(EDatasmithCompMode::ColorCorrectGamma );
	}

	FString Result;
	if (ColorCorrectionParameters.TextureSlot1 != nullptr)
	{
		Result = DumpTexture(DatasmithScene, CompTex, ColorCorrectionParameters.TextureSlot1, DATASMITH_TEXTURENAME, DATASMITH_COLORNAME, bForceInvert, bIsGrayscale);
	}
	else
	{
		CompTex->AddSurface(ColorCorrectionParameters.Color1);
	}

	CompTex->AddSurface(ColorCorrectionParameters.Tint);

	CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(ColorCorrectionParameters.HueShift, TEXT("HueShift")) );
	CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(ColorCorrectionParameters.Saturation, TEXT("Saturation")) );
	if (ColorCorrectionParameters.bAdvancedLightnessMode == false)
	{
		CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(ColorCorrectionParameters.Brightness, TEXT("Brightness")) );
		CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(ColorCorrectionParameters.Contrast, TEXT("Contrast")) );
	}
	else
	{
		CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(ColorCorrectionParameters.LiftRGB, TEXT("LiftRGB")) );
		CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(ColorCorrectionParameters.GammaRGB, TEXT("GammaRgb")) );
	}
	CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(ColorCorrectionParameters.TintStrength, TEXT("TintStrength")) );

	CompTex->AddParamVal2( IDatasmithCompositeTexture::ParamVal((float)ColorCorrectionParameters.RewireR, TEXT("RewireR")) );
	CompTex->AddParamVal2( IDatasmithCompositeTexture::ParamVal((float)ColorCorrectionParameters.RewireG, TEXT("RewireG")) );
	CompTex->AddParamVal2( IDatasmithCompositeTexture::ParamVal((float)ColorCorrectionParameters.RewireB, TEXT("RewireB")) );

	return Result;
}

FString FDatasmithMaxMatWriter::DumpMix(TSharedRef< IDatasmithScene > DatasmithScene, TSharedPtr<IDatasmithCompositeTexture>& CompTex, Texmap* InTexmap, const TCHAR* Prefix, bool bForceInvert, bool bIsGrayscale)
{
	FString ActualPrefix = FString(Prefix) + FString(TEXT("comp"));
	CompTex->SetMode( EDatasmithCompMode::Mix );

	int NumParamBlocks = InTexmap->NumParamBlocks();

	Texmap* TextureSlot1 = nullptr;
	Texmap* TextureSlot2 = nullptr;
	Texmap* textureMask = nullptr;
	bool bUseTexmap1 = true;
	bool bUseTexmap2 = true;
	bool useMask = true;
	BMM_Color_fl Color1;
	BMM_Color_fl Color2;
	double MixAmount = 0.0;

	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("map1")) == 0)
			{
				TextureSlot1 = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("map2")) == 0)
			{
				TextureSlot2 = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("Mask")) == 0)
			{
				textureMask = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("color1")) == 0)
			{
				Color1 = (BMM_Color_fl)ParamBlock2->GetColor(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("color2")) == 0)
			{
				Color2 = (BMM_Color_fl)ParamBlock2->GetColor(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("MixAmount")) == 0)
			{
				MixAmount = ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("map1enabled")) == 0)
			{
				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) == 0)
				{
					bUseTexmap1 = false;
				}
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("map2enabled")) == 0)
			{
				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) == 0)
				{
					bUseTexmap2 = false;
				}
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("maskEnabled")) == 0)
			{
				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) == 0)
				{
					useMask = false;
				}
			}
		}
		ParamBlock2->ReleaseDesc();
	}

	FString Result = TEXT("");
	if (TextureSlot1 != nullptr && bUseTexmap1)
	{
		Result = DumpTexture(DatasmithScene, CompTex, TextureSlot1, DATASMITH_TEXTURENAME, DATASMITH_COLORNAME, bForceInvert, bIsGrayscale);
	}
	else
	{
		CompTex->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(Color1));
	}

	if (TextureSlot2 != nullptr && bUseTexmap2)
	{
		DumpTexture(DatasmithScene, CompTex, TextureSlot2, DATASMITH_TEXTURENAME, DATASMITH_COLORNAME, bForceInvert, bIsGrayscale);
	}
	else
	{
		CompTex->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(Color2));
	}

	CompTex->AddParamVal1(IDatasmithCompositeTexture::ParamVal(1.f, TEXT("BaseLayerMixAmount")));
	if (textureMask != nullptr && useMask)
	{
		TSharedPtr<IDatasmithCompositeTexture> MaskTextureMap = FDatasmithSceneFactory::CreateCompositeTexture();

		DumpTexture(DatasmithScene, MaskTextureMap, textureMask, DATASMITH_MASKNAME, DATASMITH_COLORNAME, bForceInvert, bIsGrayscale);
		CompTex->AddMaskSurface(MaskTextureMap);
		CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(-1.f, TEXT("WeightUsesMask")) );
	}
	else
	{
		CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal((float)MixAmount, TEXT("MixAmount")) );
	}

	return Result;
}

FString FDatasmithMaxMatWriter::DumpCompositetex(TSharedRef< IDatasmithScene > DatasmithScene, TSharedPtr<IDatasmithCompositeTexture>& CompTex, Texmap* InTexmap, const TCHAR* Prefix, bool bForceInvert, bool bIsGrayscale)
{
	FString ActualPrefix = FString(Prefix) + FString(TEXT("comp"));
	CompTex->SetMode( EDatasmithCompMode::Composite );

	DatasmithMaxTexmapParser::FCompositeTexmapParameters TexmapParameters = DatasmithMaxTexmapParser::ParseCompositeTexmap( InTexmap );

	FString Result;

	bool bIsFirstLayer = true;

	for ( DatasmithMaxTexmapParser::FCompositeTexmapParameters::FLayer& Layer : TexmapParameters.Layers )
	{
		if ( Layer.Map.Map != nullptr && Layer.Map.bEnabled && ( !FMath::IsNearlyZero( Layer.Map.Weight ) || Layer.Mask.Map != nullptr ) )
		{
			DumpTexture( DatasmithScene, CompTex, Layer.Map.Map, DATASMITH_TEXTURENAME, DATASMITH_COLORNAME, bForceInvert, bIsGrayscale );

			if ( !bIsFirstLayer )
			{
				if ( Layer.Mask.Map != nullptr )
				{
					TSharedPtr<IDatasmithCompositeTexture> MaskTextureMap = FDatasmithSceneFactory::CreateCompositeTexture();

					DumpTexture(DatasmithScene, MaskTextureMap, Layer.Mask.Map, DATASMITH_MASKNAME, DATASMITH_COLORNAME, bForceInvert, bIsGrayscale);
					CompTex->AddMaskSurface(MaskTextureMap);
					CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(-1.f, TEXT("WeightUsesMask")) );
				}
				else
				{
					CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(Layer.Map.Weight, TEXT("Weight")) );
				}

				CompTex->AddParamVal2( IDatasmithCompositeTexture::ParamVal(float( Layer.CompositeMode ), TEXT("Mode")) );
			}
			else // layer 0
			{
				CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(1.f, TEXT("BaseLayerWeight")) ); // Weights
				CompTex->AddParamVal2( IDatasmithCompositeTexture::ParamVal(0.f, TEXT("Mode")) );   // mode
			}

			bIsFirstLayer = false;
		}
	}

	return Result;
}

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

	Texmap* TextureSlot1 = nullptr;
	Texmap* TextureSlot2 = nullptr;
	bool bUseTexmap1 = true;
	bool bUseTexmap2 = true;

	BMM_Color_fl Color1;
	BMM_Color_fl Color2;

	int Type = 1;      // perpendicular/parallel
	int Direction = 0; // z axis of camera

	float IORn = 1.5f;
	float IORk = 0.0f;

	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("map1")) == 0)
			{
				TextureSlot1 = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("map2")) == 0)
			{
				TextureSlot2 = ParamBlock2->GetTexmap(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("color1")) == 0)
			{
				Color1 = (BMM_Color_fl)ParamBlock2->GetColor(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("color2")) == 0)
			{
				Color2 = (BMM_Color_fl)ParamBlock2->GetColor(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("map1on")) == 0)
			{
				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) == 0)
				{
					bUseTexmap1 = false;
				}
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("map2on")) == 0)
			{
				if (ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime()) == 0)
				{
					bUseTexmap2 = false;
				}
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("Type")) == 0)
			{
				Type = ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("Direction")) == 0)
			{
				Direction = ParamBlock2->GetInt(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
			else if (FCString::Stricmp(ParamDefinition.int_name, TEXT("IOR")) == 0)
			{
				IORn = ParamBlock2->GetFloat(ParamDefinition.ID, GetCOREInterface()->GetTime());
			}
		}
		ParamBlock2->ReleaseDesc();
	}

	FString ActualPrefix = FString(Prefix) + FString(TEXT("comp"));
	if (Type != 2)
	{
		CompTex->SetMode( EDatasmithCompMode::Fresnel );
	}
	else
	{
		CompTex->SetMode(EDatasmithCompMode::Ior );
	}

	FString Result = TEXT("");
	if (TextureSlot1 != nullptr && bUseTexmap1)
	{
		Result = DumpTexture(DatasmithScene, CompTex, TextureSlot1, DATASMITH_TEXTURENAME, DATASMITH_COLORNAME, bForceInvert, bIsGrayscale);
	}
	else
	{
		CompTex->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(Color1));
	}

	if (TextureSlot2 != nullptr && bUseTexmap2)
	{
		DumpTexture(DatasmithScene, CompTex, TextureSlot2, DATASMITH_TEXTURENAME, DATASMITH_COLORNAME, bForceInvert, bIsGrayscale);
	}
	else
	{
		CompTex->AddSurface(FDatasmithMaxMatHelper::MaxColorToFLinearColor(Color2));
	}

	if (Type != 2)
	{
		CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(2.0f, TEXT("DefaultFalloffVal")) );  // max falloff uses 2 as constant
		CompTex->AddParamVal2( IDatasmithCompositeTexture::ParamVal(0.0f, TEXT("DefaultFalloffBase")) ); // max falloff uses 0 as Base
	}
	else
	{
		CompTex->AddParamVal1( IDatasmithCompositeTexture::ParamVal(IORn, TEXT("iorNvalue")) );
		CompTex->AddParamVal2( IDatasmithCompositeTexture::ParamVal(IORk, TEXT("iorKvalue")) );
	}

	if (Type != 1 && Type != 2)
	{
		DatasmithMaxLogger::Get().AddGeneralError(TEXT("Falloff maps work only on perpendicular/parallel or ior mode"));
	}

	if (Direction != 0)
	{
		DatasmithMaxLogger::Get().AddGeneralError(TEXT("Falloff maps work only on Z-axis"));
	}

	return Result;
}

void GetBakeableMaximumSize(Texmap* InTexmap, int &Width, int &Height)
{
	if (InTexmap == NULL)
	{
		return;
	}	
	if (InTexmap->ClassID() == RBITMAPCLASS)
	{
		BitmapTex* BitmapTexture = (BitmapTex*)InTexmap;
		Bitmap* ActualBitmap = ((BitmapTex*)InTexmap)->GetBitmap(GetCOREInterface()->GetTime());
		if (ActualBitmap != NULL)
		{
			Width = FMath::Min(Width, ActualBitmap->Width());
			Height = FMath::Min(Height, ActualBitmap->Height());
		}
	}

	for (int SubTexmap = 0; SubTexmap < InTexmap->NumSubTexmaps(); SubTexmap++)
	{
		GetBakeableMaximumSize(InTexmap->GetSubTexmap(SubTexmap), Width, Height);
	}
}

TSharedPtr< IDatasmithTextureElement > FDatasmithMaxMatWriter::AddBakeable(TSharedRef< IDatasmithScene > DatasmithScene, Texmap* InTexmap, const TCHAR* AssetsPath)
{
	if ( !InTexmap )
	{
		return TSharedPtr< IDatasmithTextureElement >();
	}

	MSTR ClassName;
	InTexmap->GetClassName(ClassName);
	FString FileName = FString(InTexmap->GetName().data()) + FString(ClassName.data()) + FString::FromInt(InTexmap->GetHandleByAnim(InTexmap));
	FileName = FDatasmithUtils::SanitizeFileName(FileName);

	FString Path = FPaths::Combine(AssetsPath, FileName + TextureBakeFormat);

	for (int i = 0; i < DatasmithScene->GetTexturesCount(); i++)
	{
		if (DatasmithScene->GetTexture(i)->GetFile() == Path)
		{
			return DatasmithScene->GetTexture(i);
		}
	}

	int BakeWidth = FDatasmithExportOptions::MaxTextureSize;
	int BakeHeight = FDatasmithExportOptions::MaxTextureSize;
	GetBakeableMaximumSize(InTexmap, BakeWidth, BakeHeight);

	BitmapInfo BitmapInformation;
	BitmapInformation.SetType(BMM_TRUE_32);

	BitmapInformation.SetWidth(BakeWidth);
	BitmapInformation.SetHeight(BakeHeight);
	BitmapInformation.SetGamma(2.2f);

	BitmapInformation.SetName(*Path);

	Bitmap* NewBitmap = TheManager->Create(&BitmapInformation);
	InTexmap->RenderBitmap(GetCOREInterface()->GetTime(), NewBitmap, 1.0f, 1);
	
	NewBitmap->OpenOutput(&BitmapInformation);
	NewBitmap->Write(&BitmapInformation);
	NewBitmap->Close(&BitmapInformation);
	NewBitmap->DeleteThis();

	FString BaseName = FPaths::GetBaseFilename(Path);
	FString Base = BaseName + TextureSuffix;
	TSharedPtr< IDatasmithTextureElement > TextureElement = FDatasmithSceneFactory::CreateTexture(*Base);
	TextureElement->SetRGBCurve(1.0f);
	TextureElement->SetFile(*Path);
	DatasmithScene->AddTexture(TextureElement);

	return TextureElement;
}

FString FDatasmithMaxMatWriter::DumpBakeable(TSharedPtr<IDatasmithCompositeTexture>& CompTex, Texmap* InTexmap, const TCHAR* Prefix, bool bForceInvert, bool bIsGrayscale)
{
	MSTR ClassName;
	InTexmap->GetClassName(ClassName);
	FString FileName = FString(InTexmap->GetName().data()) + FString(ClassName.data()) + FString::FromInt(InTexmap->GetHandleByAnim(InTexmap));

	FString Base = FDatasmithUtils::SanitizeFileName(FileName);
	FString OrigBase = FDatasmithUtils::SanitizeFileName(FileName);
	Base += TextureSuffix;

	int CurrentFrame = GetCOREInterface()->GetTime() / GetTicksPerFrame();
	TimeValue CurrentTime = TimeValue(TIME_TICKSPERSEC * ((float)CurrentFrame / GetFrameRate()));

	FDatasmithTextureSampler TextureSampler(SetupTextureSampler(InTexmap, CurrentTime, 1, bForceInvert));
	CompTex->AddSurface(*OrigBase, TextureSampler);

	return Base;
}
