///////////////////////////////////////////////////////////////////////////
//
//  System:    Simplygon
//  File:      ReductionExample.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# An extensive reduction example
//
//  In this example we demonstrate 3 common usages for the Simplygon reducer
//  and explain good starting settings for the different usages. We also discuss
//  how to use the stop conditions to setup complex reduction targets.
//
//  First, we do a high-quality reduction using symmetry awareness and 
//  high-quality normal handling, explaining most of the commonly 
//  used settings along the way
//
//  Secondly, the mesh is again reduced and the 9 input materials
//  of the original are cast into a single output material, retaining all 
//  channels from the original material
//
//  Thirdly, a cascaded reduction chain is run using faster settings
//  to show how to setup a cascaded LOD chain.
//
///////////////////////////////////////////////////////////////////////////
#include "../Common/Example.h"
void RunHighQualityReduction( const std::string& readFrom, const std::string& writeTo );
void RunReductionWithTextureCasting( const std::string& readFrom, const std::string& writeTo );
void RunCascadedLodChainReduction( const std::string& readFrom, const std::string& writeToLod1, const std::string& writeToLod2, const std::string& writeToLod3 );
class ProgressObserver : public robserver
    {
        public:
        virtual void Execute(
            IObject * subject,
            rid EventId,
            void * EventParameterBlock,
            unsigned int EventParameterBlockSize )
            {
            // only care for progress events
            if( EventId == SG_EVENT_PROGRESS )
                {
                // get the progress in percent
                int val = *((int *)EventParameterBlock);
                // tell the process to continue
                // this is required by the progress event
                *((int *)EventParameterBlock) = 1;
                // output the progress update
                PrintProgressBar( val );
                }
            }
    } progressObserver;
int main( int argc, char* argv[] )
    {
    try
    {
        InitExample();
        // Before any specific processing starts, set global variables. 
        // Using Orthonormal method for calculating tangentspace.
        sg->SetGlobalSetting("DefaultTBNType", SG_TANGENTSPACEMETHOD_ORTHONORMAL);
        std::string assetPath = GetAssetPath();
        // Run HQ reduction example, reducing a single geometry to a single LOD
        printf("Running HQ reduction... \n");
        PrintProgressBar(0);
        RunHighQualityReduction(assetPath + "SimplygonMan/SimplygonMan.obj", "SimplygonMan_HQ_LOD");
        printf("\nDone.\n\n");
        // Run reduction example that bakes all input materials into a single output material
        printf("Running reduction with material baking... \n");
        PrintProgressBar(0);
        RunReductionWithTextureCasting(assetPath + "SimplygonMan/SimplygonMan.obj", "SimplygonMan_Rebaked_Materials_LOD");
        printf("\nDone.\n\n");
        //// Run a cascaded LOD chain generation
        printf("Running cascaded LOD chain reduction... \n");
        RunCascadedLodChainReduction(assetPath + "SimplygonMan/SimplygonMan.obj", "SimplygonMan_Cascade_LOD1", "SimplygonMan_Cascade_LOD2", "SimplygonMan_Cascade_LOD3");
        printf("\nDone.\n\n");
        // Done!
        printf("All LODs complete, shutting down...");
        DeinitExample();
    }
    catch (const std::exception& ex)
    {
        std::cerr << ex.what() << std::endl;
        return -1;
    }
    return 0;
    }
