///////////////////////////////////////////////////////////////////////////
//
//  System:    Simplygon
//  File:      MaterialCastingExample.cpp
//  Language:  C++
//
//  Copyright (c) 2019 Microsoft. All rights reserved.
//
//  This is private property, and it is illegal to copy or distribute in
//  any form, without written authorization by the copyright owner(s).
//
///////////////////////////////////////////////////////////////////////////
//
//  #Description# 
//  
//  A scene is loaded from a wavefront file. It has multiple materials with
//  diffuse and specular channels. A custom user channel is added.
//
//  The geometries are reduced and a mapping image is created.
//
//  Using the mapping image, the original materials are casted to a single new 
//  material, reducing the number of materials for the processed scene. 
//  A tangent space normal map is also created to mimic the removed geometry.
//  
//  A custom user channel texture is created for the original scene and casted
//  to the processed scene.
//
//  The processed scene and textures are saved to file.
//
///////////////////////////////////////////////////////////////////////////
//For demonstration purposes, enabling this flag converts the materials supplied by the .obj reader, 
//(which are defined using the legacy material system) to the new, node-based system. 
//Texture path references are replaced by single texture nodes, and a texture table is generated for casting.
#define USE_SHADING_NETWORKS 0
#include "../Common/Example.h"
void RunExampleCasting( const std::string& readFrom, const std::string& writeTo );
spImageData GenerateGradientImage( unsigned int image_width, unsigned int image_height );
float clamp( float value, float min, float max );
int clamp( int value, int min, int max );
int main( int argc, char* argv[] )
    {
    try
    {
        InitExample();
        // Set global variable. Using Orthonormal method for calculating
        // tangentspace.
        sg->SetGlobalSetting("DefaultTBNType", SG_TANGENTSPACEMETHOD_ORTHONORMAL);
        std::string assetPath = GetAssetPath();
        // Run the example code
        RunExampleCasting(assetPath + "Helmet/helmet.obj", "helmetLOD");
        DeinitExample();
    }
    catch (const std::exception& ex)
    {
        std::cerr << ex.what() << std::endl;
        return -1;
    }
    return 0;
    }
