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