///////////////////////////////////////////////////////////////////////////
//
//  System:    Simplygon
//  File:      CustomNode.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# 
//
//  This example shows how to use the custom node in the material node 
//  network.
//
//  See MaterialNodeExample for a more general example of material nodes.
//
//  The RunExample() function will load a scene and create a new 
//  material. A material node network that uses ICustomNode is 
//  created and added to the diffuse channel of the new material. 
//
//  ICustomNode is a node used in the material node network that does not
//  have its own "material casting evaluation" or "shader code generating"
//  functions. These functions will be generated and assigned to the
//  custom node by the user.
//  
//  To use an ICustomNode the user begins by creating the object and adding
//  event handlers (observers) to it. The function(s) triggered by the 
//  event is implemented by the user.
//
//
//  The events are:
//
//  -- SG_EVENT_CUSTOM_NODE_EVALUATE --
//  This event is triggered when simplygon is trying to evaluate a color 
//  from the node. The function receives the already evaluated RGBA colors 
//  of all the input nodes plugged into the custom node.
//
//  To "return" the evaluated color from the custom node the first RGBA
//  element in the array of RGBA values should be filled with the output
//  color.
//
//  The array of RGBA values corresponds to the amount of input nodes (set 
//  by the user) in the ICustomNode. The input node order set by the user
//  into the ICustomNode will correspond to the order of evaluated RGBA 
//  values sent to the evaluation event handler function.
//
//  The block of data received should be casted to a ParameterBlockEvaluate
//  object.
//
//  struct ParameterBlockEvaluate
//      {
//      uint inputCount;
//      float ** inputs;
//      };
//
//
//  -- SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE --
//  This event is triggered when the ICustomNode is generating shader code
//  (both HLSL and GLSL). 
//
//  The user will "return" a string with the custom node evaluation shader 
//  function code. The string should end with assigning the variable 
//  "result" to a color (float4 in HLSL, vec4 in GLSL).
//
//  To use the values of the input nodes' colors, access them with
//  variable names following the "rgba_custom_input_XXX" format, where XXX
//  is replaced with the index of the input node.
//
//  The block of data received should be casted to a 
//  ParameterBlockShaderCode object.
//
//  struct ParameterBlockShaderCode
//      {
//      uint inputCount;
//      char * shaderCode;
//      };
//
///////////////////////////////////////////////////////////////////////////
#include "../Common/Example.h"
#include <time.h>
void RunExample( const std::string& readFrom );
struct ParameterBlockEvaluate
    {
    uint inputCount;
    float inputs[SG_NUM_SUPPORTED_CUSTOM_NODE_INPUTS][4];
    };
struct ParameterBlockShaderCode
    {
    uint inputCount;
    char shaderCode[SG_SUPPORTED_CUSTOM_NODE_SHADER_CODE_SIZE];
    };
//Implements a random color node
class CustomNodeRandomColorEventHandler : public robserver
    {
        public:
        virtual void Execute(
            IObject * subject,
            rid EventId,
            void * EventParameterBlock,
            unsigned int EventParameterBlockSize )
            {
            float output[4] = { 0.0f,0.0f,0.0f,0.0f };
            if( EventId == SG_EVENT_CUSTOM_NODE_EVALUATE )
                {
                ParameterBlockEvaluate * parameters = ((ParameterBlockEvaluate *)EventParameterBlock);
                output[0] = float( rand() ) / float( RAND_MAX );
                output[1] = float( rand() ) / float( RAND_MAX );
                output[2] = float( rand() ) / float( RAND_MAX );
                output[3] = float( rand() ) / float( RAND_MAX );
                parameters->inputs[0][0] = output[0];
                parameters->inputs[0][1] = output[1];
                parameters->inputs[0][2] = output[2];
                parameters->inputs[0][3] = output[3];
                }
            else if( EventId == SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE )
                {
                ParameterBlockShaderCode * parameters = ((ParameterBlockShaderCode *)EventParameterBlock);
                //Incomplete, shader code is placeholder since we can't implement a matching random shader
                sprintf(
                    parameters->shaderCode,
                    "result = float4(0.5f,0.5f,0.5f,0.5f);\n"
                    );
                }
            }
    } customNodeRandomColorEventHandler;
