VertexDataExample.cpp

<< 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();   
    }