void RunHighQualityReduction( const std::string& readFrom, const std::string& writeTo )
    {
    // Load input geometry from file
    spWavefrontImporter objReader = sg->CreateWavefrontImporter();
    objReader->SetExtractGroups( false ); //This makes the .obj reader import into a single geometry object instead of multiple
    objReader->SetImportFilePath( readFrom.c_str() );
    if( !objReader->RunImport() )
        throw std::exception("Failed to load input file!");
    // Get geometry and materials from importer
    spScene originalScene = objReader->GetScene();
    //Create a copy of the original scene on which we will run the reduction
    spScene lodScene = originalScene->NewCopy();
    // Create the reduction-processor, and set which scene to reduce
    spReductionProcessor reductionProcessor = sg->CreateReductionProcessor();
    reductionProcessor->SetScene( lodScene );
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // SETTINGS - Most of these are set to the same value by default, but are set anyway for clarity
    // The reduction settings object contains settings pertaining to the actual decimation
    spReductionSettings reductionSettings = reductionProcessor->GetReductionSettings();
    reductionSettings->SetKeepSymmetry( true ); //Try, when possible to reduce symmetrically
    reductionSettings->SetUseAutomaticSymmetryDetection( true ); //Auto-detect the symmetry plane, if one exists. Can, if required, be set manually instead.
    reductionSettings->SetUseHighQualityNormalCalculation( true ); //Drastically increases the quality of the LODs normals, at the cost of extra processing time.
    reductionSettings->SetReductionHeuristics( SG_REDUCTIONHEURISTICS_CONSISTENT ); //Choose between "fast" and "consistent" processing. Fast will look as good, but may cause inconsistent 
    //triangle counts when comparing MaxDeviation targets to the corresponding percentage targets.
    // The reducer uses importance weights for all features to decide where and how to reduce.
    // These are advanced settings and should only be changed if you have some specific reduction requirement
    /*reductionSettings->SetShadingImportance(2.f); //This would make the shading twice as important to the reducer as the other features.*/
    // The actual reduction triangle target are controlled by these settings
    reductionSettings->SetStopCondition( SG_STOPCONDITION_ANY );//The reduction stops when any of the targets below is reached
    reductionSettings->SetReductionTargets( SG_REDUCTIONTARGET_ALL );//Selects which targets should be considered when reducing
    reductionSettings->SetTriangleRatio( 0.5 ); //Targets at 50% of the original triangle count
    reductionSettings->SetTriangleCount( 10 ); //Targets when only 10 triangle remains
    reductionSettings->SetMaxDeviation( REAL_MAX ); //Targets when an error of the specified size has been reached. As set here it never happens.
    reductionSettings->SetOnScreenSize( 50 ); //Targets when the LOD is optimized for the selected on screen pixel size
    // The repair settings object contains settings to fix the geometries
    spRepairSettings repairSettings = reductionProcessor->GetRepairSettings();
    repairSettings->SetTjuncDist( 0.0f ); //Removes t-junctions with distance 0.0f
    repairSettings->SetWeldDist( 0.0f ); //Welds overlapping vertices
    // The normal calculation settings deal with the normal-specific reduction settings
    spNormalCalculationSettings normalSettings = reductionProcessor->GetNormalCalculationSettings();
    normalSettings->SetReplaceNormals( false ); //If true, this will turn off normal handling in the reducer and recalculate them all afterwards instead.
    //If false, the reducer will try to preserve the original normals as well as possible
    /*normalSettings->SetHardEdgeAngleInRadians( 3.14159f*60.0f/180.0f ); //If the normals are recalculated, this sets the hard-edge angle.*/
    //END SETTINGS 
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Add progress observer
    reductionProcessor->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
    // Run the actual processing. After this, the set geometry will have been reduced according to the settings
    reductionProcessor->RunProcessing();
    // For this reduction, the LOD will use the same material set as the original, and hence no further processing is required
    //Create an .obj exporter to save our result
    spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
    // Generate the output filenames
    std::string outputGeomFilename = GetExecutablePath() + writeTo + ".obj";
    // Do the actual exporting
    objExporter->SetExportFilePath( outputGeomFilename.c_str() );
    objExporter->SetScene( lodScene ); //This is the geometry we set as the processing geom of the reducer, retaining the materials in the original scene
    objExporter->RunExport();
    //Done! LOD created.
    }
