CustomNodeExample.cpp

<< Click to Display Table of Contents >>

Navigation:  Simplygon 7.1 examples >

CustomNodeExample.cpp

///////////////////////////////////////////////////////////////////////////
//
//  System:    Simplygon
//  File:      CustomNodeExample.cpp
//  Language:  C++
//
//  Copyright (c) 2015 Donya Labs AB. All rights reserved.
//
//  This is private property, and it is illegal to copy or distribute in
//  any form, without written authorization by the copyright owner(s).
//
///////////////////////////////////////////////////////////////////////////
//
//  #Description#
//
//  This example shows how to use the custom node in the material node
//  network.
//
//  See MaterialNodeExample for a more general example of material nodes.
//
//  The RunExample() function will load a scene and create a new
//  material. A material node network that uses ICustomNode is
//  created and added to the diffuse channel of the new material.
//
//  ICustomNode is a node used in the material node network that does not
//  have its own "material casting evaluation" or "shader code generating"
//  functions. These functions will be generated and assigned to the
//  custom node by the user.
// 
//  To use an ICustomNode the user begins by creating the object and adding
//  event handlers (observers) to it. The function(s) triggered by the
//  event is implemented by the user.
//
//
//  The events are:
//
//  -- SG_EVENT_CUSTOM_NODE_EVALUATE --
//  This event is triggered when simplygon is trying to evaluate a color
//  from the node. The function receives the already evaluated RGBA colors
//  of all the input nodes plugged into the custom node.
//
//  To "return" the evaluated color from the custom node the first RGBA
//  element in the array of RGBA values should be filled with the output
//  color.
//
//  The array of RGBA values corresponds to the amount of input nodes (set
//  by the user) in the ICustomNode. The input node order set by the user
//  into the ICustomNode will correspond to the order of evaluated RGBA
//  values sent to the evaluation event handler function.
//
//  The block of data received should be casted to a ParameterBlockEvaluate
//  object.
//
//  struct ParameterBlockEvaluate
//      {
//      uint inputCount;
//      float ** inputs;
//      };
//
//
//  -- SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE --
//  This event is triggered when the ICustomNode is generating shader code
//  (both HLSL and GLSL).
//
//  The user will "return" a string with the custom node evaluation shader
//  function code. The string should end with assigning the variable
//  "result" to a color (float4 in HLSL, vec4 in GLSL).
//
//  To use the values of the input nodes' colors, access them with
//  variable names following the "rgba_custom_input_XXX" format, where XXX
//  is replaced with the index of the input node.
//
//  The block of data received should be casted to a
//  ParameterBlockShaderCode object.
//
//  struct ParameterBlockShaderCode
//      {
//      uint inputCount;
//      char * shaderCode;
//      };
//
///////////////////////////////////////////////////////////////////////////
#include "../Common/Example.h"
#include <time.h>
void RunExample( const std::string& readFrom );
struct ParameterBlockEvaluate
    {
    uint inputCount;
    float inputs[SG_NUM_SUPPORTED_CUSTOM_NODE_INPUTS][4];
    };
struct ParameterBlockShaderCode
    {
    uint inputCount;
    char shaderCode[SG_SUPPORTED_CUSTOM_NODE_SHADER_CODE_SIZE];
    };
//Implements a random color node
class CustomNodeRandomColorEventHandler : public robserver
    {
    public :
        virtual void Execute (
            IObject * subject ,
            rid EventId ,
            void * EventParameterBlock ,
            unsigned int EventParameterBlockSize )
            {
            float output[4] = {0.0f,0.0f,0.0f,0.0f};
            if( EventId == SG_EVENT_CUSTOM_NODE_EVALUATE )
                {
                ParameterBlockEvaluate * parameters = (( ParameterBlockEvaluate *) EventParameterBlock );
                output[0] = float(rand())/float(RAND_MAX);
                output[1] = float(rand())/float(RAND_MAX);
                output[2] = float(rand())/float(RAND_MAX);
                output[3] = float(rand())/float(RAND_MAX);
                parameters->inputs[0][0] = output[0];
                parameters->inputs[0][1] = output[1];
                parameters->inputs[0][2] = output[2];
                parameters->inputs[0][3] = output[3];
                }
            else if( EventId == SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE )
                {
                ParameterBlockShaderCode * parameters = (( ParameterBlockShaderCode *) EventParameterBlock );
                //Incomplete, shader code is placeholder since we can't implement a matching random shader
                sprintf(
                    parameters->shaderCode,
                    "result = float4(0.5f,0.5f,0.5f,0.5f);\n"
                    );
                }
            }
    } customNodeRandomColorEventHandler;
