CuttingPlanesExample.cpp

<< Click to Display Table of Contents >>

Navigation:  Simplygon 7.1 examples >

CuttingPlanesExample.cpp

///////////////////////////////////////////////////////////////////////////
//
//  System:    Simplygon
//  File:      CuttingPlanesExample.cpp
//  Language:  C++
//
//  Copyright (c) 2015 Donya Labs AB. 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[] )
    {
    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();
    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() )
        return;
    spScene scene = objReader->GetScene();
    spGeometryData combinedGeom = scene->GetCombinedGeometry();
    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();
    // 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)
        {
        // Set the material properties
        lodMaterial->SetColor( SG_MATERIAL_CHANNEL_AMBIENT , 0 , 0 , 0 , 0 );
        lodMaterial->SetColor( SG_MATERIAL_CHANNEL_DIFFUSE , 1 , 1 , 1 , 1 );
        lodMaterial->SetColor( SG_MATERIAL_CHANNEL_SPECULAR , 1 , 1 , 1 , 128 );
        //Note the 128 on the specular channels alpha. Simplygon bakes shininess
        //to the alpha channel of the specular map if the caster is set to 4 channel
        //output, and it is scaled between 0 and 1 internally. To get the correct
        //scale on the output, it should be multiplied by 128.
        // Cast the data using a color caster
        spColorCaster colorCaster = sg->CreateColorCaster();
        colorCaster->SetSourceMaterials( scene->GetMaterialTable() );
        colorCaster->SetDestMaterial( lodMaterial ); //This modulates the cast color with the base colors set for the dest material above.
        //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->CastMaterials(); //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->CastMaterials(); //Do the actual casting and write to texture.
        lodMaterial->SetTexture( SG_MATERIAL_CHANNEL_DIFFUSE , outputDiffFilename.c_str() ); //Set material to point to the texture we cast to above
        lodMaterial->SetTexture( SG_MATERIAL_CHANNEL_SPECULAR , outputSpecFilename.c_str() ); //Set material to point to the texture we cast to above
        }
    // 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->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->CastMaterials();
        // Set normal map of the created material to point to the combined normal map
        lodMaterial->SetTexture( 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);
    // 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];
    }