void RunReductionWithTextureCasting( const std::string& readFrom, const std::string& writeTo )
    {
    // Load input geometry from file
    spWavefrontImporter objReader = sg->CreateWavefrontImporter();
    objReader->SetExtractGroups( false ); //This makes the .obj reader import into a single geometry object instead of multiple
    objReader->SetImportFilePath( readFrom.c_str() );
    if( !objReader->RunImport() )
        throw std::exception("Failed to load input file!");
    // Get the scene from the importer
    spScene originalScene = objReader->GetScene();
    spMaterialTable originalMaterialTable = originalScene->GetMaterialTable();
    spTextureTable originalTextures = originalScene->GetTextureTable();
    // Create a copy of the original scene on which we will run the reduction
    spScene lodScene = originalScene->NewCopy();
    // Create the reduction-processor, and set the scene to reduce
    spReductionProcessor reductionProcessor = sg->CreateReductionProcessor();
    reductionProcessor->SetScene( lodScene );
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // SETTINGS - Most of these are set to the same value by default, but are set anyway for clarity
    // The reduction settings object contains settings pertaining to the actual decimation
    spReductionSettings reductionSettings = reductionProcessor->GetReductionSettings();
    reductionSettings->SetReductionHeuristics( SG_REDUCTIONHEURISTICS_FAST ); //Choose between "fast" and "consistent" processing.
    // The actual reduction triangle target are controlled by these three settings
    reductionSettings->SetStopCondition( SG_STOPCONDITION_ANY );//The reduction stops when any of the targets below is reached
    reductionSettings->SetReductionTargets( SG_REDUCTIONTARGET_ALL );//Selects which targets should be considered when reducing
    reductionSettings->SetTriangleRatio( 0.5 ); //Targets at 50% of the original triangle count
    reductionSettings->SetTriangleCount( 10 ); //Targets when only 10 triangle remains
    reductionSettings->SetMaxDeviation( REAL_MAX ); //Targets when an error of the specified size has been reached. As set here it never happens.
    reductionSettings->SetOnScreenSize( 50 ); //Targets when the LOD is optimized for the selected on screen pixel size
    // The normal calculation settings deal with the normal-specific reduction settings
    spNormalCalculationSettings normalSettings = reductionProcessor->GetNormalCalculationSettings();
    normalSettings->SetReplaceNormals( true ); //If true, this will turn off normal handling in the reducer and recalculate them all afterwards instead.
    normalSettings->SetHardEdgeAngleInRadians( 3.14f ); //If the normals are recalculated, this sets the hard-edge angle. Large here, since we're baking a new normal map, and want large charts.
    // The Image Mapping Settings, specifically needed for the texture baking we are doing later
    spMappingImageSettings mappingSettings = reductionProcessor->GetMappingImageSettings();
    mappingSettings->SetUseFullRetexturing( true ); //This clears all existing UV sets and replaces the material IDs 
    mappingSettings->SetGenerateMappingImage( true ); //Without this we cannot fetch data from the original geometry, and thus not generate diffuse and normal-maps later on.
    mappingSettings->SetGenerateTexCoords( true );//Set to generate new texture coordinates.
    mappingSettings->SetGenerateTangents( true );//Set to generate new texture coordinates.
    mappingSettings->SetParameterizerMaxStretch( 0.8f ); //The higher the number, the fewer texture-borders.
    mappingSettings->SetGutterSpace( 2 ); //Buffer space for when texture is mip-mapped, so color values don't blend over. Greatly influences packing efficiency
    mappingSettings->SetTexCoordLevel( 0 ); //Sets the output texcoord level. For this asset, this will overwrite the original coords
    mappingSettings->SetWidth( 1024 );
    mappingSettings->SetHeight( 1024 );
    mappingSettings->SetMultisamplingLevel( 2 );
    //END SETTINGS 
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Add progress observer
    reductionProcessor->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
    // Run the actual processing. After this, the set geometry will have been reduced according to the settings
    reductionProcessor->RunProcessing();
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // CASTING
    // Now, we need to retrieve the generated mapping image and use it to cast the old materials into a new one, for each channel.
    spMappingImage mappingImage = reductionProcessor->GetMappingImage();
    // Now, for each channel, we want to cast the 9 input materials into a single output material, with one texture per channel. 
    // First, clear the lod materials (as they are copies from the original initially)
    spMaterialTable lodMaterialTable = lodScene->GetMaterialTable();
    spTextureTable lodTextureTable = lodScene->GetTextureTable();
    lodMaterialTable->Clear();
    lodTextureTable->Clear();
    // Create new material for the table.
    spMaterial lodMaterial = sg->CreateMaterial();
    lodMaterial->SetName( "SimplygonBakedMaterial" );
    lodMaterialTable->AddMaterial( lodMaterial );
    // Cast diffuse and specular texture data with a color caster
    {
    // Cast the data using a color caster
    spColorCaster colorCaster = sg->CreateColorCaster();
    colorCaster->SetSourceMaterials( originalMaterialTable );
    colorCaster->SetSourceTextures( originalTextures );
    //It means the internal shininess is multiplied by 128 before baking to texture.
    colorCaster->SetMappingImage( mappingImage ); //The mapping image we got from the reduction process.
    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->SetColorType( SG_MATERIAL_CHANNEL_DIFFUSE );
    colorCaster->SetOutputChannels( 3 ); //RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.)
    colorCaster->SetOutputFilePath( "combinedDiffuseMap.png" ); //Where the texture map will be saved to file.
    colorCaster->RunProcessing(); //Do the actual casting and write to texture.
    colorCaster->SetColorType( SG_MATERIAL_CHANNEL_SPECULAR );
    colorCaster->SetOutputChannels( 4 ); //RGBA, 4 channels! Stores spec power in A
    colorCaster->SetOutputFilePath( "combinedSpecularMap.png" ); //Where the texture map will be saved to file.
    colorCaster->RunProcessing(); //Do the actual casting and write to texture.
    AddSimplygonTexture( lodMaterial, lodTextureTable, SG_MATERIAL_CHANNEL_DIFFUSE, "combinedDiffuseMap.png" );
    AddSimplygonTexture( lodMaterial, lodTextureTable, SG_MATERIAL_CHANNEL_SPECULAR, "combinedSpecularMap.png" );
    }
    // Cast normal map texture data with the normal caster. This also compensates for any geometric errors that have appeared in the reduction process.
    {
    // cast the data using a normal caster
    spNormalCaster normalCaster = sg->CreateNormalCaster();
    normalCaster->SetSourceMaterials( originalMaterialTable );
    normalCaster->SetSourceTextures( originalTextures );
    normalCaster->SetMappingImage( mappingImage );
    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( "combinedNormalMap.png" );
    normalCaster->SetFlipBackfacingNormals( false );
    normalCaster->SetGenerateTangentSpaceNormals( true );
    normalCaster->RunProcessing();
    // Set normal map of the created material to point to the combined normal map
    AddSimplygonTexture( lodMaterial, lodTextureTable, SG_MATERIAL_CHANNEL_NORMALS, "combinedNormalMap.png" );
    }
    // END CASTING
    ///////////////////////////////////////////////////////////////////////////////////////////////
    //Create an .obj exporter to save our result
    spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
    // Generate the output filenames
    std::string outputGeomFilename = GetExecutablePath() + writeTo + ".obj";
    // Do the actual exporting
    objExporter->SetExportFilePath( outputGeomFilename.c_str() );
    objExporter->SetScene( lodScene ); //This is the geometry we set as the processing geom of the reducer
    objExporter->RunExport();
    //Done! LOD and material created.
    }
