/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2015-2016 Mario Luzeiro <mrluzeiro@ua.pt>
 * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

/**
 * @file  c3d_render_raytracing.cpp
 * @brief
 */

#include <GL/glew.h>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <climits>
#include <thread>

#include "c3d_render_raytracing.h"
#include "mortoncodes.h"
#include "../ccolorrgb.h"
#include "3d_fastmath.h"
#include "3d_math.h"
#include "../common_ogl/ogl_utils.h"
#include <profile.h>        // To use GetRunningMicroSecs or another profiling utility

// This should be used in future for the function
// convertLinearToSRGB
//#include <glm/gtc/color_space.hpp>

C3D_RENDER_RAYTRACING::C3D_RENDER_RAYTRACING( CINFO3D_VISU &aSettings ) :
                       C3D_RENDER_BASE( aSettings ),
                       m_postshader_ssao( aSettings.CameraGet() )
{
    wxLogTrace( m_logTrace, wxT( "C3D_RENDER_RAYTRACING::C3D_RENDER_RAYTRACING" ) );

    m_opengl_support_vertex_buffer_object = false;
    m_pboId       = GL_NONE;
    m_pboDataSize = 0;
    m_accelerator = NULL;
    m_stats_converted_dummy_to_plane = 0;
    m_stats_converted_roundsegment2d_to_roundsegment = 0;
    m_oldWindowsSize.x = 0;
    m_oldWindowsSize.y = 0;
    m_outlineBoard2dObjects = NULL;
    m_firstHitinfo = NULL;
    m_shaderBuffer = NULL;
    m_camera_light = NULL;

    m_xoffset = 0;
    m_yoffset = 0;

    m_isPreview = false;
    m_rt_render_state = RT_RENDER_STATE_MAX; // Set to an initial invalid state
    m_stats_start_rendering_time = 0;
    m_nrBlocksRenderProgress = 0;
}


C3D_RENDER_RAYTRACING::~C3D_RENDER_RAYTRACING()
{
    wxLogTrace( m_logTrace, wxT( "C3D_RENDER_RAYTRACING::~C3D_RENDER_RAYTRACING" ) );

    delete m_accelerator;
    m_accelerator = NULL;

    delete m_outlineBoard2dObjects;
    m_outlineBoard2dObjects = NULL;

    delete[] m_shaderBuffer;
    m_shaderBuffer = NULL;

    opengl_delete_pbo();
}


int C3D_RENDER_RAYTRACING::GetWaitForEditingTimeOut()
{
    return 1000; // ms
}


void C3D_RENDER_RAYTRACING::opengl_delete_pbo()
{
    // Delete PBO if it was created
    if( m_opengl_support_vertex_buffer_object )
    {
        if( glIsBufferARB( m_pboId ) )
            glDeleteBuffers( 1, &m_pboId );

        m_pboId = GL_NONE;
    }
}


void C3D_RENDER_RAYTRACING::SetCurWindowSize( const wxSize &aSize )
{
    if( m_windowSize != aSize )
    {
        m_windowSize = aSize;
        glViewport( 0, 0, m_windowSize.x, m_windowSize.y );

        initializeNewWindowSize();
    }
}


void C3D_RENDER_RAYTRACING::restart_render_state()
{
    m_stats_start_rendering_time = GetRunningMicroSecs();

    m_rt_render_state = RT_RENDER_STATE_TRACING;
    m_nrBlocksRenderProgress = 0;

    m_postshader_ssao.InitFrame();

    m_blockPositionsWasProcessed.resize( m_blockPositions.size() );

    // Mark the blocks not processed yet
    std::fill( m_blockPositionsWasProcessed.begin(),
               m_blockPositionsWasProcessed.end(),
               0 );
}


static inline void SetPixel( GLubyte *p, const CCOLORRGB &v )
{
    p[0] = v.c[0]; p[1] = v.c[1]; p[2] = v.c[2]; p[3] = 255;
}


bool C3D_RENDER_RAYTRACING::Redraw( bool aIsMoving, REPORTER *aStatusTextReporter )
{
    bool requestRedraw = false;

    // Initialize openGL if need
    // /////////////////////////////////////////////////////////////////////////
    if( !m_is_opengl_initialized )
    {
        if( !initializeOpenGL() )
            return false;

        //aIsMoving = true;
        requestRedraw = true;

        // It will assign the first time the windows size, so it will now
        // revert to preview mode the first time the Redraw is called
        m_oldWindowsSize = m_windowSize;
        initialize_block_positions();
    }

    wxBusyCursor dummy;

    // Reload board if it was requested
    // /////////////////////////////////////////////////////////////////////////
    if( m_reloadRequested )
    {
        if( aStatusTextReporter )
            aStatusTextReporter->Report( _( "Loading..." ) );

        //aIsMoving = true;
        requestRedraw = true;
        reload( aStatusTextReporter );
    }


    // Recalculate constants if windows size was changed
    // /////////////////////////////////////////////////////////////////////////
    if( m_windowSize != m_oldWindowsSize )
    {
        m_oldWindowsSize = m_windowSize;
        aIsMoving = true;
        requestRedraw = true;

        initialize_block_positions();
    }


    // Clear buffers
    // /////////////////////////////////////////////////////////////////////////
    glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
    glClearDepth( 1.0f );
    glClearStencil( 0x00 );
    glClear( GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );

    // 4-byte pixel alignment
    glPixelStorei( GL_UNPACK_ALIGNMENT, 4 );

    glDisable( GL_STENCIL_TEST );
    glDisable( GL_LIGHTING );
    glDisable( GL_COLOR_MATERIAL );
    glDisable( GL_DEPTH_TEST );
    glDisable( GL_TEXTURE_2D );
    glDisable( GL_BLEND );


    const bool was_camera_changed = m_settings.CameraGet().ParametersChanged();

    if( requestRedraw || aIsMoving || was_camera_changed )
        m_rt_render_state = RT_RENDER_STATE_MAX; // Set to an invalid state,
                                                 // so it will restart again latter


    // This will only render if need, otherwise it will redraw the PBO on the screen again
    if( aIsMoving || was_camera_changed )
    {
        // Set head light (camera view light) with the oposite direction of the camera
        if( m_camera_light )
            m_camera_light->SetDirection( -m_settings.CameraGet().GetDir() );

        OGL_DrawBackground( SFVEC3F(m_settings.m_BgColorTop),
                            SFVEC3F(m_settings.m_BgColorBot) );

        // Bind PBO
        glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, m_pboId );

        // Get the PBO pixel pointer to write the data
        GLubyte *ptrPBO = (GLubyte *)glMapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB,
                                                     GL_WRITE_ONLY_ARB );

        if( ptrPBO )
        {
            render_preview( ptrPBO );

            // release pointer to mapping buffer, this initialize the coping to PBO
            glUnmapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB );
        }

        glWindowPos2i( m_xoffset, m_yoffset );
    }
    else
    {
        // Bind PBO
        glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, m_pboId );

        if( m_rt_render_state != RT_RENDER_STATE_FINISH )
        {
            // Get the PBO pixel pointer to write the data
            GLubyte *ptrPBO = (GLubyte *)glMapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB,
                                                         GL_WRITE_ONLY_ARB );

            if( ptrPBO )
            {
                render( ptrPBO, aStatusTextReporter );

                if( m_rt_render_state != RT_RENDER_STATE_FINISH )
                    requestRedraw = true;

                // release pointer to mapping buffer, this initialize the coping to PBO
                glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB);
            }
        }

        if( m_rt_render_state == RT_RENDER_STATE_FINISH )
        {
            glClear( GL_COLOR_BUFFER_BIT );
            // Options if we want draw background instead
            //OGL_DrawBackground( SFVEC3F(m_settings.m_BgColorTop),
            //                    SFVEC3F(m_settings.m_BgColorBot) );
        }

        glWindowPos2i( m_xoffset, m_yoffset );
    }

    // This way it will blend the progress rendering with the last buffer. eg:
    // if it was called after a openGL.
    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
    glEnable( GL_ALPHA_TEST );

    glDrawPixels( m_realBufferSize.x,
                  m_realBufferSize.y,
                  GL_RGBA,
                  GL_UNSIGNED_BYTE,
                  0 );

    glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, 0 );

    return requestRedraw;
}


void C3D_RENDER_RAYTRACING::render( GLubyte *ptrPBO , REPORTER *aStatusTextReporter )
{
    if( (m_rt_render_state == RT_RENDER_STATE_FINISH) ||
        (m_rt_render_state >= RT_RENDER_STATE_MAX) )
    {
        restart_render_state();

        if( m_camera_light )
            m_camera_light->SetDirection( -m_settings.CameraGet().GetDir() );

        if( m_settings.RenderEngineGet() == RENDER_ENGINE_OPENGL_LEGACY )
        {
            // Set all pixels of PBO transparent (Alpha to 0)
            // This way it will draw the full buffer but only shows the updated (
            // already calculated) squares
            // /////////////////////////////////////////////////////////////////////
            unsigned int nPixels = m_realBufferSize.x * m_realBufferSize.y;
            GLubyte *tmp_ptrPBO = ptrPBO + 3;   // PBO is RGBA

            for( unsigned int i = 0; i < nPixels; ++i )
            {
                *tmp_ptrPBO = 0;
                tmp_ptrPBO += 4;                // PBO is RGBA
            }
        }

        m_BgColorTop_LinearRGB = ConvertSRGBToLinear( (SFVEC3F)m_settings.m_BgColorTop );
        m_BgColorBot_LinearRGB = ConvertSRGBToLinear( (SFVEC3F)m_settings.m_BgColorBot );
    }

    switch( m_rt_render_state )
    {
    case RT_RENDER_STATE_TRACING:
            rt_render_tracing( ptrPBO, aStatusTextReporter );
        break;

    case RT_RENDER_STATE_POST_PROCESS_SHADE:
            rt_render_post_process_shade( ptrPBO, aStatusTextReporter );
        break;

    case RT_RENDER_STATE_POST_PROCESS_BLUR_AND_FINISH:
            rt_render_post_process_blur_finish( ptrPBO, aStatusTextReporter );
        break;

    default:
        wxASSERT_MSG( false, "Invalid state on m_rt_render_state");
        restart_render_state();
        break;
    }

    if( aStatusTextReporter && (m_rt_render_state == RT_RENDER_STATE_FINISH) )
    {
        // Calculation time in seconds
        const double calculation_time = (double)( GetRunningMicroSecs() -
                                                  m_stats_start_rendering_time ) / 1e6;

        aStatusTextReporter->Report( wxString::Format( _( "Rendering time %.3f s" ),
                                                       calculation_time ) );
    }
}


