Show / Hide Table of Contents
    ///////////////////////////////////////////////////////////////////////////
    //
    //  System:    Simplygon
    //  File:      RemeshingExample.cpp
    //  Language:  C++
    //
    //  Copyright (c) 2019 Microsoft. All rights reserved.
    //
    ///////////////////////////////////////////////////////////////////////////
    //
    //  #Description# A remeshing example
    //
    //  This example shows how to use the remeshing API in Simplygon.
    //
    //  First, an asset with transparencies and internal geometry is remeshed
    //  to demonstrate the remesher's ability to remove internal geometry and 
    //  project the detail of said geometry onto transparent surfaces in the output
    //  textures.
    //
    //  Secondly, a brick wall asset is processed with a high merge distance,
    //  demoing the remesher's ability to fill cavities in meshes and generate
    //  nice, two-manifold outputs.
    //
    ///////////////////////////////////////////////////////////////////////////
    
    #include "../Common/Example.h"
    
    //Functions containing the Simplygon remeshing calls
    void RunRemeshingProcessing( const std::string& readFrom, const std::string& writeTo, int onScreenSize, int mergeDistance, int textureSize );
    void RunMaterialSeparatedRemeshingProcessing( const std::string& readFrom, const std::string& writeTo, int onScreenSize, int textureSize );
    
    
    //Progress observer
    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;
    
    /////////////////////////////////////////////////////////////////////
    // Main function with startup and shutdown code
    
    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 console mesh processing, will project interior onto transparent window
            printf("Running console asset, projecting interior...\n");
            PrintProgressBar(0); //Initial progress bar
            RunRemeshingProcessing(assetPath + "Console/console.obj", "console_remeshed", 300, 0, 512);
            printf("\nDone.\n\n");
    
            printf("Running console asset, meshing materials separately...\n");
            PrintProgressBar(0); //Initial progress bar
            RunMaterialSeparatedRemeshingProcessing(assetPath + "Console/console.obj", "console_separated_remeshed", 300, 512);
            printf("\nDone.\n\n");
    
            // Run brick wall mesh processing, merge distance 16
            printf("Running brick wall asset, high merge distance to fuse the individual bricks...\n ");
            PrintProgressBar(0); //Initial progress bar
            RunRemeshingProcessing(assetPath + "wall.obj", "wall_remeshed", 300, 16, 512);
            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;
        }
    
    //////////////////////////////////////////////////////////////////////
    
    
    //Takes a .obj path and remeshes the asset with the parameters below
    void RunRemeshingProcessing( const std::string& readFrom, const std::string& writeTo, int onScreenSize, int mergeDistance, int textureSize )
        {
        //Setup output paths
        std::string outputGeomPath = GetExecutablePath() + writeTo + ".obj";
        std::string outputDiffPath = GetExecutablePath() + writeTo + "_d.png";
        std::string outputSpecPath = GetExecutablePath() + writeTo + "_s.png";
        std::string outputNormPath = GetExecutablePath() + writeTo + "_n.png";
    
        //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 scene = objReader->GetScene();
        spMaterialTable materials = scene->GetMaterialTable();
        spTextureTable textures = scene->GetTextureTable();
    
        //Create a remeshing processor
        spRemeshingProcessor remeshingProcessor = sg->CreateRemeshingProcessor();
    
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // SETTINGS
        remeshingProcessor->SetScene( scene ); //Defines the scene on which to run the remesher
    
                                               //Geometry related settings:
        spRemeshingSettings remeshingSettings = remeshingProcessor->GetRemeshingSettings();
        //remeshingSettings->SetProcessSelectionSetID(-1); //Can be used to remesh only a specific selection set defined in the scene
        remeshingSettings->SetOnScreenSize( onScreenSize ); //The most important setting, defines the "resolution" of the remeshing, i.e. tri-count
        remeshingSettings->SetMergeDistance( mergeDistance ); //Defines how large gaps to fill in, in pixels. Relative to the setting above.
        remeshingSettings->SetSurfaceTransferMode( SG_SURFACETRANSFER_ACCURATE ); //This toggles between the two available surface mapping modes
                                                                                  //remeshingSettings->SetTransferColors(false); //Sets if the remesher should transfer the old vertex colors to the new geometry, if availible
                                                                                  //remeshingSettings->SetTransferNormals(false) //Sets if the remesher should transfer the old normals to the new geometry (generally a bad idea since the geometries don't match exactly)
        remeshingSettings->SetHardEdgeAngleInRadians( 3.141f / 2.1f ); //Sets the normal hard edge angle, used for normal recalc if TransferNormals is off. Here, slightly lower than 90 degrees.
                                                                       //remeshingSettings->SetUseCuttingPlanes( false ); //Defines whether to use cutting planes or not. Planes are defined in the scene object.
                                                                       //remeshingSettings->SetCuttingPlaneSelectionSetID( -1 ); //Sets a selection set for cutting planes, if you don't want to use all of the planes in the scene.
        remeshingSettings->SetUseEmptySpaceOverride( false ); //Overrides what the remesher considers to be "outside", so you can do interiors. Set coord with SetEmptySpaceOverride.
                                                              //remeshingSettings->SetMaxTriangleSize( 10 ); //Can be used to limit the triangle size of the output mesh, producing more triangles. Can be useful for exotic use cases.
    
                                                              //Parameterization and material casting related settings
        spMappingImageSettings mappingSettings = remeshingProcessor->GetMappingImageSettings();
        //mappingSettings->SetUseFullRetexturing( true ); //Kind of irrelevant for the remesher, since it always is a "full retexturing"
        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 tangents and bitangents.
        mappingSettings->SetParameterizerMaxStretch( 0.8f ); //The higher the number, the fewer texture-borders. Also introduces more stretch, obviously.
        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.
        mappingSettings->SetWidth( textureSize );
        mappingSettings->SetHeight( textureSize );
        mappingSettings->SetMultisamplingLevel( 2 );
        mappingSettings->SetMaximumLayers( 3 ); //IMPORTANT! This setting defines how many transparant layers the remesher will project onto the outermost surface of the remeshed geom,
                                                //and hence, how many layers will be in the generated mapping image
                                                //END SETTINGS 
                                                ///////////////////////////////////////////////////////////////////////////////////////////////
    
    
        remeshingProcessor->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
    
        //Run the remeshing
        remeshingProcessor->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 = remeshingProcessor->GetMappingImage();
    
        // Now, for each channel, we want to cast the input materials into a single output material, with one texture per channel. 
        // Create new material for the table.
        spMaterial lodMaterial = sg->CreateMaterial();
        lodMaterial->SetName( "SimplygonBakedMaterial" );
    
        // Make a new tex table
        spTextureTable lodTextures = sg->CreateTextureTable();
    
        // Cast diffuse and specular texture data with a color caster
        if( true )
            {
            // Cast the data using a color caster
            spColorCaster colorCaster = sg->CreateColorCaster();
            colorCaster->SetSourceMaterials( materials );
            colorCaster->SetSourceTextures( textures );
            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( outputDiffPath.c_str() ); //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( outputSpecPath.c_str() ); //Where the texture map will be saved to file.
            colorCaster->RunProcessing(); //Do the actual casting and write to texture.
    
            AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_DIFFUSE, outputDiffPath.c_str() );
            AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_SPECULAR, outputSpecPath.c_str() );
            }
    
        // Cast normal map texture data with the normal caster. This also compensates for any geometric errors that have appeared in the reduction process.
        if( true )
            {
            // cast the data using a normal caster
            spNormalCaster normalCaster = sg->CreateNormalCaster();
            normalCaster->SetSourceMaterials( materials );
            normalCaster->SetSourceTextures( textures );
            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( outputNormPath.c_str() );
            normalCaster->SetFlipBackfacingNormals( false );
            normalCaster->SetGenerateTangentSpaceNormals( true );
            normalCaster->RunProcessing();
    
            // Set normal map of the created material to point to the combined normal map
            AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_NORMALS, outputNormPath.c_str() );
            }
    
        //Now, we can clear the original material table in the scene, and replace its contents with our new lodMaterial
        materials->Clear();
        materials->AddMaterial( lodMaterial ); //This will be added at matId 0, which will match the remeshed geometry
    
                                               //Also, replace the texture list from the original with the new one
        textures->Copy( lodTextures );
        // END CASTING
        ///////////////////////////////////////////////////////////////////////////////////////////////
    
        //Create an .obj exporter to save our result
        spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
    
        // Do the exporting
        objExporter->SetExportFilePath( outputGeomPath.c_str() );
        objExporter->SetScene( scene ); //scene now contains the remeshed geometry and the material table we modified above
        objExporter->RunExport();
    
        //Done! LOD and material created.
        }
    
    
    //Takes a .obj path and remeshes the asset with the parameters below. Generates separate geometry for each input material in the 
    //original, which means the sub-meshes that together make up a single input material are shrink-wrapped separately from the rest of the mesh.
    void RunMaterialSeparatedRemeshingProcessing( const std::string& readFrom, const std::string& writeTo, int onScreenSize, int textureSize )
        {
        std::string outputGeomPath = GetExecutablePath() + writeTo + ".obj";
    
        //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 scene = objReader->GetScene();
        spMaterialTable materials = scene->GetMaterialTable();
        spTextureTable textures = scene->GetTextureTable();
    
        //Create a remeshing processor
        spRemeshingProcessor remeshingProcessor = sg->CreateRemeshingProcessor();
    
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // SETTINGS
        remeshingProcessor->SetScene( scene ); //Defines the scene on which to run the remesher
    
                                               //Geometry related settings:
        spRemeshingSettings remeshingSettings = remeshingProcessor->GetRemeshingSettings();
    
        remeshingSettings->SetOnScreenSize( onScreenSize ); //The most important setting, defines the "resolution" of the remeshing, i.e. tri-count
    
        spMappingImageSettings mappingSettings = remeshingProcessor->GetMappingImageSettings();
        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 tangents and bitangents.
        mappingSettings->SetParameterizerMaxStretch( 0.8f ); //The higher the number, the fewer texture-borders. Also introduces more stretch, obviously.
        mappingSettings->SetTexCoordLevel( 0 ); //Sets the output texcoord level.
        mappingSettings->SetMaximumLayers( 3 );
    
        //------------------MATERIAL MAPPING SETTINGS
        //--Each output material will be meshed separately and assigned its own mapping image, enabling you to handle
        //--transparency and other special case materials. Since the geometries are shrink-wrapped separately, you may get
        //--double-sided results depending on if your per-material submeshes are watertight. 
        //--Enabling visibility culling can get rid of some of this.
        mappingSettings->SetInputMaterialCount( materials->GetItemsCount() );
        mappingSettings->SetOutputMaterialCount( materials->GetItemsCount() );
        for( uint m = 0; m < materials->GetItemsCount(); ++m )
            {
            mappingSettings->SetInputOutputMaterialMapping( m, m ); //This just means we want the same number of output materials as input materials.
            }
    
        //Set settings for our output materials
        for( uint newM = 0; newM < materials->GetItemsCount(); ++newM )
            {
            mappingSettings->SetGutterSpace( newM, 2 );
            mappingSettings->SetMultisamplingLevel( newM, 2 );
            mappingSettings->SetWidth( newM, textureSize );
            mappingSettings->SetHeight( newM, textureSize );
            }
    
        ////Example usage:
        ////This would generate an output where materials 0-3 ends up in output material 0, and 4 ends up in output material 2.
        //mappingSettings->SetInputMaterialCount( 5 );
        //mappingSettings->SetInputMaterialCount( 2 );
        //mappingSettings->SetInputOutputMaterialMapping( 0, 0 );
        //mappingSettings->SetInputOutputMaterialMapping( 1, 0 );
        //mappingSettings->SetInputOutputMaterialMapping( 2, 0 );
        //mappingSettings->SetInputOutputMaterialMapping( 3, 0 );
        //mappingSettings->SetInputOutputMaterialMapping( 4, 1 );
        //------------------END MATERIAL MAPPING SETTINGS
    
        //END SETTINGS 
        ///////////////////////////////////////////////////////////////////////////////////////////////
    
        remeshingProcessor->AddObserver( &progressObserver, SG_EVENT_PROGRESS );
    
        //Run the remeshing
        remeshingProcessor->RunProcessing();
    
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // CASTING
    
        // Make a new tex table
        spTextureTable lodTextures = sg->CreateTextureTable();
        spMaterialTable lodMaterials = sg->CreateMaterialTable();
    
        //Now we need to do the casting per output material. 
        //Some output materials might have ended up with no triangles, in these cases the mapping image returned will be null
        //Material Ids in the output geometry will correspond to the indices of the mapping images
        for( uint matId = 0; matId < materials->GetItemsCount(); ++matId )
            {
            spMaterial lodMaterial = sg->CreateMaterial();
            lodMaterial->SetName( std::to_string( matId ).c_str() );
    
            // Cast diffuse and specular texture data with a color caster
            if( !remeshingProcessor->GetMappingImage( matId ).IsNull() )
                {
                // Cast the data using a color caster
                spColorCaster colorCaster = sg->CreateColorCaster();
                colorCaster->SetSourceMaterials( materials );
                colorCaster->SetSourceTextures( textures );
                colorCaster->SetMappingImage( remeshingProcessor->GetMappingImage( matId ) );
                colorCaster->SetOutputChannelBitDepth( 8 );
                colorCaster->SetDilation( 10 );
    
                colorCaster->SetColorType( SG_MATERIAL_CHANNEL_DIFFUSE );
                colorCaster->SetOutputChannels( 3 );
                colorCaster->SetOutputFilePath( ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_d.png" ).c_str() );  //Some ugly string wrangling to make sure we get unique texture paths
                colorCaster->RunProcessing();
    
                colorCaster->SetColorType( SG_MATERIAL_CHANNEL_SPECULAR );
                colorCaster->SetOutputChannels( 3 );
                colorCaster->SetOutputFilePath( ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_s.png" ).c_str() );
                colorCaster->RunProcessing();
    
                //These are just convenience functions to add the generated textures to our material structures
                AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_DIFFUSE, ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_d.png" ).c_str(), std::to_string( matId ).c_str() );
                AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_SPECULAR, ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_s.png" ).c_str(), std::to_string( matId ).c_str() );
                }
    
            // Cast normal map texture data with the normal caster. This also compensates for any geometric errors that have appeared in the reduction process.
            if( !remeshingProcessor->GetMappingImage( matId ).IsNull() )
                {
                // cast the data using a normal caster
                spNormalCaster normalCaster = sg->CreateNormalCaster();
                normalCaster->SetSourceMaterials( materials );
                normalCaster->SetSourceTextures( textures );
                normalCaster->SetMappingImage( remeshingProcessor->GetMappingImage( matId ) );
                normalCaster->SetOutputChannels( 3 );
                normalCaster->SetOutputChannelBitDepth( 8 );
                normalCaster->SetDilation( 10 );
                normalCaster->SetOutputFilePath( ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_n.png" ).c_str() );
                normalCaster->SetFlipBackfacingNormals( false );
                normalCaster->SetGenerateTangentSpaceNormals( true );
                normalCaster->RunProcessing();
    
                //These are just convenience functions to add the generated textures to our material structures
                AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_NORMALS, ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_n.png" ).c_str(), std::to_string( matId ).c_str() );
                }
    
            if( !remeshingProcessor->GetMappingImage( matId ).IsNull() )
                {
                // cast the data using a normal caster
                spOpacityCaster opCaster = sg->CreateOpacityCaster();
                opCaster->SetSourceMaterials( materials );
                opCaster->SetSourceTextures( textures );
                opCaster->SetMappingImage( remeshingProcessor->GetMappingImage( matId ) );
                opCaster->SetOutputChannels( 1 );
                opCaster->SetOutputChannelBitDepth( 8 );
                opCaster->SetDilation( 10 );
                opCaster->SetOutputFilePath( ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_o.png" ).c_str() );
                opCaster->RunProcessing();
    
                // Set normal map of the created material to point to the combined normal map
                AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_OPACITY, ( GetExecutablePath() + std::to_string( matId ) + writeTo + "_o.png" ).c_str(), std::to_string( matId ).c_str() );
                }
    
            //Add one new material per output mapping image
            lodMaterials->AddMaterial( lodMaterial );
            }
    
    
        //Also, replace the texture and material lists from the original with the new one
        textures->Copy( lodTextures );
        materials->Copy( lodMaterials );
        // END CASTING
        ///////////////////////////////////////////////////////////////////////////////////////////////
    
        //Create an .obj exporter to save our result
        spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
    
        // Do the exporting
        objExporter->SetExportFilePath( outputGeomPath.c_str() );
        objExporter->SetScene( scene ); //scene now contains the remeshed geometry and the material table we modified above
        objExporter->RunExport();
    
        //Done! LOD and material created.
        }
    
    Back to top Terms of Use | Privacy and cookies | Trademarks | Copyright © 2019 Microsoft