//Implements a mean color node
class CustomNodeMeanColorEventHandler : public robserver
    {
    public :
        virtual void Execute (
            IObject * subject ,
            rid EventId ,
            void * EventParameterBlock ,
            unsigned int EventParameterBlockSize )
            {
            float output[4] = {0.0f,0.0f,0.0f,0.0f};
            if( EventId == SG_EVENT_CUSTOM_NODE_EVALUATE )
                {
                ParameterBlockEvaluate * parameters = (( ParameterBlockEvaluate *) EventParameterBlock );
                //printf ( "InputCount: %d n" , parameters.inputCount );
                for( uint i = 0 ; i < parameters->inputCount ; ++i )
                    {
                    //printf ( "InputValue %d: %f %f %f %f n", i, parameters.inputs[i][0], parameters.inputs[i][1], parameters.inputs[i][2], parameters.inputs[i][3] );
                    output[0] += parameters->inputs[i][0];
                    output[1] += parameters->inputs[i][1];
                    output[2] += parameters->inputs[i][2];
                    output[3] += parameters->inputs[i][3];
                    }
                if( parameters->inputCount > 0)
                    {
                    output[0] /= (float)parameters->inputCount;
                    output[1] /= (float)parameters->inputCount;
                    output[2] /= (float)parameters->inputCount;
                    output[3] /= (float)parameters->inputCount;
                    }
                parameters->inputs[0][0] = output[0];
                parameters->inputs[0][1] = output[1];
                parameters->inputs[0][2] = output[2];
                parameters->inputs[0][3] = output[3];
                }
            else if( EventId == SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE )
                {
                ParameterBlockShaderCode * parameters = (( ParameterBlockShaderCode *) EventParameterBlock );
                sprintf(
                    parameters->shaderCode,
                    "result = float4(0.0f,0.0f,0.0f,0.0f);\n"
                    );
                for( uint i = 0 ; i < parameters->inputCount ; ++i )
                    {
                    sprintf(
                        parameters->shaderCode,
                        "%sresult += rgba_custom_input_%d / float(%d);\n",
                        parameters->shaderCode,
                        i,
                        parameters->inputCount
                        );
                    }
                }
            }
    } customNodeMeanColorEventHandler;
//Implements a squared filter node
class CustomNodeSquareColorEventHandler : public robserver
    {
    public :
        virtual void Execute (
            IObject * subject ,
            rid EventId ,
            void * EventParameterBlock ,
            unsigned int EventParameterBlockSize )
            {
            float output[4] = {0.0f,0.0f,0.0f,0.0f};
            if( EventId == SG_EVENT_CUSTOM_NODE_EVALUATE )
                {
                ParameterBlockEvaluate * parameters = (( ParameterBlockEvaluate *) EventParameterBlock );
                output[0] = parameters->inputs[0][0] * parameters->inputs[0][0];
                output[1] = parameters->inputs[0][1] * parameters->inputs[0][1];
                output[2] = parameters->inputs[0][2] * parameters->inputs[0][2];
                output[3] = parameters->inputs[0][3] * parameters->inputs[0][3];
                parameters->inputs[0][0] = output[0];
                parameters->inputs[0][1] = output[1];
                parameters->inputs[0][2] = output[2];
                parameters->inputs[0][3] = output[3];
                }
            else if( EventId == SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE )
                {
                ParameterBlockShaderCode * parameters = (( ParameterBlockShaderCode *) EventParameterBlock );
                sprintf(
                    parameters->shaderCode,
                    "result = float4(rgba_custom_input_0.r * rgba_custom_input_0.r,rgba_custom_input_0.g * rgba_custom_input_0.g,rgba_custom_input_0.b * rgba_custom_input_0.b,rgba_custom_input_0.a * rgba_custom_input_0.a);\n"
                    );
                }
            }
    } customNodeSquareColorEventHandler;
int main( int argc , char* argv[] )
    {
    InitExample();
    // Set global variable. Using Orthonormal method for calculating tangentspace.
    sg->SetGlobalSetting( "DefaultTBNType" , SG_TANGENTSPACEMETHOD_ORTHONORMAL );
    std::string assetPath = GetAssetPath();
    // Run the example code
    RunExample( assetPath + "object.obj" );
    DeinitExample();
    return 0;
    }