void C3D_RENDER_RAYTRACING::rt_render_tracing( GLubyte *ptrPBO ,
                                               REPORTER *aStatusTextReporter )
{
    m_isPreview = false;

    auto startTime = std::chrono::steady_clock::now();
    bool breakLoop = false;

    std::atomic<size_t> numBlocksRendered( 0 );
    std::atomic<size_t> currentBlock( 0 );
    std::atomic<size_t> threadsFinished( 0 );

    size_t parallelThreadCount = std::min<size_t>(
            std::max<size_t>( std::thread::hardware_concurrency(), 2 ),
            m_blockPositions.size() );
    for( size_t ii = 0; ii < parallelThreadCount; ++ii )
    {
        std::thread t = std::thread( [&]()
        {
            for( size_t iBlock = currentBlock.fetch_add( 1 );
                        iBlock < m_blockPositions.size() && !breakLoop;
                        iBlock = currentBlock.fetch_add( 1 ) )
            {
                if( !m_blockPositionsWasProcessed[iBlock] )
                {
                    rt_render_trace_block( ptrPBO, iBlock );
                    numBlocksRendered++;
                    m_blockPositionsWasProcessed[iBlock] = 1;

                    // Check if it spend already some time render and request to exit
                    // to display the progress
                    if( std::chrono::duration_cast<std::chrono::milliseconds>(
                            std::chrono::steady_clock::now() - startTime ).count() > 150 )
                        breakLoop = true;
                }
            }

            threadsFinished++;
        } );

        t.detach();
    }

    while( threadsFinished < parallelThreadCount )
        std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );

    m_nrBlocksRenderProgress += numBlocksRendered;

    if( aStatusTextReporter )
        aStatusTextReporter->Report( wxString::Format( _( "Rendering: %.0f %%" ),
                                                       (float)(m_nrBlocksRenderProgress * 100) /
                                                       (float)m_blockPositions.size() ) );

    // Check if it finish the rendering and if should continue to a post processing
    // or mark it as finished
    if( m_nrBlocksRenderProgress >= m_blockPositions.size() )
    {
        if( m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
            m_rt_render_state = RT_RENDER_STATE_POST_PROCESS_SHADE;
        else
        {
            m_rt_render_state = RT_RENDER_STATE_FINISH;
        }
    }
}

#ifdef USE_SRGB_SPACE

// This should be removed in future when the KiCad support a greater version of
// glm lib.

#define SRGB_GAMA 2.4f

// This function implements the conversion from linear RGB to sRGB
// https://github.com/g-truc/glm/blob/master/glm/gtc/color_space.inl#L12
static SFVEC3F convertLinearToSRGB( const SFVEC3F &aRGBcolor )
{
    const float gammaCorrection = 1.0f / SRGB_GAMA;
    const SFVEC3F clampedColor = glm::clamp( aRGBcolor, SFVEC3F(0.0f), SFVEC3F(1.0f) );

    return glm::mix(
        glm::pow( clampedColor, SFVEC3F(gammaCorrection) ) * 1.055f - 0.055f,
        clampedColor * 12.92f,
        glm::lessThan( clampedColor, SFVEC3F(0.0031308f) ) );
}

// This function implements the conversion from sRGB to linear RGB
// https://github.com/g-truc/glm/blob/master/glm/gtc/color_space.inl#L35
SFVEC3F ConvertSRGBToLinear( const SFVEC3F &aSRGBcolor )
{
    const float gammaCorrection = SRGB_GAMA;

    return glm::mix(
                    glm::pow( (aSRGBcolor + SFVEC3F(0.055f)) * SFVEC3F(0.94786729857819905213270142180095f),
                              SFVEC3F(gammaCorrection) ),
                    aSRGBcolor * SFVEC3F(0.07739938080495356037151702786378f),
                    glm::lessThanEqual( aSRGBcolor, SFVEC3F(0.04045f) ) );
}

#endif

void C3D_RENDER_RAYTRACING::rt_final_color( GLubyte *ptrPBO,
                                            const SFVEC3F &rgbColor,
                                            bool applyColorSpaceConversion )
{

    SFVEC3F color = rgbColor;

#ifdef USE_SRGB_SPACE

    // This should be used in future when the KiCad support a greater version of
    // glm lib.
    // if( applyColorSpaceConversion )
    //    rgbColor = glm::convertLinearToSRGB( rgbColor );

    if( applyColorSpaceConversion )
        color = convertLinearToSRGB( rgbColor );
#endif

    ptrPBO[0] = (unsigned int)glm::clamp( (int)(color.r * 255), 0, 255 );
    ptrPBO[1] = (unsigned int)glm::clamp( (int)(color.g * 255), 0, 255 );
    ptrPBO[2] = (unsigned int)glm::clamp( (int)(color.b * 255), 0, 255 );
    ptrPBO[3] = 255;
}


static void HITINFO_PACKET_init( HITINFO_PACKET *aHitPacket )
{
    // Initialize hitPacket with a "not hit" information
    for( unsigned int i = 0; i < RAYPACKET_RAYS_PER_PACKET; ++i )
    {
        aHitPacket[i].m_HitInfo.m_tHit = std::numeric_limits<float>::infinity();
        aHitPacket[i].m_HitInfo.m_acc_node_info = 0;
        aHitPacket[i].m_hitresult = false;
        aHitPacket[i].m_HitInfo.m_HitNormal = SFVEC3F( 0.0f );
        aHitPacket[i].m_HitInfo.m_ShadowFactor = 1.0f;
    }
}


void C3D_RENDER_RAYTRACING::rt_shades_packet(const SFVEC3F *bgColorY,
                                              const RAY     *aRayPkt,
                                              HITINFO_PACKET *aHitPacket,
                                              bool is_testShadow,
                                              SFVEC3F *aOutHitColor )
{
    for( unsigned int y = 0, i = 0; y < RAYPACKET_DIM; ++y )
    {
        for( unsigned int x = 0; x < RAYPACKET_DIM; ++x, ++i )
        {
            if( aHitPacket[i].m_hitresult == true )
            {
                aOutHitColor[i] = shadeHit( bgColorY[y],
                                            aRayPkt[i],
                                            aHitPacket[i].m_HitInfo,
                                            false,
                                            0,
                                            is_testShadow );
            }
            else
            {
                aOutHitColor[i] = bgColorY[y];
            }
        }
    }
}


void C3D_RENDER_RAYTRACING::rt_trace_AA_packet( const SFVEC3F *aBgColorY,
                                                const HITINFO_PACKET *aHitPck_X0Y0,
                                                const HITINFO_PACKET *aHitPck_AA_X1Y1,
                                                const RAY *aRayPck,
                                                SFVEC3F *aOutHitColor )
{
    const bool is_testShadow =  m_settings.GetFlag( FL_RENDER_RAYTRACING_SHADOWS );

    for( unsigned int y = 0, i = 0; y < RAYPACKET_DIM; ++y )
    {
        for( unsigned int x = 0; x < RAYPACKET_DIM; ++x, ++i )
        {
            const RAY &rayAA = aRayPck[i];

            HITINFO hitAA;
            hitAA.m_tHit = std::numeric_limits<float>::infinity();
            hitAA.m_acc_node_info = 0;

            bool hitted = false;

            const unsigned int idx0y1 = ( x + 0 ) + RAYPACKET_DIM * ( y + 1 );
            const unsigned int idx1y1 = ( x + 1 ) + RAYPACKET_DIM * ( y + 1 );

            // Gets the node info from the hit.
            const unsigned int nodex0y0 = aHitPck_X0Y0[ i ].m_HitInfo.m_acc_node_info;
            const unsigned int node_AA_x0y0 = aHitPck_AA_X1Y1[ i ].m_HitInfo.m_acc_node_info;

            unsigned int nodex1y0 = 0;

            if( x < (RAYPACKET_DIM - 1) )
                nodex1y0 = aHitPck_X0Y0[ i + 1 ].m_HitInfo.m_acc_node_info;

            unsigned int nodex0y1 = 0;

            if( y < (RAYPACKET_DIM - 1) )
                    nodex0y1 = aHitPck_X0Y0[ idx0y1 ].m_HitInfo.m_acc_node_info;

            unsigned int nodex1y1 = 0;

            if(  ((x < (RAYPACKET_DIM - 1)) &&
                  (y < (RAYPACKET_DIM - 1))) )
                nodex1y1 = aHitPck_X0Y0[ idx1y1 ].m_HitInfo.m_acc_node_info;


            if( ((nodex0y0 == nodex1y0) || (nodex1y0 == 0)) &&  // If all notes are equal we assume there was no change on the object hits
                ((nodex0y0 == nodex0y1) || (nodex0y1 == 0)) &&
                ((nodex0y0 == nodex1y1) || (nodex1y1 == 0)) &&
                (nodex0y0 == node_AA_x0y0) )
            {
                // Option 1
                // This option will give a very good quality on reflections (slow)
                /*
                if( m_accelerator->Intersect( rayAA, hitAA, nodex0y0 ) )
                {
                    aOutHitColor[i] += shadeHit( aBgColorY[y], rayAA, hitAA, false, 0 );
                }
                else
                {
                    if( m_accelerator->Intersect( rayAA, hitAA ) )
                        aOutHitColor[i] += shadeHit( aBgColorY[y], rayAA, hitAA, false, 0 );
                    else
                        aOutHitColor[i] += hitColor[i];
                }
                */

                // Option 2
                // Trace again with the same node,
                // then if miss just give the same color as before
                //if( m_accelerator->Intersect( rayAA, hitAA, nodex0y0 ) )
                //    aOutHitColor[i] += shadeHit( aBgColorY[y], rayAA, hitAA, false, 0 );

                // Option 3
                // Use same color

            }
            else
            {
                // Try to intersect the different nodes
                // It tests the possible combination of hitted or not hitted points
                // This will try to get the best hit for this ray

                if( nodex0y0 != 0 )
                    hitted |= m_accelerator->Intersect( rayAA, hitAA, nodex0y0 );

                if( ( nodex1y0 != 0 ) &&
                    ( nodex0y0 != nodex1y0 ) )
                    hitted |= m_accelerator->Intersect( rayAA, hitAA, nodex1y0 );

                if( ( nodex0y1 != 0 ) &&
                    ( nodex0y0 != nodex0y1 ) &&
                    ( nodex1y0 != nodex0y1 ) )
                    hitted |= m_accelerator->Intersect( rayAA, hitAA, nodex0y1 );

                if( (nodex1y1 != 0 ) &&
                    ( nodex0y0 != nodex1y1 ) &&
                    ( nodex0y1 != nodex1y1 ) &&
                    ( nodex1y0 != nodex1y1 ) )
                    hitted |= m_accelerator->Intersect( rayAA, hitAA, nodex1y1 );

                if( (node_AA_x0y0 != 0 ) &&
                    ( nodex0y0 != node_AA_x0y0 ) &&
                    ( nodex0y1 != node_AA_x0y0 ) &&
                    ( nodex1y0 != node_AA_x0y0 ) &&
                    ( nodex1y1 != node_AA_x0y0 ) )
                    hitted |= m_accelerator->Intersect( rayAA, hitAA, node_AA_x0y0 );

                if( hitted )
                {
                    // If we got any result, shade it
                    aOutHitColor[i] = shadeHit( aBgColorY[y], rayAA, hitAA, false, 0, is_testShadow );
                }
                else
                {
                    // Note: There are very few cases that will end on this situation
                    // so it is not so expensive to trace a single ray from the beginning

                    // It was missed the 'last nodes' so, trace a ray from the beginning
                    if( m_accelerator->Intersect( rayAA, hitAA ) )
                        aOutHitColor[i] = shadeHit( aBgColorY[y], rayAA, hitAA, false, 0, is_testShadow );
                }
            }
        }
    }
}

