///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: BoneReductionExample.cpp
// Language: C++
//
// Copyright (c) 2019 Microsoft. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////
//
// #Description#
//
// Part 1
// This example shows how to include skinning data
// in a geometry data object, and keep the information through the
// reduction processing. The example first generates a geometry
// that has vertex-skinning (bone and weights) applied into the vertices.
// The geometry is then reduced using the reducer along with the skinning data.
// The reducer is instructed to reduce the bone count to half.
// The original and reduced geometries are then bent using bend function
// and the result (before and after bending) is output into an .obj file.
//
//
///////////////////////////////////////////////////////////////////////////
#include "../Common/Example.h"
#include "../Common/Vector3d.h"
#include "../Common/Matrix4x4.h"
/////////////////////////////////////////////////////////////////////
// Main settings
// total number of bones in the scene
const int total_bone = 2;
// number of bones influencing a vertex
const int bone_count = 2;
/////////////////////////////////////////////////////////////////////
// Main function with startup and shutdown code
void RunExampleGenerateAndBendHelix();
void RunReductionProcessing( spScene scene, float max_dev );
spScene GenerateSimpleTube();
void BendGeometry( spScene scene );
void SaveGeometryToFile( spScene scene, const std::string& filepath );
int main( int argc, char* argv[] )
{
try
{
// init SDK
InitExample();
// Run the example code
RunExampleGenerateAndBendHelix();
// deinit SDK
DeinitExample();
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl;
return -1;
}
return 0;
}
//Traverse all the vertices to check how many unique bones are used
int NumberOfBonesUsedInScene( spScene scene )
{
spSceneBoneTable bones = scene->GetBoneTable();
const uint boneCount = bones->GetBonesCount();
bool * boneIsUsedInScene = new bool[boneCount];
for( uint i = 0; i < boneCount; ++i )
boneIsUsedInScene[i] = false;
spGeometryData geom = scene->NewCombinedGeometry();
spRidArray boneIDs = geom->GetBoneIds();
spRealArray boneWeights = geom->GetBoneWeights();
uint boneTupleSize = boneIDs->GetTupleSize();
for( uint v = 0; v < geom->GetVertexCount(); ++v )
{
for( uint component = 0; component < boneTupleSize; ++component )
{
rid bID = boneIDs->GetItem( v * boneTupleSize + component );
if( bID >= 0 )
{
if( boneWeights->GetItem( v * boneTupleSize + component ) > 0.0 )
{
boneIsUsedInScene[bID] = true;
}
}
}
}
int usedBoneCount = 0;
for( uint i = 0; i < boneCount; ++i )
if( boneIsUsedInScene[i] )
++usedBoneCount;
delete[] boneIsUsedInScene;
return usedBoneCount;
}
// This function runs the example by first generating the geometry with
// skinning data (per vertex) information (bone and weights). It then reduces
// the geometry using a max deviation and skinning data is reduced by reducing
// the bone count to half. The reduced geometry is then bent using the bone
// weights. All three versions of the geometry (original, original+bent,
// reduced, reduced+bent) are output to file.
void RunExampleGenerateAndBendHelix()
{
// generate the initial tube geometry
printf( "Generating helix-shaped tube...\n" );
spScene scene = GenerateSimpleTube();
spScene sceneCpy = GenerateSimpleTube();
std::string outputPath = GetExecutablePath() + "original_tube.obj";
//store the original data to a wavefront file
SaveGeometryToFile( scene, outputPath.c_str() );
//take a copy of the original geometry and do a bend and save to file
BendGeometry( sceneCpy );
outputPath = GetExecutablePath() + "original_bent_tube.obj";
SaveGeometryToFile( sceneCpy, outputPath.c_str() );
// now, reduce the geometry, using max deviation
printf( "Reducing using max deviation (Bone reduced by half)...\n" );
RunReductionProcessing( scene, 1.f );
outputPath = GetExecutablePath() + "reduced_tube.obj";
// store the reduced geometry to a wavefront file
SaveGeometryToFile( scene, outputPath.c_str() );
// now, bend the reduced tube using the blend weights
printf( "Bending the reduced tube using blend...\n" );
BendGeometry( scene );
outputPath = GetExecutablePath() + "bent_reduced_tube.obj";
// store the reduced geometry to a wavefront file
SaveGeometryToFile( scene, outputPath.c_str() );
}
// This function processes the geometry, by using reduction to a maximum deviation.
void RunReductionProcessing( spScene scene, float max_dev )
{
// Create the reduction processor. Set the scene that is to be processed
spReductionProcessor red = sg->CreateReductionProcessor();
red->SetScene( scene );
///////////////////////////////////////////////////
// Set the bone settings
spBoneSettings boneSettings = red->GetBoneSettings();
// Reduce bones based on percentage of bones in the scene and onscreensize
boneSettings->SetUseBoneReducer( true );
boneSettings->SetStopCondition( SG_STOPCONDITION_ALL );
boneSettings->SetBoneReductionTargets( SG_BONEREDUCTIONTARGET_BONERATIO | SG_BONEREDUCTIONTARGET_ONSCREENSIZE );
// Set the ratio for reduction to half the the number of bones in the scene, and onscreen size to 500. This means the reduction will finish once both those targets are met.
boneSettings->SetBoneRatio( 0.5 );
boneSettings->SetOnScreenSize( 500 );
///////////////////////////////////////////////////
//
// Set the Repair Settings. Current settings will mean that all visual gaps will remain in the geometry and thus
// hinder the reduction on geometries that contains gaps, holes and t-junctions.
spRepairSettings repair_settings = red->GetRepairSettings();
// Only vertices that actually share the same position will be welded together
repair_settings->SetWeldDist( 0.0f );
// Only t-junctions with no actual visual distance will be fixed.
repair_settings->SetTjuncDist( 0.0f );
///////////////////////////////////////////////////
//
// Set the Reduction Settings.
spReductionSettings reduction_settings = red->GetReductionSettings();
// Reduce until we reach max deviation.
reduction_settings->SetMaxDeviation( max_dev );
///////////////////////////////////////////////////
//
// Set the Normal Calculation Settings.
spNormalCalculationSettings normal_settings = red->GetNormalCalculationSettings();
// Will completely recalculate the normals.
normal_settings->SetReplaceNormals( true );
normal_settings->SetHardEdgeAngleInRadians( 3.14159f*90.f / 180.0f );
// Run the process
red->RunProcessing();
}
//this function generates a fixed tube
spScene GenerateSimpleTube()
{
const int vertex_count = 16;
const int triangle_count = 24;
const int corner_count = triangle_count * 3;
//create a scene object
spScene scene = sg->CreateScene();
// triangles x 3 indices ( or 3 corners )
int corner_ids[corner_count * 1] = { 0, 1, 4,
4, 1, 5,
5, 1, 6,
1, 2, 6,
6, 2, 3,
6, 3, 7,
7, 3, 0,
7, 0, 4,
4, 5, 8,
8, 5, 9,
9, 5, 10,
5, 6, 10,
10, 6, 7,
10, 7, 11,
11, 7, 4,
11, 4, 8,
8, 9, 12,
12, 9, 13,
13, 9, 14,
9, 10, 14,
14, 10, 11,
14, 11, 15,
15, 11, 8,
15, 8, 12 };
// vertices with values for the x, y and z coordinates.
float vertex_coordinates[vertex_count * 3] = { 1.0, 0.0, 1.0,
1.0, 0.0, -1.0,
-1.0, 0.0, -1.0,
-1.0, 0.0, 1.0,
1.0, 15.0, 1.0,
1.0, 15.0, -1.0,
-1.0, 15.0, -1.0,
-1.0, 15.0, 1.0,
1.0, 20.0, 1.0,
1.0, 20.0, -1.0,
-1.0, 20.0, -1.0,
-1.0, 20.0, 1.0,
1.0, 35.0, 1.0,
1.0, 35.0, -1.0,
-1.0, 35.0, -1.0,
-1.0, 35.0, 1.0 };
spGeometryData geom = sg->CreateGeometryData();
//add bone weights and ids to geometry data (per vertex)
geom->AddBoneWeights( 2 );
spRealArray coords = geom->GetCoords();
spRidArray vertex_ids = geom->GetVertexIds();
spRealArray BoneWeights = geom->GetBoneWeights();
spRidArray BoneIds = geom->GetBoneIds();
geom->SetVertexCount( vertex_count );
geom->SetTriangleCount( triangle_count );
//create the bone table
spSceneBoneTable scn_bone_table = scene->GetBoneTable();
//create an array to store bone ids
spRidArray bone_ids = sg->CreateRidArray();
//create root bone for the scene
spSceneBone root_bone = sg->CreateSceneBone();
root_bone->SetName( "root_bone" );
//add the bone to the scene bone table and get a bone id
rid root_bone_id = scn_bone_table->AddBone( root_bone );
//a
bone_ids->AddItem( root_bone_id );
spSceneBone parent_bone = root_bone;
//create bones and populate the scene bone table
for( unsigned int bone_index = 1; bone_index < total_bone; bone_index++ )
{
spSceneBone bone = sg->CreateSceneBone();
bone->SetName( "ChildBone" );
spTransform3 boneTransform = sg->CreateTransform3();
//SET UP BONE IN BIND POSE
//translate the child bone to its corrent position relative to the parent bone
boneTransform->AddTransformation( bone->GetRelativeTransform() );
boneTransform->PreMultiply();
boneTransform->AddTranslation( 0, 17.5, 0 );
//store the relatvice transform
bone->GetRelativeTransform()->DeepCopy( boneTransform->GetMatrix() );
//add bone to the scene bone table
rid bone_id = scn_bone_table->AddBone( bone );
//link bone to parent bone
parent_bone->AddChild( bone );
bone_ids->AddItem( bone_id );
parent_bone = bone;
}
for( int i = 0; i < vertex_count; ++i )
{
coords->SetTuple( i, &vertex_coordinates[i * 3] );
//real blend_val = real((rid(i)/rid(4)))/real(3);
real blend_val = 0.5;
real blend1 = real( 1 ) - blend_val;
real blend2 = blend_val;
rid bone_id_1 = 0;
rid bone_id_2 = 1;
if( i < 4 )
{
bone_id_2 = -1;
blend2 = 0;
blend1 = 1;
}
else if( i > 11 )
{
bone_id_1 = -1;
blend2 = 1;
blend1 = 0;
}
blend1 *= blend1;
blend2 *= blend2;
//set the bone weights to perform skining
BoneWeights->SetItem( (i * 2) + 0, blend1 );
BoneWeights->SetItem( (i * 2) + 1, blend2 );
//set the bone ids influencing the vertex.
BoneIds->SetItem( (i * 2) + 0, bone_id_1 );
BoneIds->SetItem( (i * 2) + 1, bone_id_2 );
}
for( int i = 0; i < corner_count; ++i )
{
vertex_ids->SetItem( i, corner_ids[i] );
}
//create a scene mesh
spSceneMesh mesh = sg->CreateSceneMesh();
mesh->SetGeometry( geom );
mesh->SetName( "mesh" );
//get the root node of the scene and add the root_bone and mesh to the scene
scene->GetRootNode()->AddChild( mesh );
scene->GetRootNode()->AddChild( root_bone );
return scene;
}
void BendGeometry( spScene scene )
{
// The two bones that influence the vertices
//spMatrix4x4 sp_bone1;
spGeometryData geom = Cast<ISceneMesh>( scene->GetRootNode()->GetChild( 0 ) )->GetGeometry();
// get the bone weights field and ids
spRealArray boneWeights = geom->GetBoneWeights();
spRidArray boneIds = geom->GetBoneIds();
// get the Coordinates field
spRealArray Coords = geom->GetCoords();
// now, transform all vertices' coordinates using the bones
for( unsigned int v = 0; v < Coords->GetTupleCount(); ++v )
{
Vector3D vtx(
Coords->GetItem( v * 3 + 0 ),
Coords->GetItem( v * 3 + 1 ),
Coords->GetItem( v * 3 + 2 )
);
rid no = boneIds->GetItemCount();
rid bone_id_1 = boneIds->GetItem( v * 2 + 0 );
rid bone_id_2 = boneIds->GetItem( v * 2 + 1 );
spSceneBone bone_1;
spSceneBone bone_2;
Matrix4x4 b1gtMat;
Matrix4x4 b2gtMat;
Vector3D vtx1( vtx );
Vector3D vtx2( vtx );
if( bone_id_1 != -1 )
{
bone_1 = scene->GetBoneTable()->GetBone( bone_id_1 );
//retrieve the global transform of the bone in bind space
spMatrix4x4 rootGlobalTransform = sg->CreateMatrix4x4();
bone_1->EvaluateDefaultGlobalTransformation( rootGlobalTransform );
//apply transfrom to animate bone
spTransform3 bone1_transform = sg->CreateTransform3();
bone1_transform->AddTransformation( rootGlobalTransform );
bone1_transform->AddRotation( GetRadFromDegrees( -30 ), 1, 0, 0 );
rootGlobalTransform = bone1_transform->GetMatrix();
//apply transform
b1gtMat = GetMatrix4x4FromIMatrix( rootGlobalTransform );
vtx1 = b1gtMat.MultiplyPointVector( vtx );
}
if( bone_id_2 != -1 )
{
bone_2 = scene->GetBoneTable()->GetBone( bone_id_2 );
spMatrix4x4 boneGlobalTransform = sg->CreateMatrix4x4();
bone_2->EvaluateDefaultGlobalTransformation( boneGlobalTransform );
spTransform3 bone2_transform = sg->CreateTransform3();
//transform into bone2's local space and apply transform
bone2_transform->PreMultiply();
boneGlobalTransform->Invert();
bone2_transform->AddTransformation( boneGlobalTransform );
bone2_transform->AddRotation( GetRadFromDegrees( -30 ), 1, 0, 0 );
bone2_transform->AddTranslation( 0, 17.5, 0 );
//apply transform of the first bone
bone2_transform->AddRotation( GetRadFromDegrees( -30 ), 1, 0, 0 );
//get the global transform matrix for the animation pose
boneGlobalTransform = bone2_transform->GetMatrix();
b2gtMat = GetMatrix4x4FromIMatrix( boneGlobalTransform );
//apply transform to the vertex
vtx2 = b2gtMat.MultiplyPointVector( vtx );
}
//get the bone weights from the geometry data
real blend1 = boneWeights->GetItem( v * 2 + 0 );
real blend2 = boneWeights->GetItem( v * 2 + 1 );
// normalize the blend
real blend_scale = real( 1 ) / (blend1 + blend2);
blend1 *= blend_scale;
blend2 *= blend_scale;
// do a linear blend
vtx = vtx1*blend1 + vtx2*blend2;
// store in coordinates
Coords->SetItem( v * 3 + 0, vtx.x );
Coords->SetItem( v * 3 + 1, vtx.y );
Coords->SetItem( v * 3 + 2, vtx.z );
}
}
// This function stores the data into an .obj file
void SaveGeometryToFile( spScene scene, const std::string& filepath )
{
// create the wavefront exporter
spWavefrontExporter exp = sg->CreateWavefrontExporter();
exp->SetScene( scene );
// set file path
exp->SetExportFilePath( filepath.c_str() );
// export to file
exp->RunExport();
}