void RunExampleCasting( const std::string& readFrom, const std::string& writeTo )
    {
    // Import scene from a file and optimize it. Generate a mapping image from
    // the original geometries and use it to cast diffuse, specular and normal maps
    // to the processed scene.
    std::string exePath = GetExecutablePath();
    std::string output_geometry_filename = exePath + writeTo + ".obj";
    std::string output_diffuse_filename = exePath + writeTo + "_diffuse.png";
    std::string output_specular_filename = exePath + writeTo + "_specular.png";
    std::string output_normals_filename = exePath + writeTo + "_normals.png";
    std::string output_userchannel_filename = exePath + writeTo + "_userchannel.png";
    //Load object from file
    spWavefrontImporter objReader = sg->CreateWavefrontImporter();
    objReader->SetImportFilePath( readFrom.c_str() );
    if( !objReader->RunImport() )
        throw std::exception("Failed to load input file!");
    //Get the scene
    spScene scene = objReader->GetScene();
    //Get material table and add a custom user channel
    spMaterialTable original_materials = scene->GetMaterialTable();
    spTextureTable original_textures = scene->GetTextureTable();
    original_materials->GetMaterial( 0 )->AddUserChannel( "user_channel" );
    original_materials->GetMaterial( 1 )->AddUserChannel( "user_channel" );
    //Create an ImageData with the user_channel texture
    spImageData imgUserChannelGradient = GenerateGradientImage( 512, 512 );
    spTexture userChannelTexture = sg->CreateTexture();
    userChannelTexture->SetName( "user_channel" );
    userChannelTexture->SetImageData( imgUserChannelGradient );
    original_textures->AddTexture( userChannelTexture );
    //Make a texture node and set as input in the material
    spShadingTextureNode userNode = sg->CreateShadingTextureNode();
    userNode->SetTextureName( "user_channel" );
    original_materials->GetMaterial( 0 )->SetShadingNetwork( "user_channel", userNode );
    original_materials->GetMaterial( 1 )->SetShadingNetwork( "user_channel", userNode );
    //Reducer
    spReductionProcessor red = sg->CreateReductionProcessor();
    red->SetScene( scene );
    // Set the Repair Settings.
    spRepairSettings repair_settings = red->GetRepairSettings();
    repair_settings->SetWeldDist( 0.0f );
    repair_settings->SetTjuncDist( 0.0f );
    // Set the Reduction Settings.
    spReductionSettings reduction_settings = red->GetReductionSettings();
    //Reduce the triangle count
    reduction_settings->SetTriangleRatio( 0.3f );
    //Keep symmetry
    reduction_settings->SetKeepSymmetry( true );
    reduction_settings->SetUseAutomaticSymmetryDetection( true );
    // Set the Normal Calculation Settings.
    spNormalCalculationSettings normal_settings = red->GetNormalCalculationSettings();
    normal_settings->SetReplaceNormals( true );
    normal_settings->SetHardEdgeAngleInRadians( 3.14159f*70.0f / 180.0f );
    // Set the Image Mapping Settings.
    spMappingImageSettings mapping_settings = red->GetMappingImageSettings();
    // Without this we cannot fetch data from the original geometry, and thus not
    // generate diffuse, specular, normal maps and custom channel later.
    mapping_settings->SetGenerateMappingImage( true );
    // Set to generate new texture coordinates.
    mapping_settings->SetGenerateTexCoords( true );
    // The higher the number, the fewer texture-borders.
    mapping_settings->SetParameterizerMaxStretch( 0.3f );
    // Buffer space for when texture is mip-mapped, so color values dont blend over.
    mapping_settings->SetGutterSpace( 1 );
    mapping_settings->SetWidth( 1024 );
    mapping_settings->SetHeight( 1024 );
    mapping_settings->SetMultisamplingLevel( 2 );
    red->RunProcessing();
    // Mapping image is needed later on for texture casting.
    spMappingImage mapping_image = red->GetMappingImage();
    // Create new material table.
    spMaterialTable output_materials = sg->CreateMaterialTable();
    spTextureTable output_textures = sg->CreateTextureTable();
    //  Create new material for the table.
    spMaterial output_material = sg->CreateMaterial();
    output_material->SetName( "output_material" );
    output_material->AddUserChannel( "user_channel" );
    //Add the new material to the table
    output_materials->AddMaterial( output_material );
    // DIFFUSE
    // Create a color caster to cast the diffuse texture data
    spColorCaster ColorCaster = sg->CreateColorCaster();
    ColorCaster->SetColorType( SG_MATERIAL_CHANNEL_DIFFUSE ); //Select the diffuse channel from the original material 
    ColorCaster->SetSourceMaterials( original_materials );
    ColorCaster->SetSourceTextures( original_textures ); //If we are casting materials defined by shading networks, a source texture table also needs to be set
    ColorCaster->SetMappingImage( mapping_image ); // The mapping image we got from the reduction process, reduced to half-width/height, just for testing purposes  
    ColorCaster->SetOutputChannels( 3 ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.)
    ColorCaster->SetOutputChannelBitDepth( 8 ); // 8 bits per channel. So in this case we will have 24bit colors RGB.
    ColorCaster->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree as well.
    ColorCaster->SetOutputFilePath( output_diffuse_filename.c_str() ); // Where the texture map will be saved to file.
    ColorCaster->RunProcessing();
    // set the material properties 
    // Set material to point to created texture filename.
    AddSimplygonTexture( output_material, output_textures, SG_MATERIAL_CHANNEL_DIFFUSE, output_diffuse_filename.c_str() );
    // SPECULAR
    // Modify the color caster to cast specular texture data
    ColorCaster->SetColorType( SG_MATERIAL_CHANNEL_SPECULAR ); //Select the specular channel from the original material 
    ColorCaster->SetOutputFilePath( output_specular_filename.c_str() ); // Where the texture map will be saved to file.
    ColorCaster->RunProcessing();
    // set the material properties 
    // Set material to point to created texture filename.
    AddSimplygonTexture( output_material, output_textures, SG_MATERIAL_CHANNEL_SPECULAR, output_specular_filename.c_str() );
    // USER CHANNEL
    ColorCaster->SetColorType( "user_channel" ); //Select the diffuse channel from the original material
    ColorCaster->SetOutputFilePath( output_userchannel_filename.c_str() ); // Where the texture map will be saved to file.
    ColorCaster->RunProcessing();
    // set the material properties 
    // Set material to point to created texture filename.
    AddSimplygonTexture( output_material, output_textures, "user_channel", output_userchannel_filename.c_str() );
    // NORMAL MAP
    // cast the normal map texture data
    spNormalCaster NormalCaster = sg->CreateNormalCaster();
    NormalCaster->SetSourceMaterials( original_materials );
    NormalCaster->SetSourceTextures( original_textures ); //If we are casting materials defined by shading networks, a source texture table also needs to be set
    NormalCaster->SetMappingImage( mapping_image );
    NormalCaster->SetOutputChannels( 3 ); // RGB, 3 channels! (But really the x, y and z values for the normal)
    NormalCaster->SetOutputChannelBitDepth( 8 );
    NormalCaster->SetDilation( 10 );
    NormalCaster->SetOutputFilePath( output_normals_filename.c_str() );
    NormalCaster->SetFlipBackfacingNormals( true );
    NormalCaster->SetGenerateTangentSpaceNormals( true );
    NormalCaster->RunProcessing();
    // Set material to point to created texture filename.
    AddSimplygonTexture( output_material, output_textures, SG_MATERIAL_CHANNEL_NORMALS, output_normals_filename.c_str() );
    //Overwrite the scene's material table with the casted materials
    scene->GetMaterialTable()->Clear();
    scene->GetMaterialTable()->Copy( output_materials );
    scene->GetTextureTable()->Clear();
    scene->GetTextureTable()->Copy( output_textures );
    //Store the finished processed scene
    spWavefrontExporter objexp = sg->CreateWavefrontExporter();
    objexp->SetExportFilePath( output_geometry_filename.c_str() );
    objexp->SetScene( scene );
    objexp->RunExport();
    }