void RunExample( const std::string& readFrom )
    {
    const std::string writeTo = "output";
    std::string output_geometry_filename = GetExecutablePath() + writeTo + ".obj";
    std::string output_diffuse_filename = GetExecutablePath() + writeTo + "_diffuse.png";
    // Load object from file
    spWavefrontImporter objReader = sg->CreateWavefrontImporter();
    objReader->SetImportFilePath( readFrom.c_str() );
    if( !objReader->RunImport() )
        exit(-1);
    spScene scene = objReader->GetScene();
    spMaterialTable materialTable = scene->GetMaterialTable();
    materialTable->Clear();
    spMaterial customMaterial = sg->CreateMaterial();
    materialTable->AddMaterial( customMaterial );
    // Create a material node network that uses ICustomNode
    spShadingCustomNode custom_node_random_color = sg->CreateShadingCustomNode();
    spShadingCustomNode custom_node_square_color = sg->CreateShadingCustomNode();
    spShadingCustomNode custom_node_mean_color = sg->CreateShadingCustomNode();
    spShadingColorNode red_node = sg->CreateShadingColorNode();
    red_node->SetColor(1.0f, 0.0f,0.0f,1.0f);
    //Set the event handler functions for the custom nodes and their input properties
    custom_node_random_color->AddObserver( &customNodeRandomColorEventHandler, SG_EVENT_CUSTOM_NODE_EVALUATE ); //Evaluate
    custom_node_random_color->AddObserver( &customNodeRandomColorEventHandler, SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE ); //Generate shader
    custom_node_random_color->SetInputCount(0);
    custom_node_mean_color->AddObserver( &customNodeMeanColorEventHandler, SG_EVENT_CUSTOM_NODE_EVALUATE ); //Evaluate
    custom_node_mean_color->AddObserver( &customNodeMeanColorEventHandler, SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE ); //Generate shader
    custom_node_mean_color->SetInputCount(2);
    custom_node_mean_color->SetInput(0, custom_node_random_color);
    custom_node_mean_color->SetInput(1, red_node);
    custom_node_square_color->AddObserver( &customNodeSquareColorEventHandler, SG_EVENT_CUSTOM_NODE_EVALUATE ); //Evaluate
    custom_node_square_color->AddObserver( &customNodeSquareColorEventHandler, SG_EVENT_CUSTOM_NODE_GENERATE_SHADER_CODE ); //Generate shader
    custom_node_square_color->SetInputCount(1);
    custom_node_square_color->SetInput(0, custom_node_mean_color);
    // Assign the exit node of the material node network to the diffuse channel in the material
    customMaterial->SetShadingNetwork( SG_MATERIAL_CHANNEL_DIFFUSE, custom_node_square_color );
    ///////////////////////////////////
    // Export HLSL shader code from the material network
    //
    // We create a shader data object
    spShaderGenerator custom_shader = sg->CreateShaderGenerator();
    // We add the material to it
    custom_shader->SetMaterial( customMaterial );
    // We tell the shader generator object to traverse the material node tree
    // and gather the information needed to create a shader from the nodes
    custom_shader->GenerateShaderData();
    // Get the entire shader code for HLSL
    spString hlsl_code = custom_shader->GetHLSLCode();
    // Store the shader to file in the current folder
    FILE * file_hlsl = fopen( "shader_hlsl.txt", "wt" );
    fprintf( file_hlsl, "%s", hlsl_code->GetText() );
    fclose( file_hlsl );
    //
    ///////////////////////////////////
#ifdef _WIN32
    ///////////////////////////////////
    // Preview the model with the material node network
    //
    // Create a DirectX renderer
    spDirectXRenderer renderer = sg->CreateDirectXRenderer();
    // Setup the previewer with the window dimensions
    renderer->CreatePreviewer( 512, 512 );
    // Send the geometry and the material table into the renderer
    // Make sure the material table has materials that have a material
    // node network attached to it
    renderer->LoadGeometryDataWithMaterialShadingNetwork( scene );
    // Create a camera for the renderer
    spSceneCamera sceneCamera = sg->CreateSceneCamera();
    sceneCamera->SetCustomSphereCameraPath(
        1,
        0.0f,
        0.0f,
        360.0f
        );
    scene->GetRootNode()->AddChild(sceneCamera);
    // Select the camera
    spSelectionSet cameraSelectionSet = sg->CreateSelectionSet();
    cameraSelectionSet->AddItem(sceneCamera->GetNodeGUID());
    rid cameraSelectionSetID = scene->GetSelectionSetTable()->AddSelectionSet(cameraSelectionSet);
    // Render the model with the material node network and store the frames
    // This will look slightly different from the baked material since our random node shader code is placeholder.
    renderer->RenderAlongCameraPathAndStorePics( cameraSelectionSetID, "screenshot", "png" );
    //
    ///////////////////////////////////
#endif
   
    ///////////////////////////////////
    // Reduce the geometry and create a mapping image for it to be able to
    // perform material casting.
    //
    // Cast the diffuse channel from the material node network to the new
    // material.
    //
    // Reducer
    spReductionProcessor red = sg->CreateReductionProcessor();
    red->SetScene(scene);
    // Set the Repair Settings.
    spRepairSettings repair_settings = red->GetRepairSettings();
    // Will take care of holes that are of size 0.2 or less, so small gaps etc are removed.
    repair_settings->SetWeldDist( 0.2f );
    repair_settings->SetTjuncDist( 0.2f );
    // Set the Reduction Settings.
    spReductionSettings reduction_settings = red->GetReductionSettings();
    // Keep all triangles
    reduction_settings->SetTriangleRatio(1.0);
    // Set the Mapping Image Settings.
    spMappingImageSettings mapping_settings = red->GetMappingImageSettings();
    // Without this we cannot fetch data from the original geometry
    mapping_settings->SetGenerateMappingImage( true );
    // Set to generate new texture coordinates.
    mapping_settings->SetGenerateTexCoords( true );
    // The higher the number, the fewer texture-borders.
    mapping_settings->SetParameterizerMaxStretch( 0.2f );
    // Buffer space for when texture is mip-mapped, so color values dont blend over.
    mapping_settings->SetGutterSpace( 4 );
    // Set the new material texture dimensions
    mapping_settings->SetUseAutomaticTextureSize( false );
    mapping_settings->SetWidth( 512 );
    mapping_settings->SetHeight( 512 );
    mapping_settings->SetMultisamplingLevel( 2 );
    // Run the reduction
    red->RunProcessing();
    // Mapping image is needed for texture casting.
    spMappingImage mapping_image = red->GetMappingImage();
    // Create new material table.
    spMaterialTable output_materials = sg->CreateMaterialTable();
    //Create new material for the table.
    spMaterial output_material = sg->CreateMaterial();
    output_material->SetName( "output_material" );
    //Add the new material to the table
    output_materials->AddMaterial( output_material );
    // Cast the diffuse texture data using a color caster
    spColorCaster cast = sg->CreateColorCaster();
    cast->SetColorType( SG_MATERIAL_CHANNEL_DIFFUSE ); //Select the diffuse channel from the original material
    cast->SetSourceMaterials( materialTable );
    cast->SetMappingImage( mapping_image ); // The mapping image we got from the reduction process.
    cast->SetOutputChannels( 3 ); // RGB, 3 channels! (1 would be for grey scale, and 4 would be for RGBA.)
    cast->SetOutputChannelBitDepth( 8 ); // 8 bits per channel. So in this case we will have 24bit colors RGB.
    cast->SetDilation( 10 ); // To avoid mip-map artifacts, the empty pixels on the map needs to be filled to a degree as well.
    cast->SetOutputFilePath( output_diffuse_filename.c_str() ); // Where the texture map will be saved to file.
    cast->CastMaterials(); // Fetch!
    // Set the material properties
    // Set the diffuse multiplier for the texture. 1 means it will not differ from original texture
    output_material->SetColor(SG_MATERIAL_CHANNEL_DIFFUSE, 1.0f, 1.0f, 1.0f, 1.0f );
    // Set material to point to the created texture filename.
    output_material->SetTexture( SG_MATERIAL_CHANNEL_DIFFUSE , output_diffuse_filename.c_str() );
    spWavefrontExporter objexp = sg->CreateWavefrontExporter();
    scene->GetMaterialTable()->Copy(output_materials); //What is copied here are the references to the materials, not the materials themselves.
    objexp->SetExportFilePath( output_geometry_filename.c_str() );
    objexp->SetScene(scene);
    objexp->RunExport();
    //
    ///////////////////////////////////
    }