#define DISP_FACTOR 0.075f

void C3D_RENDER_RAYTRACING::rt_render_trace_block( GLubyte *ptrPBO ,
                                                   signed int iBlock )
{
    // Initialize ray packets
    // /////////////////////////////////////////////////////////////////////////
    const SFVEC2UI &blockPos = m_blockPositions[iBlock];
    const SFVEC2I blockPosI = SFVEC2I( blockPos.x + m_xoffset,
                                       blockPos.y + m_yoffset );

    RAYPACKET blockPacket( m_settings.CameraGet(),
                           (SFVEC2F)blockPosI + SFVEC2F(DISP_FACTOR, DISP_FACTOR),
                           SFVEC2F(DISP_FACTOR, DISP_FACTOR) // Displacement random factor
                           );

    HITINFO_PACKET hitPacket_X0Y0[RAYPACKET_RAYS_PER_PACKET];

    HITINFO_PACKET_init( hitPacket_X0Y0 );

    // Calculate background gradient color
    // /////////////////////////////////////////////////////////////////////////
    SFVEC3F bgColor[RAYPACKET_DIM];// Store a vertical gradient color

    for( unsigned int y = 0; y < RAYPACKET_DIM; ++y )
    {
        const float posYfactor = (float)(blockPosI.y + y) / (float)m_windowSize.y;

        bgColor[y] = m_BgColorTop_LinearRGB * SFVEC3F(posYfactor) +
                     m_BgColorBot_LinearRGB * ( SFVEC3F(1.0f) - SFVEC3F(posYfactor) );
    }

    // Intersect ray packets (calculate the intersection with rays and objects)
    // /////////////////////////////////////////////////////////////////////////
    if( !m_accelerator->Intersect( blockPacket, hitPacket_X0Y0 ) )
    {

        // If block is empty then set shades and continue
        if( m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
        {
            for( unsigned int y = 0; y < RAYPACKET_DIM; ++y )
            {
                const SFVEC3F &outColor = bgColor[y];

                const unsigned int yBlockPos = blockPos.y + y;

                for( unsigned int x = 0; x < RAYPACKET_DIM; ++x )
                {
                        m_postshader_ssao.SetPixelData( blockPos.x + x,
                                                        yBlockPos,
                                                        SFVEC3F( 0.0f ),
                                                        outColor,
                                                        SFVEC3F( 0.0f ),
                                                        0,
                                                        1.0f );
                }
            }
        }

        // This will set the output color to be displayed
        // If post processing is enabled, it will not reflect the final result
        // (as the final color will be computed on post processing)
        // but it is used for report progress

        const bool isFinalColor = !m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING );

        for( unsigned int y = 0; y < RAYPACKET_DIM; ++y )
        {
            const SFVEC3F &outColor = bgColor[y];

            const unsigned int yConst = blockPos.x +
                                        ( (y + blockPos.y) * m_realBufferSize.x);

            for( unsigned int x = 0; x < RAYPACKET_DIM; ++x )
            {
                GLubyte *ptr = &ptrPBO[ (yConst + x) * 4 ];

                rt_final_color( ptr, outColor, isFinalColor );
            }
        }

        // There is nothing more here to do.. there are no hits ..
        // just background so continue
        return;
    }


    SFVEC3F hitColor_X0Y0[RAYPACKET_RAYS_PER_PACKET];

    // Shade original (0, 0) hits ("paint" the intersected objects)
    // /////////////////////////////////////////////////////////////////////////
    rt_shades_packet( bgColor,
                      blockPacket.m_ray,
                      hitPacket_X0Y0,
                      m_settings.GetFlag( FL_RENDER_RAYTRACING_SHADOWS ),
                      hitColor_X0Y0 );

    if( m_settings.GetFlag( FL_RENDER_RAYTRACING_ANTI_ALIASING ) )
    {
        SFVEC3F hitColor_AA_X1Y1[RAYPACKET_RAYS_PER_PACKET];


        // Intersect one blockPosI + (0.5, 0.5) used for anti aliasing calculation
        // /////////////////////////////////////////////////////////////////////////
        HITINFO_PACKET hitPacket_AA_X1Y1[RAYPACKET_RAYS_PER_PACKET];
        HITINFO_PACKET_init( hitPacket_AA_X1Y1 );

        RAYPACKET blockPacket_AA_X1Y1( m_settings.CameraGet(),
                                       (SFVEC2F)blockPosI + SFVEC2F(0.5f, 0.5f),
                                       SFVEC2F(DISP_FACTOR, DISP_FACTOR) // Displacement random factor
                                       );

        if( !m_accelerator->Intersect( blockPacket_AA_X1Y1, hitPacket_AA_X1Y1 ) )
        {
            // Missed all the package
            for( unsigned int y = 0, i = 0; y < RAYPACKET_DIM; ++y )
            {
                const SFVEC3F &outColor = bgColor[y];

                for( unsigned int x = 0; x < RAYPACKET_DIM; ++x, ++i )
                {
                    hitColor_AA_X1Y1[i] = outColor;
                }
            }
        }
        else
        {
            rt_shades_packet( bgColor,
                              blockPacket_AA_X1Y1.m_ray,
                              hitPacket_AA_X1Y1,
                              m_settings.GetFlag( FL_RENDER_RAYTRACING_SHADOWS ),
                              hitColor_AA_X1Y1
                              );
        }

        SFVEC3F hitColor_AA_X1Y0[RAYPACKET_RAYS_PER_PACKET];
        SFVEC3F hitColor_AA_X0Y1[RAYPACKET_RAYS_PER_PACKET];
        SFVEC3F hitColor_AA_X0Y1_half[RAYPACKET_RAYS_PER_PACKET];

        for( unsigned int i = 0; i < RAYPACKET_RAYS_PER_PACKET; ++i )
        {
            const SFVEC3F color_average = ( hitColor_X0Y0[i] +
                                            hitColor_AA_X1Y1[i] ) * SFVEC3F(0.5f);

            hitColor_AA_X1Y0[i] = color_average;
            hitColor_AA_X0Y1[i] = color_average;
            hitColor_AA_X0Y1_half[i] = color_average;
        }

        RAY blockRayPck_AA_X1Y0[RAYPACKET_RAYS_PER_PACKET];
        RAY blockRayPck_AA_X0Y1[RAYPACKET_RAYS_PER_PACKET];
        RAY blockRayPck_AA_X1Y1_half[RAYPACKET_RAYS_PER_PACKET];

        RAYPACKET_InitRays_with2DDisplacement( m_settings.CameraGet(),
                                               (SFVEC2F)blockPosI + SFVEC2F(0.5f - DISP_FACTOR, DISP_FACTOR),
                                               SFVEC2F(DISP_FACTOR, DISP_FACTOR), // Displacement random factor
                                               blockRayPck_AA_X1Y0 );

        RAYPACKET_InitRays_with2DDisplacement( m_settings.CameraGet(),
                                               (SFVEC2F)blockPosI + SFVEC2F(DISP_FACTOR, 0.5f - DISP_FACTOR),
                                               SFVEC2F(DISP_FACTOR, DISP_FACTOR), // Displacement random factor
                                               blockRayPck_AA_X0Y1 );

        RAYPACKET_InitRays_with2DDisplacement( m_settings.CameraGet(),
                                               (SFVEC2F)blockPosI + SFVEC2F(0.25f - DISP_FACTOR, 0.25f - DISP_FACTOR),
                                               SFVEC2F(DISP_FACTOR, DISP_FACTOR), // Displacement random factor
                                               blockRayPck_AA_X1Y1_half );

        rt_trace_AA_packet( bgColor,
                            hitPacket_X0Y0, hitPacket_AA_X1Y1,
                            blockRayPck_AA_X1Y0,
                            hitColor_AA_X1Y0 );

        rt_trace_AA_packet( bgColor,
                            hitPacket_X0Y0, hitPacket_AA_X1Y1,
                            blockRayPck_AA_X0Y1,
                            hitColor_AA_X0Y1 );

        rt_trace_AA_packet( bgColor,
                            hitPacket_X0Y0, hitPacket_AA_X1Y1,
                            blockRayPck_AA_X1Y1_half,
                            hitColor_AA_X0Y1_half );

        // Average the result
        for( unsigned int i = 0; i < RAYPACKET_RAYS_PER_PACKET; ++i )
        {
            hitColor_X0Y0[i] = ( hitColor_X0Y0[i] +
                                 hitColor_AA_X1Y1[i] +
                                 hitColor_AA_X1Y0[i] +
                                 hitColor_AA_X0Y1[i] +
                                 hitColor_AA_X0Y1_half[i]
                                 ) * SFVEC3F(1.0f / 5.0f);
        }
    }


    // Copy results to the next stage
    // /////////////////////////////////////////////////////////////////////

    GLubyte *ptr = &ptrPBO[ ( blockPos.x +
                              (blockPos.y * m_realBufferSize.x) ) * 4 ];

    const uint32_t ptrInc = (m_realBufferSize.x - RAYPACKET_DIM) * 4;

    if( m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
    {
        SFVEC2I bPos;
        bPos.y = blockPos.y;

        for( unsigned int y = 0, i = 0; y < RAYPACKET_DIM; ++y )
        {
            bPos.x = blockPos.x;

            for( unsigned int x = 0; x < RAYPACKET_DIM; ++x, ++i )
            {
                const SFVEC3F &hColor = hitColor_X0Y0[i];

                if( hitPacket_X0Y0[i].m_hitresult == true )
                    m_postshader_ssao.SetPixelData( bPos.x, bPos.y,
                                                    hitPacket_X0Y0[i].m_HitInfo.m_HitNormal,
                                                    hColor,
                                                    blockPacket.m_ray[i].at(
                                                        hitPacket_X0Y0[i].m_HitInfo.m_tHit ),
                                                    hitPacket_X0Y0[i].m_HitInfo.m_tHit,
                                                    hitPacket_X0Y0[i].m_HitInfo.m_ShadowFactor );
                else
                    m_postshader_ssao.SetPixelData( bPos.x, bPos.y,
                                                    SFVEC3F( 0.0f ),
                                                    hColor,
                                                    SFVEC3F( 0.0f ),
                                                    0,
                                                    1.0f );

                rt_final_color( ptr, hColor, false );

                bPos.x++;
                ptr += 4;
            }

            ptr += ptrInc;
            bPos.y++;
        }
    }
    else
    {
        for( unsigned int y = 0, i = 0; y < RAYPACKET_DIM; ++y )
        {
            for( unsigned int x = 0; x < RAYPACKET_DIM; ++x, ++i )
            {
                rt_final_color( ptr, hitColor_X0Y0[i], true );
                ptr += 4;
            }

            ptr += ptrInc;
        }
    }
}


void C3D_RENDER_RAYTRACING::rt_render_post_process_shade( GLubyte *ptrPBO,
                                                          REPORTER *aStatusTextReporter )
{
    (void)ptrPBO; // unused

    if( m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
    {
        if( aStatusTextReporter )
            aStatusTextReporter->Report( _("Rendering: Post processing shader") );

        std::atomic<size_t> nextBlock( 0 );
        std::atomic<size_t> threadsFinished( 0 );

        size_t parallelThreadCount = std::max<size_t>( std::thread::hardware_concurrency(), 2 );
        for( size_t ii = 0; ii < parallelThreadCount; ++ii )
        {
            std::thread t = std::thread( [&]()
            {
                for( size_t y = nextBlock.fetch_add( 1 );
                            y < m_realBufferSize.y;
                            y = nextBlock.fetch_add( 1 ) )
                {
                    SFVEC3F *ptr = &m_shaderBuffer[ y * m_realBufferSize.x ];

                    for( signed int x = 0; x < (int)m_realBufferSize.x; ++x )
                    {
                        *ptr = m_postshader_ssao.Shade( SFVEC2I( x, y ) );
                        ptr++;
                    }
                }

                threadsFinished++;
            } );

            t.detach();
        }

        while( threadsFinished < parallelThreadCount )
            std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );

        // Set next state
        m_rt_render_state = RT_RENDER_STATE_POST_PROCESS_BLUR_AND_FINISH;
    }
    else
    {
        // As this was an invalid state, set to finish
        m_rt_render_state = RT_RENDER_STATE_FINISH;
    }
}


void C3D_RENDER_RAYTRACING::rt_render_post_process_blur_finish( GLubyte *ptrPBO,
                                                                REPORTER *aStatusTextReporter )
{
    (void)aStatusTextReporter; //unused

    if( m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
    {
        // Now blurs the shader result and compute the final color
        std::atomic<size_t> nextBlock( 0 );
        std::atomic<size_t> threadsFinished( 0 );

        size_t parallelThreadCount = std::max<size_t>( std::thread::hardware_concurrency(), 2 );
        for( size_t ii = 0; ii < parallelThreadCount; ++ii )
        {
            std::thread t = std::thread( [&]()
            {
                for( size_t y = nextBlock.fetch_add( 1 );
                            y < m_realBufferSize.y;
                            y = nextBlock.fetch_add( 1 ) )
                {
                    GLubyte *ptr = &ptrPBO[ y * m_realBufferSize.x * 4 ];

                    const SFVEC3F *ptrShaderY0 =
                            &m_shaderBuffer[ glm::max((int)y - 2, 0) * m_realBufferSize.x ];
                    const SFVEC3F *ptrShaderY1 =
                            &m_shaderBuffer[ glm::max((int)y - 1, 0) * m_realBufferSize.x ];
                    const SFVEC3F *ptrShaderY2 =
                            &m_shaderBuffer[ y * m_realBufferSize.x ];
                    const SFVEC3F *ptrShaderY3 =
                            &m_shaderBuffer[ glm::min((int)y + 1, (int)(m_realBufferSize.y - 1)) *
                                             m_realBufferSize.x ];
                    const SFVEC3F *ptrShaderY4 =
                            &m_shaderBuffer[ glm::min((int)y + 2, (int)(m_realBufferSize.y - 1)) *
                                             m_realBufferSize.x ];

                    for( signed int x = 0; x < (int)m_realBufferSize.x; ++x )
                    {
        // This #if should be 1, it is here that can be used for debug proposes during development
        #if 1
                        int idx = x > 1 ? -2 : 0;
                        SFVEC3F bluredShadeColor = ptrShaderY0[idx] * 1.0f / 273.0f +
                                                   ptrShaderY1[idx] * 4.0f / 273.0f +
                                                   ptrShaderY2[idx] * 7.0f / 273.0f +
                                                   ptrShaderY3[idx] * 4.0f / 273.0f +
                                                   ptrShaderY4[idx] * 1.0f / 273.0f;

                        idx = x > 0 ? -1 : 0;
                        bluredShadeColor += ptrShaderY0[idx] *  4.0f / 273.0f +
                                            ptrShaderY1[idx] * 16.0f / 273.0f +
                                            ptrShaderY2[idx] * 26.0f / 273.0f +
                                            ptrShaderY3[idx] * 16.0f / 273.0f +
                                            ptrShaderY4[idx] *  4.0f / 273.0f;

                        bluredShadeColor += (*ptrShaderY0) *  7.0f / 273.0f +
                                            (*ptrShaderY1) * 26.0f / 273.0f +
                                            (*ptrShaderY2) * 41.0f / 273.0f +
                                            (*ptrShaderY3) * 26.0f / 273.0f +
                                            (*ptrShaderY4) *  7.0f / 273.0f;

                        idx = (x < (int)m_realBufferSize.x - 1) ? 1 : 0;
                        bluredShadeColor += ptrShaderY0[idx] * 4.0f / 273.0f +
                                            ptrShaderY1[idx] *16.0f / 273.0f +
                                            ptrShaderY2[idx] *26.0f / 273.0f +
                                            ptrShaderY3[idx] *16.0f / 273.0f +
                                            ptrShaderY4[idx] * 4.0f / 273.0f;

                        idx = (x < (int)m_realBufferSize.x - 2) ? 2 : 0;
                        bluredShadeColor += ptrShaderY0[idx] * 1.0f / 273.0f +
                                            ptrShaderY1[idx] * 4.0f / 273.0f +
                                            ptrShaderY2[idx] * 7.0f / 273.0f +
                                            ptrShaderY3[idx] * 4.0f / 273.0f +
                                            ptrShaderY4[idx] * 1.0f / 273.0f;

                        // process next pixel
                        ++ptrShaderY0;
                        ++ptrShaderY1;
                        ++ptrShaderY2;
                        ++ptrShaderY3;
                        ++ptrShaderY4;

        #ifdef USE_SRGB_SPACE
                        const SFVEC3F originColor = convertLinearToSRGB( m_postshader_ssao.GetColorAtNotProtected( SFVEC2I( x,y ) ) );
        #else
                        const SFVEC3F originColor = m_postshader_ssao.GetColorAtNotProtected( SFVEC2I( x,y ) );
        #endif

                        const SFVEC3F shadedColor = m_postshader_ssao.ApplyShadeColor( SFVEC2I( x,y ), originColor, bluredShadeColor );
        #else
                        // Debug code
                        //const SFVEC3F shadedColor =  SFVEC3F( 1.0f ) -
                        //                             m_shaderBuffer[ y * m_realBufferSize.x + x];
                        const SFVEC3F shadedColor =  m_shaderBuffer[ y * m_realBufferSize.x + x ];
        #endif

                        rt_final_color( ptr, shadedColor, false );

                        ptr += 4;
                    }
                }

                threadsFinished++;
            } );

            t.detach();
        }

        while( threadsFinished < parallelThreadCount )
            std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );


        // Debug code
        //m_postshader_ssao.DebugBuffersOutputAsImages();
    }

    // End rendering
    m_rt_render_state = RT_RENDER_STATE_FINISH;
}


void C3D_RENDER_RAYTRACING::render_preview( GLubyte *ptrPBO )
{
    m_isPreview = true;

    std::atomic<size_t> nextBlock( 0 );
    std::atomic<size_t> threadsFinished( 0 );

    size_t parallelThreadCount = std::min<size_t>(
            std::max<size_t>( std::thread::hardware_concurrency(), 2 ),
            m_blockPositions.size() );
    for( size_t ii = 0; ii < parallelThreadCount; ++ii )
    {
        std::thread t = std::thread( [&]()
        {
            for( size_t iBlock = nextBlock.fetch_add( 1 );
                        iBlock < m_blockPositionsFast.size();
                        iBlock = nextBlock.fetch_add( 1 ) )
            {
                const SFVEC2UI &windowPosUI = m_blockPositionsFast[ iBlock ];
                const SFVEC2I windowsPos = SFVEC2I( windowPosUI.x + m_xoffset,
                                                    windowPosUI.y + m_yoffset );

                RAYPACKET blockPacket( m_settings.CameraGet(), windowsPos, 4 );

                HITINFO_PACKET hitPacket[RAYPACKET_RAYS_PER_PACKET];

                // Initialize hitPacket with a "not hit" information
                for( unsigned int i = 0; i < RAYPACKET_RAYS_PER_PACKET; ++i )
                {
                    hitPacket[i].m_HitInfo.m_tHit = std::numeric_limits<float>::infinity();
                    hitPacket[i].m_HitInfo.m_acc_node_info = 0;
                    hitPacket[i].m_hitresult = false;
                }

                //  Intersect packet block
                m_accelerator->Intersect( blockPacket, hitPacket );


                // Calculate background gradient color
                // /////////////////////////////////////////////////////////////////////
                SFVEC3F bgColor[RAYPACKET_DIM];

                for( unsigned int y = 0; y < RAYPACKET_DIM; ++y )
                {
                    const float posYfactor = (float)(windowsPos.y + y * 4.0f) / (float)m_windowSize.y;

                    bgColor[y] = (SFVEC3F)m_settings.m_BgColorTop * SFVEC3F(posYfactor) +
                                 (SFVEC3F)m_settings.m_BgColorBot * ( SFVEC3F(1.0f) - SFVEC3F(posYfactor) );
                }

                CCOLORRGB hitColorShading[RAYPACKET_RAYS_PER_PACKET];

                for( unsigned int i = 0; i < RAYPACKET_RAYS_PER_PACKET; ++i )
                {
                    const SFVEC3F bhColorY = bgColor[i / RAYPACKET_DIM];

                    if( hitPacket[i].m_hitresult == true )
                    {
                        const SFVEC3F hitColor = shadeHit( bhColorY,
                                                           blockPacket.m_ray[i],
                                                           hitPacket[i].m_HitInfo,
                                                           false,
                                                           0,
                                                           false );

                        hitColorShading[i] = CCOLORRGB( hitColor );
                    }
                    else
                        hitColorShading[i] = bhColorY;
                }

                CCOLORRGB cLRB_old[(RAYPACKET_DIM - 1)];

                for( unsigned int y = 0; y < (RAYPACKET_DIM - 1); ++y )
                {

                    const SFVEC3F     bgColorY = bgColor[y];
                    const CCOLORRGB   bgColorYRGB = CCOLORRGB( bgColorY );

                    // This stores cRTB from the last block to be reused next time in a cLTB pixel
                    CCOLORRGB cRTB_old;

                    //RAY       cRTB_ray;
                    //HITINFO   cRTB_hitInfo;

                    for( unsigned int x = 0; x < (RAYPACKET_DIM - 1); ++x )
                    {
                        //      pxl 0  pxl 1  pxl 2  pxl 3  pxl 4
                        //        x0                          x1  ...
                        //     .---------------------------.
                        // y0  | cLT  | cxxx | cLRT | cxxx | cRT  |
                        //     | cxxx | cLTC | cxxx | cRTC | cxxx |
                        //     | cLTB | cxxx | cC   | cxxx | cRTB |
                        //     | cxxx | cLBC | cxxx | cRBC | cxxx |
                        //     '---------------------------'
                        // y1  | cLB  | cxxx | cLRB | cxxx | cRB  |

                        const unsigned int iLT = ((x + 0) + RAYPACKET_DIM * (y + 0));
                        const unsigned int iRT = ((x + 1) + RAYPACKET_DIM * (y + 0));
                        const unsigned int iLB = ((x + 0) + RAYPACKET_DIM * (y + 1));
                        const unsigned int iRB = ((x + 1) + RAYPACKET_DIM * (y + 1));

                        // !TODO: skip when there are no hits


                        const CCOLORRGB &cLT = hitColorShading[ iLT ];
                        const CCOLORRGB &cRT = hitColorShading[ iRT ];
                        const CCOLORRGB &cLB = hitColorShading[ iLB ];
                        const CCOLORRGB &cRB = hitColorShading[ iRB ];

                        // Trace and shade cC
                        // /////////////////////////////////////////////////////////////
                        CCOLORRGB cC = bgColorYRGB;

                        const SFVEC3F &oriLT = blockPacket.m_ray[ iLT ].m_Origin;
                        const SFVEC3F &oriRB = blockPacket.m_ray[ iRB ].m_Origin;

                        const SFVEC3F &dirLT = blockPacket.m_ray[ iLT ].m_Dir;
                        const SFVEC3F &dirRB = blockPacket.m_ray[ iRB ].m_Dir;

                        SFVEC3F oriC;
                        SFVEC3F dirC;

                        HITINFO centerHitInfo;
                        centerHitInfo.m_tHit = std::numeric_limits<float>::infinity();

                        bool hittedC = false;

                        if( (hitPacket[ iLT ].m_hitresult == true) ||
                            (hitPacket[ iRT ].m_hitresult == true) ||
                            (hitPacket[ iLB ].m_hitresult == true) ||
                            (hitPacket[ iRB ].m_hitresult == true) )
                        {

                            oriC = ( oriLT + oriRB ) * 0.5f;
                            dirC = glm::normalize( ( dirLT + dirRB ) * 0.5f );

                            // Trace the center ray
                            RAY centerRay;
                            centerRay.Init( oriC, dirC );

                            const unsigned int nodeLT = hitPacket[ iLT ].m_HitInfo.m_acc_node_info;
                            const unsigned int nodeRT = hitPacket[ iRT ].m_HitInfo.m_acc_node_info;
                            const unsigned int nodeLB = hitPacket[ iLB ].m_HitInfo.m_acc_node_info;
                            const unsigned int nodeRB = hitPacket[ iRB ].m_HitInfo.m_acc_node_info;

                            if( nodeLT != 0 )
                                hittedC |= m_accelerator->Intersect( centerRay, centerHitInfo, nodeLT );

                            if( ( nodeRT != 0 ) &&
                                ( nodeRT != nodeLT ) )
                                hittedC |= m_accelerator->Intersect( centerRay, centerHitInfo, nodeRT );

                            if( ( nodeLB != 0 ) &&
                                ( nodeLB != nodeLT ) &&
                                ( nodeLB != nodeRT ) )
                                    hittedC |= m_accelerator->Intersect( centerRay, centerHitInfo, nodeLB );

                            if( ( nodeRB != 0 ) &&
                                ( nodeRB != nodeLB ) &&
                                ( nodeRB != nodeLT ) &&
                                ( nodeRB != nodeRT ) )
                                    hittedC |= m_accelerator->Intersect( centerRay, centerHitInfo, nodeRB );

                            if( hittedC )
                                cC = CCOLORRGB( shadeHit( bgColorY, centerRay, centerHitInfo, false, 0, false ) );
                            else
                            {
                                centerHitInfo.m_tHit = std::numeric_limits<float>::infinity();
                                hittedC = m_accelerator->Intersect( centerRay, centerHitInfo );

                                if( hittedC )
                                    cC = CCOLORRGB( shadeHit( bgColorY,
                                                              centerRay,
                                                              centerHitInfo,
                                                              false,
                                                              0,
                                                              false ) );
                            }
                        }

                        // Trace and shade cLRT
                        // /////////////////////////////////////////////////////////////
                        CCOLORRGB cLRT = bgColorYRGB;

                        const SFVEC3F &oriRT = blockPacket.m_ray[ iRT ].m_Origin;
                        const SFVEC3F &dirRT = blockPacket.m_ray[ iRT ].m_Dir;

                        if( y == 0 )
                        {
                            // Trace the center ray
                            RAY rayLRT;
                            rayLRT.Init( ( oriLT + oriRT ) * 0.5f,
                                            glm::normalize( ( dirLT + dirRT ) * 0.5f ) );

                            HITINFO hitInfoLRT;
                            hitInfoLRT.m_tHit = std::numeric_limits<float>::infinity();

                            if( hitPacket[ iLT ].m_hitresult &&
                                hitPacket[ iRT ].m_hitresult &&
                                (hitPacket[ iLT ].m_HitInfo.pHitObject == hitPacket[ iRT ].m_HitInfo.pHitObject) )
                            {
                                hitInfoLRT.pHitObject = hitPacket[ iLT ].m_HitInfo.pHitObject;
                                hitInfoLRT.m_tHit = ( hitPacket[ iLT ].m_HitInfo.m_tHit +
                                                      hitPacket[ iRT ].m_HitInfo.m_tHit ) * 0.5f;
                                hitInfoLRT.m_HitNormal =
                                        glm::normalize( ( hitPacket[ iLT ].m_HitInfo.m_HitNormal +
                                                          hitPacket[ iRT ].m_HitInfo.m_HitNormal ) * 0.5f );

                                cLRT = CCOLORRGB( shadeHit( bgColorY, rayLRT, hitInfoLRT, false, 0, false ) );
                                cLRT = BlendColor( cLRT, BlendColor( cLT, cRT) );
                            }
                            else
                            {
                                if( hitPacket[ iLT ].m_hitresult ||
                                    hitPacket[ iRT ].m_hitresult )                  // If any hits
                                {
                                    const unsigned int nodeLT = hitPacket[ iLT ].m_HitInfo.m_acc_node_info;
                                    const unsigned int nodeRT = hitPacket[ iRT ].m_HitInfo.m_acc_node_info;

                                    bool hittedLRT = false;

                                    if( nodeLT != 0 )
                                        hittedLRT |= m_accelerator->Intersect( rayLRT, hitInfoLRT, nodeLT );

                                    if( ( nodeRT != 0 ) &&
                                        ( nodeRT != nodeLT ) )
                                        hittedLRT |= m_accelerator->Intersect( rayLRT,
                                                                               hitInfoLRT,
                                                                               nodeRT );

                                    if( hittedLRT )
                                        cLRT = CCOLORRGB( shadeHit( bgColorY,
                                                                    rayLRT,
                                                                    hitInfoLRT,
                                                                    false,
                                                                    0,
                                                                    false ) );
                                    else
                                    {
                                        hitInfoLRT.m_tHit = std::numeric_limits<float>::infinity();

                                        if( m_accelerator->Intersect( rayLRT,hitInfoLRT ) )
                                            cLRT = CCOLORRGB( shadeHit( bgColorY,
                                                                        rayLRT,
                                                                        hitInfoLRT,
                                                                        false,
                                                                        0,
                                                                        false ) );
                                    }
                                }
                            }
                        }
                        else
                            cLRT = cLRB_old[x];


                        // Trace and shade cLTB
                        // /////////////////////////////////////////////////////////////
                        CCOLORRGB cLTB = bgColorYRGB;

                        if( x == 0 )
                        {
                            const SFVEC3F &oriLB = blockPacket.m_ray[ iLB ].m_Origin;
                            const SFVEC3F &dirLB = blockPacket.m_ray[ iLB ].m_Dir;

                            // Trace the center ray
                            RAY rayLTB;
                            rayLTB.Init( ( oriLT + oriLB ) * 0.5f,
                                            glm::normalize( ( dirLT + dirLB ) * 0.5f ) );

                            HITINFO hitInfoLTB;
                            hitInfoLTB.m_tHit = std::numeric_limits<float>::infinity();

                            if( hitPacket[ iLT ].m_hitresult &&
                                hitPacket[ iLB ].m_hitresult &&
                                ( hitPacket[ iLT ].m_HitInfo.pHitObject ==
                                  hitPacket[ iLB ].m_HitInfo.pHitObject ) )
                            {
                                hitInfoLTB.pHitObject = hitPacket[ iLT ].m_HitInfo.pHitObject;
                                hitInfoLTB.m_tHit = ( hitPacket[ iLT ].m_HitInfo.m_tHit +
                                                      hitPacket[ iLB ].m_HitInfo.m_tHit ) * 0.5f;
                                hitInfoLTB.m_HitNormal =
                                        glm::normalize( ( hitPacket[ iLT ].m_HitInfo.m_HitNormal +
                                                          hitPacket[ iLB ].m_HitInfo.m_HitNormal ) * 0.5f );
                                cLTB = CCOLORRGB( shadeHit( bgColorY, rayLTB, hitInfoLTB, false, 0, false ) );
                                cLTB = BlendColor( cLTB, BlendColor( cLT, cLB) );
                            }
                            else
                            {
                                if( hitPacket[ iLT ].m_hitresult ||
                                    hitPacket[ iLB ].m_hitresult )                  // If any hits
                                {
                                    const unsigned int nodeLT = hitPacket[ iLT ].m_HitInfo.m_acc_node_info;
                                    const unsigned int nodeLB = hitPacket[ iLB ].m_HitInfo.m_acc_node_info;

                                    bool hittedLTB = false;

                                    if( nodeLT != 0 )
                                        hittedLTB |= m_accelerator->Intersect( rayLTB,
                                                                               hitInfoLTB,
                                                                               nodeLT );

                                    if( ( nodeLB != 0 ) &&
                                        ( nodeLB != nodeLT ) )
                                        hittedLTB |= m_accelerator->Intersect( rayLTB,
                                                                               hitInfoLTB,
                                                                               nodeLB );

                                    if( hittedLTB )
                                        cLTB = CCOLORRGB( shadeHit( bgColorY,
                                                                    rayLTB,
                                                                    hitInfoLTB,
                                                                    false,
                                                                    0,
                                                                    false ) );
                                    else
                                    {
                                        hitInfoLTB.m_tHit = std::numeric_limits<float>::infinity();

                                        if( m_accelerator->Intersect( rayLTB, hitInfoLTB ) )
                                            cLTB = CCOLORRGB( shadeHit( bgColorY,
                                                                        rayLTB,
                                                                        hitInfoLTB,
                                                                        false,
                                                                        0,
                                                                        false ) );
                                    }
                                }
                            }
                        }
                        else
                            cLTB = cRTB_old;


                        // Trace and shade cRTB
                        // /////////////////////////////////////////////////////////////
                        CCOLORRGB cRTB = bgColorYRGB;

                        // Trace the center ray
                        RAY rayRTB;
                        rayRTB.Init( ( oriRT + oriRB ) * 0.5f,
                                        glm::normalize( ( dirRT + dirRB ) * 0.5f ) );

                        HITINFO hitInfoRTB;
                        hitInfoRTB.m_tHit = std::numeric_limits<float>::infinity();

                        if( hitPacket[ iRT ].m_hitresult &&
                            hitPacket[ iRB ].m_hitresult &&
                            ( hitPacket[ iRT ].m_HitInfo.pHitObject ==
                              hitPacket[ iRB ].m_HitInfo.pHitObject ) )
                        {
                            hitInfoRTB.pHitObject = hitPacket[ iRT ].m_HitInfo.pHitObject;

                            hitInfoRTB.m_tHit = ( hitPacket[ iRT ].m_HitInfo.m_tHit +
                                                  hitPacket[ iRB ].m_HitInfo.m_tHit ) * 0.5f;

                            hitInfoRTB.m_HitNormal =
                                    glm::normalize( ( hitPacket[ iRT ].m_HitInfo.m_HitNormal +
                                                      hitPacket[ iRB ].m_HitInfo.m_HitNormal ) * 0.5f );

                            cRTB = CCOLORRGB( shadeHit( bgColorY, rayRTB, hitInfoRTB, false, 0, false ) );
                            cRTB = BlendColor( cRTB, BlendColor( cRT, cRB) );
                        }
                        else
                        {
                            if( hitPacket[ iRT ].m_hitresult ||
                                hitPacket[ iRB ].m_hitresult )                  // If any hits
                            {
                                const unsigned int nodeRT = hitPacket[ iRT ].m_HitInfo.m_acc_node_info;
                                const unsigned int nodeRB = hitPacket[ iRB ].m_HitInfo.m_acc_node_info;

                                bool hittedRTB = false;

                                if( nodeRT != 0 )
                                    hittedRTB |= m_accelerator->Intersect( rayRTB, hitInfoRTB, nodeRT );

                                if( ( nodeRB != 0 ) &&
                                    ( nodeRB != nodeRT ) )
                                    hittedRTB |= m_accelerator->Intersect( rayRTB, hitInfoRTB, nodeRB );

                                if( hittedRTB )
                                    cRTB = CCOLORRGB( shadeHit( bgColorY,
                                                                rayRTB,
                                                                hitInfoRTB,
                                                                false,
                                                                0,
                                                                false) );
                                else
                                {
                                    hitInfoRTB.m_tHit = std::numeric_limits<float>::infinity();

                                    if( m_accelerator->Intersect( rayRTB, hitInfoRTB ) )
                                        cRTB = CCOLORRGB( shadeHit( bgColorY,
                                                                    rayRTB,
                                                                    hitInfoRTB,
                                                                    false,
                                                                    0,
                                                                    false ) );
                                }
                            }
                        }

                        cRTB_old = cRTB;


                        // Trace and shade cLRB
                        // /////////////////////////////////////////////////////////////
                        CCOLORRGB cLRB = bgColorYRGB;

                        const SFVEC3F &oriLB = blockPacket.m_ray[ iLB ].m_Origin;
                        const SFVEC3F &dirLB = blockPacket.m_ray[ iLB ].m_Dir;

                        // Trace the center ray
                        RAY rayLRB;
                        rayLRB.Init( ( oriLB + oriRB ) * 0.5f,
                                        glm::normalize( ( dirLB + dirRB ) * 0.5f ) );

                        HITINFO hitInfoLRB;
                        hitInfoLRB.m_tHit = std::numeric_limits<float>::infinity();

                        if( hitPacket[ iLB ].m_hitresult &&
                            hitPacket[ iRB ].m_hitresult &&
                            ( hitPacket[ iLB ].m_HitInfo.pHitObject ==
                              hitPacket[ iRB ].m_HitInfo.pHitObject ) )
                        {
                            hitInfoLRB.pHitObject = hitPacket[ iLB ].m_HitInfo.pHitObject;

                            hitInfoLRB.m_tHit = ( hitPacket[ iLB ].m_HitInfo.m_tHit +
                                                  hitPacket[ iRB ].m_HitInfo.m_tHit ) * 0.5f;

                            hitInfoLRB.m_HitNormal =
                                    glm::normalize( ( hitPacket[ iLB ].m_HitInfo.m_HitNormal +
                                                      hitPacket[ iRB ].m_HitInfo.m_HitNormal ) * 0.5f );

                            cLRB = CCOLORRGB( shadeHit( bgColorY, rayLRB, hitInfoLRB, false, 0, false ) );
                            cLRB = BlendColor( cLRB, BlendColor( cLB, cRB) );
                        }
                        else
                        {
                            if( hitPacket[ iLB ].m_hitresult ||
                                hitPacket[ iRB ].m_hitresult )                  // If any hits
                            {
                                const unsigned int nodeLB = hitPacket[ iLB ].m_HitInfo.m_acc_node_info;
                                const unsigned int nodeRB = hitPacket[ iRB ].m_HitInfo.m_acc_node_info;

                                bool hittedLRB = false;

                                if( nodeLB != 0 )
                                    hittedLRB |= m_accelerator->Intersect( rayLRB, hitInfoLRB, nodeLB );

                                if( ( nodeRB != 0 ) &&
                                    ( nodeRB != nodeLB ) )
                                    hittedLRB |= m_accelerator->Intersect( rayLRB, hitInfoLRB, nodeRB );

                                if( hittedLRB )
                                    cLRB = CCOLORRGB( shadeHit( bgColorY, rayLRB, hitInfoLRB, false, 0, false ) );
                                else
                                {
                                    hitInfoLRB.m_tHit = std::numeric_limits<float>::infinity();

                                    if( m_accelerator->Intersect( rayLRB, hitInfoLRB ) )
                                        cLRB = CCOLORRGB( shadeHit( bgColorY,
                                                                    rayLRB,
                                                                    hitInfoLRB,
                                                                    false,
                                                                    0,
                                                                    false ) );
                                }
                            }
                        }

                        cLRB_old[x] = cLRB;


                        // Trace and shade cLTC
                        // /////////////////////////////////////////////////////////////
                        CCOLORRGB cLTC = BlendColor( cLT , cC );

                        if( hitPacket[ iLT ].m_hitresult || hittedC )
                        {
                            // Trace the center ray
                            RAY rayLTC;
                            rayLTC.Init( ( oriLT + oriC ) * 0.5f,
                                         glm::normalize( ( dirLT + dirC ) * 0.5f ) );

                            HITINFO hitInfoLTC;
                            hitInfoLTC.m_tHit = std::numeric_limits<float>::infinity();

                            bool hitted = false;

                            if( hittedC )
                                hitted = centerHitInfo.pHitObject->Intersect( rayLTC, hitInfoLTC );
                            else
                                if( hitPacket[ iLT ].m_hitresult )
                                    hitted = hitPacket[ iLT ].m_HitInfo.pHitObject->Intersect( rayLTC,
                                                                                               hitInfoLTC );

                            if( hitted )
                                cLTC = CCOLORRGB( shadeHit( bgColorY, rayLTC, hitInfoLTC, false, 0, false ) );
                        }


                        // Trace and shade cRTC
                        // /////////////////////////////////////////////////////////////
                        CCOLORRGB cRTC = BlendColor( cRT , cC );

                        if( hitPacket[ iRT ].m_hitresult || hittedC )
                        {
                            // Trace the center ray
                            RAY rayRTC;
                            rayRTC.Init( ( oriRT + oriC ) * 0.5f,
                                         glm::normalize( ( dirRT + dirC ) * 0.5f ) );

                            HITINFO hitInfoRTC;
                            hitInfoRTC.m_tHit = std::numeric_limits<float>::infinity();

                            bool hitted = false;

                            if( hittedC )
                                hitted = centerHitInfo.pHitObject->Intersect( rayRTC, hitInfoRTC );
                            else
                                if( hitPacket[ iRT ].m_hitresult )
                                    hitted = hitPacket[ iRT ].m_HitInfo.pHitObject->Intersect( rayRTC,
                                                                                               hitInfoRTC );

                            if( hitted )
                                cRTC = CCOLORRGB( shadeHit( bgColorY, rayRTC, hitInfoRTC, false, 0, false ) );
                        }


                        // Trace and shade cLBC
                        // /////////////////////////////////////////////////////////////
                        CCOLORRGB cLBC = BlendColor( cLB , cC );

                        if( hitPacket[ iLB ].m_hitresult || hittedC )
                        {
                            // Trace the center ray
                            RAY rayLBC;
                            rayLBC.Init( ( oriLB + oriC ) * 0.5f,
                                         glm::normalize( ( dirLB + dirC ) * 0.5f ) );

                            HITINFO hitInfoLBC;
                            hitInfoLBC.m_tHit = std::numeric_limits<float>::infinity();

                            bool hitted = false;

                            if( hittedC )
                                hitted = centerHitInfo.pHitObject->Intersect( rayLBC, hitInfoLBC );
                            else
                                if( hitPacket[ iLB ].m_hitresult )
                                    hitted = hitPacket[ iLB ].m_HitInfo.pHitObject->Intersect( rayLBC,
                                                                                               hitInfoLBC );

                            if( hitted )
                                cLBC = CCOLORRGB( shadeHit( bgColorY, rayLBC, hitInfoLBC, false, 0, false ) );
                        }


                        // Trace and shade cRBC
                        // /////////////////////////////////////////////////////////////
                        CCOLORRGB cRBC = BlendColor( cRB , cC );

                        if( hitPacket[ iRB ].m_hitresult || hittedC )
                        {
                            // Trace the center ray
                            RAY rayRBC;
                            rayRBC.Init( ( oriRB + oriC ) * 0.5f,
                                         glm::normalize( ( dirRB + dirC ) * 0.5f ) );

                            HITINFO hitInfoRBC;
                            hitInfoRBC.m_tHit = std::numeric_limits<float>::infinity();

                            bool hitted = false;

                            if( hittedC )
                                hitted = centerHitInfo.pHitObject->Intersect( rayRBC, hitInfoRBC );
                            else
                                if( hitPacket[ iRB ].m_hitresult )
                                    hitted = hitPacket[ iRB ].m_HitInfo.pHitObject->Intersect( rayRBC,
                                                                                               hitInfoRBC );

                            if( hitted )
                                cRBC = CCOLORRGB( shadeHit( bgColorY, rayRBC, hitInfoRBC, false, 0, false ) );
                        }


                        // Set pixel colors
                        // /////////////////////////////////////////////////////////////

                        GLubyte *ptr = &ptrPBO[ (4 * x + m_blockPositionsFast[iBlock].x +
                                                 m_realBufferSize.x *
                                                 (m_blockPositionsFast[iBlock].y + 4 * y)) * 4 ];
                        SetPixel( ptr +  0, cLT );
                        SetPixel( ptr +  4, BlendColor( cLT, cLRT, cLTC ) );
                        SetPixel( ptr +  8, cLRT );
                        SetPixel( ptr + 12, BlendColor( cLRT, cRT, cRTC ) );

                        ptr += m_realBufferSize.x * 4;
                        SetPixel( ptr +  0, BlendColor( cLT , cLTB, cLTC ) );
                        SetPixel( ptr +  4, BlendColor( cLTC, BlendColor( cLT , cC ) ) );
                        SetPixel( ptr +  8, BlendColor( cC, BlendColor( cLRT, cLTC, cRTC ) ) );
                        SetPixel( ptr + 12, BlendColor( cRTC, BlendColor( cRT , cC ) ) );

                        ptr += m_realBufferSize.x * 4;
                        SetPixel( ptr +  0, cLTB );
                        SetPixel( ptr +  4, BlendColor( cC, BlendColor( cLTB, cLTC, cLBC ) ) );
                        SetPixel( ptr +  8, cC );
                        SetPixel( ptr + 12, BlendColor( cC, BlendColor( cRTB, cRTC, cRBC ) ) );

                        ptr += m_realBufferSize.x * 4;
                        SetPixel( ptr +  0, BlendColor( cLB , cLTB, cLBC ) );
                        SetPixel( ptr +  4, BlendColor( cLBC, BlendColor( cLB , cC ) ) );
                        SetPixel( ptr +  8, BlendColor( cC, BlendColor( cLRB, cLBC, cRBC ) ) );
                        SetPixel( ptr + 12, BlendColor( cRBC, BlendColor( cRB , cC ) ) );
                    }
                }
            }

            threadsFinished++;
        } );

        t.detach();
    }

    while( threadsFinished < parallelThreadCount )
        std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
}


#define USE_EXPERIMENTAL_SOFT_SHADOWS 1

SFVEC3F C3D_RENDER_RAYTRACING::shadeHit( const SFVEC3F &aBgColor,
                                         const RAY &aRay,
                                         HITINFO &aHitInfo,
                                         bool aIsInsideObject,
                                         unsigned int aRecursiveLevel,
                                         bool is_testShadow ) const
{
    if( aRecursiveLevel > 2 )
        return SFVEC3F( 0.0f );

    SFVEC3F hitPoint = aHitInfo.m_HitPoint;

    if( !m_isPreview )
        hitPoint += aHitInfo.m_HitNormal * m_settings.GetNonCopperLayerThickness3DU() * 1.0f;

    const CMATERIAL *objMaterial = aHitInfo.pHitObject->GetMaterial();
    wxASSERT( objMaterial != NULL );

    const SFVEC3F diffuseColorObj = aHitInfo.pHitObject->GetDiffuseColor( aHitInfo );

    SFVEC3F outColor = objMaterial->GetEmissiveColor();

    const LIST_LIGHT &lightList = m_lights.GetList();

#if USE_EXPERIMENTAL_SOFT_SHADOWS
    const bool is_aa_enabled = m_settings.GetFlag( FL_RENDER_RAYTRACING_ANTI_ALIASING ) &&
                               (!m_isPreview);
#endif

    float shadow_att_factor_sum = 0.0f;

    unsigned int nr_lights_that_can_cast_shadows = 0;

    for( LIST_LIGHT::const_iterator ii = lightList.begin();
         ii != lightList.end();
         ++ii )
    {
        const CLIGHT *light = (CLIGHT *)*ii;

        SFVEC3F vectorToLight;
        SFVEC3F colorOfLight;
        float   distToLight;

        light->GetLightParameters( hitPoint, vectorToLight, colorOfLight, distToLight );

        if( m_isPreview )
            colorOfLight = SFVEC3F( 1.0f );

        /*
        if( (!m_isPreview) &&
            // Little hack to make randomness to the shading and shadows
            m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
            vectorToLight = glm::normalize( vectorToLight +
                                            UniformRandomHemisphereDirection() * 0.1f );
         */

        const float NdotL = glm::dot( aHitInfo.m_HitNormal, vectorToLight );

        // Only calc shade if the normal is facing the direction of light,
        // otherwise it is in the shadow
        if( NdotL >= FLT_EPSILON )
        {
            float shadow_att_factor_light = 1.0f;

            if( is_testShadow && light->GetCastShadows() )
            {
                nr_lights_that_can_cast_shadows++;
#if USE_EXPERIMENTAL_SOFT_SHADOWS
                if( (!is_aa_enabled) ||

                    // For rays that are recursive, just calculate one hit shadow
                    (aRecursiveLevel > 0) ||

                    // Only use soft shadows if using post processing
                    (!m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
                  )
                {
#endif
                    RAY rayToLight;
                    rayToLight.Init( hitPoint, vectorToLight );

                    // Test if point is not in the shadow.
                    // Test for any hit from the point in the direction of light
                    if( m_accelerator->IntersectP( rayToLight, distToLight ) )
                        shadow_att_factor_light = 0.0f;

#if USE_EXPERIMENTAL_SOFT_SHADOWS
                }

                // Experimental softshadow calculation
                else
                {

                    const unsigned int shadow_number_of_samples = 3;
                    const float shadow_inc_factor = 1.0f / (float)(shadow_number_of_samples);

                    for( unsigned int i = 0; i < shadow_number_of_samples; ++i )
                    {
                        const SFVEC3F unifVector = UniformRandomHemisphereDirection();
                        const SFVEC3F disturbed_vector_to_light = glm::normalize( vectorToLight +
                                                                                  unifVector *
                                                                                  0.05f );

                        RAY rayToLight;
                        rayToLight.Init( hitPoint, disturbed_vector_to_light );

                        // !TODO: there are multiple ways that this tests can be
                        // optimized. Eg: by packing rays or to test against the
                        // latest hit object.

                        if( m_accelerator->IntersectP( rayToLight, distToLight ) )
                        {
                            shadow_att_factor_light -= shadow_inc_factor;
                        }
                    }
                }
#endif
                shadow_att_factor_sum += shadow_att_factor_light;
            }

            if( !m_settings.GetFlag( FL_RENDER_RAYTRACING_POST_PROCESSING ) )
            {
                outColor += objMaterial->Shade( aRay,
                                                aHitInfo,
                                                NdotL,
                                                diffuseColorObj,
                                                vectorToLight,
                                                colorOfLight,
                                                shadow_att_factor_light );
            }
            else
            {
                // This is a render hack in order to compensate for the lack of
                // ambient and too much darkness when using post process shader
                // It will calculate as it was not in shadow
                outColor += objMaterial->Shade( aRay,
                                                aHitInfo,
                                                NdotL,
                                                diffuseColorObj,
                                                vectorToLight,
                                                colorOfLight,
                                                // The sampled point will be darkshaded by the post
                                                // processing, so here it compensates to not shadow
                                                // so much
                                                glm::min( shadow_att_factor_light + (3.0f / 6.0f), 1.0f )
                                                );
            }
        }
        else
        {
            outColor += objMaterial->GetAmbientColor();
        }

        // Only use the headlight for preview
        if( m_isPreview )
            break;
    }

    // Improvement: this is not taking in account the lightcolor
    if( nr_lights_that_can_cast_shadows > 0 )
    {
        aHitInfo.m_ShadowFactor = glm::max( shadow_att_factor_sum /
                                  (float)(nr_lights_that_can_cast_shadows * 1.0f), 0.0f );
    }
    else
    {
        aHitInfo.m_ShadowFactor = 1.0f;
    }

    // Clamp color to not be brighter than 1.0f
    outColor = glm::min( outColor, SFVEC3F( 1.0f ) );

    if( !m_isPreview )
    {
        // Reflections
        // /////////////////////////////////////////////////////////////////////

        if( !aIsInsideObject &&
            (objMaterial->GetReflection() > 0.0f) &&
            m_settings.GetFlag( FL_RENDER_RAYTRACING_REFLECTIONS ) )
        {
            const unsigned int reflection_number_of_samples = objMaterial->GetNrReflectionsSamples();

            SFVEC3F sum_color = SFVEC3F(0.0f);

            const SFVEC3F reflectVector = aRay.m_Dir -
                                    2.0f * glm::dot( aRay.m_Dir, aHitInfo.m_HitNormal ) *
                                    aHitInfo.m_HitNormal;

            for( unsigned int i = 0; i < reflection_number_of_samples; ++i )
            {
                // Apply some randomize to the reflected vector
                 const SFVEC3F random_reflectVector =
                         glm::normalize( reflectVector +
                                         UniformRandomHemisphereDirection() *
                                         0.025f );

                RAY reflectedRay;
                reflectedRay.Init( hitPoint, random_reflectVector );

                HITINFO reflectedHit;
                reflectedHit.m_tHit = std::numeric_limits<float>::infinity();

                if( m_accelerator->Intersect( reflectedRay, reflectedHit ) )
                {
                    sum_color += ( diffuseColorObj + objMaterial->GetSpecularColor() ) *
                                 shadeHit( aBgColor,
                                           reflectedRay,
                                           reflectedHit,
                                           false,
                                           aRecursiveLevel + 1,
                                           is_testShadow ) *
                                 SFVEC3F( objMaterial->GetReflection() *
                                          // Falloff factor
                                          (1.0f / ( 1.0f + 0.75f * reflectedHit.m_tHit *
                                                    reflectedHit.m_tHit) ) );
                }
            }

            outColor += (sum_color / SFVEC3F( (float)reflection_number_of_samples) );
        }


        // Refractions
        // /////////////////////////////////////////////////////////////////////

        if( (objMaterial->GetTransparency() > 0.0f) &&
            m_settings.GetFlag( FL_RENDER_RAYTRACING_REFRACTIONS ) )
        {
            const float airIndex = 1.000293f;
            const float glassIndex = 1.49f;
            const float air_over_glass = airIndex / glassIndex;
            const float glass_over_air = glassIndex / airIndex;

            const float refractionRatio = aIsInsideObject?glass_over_air:air_over_glass;

            SFVEC3F refractedVector;

            if( Refract( aRay.m_Dir,
                         aHitInfo.m_HitNormal,
                         refractionRatio,
                         refractedVector ) )
            {
                const float objTransparency = objMaterial->GetTransparency();

                // This increase the start point by a "fixed" factor so it will work the
                // same for all distances
                const SFVEC3F startPoint = aRay.at( NextFloatUp(
                                                    NextFloatUp(
                                                    NextFloatUp( aHitInfo.m_tHit ) ) ) );

                const unsigned int refractions_number_of_samples = objMaterial->GetNrRefractionsSamples();

                SFVEC3F sum_color = SFVEC3F(0.0f);

                for( unsigned int i = 0; i < refractions_number_of_samples; ++i )
                {
                    RAY refractedRay;

                    if( refractions_number_of_samples > 1 )
                    {
                        // apply some randomize to the refracted vector
                        const SFVEC3F randomizeRefractedVector = glm::normalize( refractedVector +
                                                                                 UniformRandomHemisphereDirection() *
                                                                                 0.15f *
                                                                                 (1.0f - objTransparency) );

                        refractedRay.Init( startPoint, randomizeRefractedVector );
                    }
                    else
                    {
                        refractedRay.Init( startPoint, refractedVector );
                    }

                    HITINFO refractedHit;
                    refractedHit.m_tHit = std::numeric_limits<float>::infinity();

                    SFVEC3F refractedColor = objMaterial->GetAmbientColor();

                    if( m_accelerator->Intersect( refractedRay, refractedHit ) )
                    {
                        refractedColor = shadeHit( aBgColor,
                                                   refractedRay,
                                                   refractedHit,
                                                   true,
                                                   aRecursiveLevel + 1,
                                                   false );

                        const SFVEC3F absorbance = ( SFVEC3F(1.0f) - diffuseColorObj ) *
                                                   (1.0f - objTransparency ) *
                                                   objMaterial->GetAbsorvance() *   // Adjust falloff factor
                                                   -refractedHit.m_tHit;

                        const SFVEC3F transparency = SFVEC3F( expf( absorbance.r ),
                                                              expf( absorbance.g ),
                                                              expf( absorbance.b ) );

                        sum_color += refractedColor * transparency * objTransparency;
                    }
                    else
                    {
                        sum_color += refractedColor * objTransparency;
                    }
                }

                outColor = outColor * (1.0f - objTransparency) +
                           (sum_color / SFVEC3F( (float)refractions_number_of_samples) );
            }
        }
    }

   //outColor += glm::max( -glm::dot( aHitInfo.m_HitNormal, aRay.m_Dir ), 0.0f ) *
   //            objMaterial->GetAmbientColor();

    return outColor;
}


void C3D_RENDER_RAYTRACING::initializeNewWindowSize()
{
    opengl_init_pbo();
}


void C3D_RENDER_RAYTRACING::opengl_init_pbo()
{
    if( GLEW_ARB_pixel_buffer_object )
    {
        m_opengl_support_vertex_buffer_object = true;

        // Try to delete vbo if it was already initialized
        opengl_delete_pbo();

        // Learn about Pixel buffer objects at:
        // http://www.songho.ca/opengl/gl_pbo.html
        // http://web.eecs.umich.edu/~sugih/courses/eecs487/lectures/25-PBO+Mipmapping.pdf
        // "create 2 pixel buffer objects, you need to delete them when program exits.
        // glBufferDataARB with NULL pointer reserves only memory space."

        // This sets the number of RGBA pixels
        m_pboDataSize =  m_realBufferSize.x * m_realBufferSize.y * 4;

        glGenBuffersARB( 1, &m_pboId );
        glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, m_pboId );
        glBufferDataARB( GL_PIXEL_UNPACK_BUFFER_ARB, m_pboDataSize, 0, GL_STREAM_DRAW_ARB );
        glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, 0 );

        wxLogTrace( m_logTrace,
                    wxT( "C3D_RENDER_RAYTRACING:: GLEW_ARB_pixel_buffer_object is supported" ) );
    }
}


