///////////////////////////////////////////////////////////////////////////
//
// 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];
}