void RunCascadedLodChainReduction( const std::string& readFrom, const std::string& writeToLod1, const std::string& writeToLod2, const std::string& writeToLod3 )
    {
    // Load input geometry from file
    spWavefrontImporter objReader = sg->CreateWavefrontImporter();
    objReader->SetExtractGroups( false ); //This makes the .obj reader import into a single geometry object instead of multiple
    objReader->SetImportFilePath( readFrom.c_str() );
    if( !objReader->RunImport() )
        throw std::exception("Failed to load input file!");
    // Get the scene from the importer
    spScene originalScene = objReader->GetScene();
    // Create a copy of the original scene on which we will run the reduction
    spScene lodScene = originalScene->NewCopy();
    // Create the reduction-processor, and set the scene to reduce
    spReductionProcessor reductionProcessor = sg->CreateReductionProcessor();
    reductionProcessor->SetScene( lodScene );
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // SETTINGS 
    // The reduction settings object contains settings pertaining to the actual decimation
    spReductionSettings reductionSettings = reductionProcessor->GetReductionSettings();
    reductionSettings->SetReductionHeuristics( SG_REDUCTIONHEURISTICS_FAST ); //Choose between "fast" and "consistent" processing.
    // The normal calculation settings deal with the normal-specific reduction settings
    spNormalCalculationSettings normalSettings = reductionProcessor->GetNormalCalculationSettings();
    normalSettings->SetReplaceNormals( false ); //If true, this will turn off normal handling in the reducer and recalculate them all afterwards instead.
    //normalSettings->SetHardEdgeAngleInRadians( 3.14159f*70.0f/180.0f ); //If the normals are recalculated, this sets the hard-edge angle.
    // The actual reduction triangle target are controlled by these settings
    reductionSettings->SetStopCondition( SG_STOPCONDITION_ANY ); //The reduction stops when either of the targets is reached
    reductionSettings->SetReductionTargets( SG_REDUCTIONTARGET_ONSCREENSIZE );//The max deviation target determines when to stop the reduction. It is set in the loop below.
    //END SETTINGS 
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Add progress observer
    reductionProcessor->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
    //Create an .obj exporter to save our result
    spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
    objExporter->SetScene( lodScene ); //This is the geometry we set as the processing geom of the reducer
    //Set reduction targets using on-screen size
    uint onScreenSizeTargets[3];
    onScreenSizeTargets[0] = 500; //Gives a deviation of max 1 pixel at ~500 pixels on-screen
    onScreenSizeTargets[1] = 100; //Gives a deviation of max 1 pixel at ~100 pixels on-screen
    onScreenSizeTargets[2] = 50; //Gives a deviation of max 1 pixel at ~50 pixels on-screen
    //Generate the output filenames
    std::string outputGeomFilename[3];
    outputGeomFilename[0] = GetExecutablePath() + writeToLod1 + ".obj";
    outputGeomFilename[1] = GetExecutablePath() + writeToLod2 + ".obj";
    outputGeomFilename[2] = GetExecutablePath() + writeToLod3 + ".obj";
    // Run the iterative processing, saving the output geometry after every process
    for( int reductionIteration = 0; reductionIteration < 3; ++reductionIteration )
        {
        PrintProgressBar( 0 ); //Initial progress bar
        // The geometry still uses the same pointer, so it does not need to be re-set for the exporter or reducer after each pass.
        reductionSettings->SetOnScreenSize( onScreenSizeTargets[reductionIteration] ); //Stops when an error of the specified size has been reached. 
        reductionProcessor->RunProcessing();
        // Do the exporting
        objExporter->SetMaterialFilePath( NULL ); //Reset the material file path so it's set by ExportFilePath
        objExporter->SetExportFilePath( outputGeomFilename[reductionIteration].c_str() );
        objExporter->RunExport();
        }
    //Done! 3 cascaded LODs created.
    }