bool C3D_RENDER_RAYTRACING::initializeOpenGL()
{
    m_is_opengl_initialized = true;

    return true;
}


static float distance( const SFVEC2UI& a, const SFVEC2UI& b )
{
    const float dx = (float) a.x - (float) b.x;
    const float dy = (float) a.y - (float) b.y;
    return hypotf( dx, dy );
}

void C3D_RENDER_RAYTRACING::initialize_block_positions()
{

    m_realBufferSize = SFVEC2UI( 0 );

    // Calc block positions for fast preview mode
    // /////////////////////////////////////////////////////////////////////
    m_blockPositionsFast.clear();

    unsigned int i = 0;

    while(1)
    {
        const unsigned int mX = DecodeMorton2X(i);
        const unsigned int mY = DecodeMorton2Y(i);

        i++;

        const SFVEC2UI blockPos( mX * 4 * RAYPACKET_DIM - mX * 4,
                                 mY * 4 * RAYPACKET_DIM - mY * 4);

        if( ( blockPos.x >= ( (unsigned int)m_windowSize.x - ( 4 * RAYPACKET_DIM + 4 ) ) ) &&
            ( blockPos.y >= ( (unsigned int)m_windowSize.y - ( 4 * RAYPACKET_DIM + 4 ) ) ) )
            break;

        if( ( blockPos.x < ( (unsigned int)m_windowSize.x - ( 4 * RAYPACKET_DIM + 4) ) ) &&
            ( blockPos.y < ( (unsigned int)m_windowSize.y - ( 4 * RAYPACKET_DIM + 4) ) ) )
        {
            m_blockPositionsFast.push_back( blockPos );

            if( blockPos.x > m_realBufferSize.x )
                m_realBufferSize.x = blockPos.x;

            if( blockPos.y > m_realBufferSize.y )
                m_realBufferSize.y = blockPos.y;
        }
    }

    m_fastPreviewModeSize = m_realBufferSize;

    m_realBufferSize.x = ((m_realBufferSize.x + RAYPACKET_DIM * 4) & RAYPACKET_INVMASK);
    m_realBufferSize.y = ((m_realBufferSize.y + RAYPACKET_DIM * 4) & RAYPACKET_INVMASK);

    m_xoffset = (m_windowSize.x - m_realBufferSize.x) / 2;
    m_yoffset = (m_windowSize.y - m_realBufferSize.y) / 2;

    m_postshader_ssao.UpdateSize( m_realBufferSize );


    // Calc block positions for regular rendering. Choose an 'inside out'
    // style of rendering
    // /////////////////////////////////////////////////////////////////////
    m_blockPositions.clear();
    const int blocks_x = m_realBufferSize.x / RAYPACKET_DIM;
    const int blocks_y = m_realBufferSize.y / RAYPACKET_DIM;
    m_blockPositions.reserve( blocks_x * blocks_y );

    for( int x = 0; x < blocks_x; ++x )
        for( int y = 0; y < blocks_y; ++y )
            m_blockPositions.push_back( SFVEC2UI( x * RAYPACKET_DIM, y * RAYPACKET_DIM ) );

    const SFVEC2UI center( m_realBufferSize.x / 2, m_realBufferSize.y / 2 );
    std::sort( m_blockPositions.begin(), m_blockPositions.end(),
            [&]( const SFVEC2UI& a, const SFVEC2UI& b ) {
                // Sort order: inside out.
                return distance( a, center ) < distance( b, center );
            } );

    // Create m_shader buffer
    delete[] m_shaderBuffer;
    m_shaderBuffer = new SFVEC3F[m_realBufferSize.x * m_realBufferSize.y];

    opengl_init_pbo();
}
