///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: ClippingGeometryExample.cpp
// Language: C++
//
// Copyright (c) 2019 Microsoft. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////
//
// #Description#
//
// This example demonstrates how to use the clipping geometry functionality
// of the remeshing processor to use a terrain to clip remeshed objects,
// saving texture space and avoiding two-sided geometry for assets like houses
// (or, in this case, some nice teapot programmer art) that are placed so they are
// partially sticking out of the terrain.
//
// Note that the clipping geometry functionality can also be used to do boolean type operations
// with objects other than terrain.
//
// This only works if the seal between the process geometry and the clipping geometry is
// completely watertight, with no leaks within the bounding volume of the process geometry.
// So, if your potential "buildings" do not in fact clip tightly into the ground
// plane, the clipping geometry will not work as intended.
//
///////////////////////////////////////////////////////////////////////////
#include <ios>
#include "../Common/Example.h"
//Basic vector define
typedef real real3[3];
//This function runs the remeshing processing for the given clipping mode
void RunRemeshingWithClipping( bool UseTerrainClipping, const std::string& readFrom, const std::string& writeTo );
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 without terrain clipping
printf("Running remeshing without terrain clipping... ");
RunRemeshingWithClipping(false, assetPath + "IslandOfTeapots/IslandOfTeapots.obj", "TeapotIslandUnclipped");
printf("Done.\n");
//Run remeshing with terrain clipping
printf("Running remeshing with terrain clipping... ");
RunRemeshingWithClipping(true, assetPath + "IslandOfTeapots/IslandOfTeapots.obj", "TeapotIslandClipped");
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 RunRemeshingWithClipping( bool UseClipping, const std::string& readFrom, const std::string& writeTo )
{
std::string exePath = GetExecutablePath();
//Load input geometry from file
spWavefrontImporter objReader = sg->CreateWavefrontImporter();
objReader->SetExtractGroups( true );
objReader->SetImportFilePath( readFrom.c_str() );
if( !objReader->RunImport() )
throw std::exception("Failed to load input file!");
spScene scene = objReader->GetScene();
spScene processScene = scene->NewCopy();
spTextureTable lodTextures = sg->CreateTextureTable();
lodTextures->Copy( scene->GetTextureTable() );
//Selection set setup
//We know the layout of the .obj file, it has the terrain in group 0 and teapots in the rest, which are put in sequential root children by the .obj reader
spSelectionSet terrainSet = sg->CreateSelectionSet();
spSelectionSet processSet = sg->CreateSelectionSet();
int terrainSetId = processScene->GetSelectionSetTable()->AddSelectionSet( terrainSet );
int processSetId = processScene->GetSelectionSetTable()->AddSelectionSet( processSet );
terrainSet->AddItem( processScene->GetRootNode()->GetChild( 0 )->GetNodeGUID() );
for( uint i = 1; i < processScene->GetRootNode()->GetChildCount(); ++i )
{
processSet->AddItem( processScene->GetRootNode()->GetChild( i )->GetNodeGUID() );
}
//Create processor and set scene
spRemeshingProcessor remeshingProcessor = sg->CreateRemeshingProcessor();
remeshingProcessor->SetScene( processScene );
///////////////////////////////////////////////////////////////////////////////////////////////
// SETTINGS
spRemeshingSettings remeshingSettings = remeshingProcessor->GetRemeshingSettings();
remeshingSettings->SetOnScreenSize( 800 );
remeshingSettings->SetMergeDistance( 6 ); //The asset has some gaps, try to merge
remeshingSettings->SetSurfaceTransferMode( SG_SURFACETRANSFER_ACCURATE );
//Now we have two selection sets with potential cutting planes, use mode switch to determine which set to use.
if( UseClipping )
{
remeshingSettings->SetUseClippingGeometry( true );
remeshingSettings->SetClippingGeometrySelectionSetID( terrainSetId );
//The remesher will try to automatically find what side of the clipping geometry should be considered "outside" by using the triangle winding.
//If this behaviour is not wanted, you can use the override coord to set what is considered empty space for the clipping geometry
//real3 overrideCoord = { 0.f, 0.f, 0.f };
//remeshingSettings->SetClippingGeometryEmptySpaceOverride( overrideCoord );
}
remeshingSettings->SetProcessSelectionSetID( processSetId ); //the -1 selection set contains all scene nodes, ie. all planes in the scene will be used.
// 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.8f ); //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" );
// Cast diffuse and specular texture data with a color caster
{
// Cast the data using a color caster
spColorCaster colorCaster = sg->CreateColorCaster();
colorCaster->SetSourceMaterials( scene->GetMaterialTable() );
colorCaster->SetSourceTextures( scene->GetTextureTable() );
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, lodTextures, SG_MATERIAL_CHANNEL_DIFFUSE, outputDiffFilename.c_str() );
AddSimplygonTexture( lodMaterial, lodTextures, SG_MATERIAL_CHANNEL_SPECULAR, outputSpecFilename.c_str() );
//Set the spec power based on what it was in the original material, the process geom uses mat 1
lodMaterial->SetShadingNetwork( SG_MATERIAL_CHANNEL_SHININESS, scene->GetMaterialTable()->GetMaterial( 1 )->GetShadingNetwork( SG_MATERIAL_CHANNEL_SHININESS ) );
}
// 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( processScene->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->RunProcessing();
// Set normal map of the created material to point to the combined normal map
AddSimplygonTexture( lodMaterial, lodTextures, 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";
std::string outputGeomWithTerrainFilename = exePath + writeTo + "WithTerrain" + ".obj";
//This add lod material, this scene now has both original and lod materials
processScene->GetMaterialTable()->AddMaterial( lodMaterial );
processScene->GetTextureTable()->Copy( lodTextures );
//Set the material id of the new geometry to 2 to correspond to the material we just added to the table
spSceneNode processGeomNode = processScene->GetNodeByGUID( processScene->GetSelectionSetTable()->GetSelectionSet( remeshingProcessor->GetResultSelectionSetId() )->GetItem( 0 ) );
spGeometryData processGeometry = SafeCast<ISceneMesh>( processGeomNode )->GetGeometry();
if(!processGeometry->GetMaterialIds())
processGeometry->AddMaterialIds();
for( uint t = 0; t < processGeometry->GetTriangleCount(); ++t )
{
processGeometry->GetMaterialIds()->SetItem( t, 2 );
}
//Add the terrain to the processed scene which now only contains the processed geometry
processScene->GetRootNode()->AddChild( scene->GetRootNode()->GetChild( 0 ) );
//Do the actual exporting, first with all geometry
objExporter->SetExportFilePath( outputGeomWithTerrainFilename.c_str() );
objExporter->SetScene( processScene ); //This is the geometry we set as the processing geom of the reducer
objExporter->RunExport();
//Export a version only containing the processed geometry
objExporter->SetMaterialFilePath( NULL ); //This just resets the auto-set material path, so that it'll be set by the ExportFilePath
objExporter->SetExportFilePath( outputGeomFilename.c_str() );
objExporter->SetScene( processScene ); //This is the geometry we set as the processing geom of the reducer
objExporter->SetSelectionSet( remeshingProcessor->GetResultSelectionSetId() );
objExporter->RunExport();
//Done! LOD and material created.
}