<< Click to Display Table of Contents >> Navigation: Simplygon 7.1 examples > VertexDataExample.cpp |
///////////////////////////////////////////////////////////////////////////
//
// System: Simplygon
// File: VertexDataExample.cpp
// Language: C++
//
// Copyright (c) 2015 Donya Labs AB. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////
//
// #Description#
//
// This example shows how to include custom vertex-data
// in a geometry data object, and keep the information through the
// reduction processing. The example first generates a geometry (a helix)
// that has vertex-skinning-blend weights applied into the vertices.
// The helix is then reduced using the reducer. The resulting geometry
// is then bent using 2 bones and the blend weights, 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
// divisions along the length of the tube
const int divs_along_tube = 1000;
// divisions on the axis of the tube
const int divs_axis_tube = 20;
// the length of the helix
const real helix_length = real(200);
// the radius of the helix
const real helix_radius = real(15);
// number of turns of the helix
const int helix_num_turns = 20;
// the radius of the tube (thickness of the helix)
const real tube_radius = real(3);
/////////////////////////////////////////////////////////////////////
// Main function with startup and shutdown code
void RunExample();
void RunReductionProcessing( spScene scene , float max_dev );
void save_geometry_to_file( spScene scene , const std::string& filepath );
spGeometryData generate_helix_tube();
void bend_geometry( spGeometryData geom );
int main( int argc , char* argv[] )
{
// init SDK
InitExample();
// Run the example code
RunExample();
// deinit SDK
DeinitExample();
return 0;
}
// This function runs the example by first generating the geometry with
// applied custom vertex-data information (blend weights). It then reduces
// the geometry using a max deviation. The reduced
// geometry is then bent using the blend weights. All three versions of
// the geometry (original, reduced, reduced+bent) are output to file.
void RunExample()
{
spScene scene = sg->CreateScene();
// generate the initial tube geometry
printf("Generating helix-shaped tube...\n");
spGeometryData geom = generate_helix_tube();
scene->GetRootNode()->CreateChildMesh(geom);
// store the original data to a wavefront file
std::string outputObjPath = GetExecutablePath() + "original_tube.obj";
save_geometry_to_file( scene, outputObjPath.c_str() );
// now, reduce the geometry, using max deviation
printf("Reducing using max deviation...\n");
RunReductionProcessing( scene, 1.f );
// store the reduced geometry to a wavefront file
outputObjPath = GetExecutablePath() + "reduced_tube.obj";
save_geometry_to_file( scene, outputObjPath.c_str() );
// now, bend the reduced tube using the blend weights
printf("Bending the tube using blend...\n");
bend_geometry( geom );
// store the reduced geometry to a wavefront file.
outputObjPath = GetExecutablePath() + "bent_reduced_tube.obj";
save_geometry_to_file( scene, outputObjPath.c_str() );
}
/////////////////////////////////////////////////////////////////////
// Helix generation code
// this function returns the center coordinate of a ring in the helix.
Vector3D get_helix_base_coordinate( int ring_index )
{
Vector3D base;
// get an alpha value for the index along the tube
// this will be used for interpolations below
real alpha = real(ring_index) / real(divs_along_tube);
base.x = real( sin(alpha*2*pi*double(helix_num_turns)) * helix_radius );
base.y = real(helix_length)*alpha;
base.z = real( cos(alpha*2*pi*double(helix_num_turns)) * helix_radius );
return base;
}
// creates the 3d-coordinate of a vertex in the tube, using
// the index along the tube and index within the ring
Vector3D get_coordinate_in_tube( int ring_index , int vertex_index )
{
Vector3D base,next_base;
Vector3D vec;
Vector3D t,u,v;
// get an alpha value for the index along the tube
// this will be used for interpolations below
real alpha = real(ring_index) / real(divs_along_tube);
// the beta value is along the ring
real beta = real(vertex_index) / real(divs_axis_tube);
// the base coordinate is on a helix
base = get_helix_base_coordinate( ring_index );
next_base = get_helix_base_coordinate( ring_index+1 );
// calc tangent space
t = next_base - base;
t.Normalize();
v = Vector3D(0,1,0);
u = v.Cross(t);
u.Normalize();
v = t.Cross(u);
// now, define the ring on the helix, in tangent space
vec = base +
(real( cos(beta*2*pi) * tube_radius ) * u) +
(real( sin(beta*2*pi) * tube_radius ) * v);
return vec;
}
// This function generates the tube geometry data object.
// The helix contains coords/triangles but also texture coordinates
// and a custom vertex field called BlendWeights, which has
// two values per vertex, the blend weights of the bones.
// Please note that for clarity, this example uses a simplified
// version of skinning, where there are only two bones. The
// example could easily be extended into a fully working
// skinning setup using e.g. 4 blends per vertex and another
// field with the bone indices of each blend value.
spGeometryData generate_helix_tube()
{
// create a geometry data object
spGeometryData dest_geom = sg->CreateGeometryData();
// add a texture coordinate field. this field is defined
// per triangle corner, NOT per vertex.
dest_geom->AddTexCoords(0);
// also add a custom per-vertex field, where we will
// set blend weights for the two bones. The field will
// contain two items per vertex, i.e. the tuple size is 2
spRealArray BlendWeights = Cast<IRealArray>(dest_geom->AddBaseTypeUserVertexField( TYPES_ID_REAL, "BlendWeights", 2 ) );
// setup the number of triangles and vertices, this will allocate the data
dest_geom->SetVertexCount( (divs_along_tube+1)*divs_axis_tube );
dest_geom->SetTriangleCount( divs_along_tube*divs_axis_tube*2 );
// get the needed arrays of the geometry
spRealArray Coords = dest_geom->GetCoords();
spRidArray VertexIds = dest_geom->GetVertexIds();
spRealArray TexCoords = dest_geom->GetTexCoords(0);
// generate the rings along the tube
int vertex_index = 0;
for( int ring_index = 0; ring_index<(divs_along_tube+1); ++ring_index )
{
// for each ring, generate the vertices
for( int v_index = 0; v_index<divs_axis_tube; ++v_index )
{
// create the 3d coordinate of the vertex
Vector3D v = get_coordinate_in_tube(ring_index,v_index);
// set it in the coordinates array
Coords->SetItem( (vertex_index*3)+0 , v.x );
Coords->SetItem( (vertex_index*3)+1 , v.y );
Coords->SetItem( (vertex_index*3)+2 , v.z );
// set the blend weights of the two bones
// do a quadratic blend
real blend_val = real(ring_index)/real(divs_along_tube);
real blend1 = real(1) - blend_val;
real blend2 = blend_val;
blend1 *= blend1;
blend2 *= blend2;
BlendWeights->SetItem( (vertex_index*2)+0 , blend1 );
BlendWeights->SetItem( (vertex_index*2)+1 , blend2 );
++vertex_index;
}
}
// now, connect the vertices using triangles
int triangle_index = 0;
for( int ring_index = 0; ring_index<divs_along_tube; ++ring_index )
{
// for each ring, generate triangles, 2 triangles for each iteration (quad)
for( int q_index = 0; q_index<divs_axis_tube; ++q_index )
{
int triangles[2][3];
// the base index of this and next ring, and next local index within the ring
int ring_base_index = (ring_index * divs_axis_tube);
int next_ring_base_index = ((ring_index+1) * divs_axis_tube);
int next_q_index = (q_index+1) % divs_axis_tube; // next index, modulated to wrap
// set the indices of the vertices in the triangles
triangles[0][0] = ring_base_index + q_index;
triangles[0][1] = ring_base_index + next_q_index;
triangles[0][2] = next_ring_base_index + q_index;
triangles[1][0] = ring_base_index + next_q_index;
triangles[1][1] = next_ring_base_index + next_q_index;
triangles[1][2] = next_ring_base_index + q_index;
// set the VertexIds and TexCoords in each triangle
for( int t=0; t<2; ++t,++triangle_index )
{
// do each corner
for( int c=0; c<3; ++c )
{
int corner_id = (triangle_index*3)+c;
int vertex_id = triangles[t][c];
// set the corner item, one value per corner
VertexIds->SetItem( corner_id , vertex_id );
// calculate the texture coordinate based on the vertex id
real u = real(vertex_id % divs_axis_tube) / real(divs_axis_tube); // around tube
real v = real(vertex_id / divs_axis_tube) / real(divs_along_tube); // along tube
// set coords, 2 values per corner
TexCoords->SetItem( (corner_id*2)+0 , u );
TexCoords->SetItem( (corner_id*2)+1 , v );
}
}
}
}
// done
return dest_geom;
}
// This function bends the geometry by displacing the
// vertices using the bones and blend weights that are
// stored per-vertex. The result is written back into the
// vertex coordinate.
void bend_geometry( spGeometryData geom )
{
// The two bones that influence the vertices
Matrix4x4 bones[2];
// setup bones
bones[0].SetToRotationZ(0);
bones[1].SetToRotationZ(-30);
bones[1].SetTAxis( Vector3D(-helix_length*real(0.2),0,0) );
// get the user defined "BlendWeights" field
spRealArray BlendWeights = Cast<IRealArray>(geom->GetUserVertexField("BlendWeights"));
// 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)
);
// transform using the 2 bones
Vector3D vtx1 = bones[0].MultiplyPointVector( vtx );
Vector3D vtx2 = bones[1].MultiplyPointVector( vtx );
// get the weights
real blend1 = BlendWeights->GetItem(v*2+0);
real blend2 = BlendWeights->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 save_geometry_to_file( spScene scene , const std::string& filepath )
{
// create the wavefront exporter
spWavefrontExporter exp = sg->CreateWavefrontExporter();
// set the scene
exp->SetScene(scene);
// set file path
exp->SetExportFilePath( filepath.c_str() );
// export to file
exp->RunExport();
}
// 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 geometry that is to be processed
spReductionProcessor red = sg->CreateReductionProcessor();
red->SetScene(scene);
///////////////////////////////////////////////////
//
// Set the Repair Settings. Current settings will mean that all visual gaps will remain in the geomtery and thus
// hinder the reduction on geometries that contains gaps, holes and tjunctions.
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();
}