//Implements a mean color node
class CustomNodeMeanColorEventHandler : public robserver
    {
        public:
        virtual void Execute(
            IObject * subject,
            rid EventId,
            void * EventParameterBlock,
            unsigned int EventParameterBlockSize )
            {
            float output[4] = { 0.0f,0.0f,0.0f,0.0f };
            if( EventId == SG_EVENT_CUSTOM_NODE_EVALUATE )
                {
                ParameterBlockEvaluate * parameters = ((ParameterBlockEvaluate *)EventParameterBlock);
                //printf ( "InputCount: %d n" , parameters.inputCount );
                for( uint i = 0; i < parameters->inputCount; ++i )
                    {
                    //printf ( "InputValue %d: %f %f %f %f n", i, parameters.inputs[i][0], parameters.inputs[i][1], parameters.inputs[i][2], parameters.inputs[i][3] );
                    output[0] += parameters->inputs[i][0];
                    output[1] += parameters->inputs[i][1];
                    output[2] += parameters->inputs[i][2];
                    output[3] += parameters->inputs[i][3];
                    }
                if( parameters->inputCount > 0 )
                    {
                    output[0] /= (float)parameters->inputCount;
                    output[1] /= (float)parameters->inputCount;
                    output[2] /= (float)parameters->inputCount;
                    output[3] /= (float)parameters->inputCount;
                    }
                parameters->inputs[0][0] = output[0];
                parameters->inputs[0][1] = output[1];
                parameters->inputs[0][2] = output[2];
                parameters->inputs[0][3] = output[3];
                }
            else if( EventId == SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE )
                {
                ParameterBlockShaderCode * parameters = ((ParameterBlockShaderCode *)EventParameterBlock);
                sprintf(
                    parameters->shaderCode,
                    "result = float4(0.0f,0.0f,0.0f,0.0f);\n"
                    );
                for( uint i = 0; i < parameters->inputCount; ++i )
                    {
                    sprintf(
                        parameters->shaderCode,
                        "%sresult += rgba_custom_input_%d / float(%d);\n",
                        parameters->shaderCode,
                        i,
                        parameters->inputCount
                        );
                    }
                }
            }
    } customNodeMeanColorEventHandler;
//Implements a squared filter node
class CustomNodeSquareColorEventHandler : public robserver
    {
        public:
        virtual void Execute(
            IObject * subject,
            rid EventId,
            void * EventParameterBlock,
            unsigned int EventParameterBlockSize )
            {
            float output[4] = { 0.0f,0.0f,0.0f,0.0f };
            if( EventId == SG_EVENT_CUSTOM_NODE_EVALUATE )
                {
                ParameterBlockEvaluate * parameters = ((ParameterBlockEvaluate *)EventParameterBlock);
                output[0] = parameters->inputs[0][0] * parameters->inputs[0][0];
                output[1] = parameters->inputs[0][1] * parameters->inputs[0][1];
                output[2] = parameters->inputs[0][2] * parameters->inputs[0][2];
                output[3] = parameters->inputs[0][3] * parameters->inputs[0][3];
                parameters->inputs[0][0] = output[0];
                parameters->inputs[0][1] = output[1];
                parameters->inputs[0][2] = output[2];
                parameters->inputs[0][3] = output[3];
                }
            else if( EventId == SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE )
                {
                ParameterBlockShaderCode * parameters = ((ParameterBlockShaderCode *)EventParameterBlock);
                sprintf(
                    parameters->shaderCode,
                    "result = float4(rgba_custom_input_0.r * rgba_custom_input_0.r,rgba_custom_input_0.g * rgba_custom_input_0.g,rgba_custom_input_0.b * rgba_custom_input_0.b,rgba_custom_input_0.a * rgba_custom_input_0.a);\n"
                    );
                }
            }
    } customNodeSquareColorEventHandler;
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
        RunExample(assetPath + "object.obj");
        DeinitExample();
    }
    catch (const std::exception& ex)
    {
        std::cerr << ex.what() << std::endl;
        return -1;
    }
    return 0;
    }
