Show / Hide Table of Contents
    ///////////////////////////////////////////////////////////////////////////
    //
    //  System:    Simplygon
    //  File:      CuttingPlanesExample.cpp
    //  Language:  C++
    //
    //  Copyright (c) 2019 Microsoft. All rights reserved.
    //
    ///////////////////////////////////////////////////////////////////////////
    //
    //  #Description#
    //
    //  This example demonstrates how to use the cutting plane functionality 
    //  in the remeshing processor, using plane nodes defined in the scene
    //  and selection sets to define which plane nodes will be used as cutting planes.
    //  
    //  First, an asset is remeshed using a single non-axis-aligned cutting plane.
    //  
    //  Secondly, a method of creating tiling mesh parts is implemented by
    //  applying 5 axis-aligned cutting planes at every face of the object bounding
    //  box except the face facing a set viewer vector, effectively culling the "backside". 
    //  This will work great for things like wall segments, where the edges are rectangular 
    //  and aligned with the axes of the asset, so the cutting planes will form a perfect seal.
    //  
    ///////////////////////////////////////////////////////////////////////////
    
    #include "../Common/Example.h"
    
    //Basic vector define
    typedef real real3[3];
    //Remeshing mode switch
    enum RemeshingMode
        {
        SingleCuttingPlaneMode,
        BoundingBoxCuttingPlaneMode
        };
    
    //This function populates the remeshing settings with cutting planes based on the geometry bounding box and a view direction
    int GenerateViewDependentBoundingBoxAlignedCuttingPlanes( real3 geomInf, real3 geomSup, real3 viewer, spScene &scene );
    
    //This function runs the remeshing processing for the given cutting plane mode
    void RunRemeshingWithCuttingPlanes( RemeshingMode mode, const std::string& readFrom, const std::string& writeTo );
    
    //Basic 3D dot
    real Dot3( const real3 in1, const real3 in2 );
    
    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 remeshing with a single cutting plane, cutting the asset diagonally
            printf("Running remeshing with single cutting plane... ");
            RunRemeshingWithCuttingPlanes(SingleCuttingPlaneMode, assetPath + "ObscuredTeapot/ObscuredTeapot.obj", "ObscuredTeapotSingleCut");
            printf("Done.\n");
    
            //Run remeshing with 5 view-dependent axis aligned cutting planes, creating a tillable result
            printf("Running remeshing with 5 axis- and bounding box-aligned cutting planes... ");
            RunRemeshingWithCuttingPlanes(BoundingBoxCuttingPlaneMode, assetPath + "ObscuredTeapot/ObscuredTeapot.obj", "ObscuredTeapotBoundingCut");
            printf("Done.\n");
    
            //Done!
            printf("\nAll LODs complete, shutting down...");
            DeinitExample();
        }
        catch (const std::exception& ex)
        {
            std::cerr << ex.what() << std::endl;
            return -1;
        }
    
        return 0;
        }
    
    void RunRemeshingWithCuttingPlanes( RemeshingMode mode, const std::string& readFrom, const std::string& writeTo )
        {
        std::string exePath = GetExecutablePath();
    
        //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!");
    
        spScene scene = objReader->GetScene();
        spGeometryData combinedGeom = scene->NewCombinedGeometry();
    
        spRemeshingProcessor remeshingProcessor = sg->CreateRemeshingProcessor();
        remeshingProcessor->SetScene( scene );
    
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // SETTINGS
    
        spRemeshingSettings remeshingSettings = remeshingProcessor->GetRemeshingSettings();
        remeshingSettings->SetOnScreenSize( 200 );
        remeshingSettings->SetMergeDistance( 10 ); //The asset has some gaps, try to merge
        remeshingSettings->SetSurfaceTransferMode( SG_SURFACETRANSFER_ACCURATE );
    
        //----Cutting plane related
        remeshingSettings->SetUseCuttingPlanes( true ); //The list of set cutting planes are now used to cut the remeshed geom
    
        //Cutting planes are defined with a position and a normal.
        //We add the planes we define to selection sets which will be used to define which plane nodes contained in the scene will be used as cutting planes when remeshing
    
        //Define first cutting plane, add it to the scene, and create a selection set containing it.
        real3 cuttingPlanePos = { 0.f, 0.f, 0.f };
        real3 cuttingPlaneNormal = { 1.0f, 0.0f, 1.0f }; //This doesn't need to be normalized
        spScenePlane diagPlane = scene->GetRootNode()->CreateChildPlane( cuttingPlanePos, cuttingPlaneNormal ); //Could also create new spScenePlane and AddChild it to scene
        spSelectionSet diagSet = sg->CreateSelectionSet();
        diagSet->AddItem( diagPlane->GetNodeGUID() );
        int diagSetId = scene->GetSelectionSetTable()->AddSelectionSet( diagSet );
    
        //Define the bounding box aligned planes and add them to the scene and a selection set.
        real3 viewer = { 0.0f, 0.0f, -1.0f }; //This can obviously be anything you like
        real3 inf, sup;
        //Calculate extents of the geom for bounding box
        combinedGeom->CalculateExtents( true );
        combinedGeom->GetInf( inf );
        combinedGeom->GetSup( sup );
    
        //Call the function responsible for setting up the bounding box aligned cutting planes in the scene, it returns the ID of the selection set containing the planes
        int bbSetId = GenerateViewDependentBoundingBoxAlignedCuttingPlanes( inf, sup, viewer, scene );
    
        //Now we have two selection sets with potential cutting planes, use mode switch to determine which set to use.
        if( mode == SingleCuttingPlaneMode )
            remeshingSettings->SetCuttingPlaneSelectionSetID( diagSetId );
        else if( mode == BoundingBoxCuttingPlaneMode )
            remeshingSettings->SetCuttingPlaneSelectionSetID( bbSetId );
        else
            remeshingSettings->SetCuttingPlaneSelectionSetID( -1 ); //the -1 selection set contains all scene nodes, ie. all planes in the scene will be used.
        //----End cutting plane related
    
        // The Image Mapping Settings, specifically needed for the texture baking we are doing later
        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->SetParameterizerMaxStretch( 0.2f ); //The higher the number, the fewer texture-borders.
        mappingSettings->SetGutterSpace( 1 ); //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( 512 );
        mappingSettings->SetHeight( 512 );
        mappingSettings->SetMultisamplingLevel( 2 );
    
        //END SETTINGS 
        ///////////////////////////////////////////////////////////////////////////////////////////////
    
    
        // Run the actual processing. After this, the set geometry will have been reduced according to the settings
        remeshingProcessor->RunProcessing();
    
    
        ///////////////////////////////////////////////////////////////////////////////////////////////
        // CASTING
    
        // Generate the output filenames
        std::string outputDiffFilename = exePath + writeTo + "_d.png";
        std::string outputSpecFilename = exePath + writeTo + "_s.png";
        std::string outputNormFilename = exePath + writeTo + "_n.png";
    
        // 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();
    
        // First, create a new material table.
        spMaterialTable lodMaterialTable = sg->CreateMaterialTable();
        spTextureTable lodTextureTable = sg->CreateTextureTable();
    
        // 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
        if( true )
            {
            // Cast the data using a color caster
            spColorCaster colorCaster = sg->CreateColorCaster();
            colorCaster->SetSourceMaterials( scene->GetMaterialTable() );
            colorCaster->SetSourceTextures( scene->GetTextureTable() );
            //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( outputDiffFilename.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( outputSpecFilename.c_str() ); //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, outputDiffFilename.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( scene->GetMaterialTable() );
            normalCaster->SetSourceTextures( scene->GetTextureTable() );
            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( outputNormFilename.c_str() );
            normalCaster->SetFlipBackfacingNormals( true );
            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, outputNormFilename.c_str() );
            }
    
        // END CASTING
        ///////////////////////////////////////////////////////////////////////////////////////////////
    
    
    
        //Create an .obj exporter to save our result
        spWavefrontExporter objExporter = sg->CreateWavefrontExporter();
    
        // Generate the output filenames
        std::string outputGeomFilename = exePath + writeTo + ".obj";
    
        scene->GetMaterialTable()->Copy( lodMaterialTable );
        scene->GetTextureTable()->Copy( lodTextureTable );
    
        // Do the actual exporting
        objExporter->SetExportFilePath( outputGeomFilename.c_str() );
        objExporter->SetScene( scene ); //This is the geometry we set as the processing geom of the reducer
        objExporter->RunExport();
    
        //Done! LOD and material created.
        }
    
    int GenerateViewDependentBoundingBoxAlignedCuttingPlanes( real3 geomInf, real3 geomSup, real3 viewer, spScene &scene )
        {
        //Setup normals for each face
        real3 normals[6] =
            {
                {1.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, //plane at xMin and xMax
                {0.0f, 1.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, //plane at yMin and yMax
                {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, -1.0f}  //plane at zMin and zMax
            };
    
        //Check which cutting plane aligns the most with the supplied viewer
        real bestMatchDot = -REAL_MIN;
        int bestMatchId = -1;
        for( uint i = 0; i < 6; ++i )
            {
            real currentDot = Dot3( viewer, normals[i] );
            if( currentDot > bestMatchDot )
                {
                bestMatchDot = currentDot;
                bestMatchId = i;
                }
            }
    
        spSelectionSet bbSet = sg->CreateSelectionSet();
    
        //Add all cutting planes, except the one that was best aligned ("front")
        for( uint i = 0; i < 6; ++i )
            {
            //Skip match
            if( i == bestMatchId )
                continue;
    
            spScenePlane newPlane;
    
            //Set origin to either inf or sup depending on if we're on the negative or positive bounding face
            if( i % 2 == 0 )
                newPlane = scene->GetRootNode()->CreateChildPlane( geomInf, normals[i] );
            else
                newPlane = scene->GetRootNode()->CreateChildPlane( geomSup, normals[i] );
    
            bbSet->AddItem( newPlane->GetNodeGUID() );
            }
    
        return scene->GetSelectionSetTable()->AddSelectionSet( bbSet );
        }
    
    real Dot3( const real3 in1, const real3 in2 )
        {
        return in1[0] * in2[0] + in1[1] * in2[1] + in1[2] * in2[2];
        }
    
    Back to top Terms of Use | Privacy and cookies | Trademarks | Copyright © 2019 Microsoft