spImageData GenerateGradientImage( unsigned int image_width, unsigned int image_height )
    {
    spImageData img = sg->CreateImageData();
    //Set imagedata dimensions
    img->Set2DSize( image_width, image_height );
    //Add rgb colors to imagedata
    img->AddColors( TYPES_ID_UCHAR, "RGB" );
    //Get the reference to the colors to array with tuple size 3, tuple count (image_width x image_height)
    spUnsignedCharArray colors = SafeCast<IUnsignedCharArray>( img->GetColors() );
    //Gradient texture parameters
    int gradient_RL = 255;
    int gradient_GL = 255;
    int gradient_BL = 255;
    int gradient_RR = 0;
    int gradient_GR = 0;
    int gradient_BR = 0;
    //Iterate over the image pixels and set the array values
    for( unsigned int y = 0; y < image_height; y++ )
        {
        for( unsigned int x = 0; x < image_width; x++ )
            {
            //One dimensional index
            int i = image_width * y + x;
            //Gradient colors to array
            colors->SetItem( i * 3 + 0, clamp( (int)(((image_width - x) * gradient_RL + x * gradient_RR) / image_width), 0, 255 ) );
            colors->SetItem( i * 3 + 1, clamp( (int)(((image_width - x) * gradient_GL + x * gradient_GR) / image_width), 0, 255 ) );
            colors->SetItem( i * 3 + 2, clamp( (int)(((image_width - x) * gradient_BL + x * gradient_BR) / image_width), 0, 255 ) );
            colors->SetItem( i * 3 + 0, (x * 255) / image_width );
            colors->SetItem( i * 3 + 1, (x * 255) / image_width );
            colors->SetItem( i * 3 + 2, (x * 255) / image_width );
            }
        }
    //Save generated image to file for demo purposes, the caster will just use the image data that's already in memory
    spImageDataExporter imExp = sg->CreateImageDataExporter();
    imExp->SetImage( img );
    imExp->SetExportFilePath( "gradient_texture.png" );
    imExp->RunExport();
    return img;
    }
float clamp( float value, float min, float max )
    {
    return value < min ? min : (value > max ? max : value);
    }
int clamp( int value, int min, int max )
    {
    return value < min ? min : (value > max ? max : value);
    }