void RunExample( const std::string& readFrom )
    {
    const std::string writeTo = "output";
    std::string output_geometry_filename = GetExecutablePath() + writeTo + ".obj";
    std::string output_diffuse_filename = GetExecutablePath() + writeTo + "_diffuse.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!");
    spScene scene = objReader->GetScene();
    spMaterialTable materialTable = scene->GetMaterialTable();
    materialTable->Clear();
    spMaterial customMaterial = sg->CreateMaterial();
    materialTable->AddMaterial( customMaterial );
    // Create a material node network that uses ICustomNode
    spShadingCustomNode custom_node_random_color = sg->CreateShadingCustomNode();
    spShadingCustomNode custom_node_square_color = sg->CreateShadingCustomNode();
    spShadingCustomNode custom_node_mean_color = sg->CreateShadingCustomNode();
    spShadingColorNode red_node = sg->CreateShadingColorNode();
    red_node->SetColor( 1.0f, 0.0f, 0.0f, 1.0f );
    //Set the event handler functions for the custom nodes and their input properties
    custom_node_random_color->AddObserver( &customNodeRandomColorEventHandler, SG_EVENT_CUSTOM_NODE_EVALUATE ); //Evaluate
    custom_node_random_color->AddObserver( &customNodeRandomColorEventHandler, SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE ); //Generate shader
    custom_node_random_color->SetInputCount( 0 );
    custom_node_mean_color->AddObserver( &customNodeMeanColorEventHandler, SG_EVENT_CUSTOM_NODE_EVALUATE ); //Evaluate
    custom_node_mean_color->AddObserver( &customNodeMeanColorEventHandler, SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE ); //Generate shader
    custom_node_mean_color->SetInputCount( 2 );
    custom_node_mean_color->SetInput( 0, custom_node_random_color );
    custom_node_mean_color->SetInput( 1, red_node );
    custom_node_square_color->AddObserver( &customNodeSquareColorEventHandler, SG_EVENT_CUSTOM_NODE_EVALUATE ); //Evaluate
    custom_node_square_color->AddObserver( &customNodeSquareColorEventHandler, SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE ); //Generate shader
    custom_node_square_color->SetInputCount( 1 );
    custom_node_square_color->SetInput( 0, custom_node_mean_color );
    // Assign the exit node of the material node network to the diffuse channel in the material
    customMaterial->SetShadingNetwork( SG_MATERIAL_CHANNEL_DIFFUSE, custom_node_square_color );
    ///////////////////////////////////
    // Export HLSL shader code from the material network
    //
    // We create a shader data object
    spShaderGenerator custom_shader = sg->CreateShaderGenerator();
    // We add the material to it
    custom_shader->SetMaterial( customMaterial );
    // We tell the shader generator object to traverse the material node tree 
    // and gather the information needed to create a shader from the nodes 
    custom_shader->GenerateShaderData();
    // Get the entire shader code for HLSL 
    rstring hlsl_code = custom_shader->GetHLSLCode();
    // Store the shader to file in the current folder 
    FILE * file_hlsl = fopen( "shader_hlsl.txt", "wt" );
    fprintf( file_hlsl, "%s", hlsl_code.GetText() );
    fclose( file_hlsl );
    //
    ///////////////////////////////////
#ifdef _WIN32
    ///////////////////////////////////
    // Preview the model with the material node network 
    //
#ifndef _HEADLESS
    // Create a DirectX renderer
    spDirectXRenderer renderer = sg->CreateDirectXRenderer();
    // Setup the previewer with the window dimensions
    renderer->CreatePreviewer( 512, 512 );
    // Send the geometry and the material table into the renderer
    // Make sure the material table has materials that have a material 
    // node network attached to it
    renderer->LoadGeometryDataWithMaterialShadingNetwork( scene );
    // Create a camera for the renderer
    spSceneCamera sceneCamera = sg->CreateSceneCamera();
    sceneCamera->SetCustomSphereCameraPath(
        1,
        0.0f,
        0.0f,
        360.0f
        );
    scene->GetRootNode()->AddChild( sceneCamera );
    // Select the camera
    spSelectionSet cameraSelectionSet = sg->CreateSelectionSet();
    cameraSelectionSet->AddItem( sceneCamera->GetNodeGUID() );
    rid cameraSelectionSetID = scene->GetSelectionSetTable()->AddSelectionSet( cameraSelectionSet );
    // Render the model with the material node network and store the frames
    // This will look slightly different from the baked material since our random node shader code is placeholder.
    renderer->RenderAlongCameraPathAndStorePics( cameraSelectionSetID, "screenshot", "png" );
#endif
    //
    ///////////////////////////////////
#endif
    ///////////////////////////////////
    // Reduce the geometry and create a mapping image for it to be able to 
    // perform material casting.
    // 
    // Cast the diffuse channel from the material node network to the new 
    // material.
    //
    // Reducer
    spReductionProcessor red = sg->CreateReductionProcessor();
    red->SetScene( scene );
    // Set the Repair Settings.
    spRepairSettings repair_settings = red->GetRepairSettings();
    // Will take care of holes that are of size 0.2 or less, so small gaps etc are removed.
    repair_settings->SetWeldDist( 0.2f );
    repair_settings->SetTjuncDist( 0.2f );
    // Set the Reduction Settings.
    spReductionSettings reduction_settings = red->GetReductionSettings();
    // Keep all triangles
    reduction_settings->SetTriangleRatio( 1.0 );
    // Set the Mapping Image Settings.
    spMappingImageSettings mapping_settings = red->GetMappingImageSettings();
    // Without this we cannot fetch data from the original geometry
    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.2f );
    // Buffer space for when texture is mip-mapped, so color values dont blend over.
    mapping_settings->SetGutterSpace( 4 );
    // Set the new material texture dimensions
    mapping_settings->SetUseAutomaticTextureSize( false );
    mapping_settings->SetWidth( 512 );
    mapping_settings->SetHeight( 512 );
    mapping_settings->SetMultisamplingLevel( 2 );
    // Run the reduction
    red->RunProcessing();
    // Mapping image is needed 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" );
    //Add the new material to the table
    output_materials->AddMaterial( output_material );
    // Cast the diffuse texture data using a color caster
    spColorCaster cast = sg->CreateColorCaster();
    cast->SetColorType( SG_MATERIAL_CHANNEL_DIFFUSE ); //Select the diffuse channel from the original material 
    cast->SetSourceMaterials( materialTable );
    cast->SetMappingImage( mapping_image ); // The mapping image we got from the reduction process.
    cast->SetOutputChannels( 3 ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.)
    cast->SetOutputChannelBitDepth( 8 ); // 8 bits per channel. So in this case we will have 24bit colors RGB.
    cast->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree as well.
    cast->SetOutputFilePath( output_diffuse_filename.c_str() ); // Where the texture map will be saved to file.
    cast->RunProcessing(); // Fetch!
    // Set the material properties 
    // Set material to point to the created texture filename.
    AddSimplygonTexture( output_material, output_textures, SG_MATERIAL_CHANNEL_DIFFUSE, output_diffuse_filename.c_str() );
    spWavefrontExporter objexp = sg->CreateWavefrontExporter();
    scene->GetMaterialTable()->Copy( output_materials ); //What is copied here are the references to the materials, not the materials themselves.
    scene->GetTextureTable()->Copy( output_textures ); //What is copied here are the references to the materials, not the materials themselves.
    objexp->SetExportFilePath( output_geometry_filename.c_str() );
    objexp->SetScene( scene );
    objexp->RunExport();
    //
    ///////////////////////////////////
    }