/*
 * 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-2021 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
 */

#include "layer_triangles.h"
#include "../raytracing/ray.h"
#include <wx/debug.h>   // For the wxASSERT
#include <mutex>


TRIANGLE_LIST::TRIANGLE_LIST( unsigned int aNrReservedTriangles, bool aReserveNormals )
{
    wxASSERT( aNrReservedTriangles > 0 );

    m_vertexs.clear();
    m_normals.clear();

    m_vertexs.reserve( aNrReservedTriangles * 3 );

    if( aReserveNormals )
        m_normals.reserve( aNrReservedTriangles * 3 );
}


void TRIANGLE_LIST::Reserve_More( unsigned int aNrReservedTriangles, bool aReserveNormals )
{
    m_vertexs.reserve( m_vertexs.size() + aNrReservedTriangles * 3 );

    if( aReserveNormals )
        m_normals.reserve( m_normals.size() + aNrReservedTriangles * 3 );
}


void TRIANGLE_LIST::AddQuad( const SFVEC3F& aV1, const SFVEC3F& aV2, const SFVEC3F& aV3,
                             const SFVEC3F& aV4 )
{
    m_vertexs.push_back( aV1 );
    m_vertexs.push_back( aV2 );
    m_vertexs.push_back( aV3 );

    m_vertexs.push_back( aV3 );
    m_vertexs.push_back( aV4 );
    m_vertexs.push_back( aV1 );
}


void TRIANGLE_LIST::AddTriangle( const SFVEC3F& aV1, const SFVEC3F& aV2, const SFVEC3F& aV3 )
{
    m_vertexs.push_back( aV1 );
    m_vertexs.push_back( aV2 );
    m_vertexs.push_back( aV3 );
}


void TRIANGLE_LIST::AddNormal( const SFVEC3F& aN1, const SFVEC3F& aN2, const SFVEC3F& aN3 )
{
    m_normals.push_back( aN1 );
    m_normals.push_back( aN2 );
    m_normals.push_back( aN3 );
}

void TRIANGLE_LIST::AddNormal( const SFVEC3F& aN1, const SFVEC3F& aN2, const SFVEC3F& aN3,
                               const SFVEC3F& aN4 )
{
    m_normals.push_back( aN1 );
    m_normals.push_back( aN2 );
    m_normals.push_back( aN3 );

    m_normals.push_back( aN3 );
    m_normals.push_back( aN4 );
    m_normals.push_back( aN1 );
}


TRIANGLE_DISPLAY_LIST::TRIANGLE_DISPLAY_LIST( unsigned int aNrReservedTriangles )
{
    wxASSERT( aNrReservedTriangles > 0 );

    m_layer_top_segment_ends        = new TRIANGLE_LIST( aNrReservedTriangles, false );
    m_layer_top_triangles           = new TRIANGLE_LIST( aNrReservedTriangles, false );
    m_layer_middle_contourns_quads  = new TRIANGLE_LIST( aNrReservedTriangles, true  );
    m_layer_bot_triangles           = new TRIANGLE_LIST( aNrReservedTriangles, false );
    m_layer_bot_segment_ends        = new TRIANGLE_LIST( aNrReservedTriangles, false );
}


TRIANGLE_DISPLAY_LIST::~TRIANGLE_DISPLAY_LIST()
{
    delete m_layer_top_segment_ends;
    m_layer_top_segment_ends = nullptr;

    delete m_layer_top_triangles;
    m_layer_top_triangles = nullptr;

    delete m_layer_middle_contourns_quads;
    m_layer_middle_contourns_quads = nullptr;

    delete m_layer_bot_triangles;
    m_layer_bot_triangles = nullptr;

    delete m_layer_bot_segment_ends;
    m_layer_bot_segment_ends = nullptr;
}


void TRIANGLE_DISPLAY_LIST::AddToMiddleContourns( const std::vector< SFVEC2F >& aContournPoints,
                                                  float zBot, float zTop, bool aInvertFaceDirection,
                                                  const BVH_CONTAINER_2D* aThroughHoles )
{
    if( aContournPoints.size() >= 4 )
    {
        // Calculate normals of each segment of the contourn
        std::vector< SFVEC2F > contournNormals;

        contournNormals.clear();
        contournNormals.resize( aContournPoints.size() - 1 );

        if( aInvertFaceDirection )
        {
            for( unsigned int i = 0; i < ( aContournPoints.size() - 1 ); ++i )
            {
                const SFVEC2F& v0 = aContournPoints[i + 0];
                const SFVEC2F& v1 = aContournPoints[i + 1];
                const SFVEC2F n = glm::normalize( v1 - v0 );

                contournNormals[i] = SFVEC2F( n.y,-n.x );
            }
        }
        else
        {
            for( unsigned int i = 0; i < ( aContournPoints.size() - 1 ); ++i )
            {
                const SFVEC2F& v0 = aContournPoints[i + 0];
                const SFVEC2F& v1 = aContournPoints[i + 1];
                const SFVEC2F n = glm::normalize( v1 - v0 );

                contournNormals[i] = SFVEC2F( -n.y, n.x );
            }
        }


        if( aInvertFaceDirection )
            std::swap( zBot, zTop );

        const unsigned int nContournsToProcess = ( aContournPoints.size() - 1 );

        for( unsigned int i = 0; i < nContournsToProcess; ++i )
        {
            SFVEC2F lastNormal;

            if( i > 0 )
                lastNormal = contournNormals[i - 1];
            else
                lastNormal = contournNormals[nContournsToProcess - 1];

            SFVEC2F n0 = contournNormals[i];

            // Only interpolate the normal if the angle is closer
            if( glm::dot( n0, lastNormal ) > 0.5f )
                n0 = glm::normalize( n0 + lastNormal );

            SFVEC2F nextNormal;

            if( i < (nContournsToProcess - 1) )
                nextNormal = contournNormals[i + 1];
            else
                nextNormal = contournNormals[0];

            SFVEC2F n1 = contournNormals[i];

            if( glm::dot( n1, nextNormal ) > 0.5f )
                n1 = glm::normalize( n1 + nextNormal );

            const SFVEC3F n3d0 = SFVEC3F( n0.x, n0.y, 0.0f );
            const SFVEC3F n3d1 = SFVEC3F( n1.x, n1.y, 0.0f );

            const SFVEC2F& v0 = aContournPoints[i + 0];
            const SFVEC2F& v1 = aContournPoints[i + 1];

            if( aThroughHoles && aThroughHoles->IntersectAny( RAYSEG2D( v0, v1 ) ) )
            {
                continue;
            }
            else
            {
                std::lock_guard<std::mutex> lock( m_middle_layer_lock );
                m_layer_middle_contourns_quads->AddQuad( SFVEC3F( v0.x, v0.y, zTop ),
                                                         SFVEC3F( v1.x, v1.y, zTop ),
                                                         SFVEC3F( v1.x, v1.y, zBot ),
                                                         SFVEC3F( v0.x, v0.y, zBot ) );

                m_layer_middle_contourns_quads->AddNormal( n3d0, n3d1, n3d1, n3d0 );
            }
        }
    }
}


void TRIANGLE_DISPLAY_LIST::AddToMiddleContourns( const SHAPE_LINE_CHAIN& outlinePath, float zBot,
                                                  float zTop, double aBiuTo3Du,
                                                  bool aInvertFaceDirection,
                                                  const BVH_CONTAINER_2D* aThroughHoles )
{
    std::vector< SFVEC2F >contournPoints;

    contournPoints.clear();
    contournPoints.reserve( outlinePath.PointCount() + 2 );

    const VECTOR2I& firstV = outlinePath.CPoint( 0 );

    SFVEC2F lastV = SFVEC2F( firstV.x * aBiuTo3Du, -firstV.y * aBiuTo3Du );

    contournPoints.push_back( lastV );

    for( unsigned int i = 1; i < (unsigned int)outlinePath.PointCount(); ++i )
    {
        const VECTOR2I&  v = outlinePath.CPoint( i );

        const SFVEC2F vf = SFVEC2F(  v.x * aBiuTo3Du, -v.y * aBiuTo3Du );

        if( vf != lastV ) // Do not add repeated points
        {
            lastV = vf;
            contournPoints.push_back( vf );
        }
    }

    // Add first position fo the list to close the path
    if( lastV != contournPoints[0] )
        contournPoints.push_back( contournPoints[0] );

    AddToMiddleContourns( contournPoints, zBot, zTop, aInvertFaceDirection, aThroughHoles );
}


void TRIANGLE_DISPLAY_LIST::AddToMiddleContourns( const SHAPE_POLY_SET& aPolySet, float zBot,
                                                  float zTop, double aBiuTo3Du,
                                                  bool aInvertFaceDirection,
                                                  const BVH_CONTAINER_2D* aThroughHoles )
{
    if( aPolySet.OutlineCount() == 0 )
        return;

    // Calculate an estimation of points to reserve
    unsigned int nrContournPointsToReserve = 0;

    for( int i = 0; i < aPolySet.OutlineCount(); ++i )
    {
        const SHAPE_LINE_CHAIN& pathOutline = aPolySet.COutline( i );

        nrContournPointsToReserve += pathOutline.PointCount();

        for( int h = 0; h < aPolySet.HoleCount( i ); ++h )
        {
            const SHAPE_LINE_CHAIN& hole = aPolySet.CHole( i, h );

            nrContournPointsToReserve += hole.PointCount();
        }
    }

    // Request to reserve more space
    m_layer_middle_contourns_quads->Reserve_More( nrContournPointsToReserve * 2, true );

    for( int i = 0; i < aPolySet.OutlineCount(); i++ )
    {
        // Add outline
        const SHAPE_LINE_CHAIN& pathOutline = aPolySet.COutline( i );

        AddToMiddleContourns( pathOutline, zBot, zTop, aBiuTo3Du, aInvertFaceDirection,
                              aThroughHoles );

        // Add holes for this outline
        for( int h = 0; h < aPolySet.HoleCount( i ); ++h )
        {
            const SHAPE_LINE_CHAIN& hole = aPolySet.CHole( i, h );
            AddToMiddleContourns( hole, zBot, zTop, aBiuTo3Du, aInvertFaceDirection,
                                  aThroughHoles );
        }
    }
}


OPENGL_RENDER_LIST::OPENGL_RENDER_LIST( const TRIANGLE_DISPLAY_LIST& aLayerTriangles,
                                        GLuint aTextureIndexForSegEnds,
                                        float aZBot, float aZTop )
{
    m_zBot = aZBot;
    m_zTop = aZTop;

    m_layer_top_segment_ends        = 0;
    m_layer_top_triangles           = 0;
    m_layer_middle_contourns_quads  = 0;
    m_layer_bot_triangles           = 0;
    m_layer_bot_segment_ends        = 0;

    if( aTextureIndexForSegEnds )
    {
        wxASSERT( glIsTexture( aTextureIndexForSegEnds ) );

        if( glIsTexture( aTextureIndexForSegEnds ) )
        {
            m_layer_top_segment_ends =
                    generate_top_or_bot_seg_ends( aLayerTriangles.m_layer_top_segment_ends,
                                                  true, aTextureIndexForSegEnds );

            m_layer_bot_segment_ends =
                    generate_top_or_bot_seg_ends( aLayerTriangles.m_layer_bot_segment_ends,
                                                  false, aTextureIndexForSegEnds );
        }
    }

    m_layer_top_triangles = generate_top_or_bot_triangles( aLayerTriangles.m_layer_top_triangles,
                                                           true );

    m_layer_bot_triangles = generate_top_or_bot_triangles( aLayerTriangles.m_layer_bot_triangles,
                                                           false );


    if( aLayerTriangles.m_layer_middle_contourns_quads->GetVertexSize() > 0 )
    {
        m_layer_middle_contourns_quads =
                generate_middle_triangles( aLayerTriangles.m_layer_middle_contourns_quads );
    }

    m_draw_it_transparent = false;
    m_haveTransformation  = false;
    m_zPositionTransformation = 0.0f;
    m_zScaleTransformation    = 0.0f;
}


OPENGL_RENDER_LIST::~OPENGL_RENDER_LIST()
{
    if( glIsList( m_layer_top_segment_ends ) )
        glDeleteLists( m_layer_top_segment_ends, 1 );

    if( glIsList( m_layer_top_triangles ) )
        glDeleteLists( m_layer_top_triangles, 1 );

    if( glIsList( m_layer_middle_contourns_quads ) )
        glDeleteLists( m_layer_middle_contourns_quads, 1 );

    if( glIsList( m_layer_bot_triangles ) )
        glDeleteLists( m_layer_bot_triangles, 1 );

    if( glIsList( m_layer_bot_segment_ends ) )
        glDeleteLists( m_layer_bot_segment_ends, 1 );

    m_layer_top_segment_ends        = 0;
    m_layer_top_triangles           = 0;
    m_layer_middle_contourns_quads  = 0;
    m_layer_bot_triangles           = 0;
    m_layer_bot_segment_ends        = 0;
}


void OPENGL_RENDER_LIST::DrawTopAndMiddle() const
{
    beginTransformation();

    if( glIsList( m_layer_middle_contourns_quads ) )
        glCallList( m_layer_middle_contourns_quads );

    if( glIsList( m_layer_top_triangles ) )
        glCallList( m_layer_top_triangles );

    if( glIsList( m_layer_top_segment_ends ) )
        glCallList( m_layer_top_segment_ends );

    endTransformation();
}


void OPENGL_RENDER_LIST::DrawBotAndMiddle() const
{
    beginTransformation();

    if( glIsList( m_layer_middle_contourns_quads ) )
        glCallList( m_layer_middle_contourns_quads );

    if( glIsList( m_layer_bot_triangles ) )
        glCallList( m_layer_bot_triangles );

    if( glIsList( m_layer_bot_segment_ends ) )
        glCallList( m_layer_bot_segment_ends );

    endTransformation();
}


void OPENGL_RENDER_LIST::DrawTop() const
{
    beginTransformation();

    if( glIsList( m_layer_top_triangles ) )
        glCallList( m_layer_top_triangles );

    if( glIsList( m_layer_top_segment_ends ) )
        glCallList( m_layer_top_segment_ends );

    endTransformation();
}


void OPENGL_RENDER_LIST::DrawBot() const
{
    beginTransformation();

    if( glIsList( m_layer_bot_triangles ) )
        glCallList( m_layer_bot_triangles );

    if( glIsList( m_layer_bot_segment_ends ) )
        glCallList( m_layer_bot_segment_ends );

    endTransformation();
}


void OPENGL_RENDER_LIST::DrawMiddle() const
{
    beginTransformation();

    if( glIsList( m_layer_middle_contourns_quads ) )
        glCallList( m_layer_middle_contourns_quads );

    endTransformation();
}


void OPENGL_RENDER_LIST::DrawAll( bool aDrawMiddle ) const
{
    beginTransformation();

    if( aDrawMiddle )
        if( glIsList( m_layer_middle_contourns_quads ) )
            glCallList( m_layer_middle_contourns_quads );

    if( glIsList( m_layer_top_triangles ) )
        glCallList( m_layer_top_triangles );

    if( glIsList( m_layer_bot_triangles ) )
        glCallList( m_layer_bot_triangles );

    if( glIsList( m_layer_top_segment_ends ) )
        glCallList( m_layer_top_segment_ends );

    if( glIsList( m_layer_bot_segment_ends ) )
        glCallList( m_layer_bot_segment_ends );

    endTransformation();
}


void OPENGL_RENDER_LIST::DrawAllCameraCulled( float zCameraPos, bool aDrawMiddle ) const
{
    zCameraPos = m_haveTransformation
            ? ( ( zCameraPos - m_zPositionTransformation ) / m_zScaleTransformation )
            : zCameraPos;

    if( aDrawMiddle )
        DrawMiddle();

    if( zCameraPos > m_zTop )
    {
        DrawTop();
    }
    else
    {
        if( zCameraPos < m_zBot )
        {
            DrawBot();
        }
        else
        {
            // If camera is in the middle dont draw it.
        }
    }
}


void OPENGL_RENDER_LIST::DrawAllCameraCulledSubtractLayer( bool aDrawMiddle,
                                        const OPENGL_RENDER_LIST* aLayerToSubtractA,
                                        const OPENGL_RENDER_LIST* aLayerToSubtractB,
                                        const OPENGL_RENDER_LIST* aLayerToSubtractC,
                                        const OPENGL_RENDER_LIST* aLayerToSubtractD ) const
{
    glClearStencil( 0x00 );
    glClear( GL_STENCIL_BUFFER_BIT );

    glEnable( GL_CULL_FACE );
    glCullFace( GL_BACK );

    glDisable( GL_DEPTH_TEST );
    glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
    glDepthMask( GL_FALSE );
    glEnable( GL_STENCIL_TEST );
    glStencilFunc( GL_ALWAYS, 1, 0 );
    glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );

    if( aLayerToSubtractA )
        aLayerToSubtractA->DrawBot();

    if( aLayerToSubtractB )
        aLayerToSubtractB->DrawBot();

    if( aLayerToSubtractC )
        aLayerToSubtractC->DrawBot();

    if( aLayerToSubtractD )
        aLayerToSubtractD->DrawBot();

    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);

    glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
    glStencilFunc( GL_EQUAL, 0, 1 );
    glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
    DrawBot();

    glDisable( GL_DEPTH_TEST );
    glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
    glDepthMask( GL_FALSE );
    glEnable( GL_STENCIL_TEST );
    glStencilFunc( GL_ALWAYS, 2, 0 );
    glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );

    if( aLayerToSubtractA )
        aLayerToSubtractA->DrawTop();

    if( aLayerToSubtractB )
        aLayerToSubtractB->DrawTop();

    if( aLayerToSubtractC )
        aLayerToSubtractC->DrawTop();

    if( aLayerToSubtractD )
        aLayerToSubtractD->DrawTop();

    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
    glStencilFunc( GL_NOTEQUAL, 2, 0x03 );
    glStencilOp( GL_KEEP, GL_KEEP, GL_INCR );
    DrawTop();

    if( aDrawMiddle )
        DrawMiddle();

    glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE );

    glCullFace( GL_FRONT );
    glStencilFunc( GL_GEQUAL, 3, 0x03 );
    glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
    glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );

    if( aDrawMiddle )
    {
        if( aLayerToSubtractA )
            aLayerToSubtractA->DrawMiddle();
    }

    glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE );

    glCullFace( GL_BACK );
    glDisable( GL_STENCIL_TEST );
}


void OPENGL_RENDER_LIST::ApplyScalePosition( float aZposition, float aZscale )
{
    wxASSERT( aZscale > FLT_EPSILON );

    m_zPositionTransformation = aZposition;
    m_zScaleTransformation = aZscale;
    m_haveTransformation = true;
}


void OPENGL_RENDER_LIST::SetItIsTransparent( bool aSetTransparent )
{
    m_draw_it_transparent = aSetTransparent;
}


GLuint OPENGL_RENDER_LIST::generate_top_or_bot_seg_ends(
        const TRIANGLE_LIST* aTriangleContainer, bool aIsNormalUp, GLuint aTextureId ) const
{
    wxASSERT( aTriangleContainer != nullptr );

    wxASSERT( ( aTriangleContainer->GetVertexSize() % 3 ) == 0 );

    // Top and Bot dont have normals array stored in container
    wxASSERT( aTriangleContainer->GetNormalsSize() == 0 );

    if( ( aTriangleContainer->GetVertexSize() > 0 )
      && ( ( aTriangleContainer->GetVertexSize() % 3 ) == 0 ) )
    {
        GLuint listIdx = glGenLists( 1 );

        if( glIsList( listIdx ) )
        {
            // Prepare an array of UV text coordinates
            SFVEC2F* uvArray = new SFVEC2F[aTriangleContainer->GetVertexSize()];

            for( unsigned int i = 0; i < aTriangleContainer->GetVertexSize(); i += 3 )
            {
                uvArray[i + 0] = SFVEC2F( 1.0f, 0.0f );
                uvArray[i + 1] = SFVEC2F( 0.0f, 1.0f );
                uvArray[i + 2] = SFVEC2F( 0.0f, 0.0f );
            }

            glEnableClientState( GL_TEXTURE_COORD_ARRAY );
            glDisableClientState( GL_COLOR_ARRAY );
            glDisableClientState( GL_NORMAL_ARRAY );
            glEnableClientState( GL_VERTEX_ARRAY );
            glVertexPointer( 3, GL_FLOAT, 0, aTriangleContainer->GetVertexPointer() );
            glTexCoordPointer( 2, GL_FLOAT, 0, uvArray );

            glNewList( listIdx, GL_COMPILE );

            glDisable( GL_COLOR_MATERIAL );

            glEnable( GL_TEXTURE_2D );
            glBindTexture( GL_TEXTURE_2D, aTextureId );

            glAlphaFunc( GL_GREATER, 0.2f );
            glEnable( GL_ALPHA_TEST );

            glNormal3f( 0.0f, 0.0f, aIsNormalUp?1.0f:-1.0f );

            glDrawArrays( GL_TRIANGLES, 0, aTriangleContainer->GetVertexSize() );

            glBindTexture( GL_TEXTURE_2D, 0 );
            glDisable( GL_TEXTURE_2D );
            glDisable( GL_ALPHA_TEST );
            glDisable( GL_BLEND );

            glEndList();

            glDisableClientState( GL_VERTEX_ARRAY );
            glDisableClientState( GL_TEXTURE_COORD_ARRAY );

            delete [] uvArray;
            return listIdx;
        }
    }

    return 0;
}


GLuint OPENGL_RENDER_LIST::generate_top_or_bot_triangles( const TRIANGLE_LIST* aTriangleContainer,
                                                          bool aIsNormalUp ) const
{
    wxASSERT( aTriangleContainer != nullptr );

    wxASSERT( ( aTriangleContainer->GetVertexSize() % 3 ) == 0 );

    // Top and Bot dont have normals array stored in container
    wxASSERT( aTriangleContainer->GetNormalsSize() == 0 );

    if( ( aTriangleContainer->GetVertexSize() > 0 )
      && ( ( aTriangleContainer->GetVertexSize() % 3 ) == 0 ) )
    {
        const GLuint listIdx = glGenLists( 1 );

        if( glIsList( listIdx ) )
        {
            glDisableClientState( GL_TEXTURE_COORD_ARRAY );
            glDisableClientState( GL_COLOR_ARRAY );
            glDisableClientState( GL_NORMAL_ARRAY );
            glEnableClientState( GL_VERTEX_ARRAY );
            glVertexPointer( 3, GL_FLOAT, 0, aTriangleContainer->GetVertexPointer() );

            glNewList( listIdx, GL_COMPILE );

            setBlendfunction();

            glNormal3f( 0.0f, 0.0f, aIsNormalUp?1.0f:-1.0f );

            glDrawArrays( GL_TRIANGLES, 0, aTriangleContainer->GetVertexSize() );

            glDisable( GL_BLEND );
            glEndList();

            glDisableClientState( GL_VERTEX_ARRAY );

            return listIdx;
        }
    }

    return 0;
}


GLuint OPENGL_RENDER_LIST::generate_middle_triangles(
        const TRIANGLE_LIST* aTriangleContainer ) const
{
    wxASSERT( aTriangleContainer != nullptr );

    // We expect that it is a multiple of 3 vertex
    wxASSERT( ( aTriangleContainer->GetVertexSize() % 3 ) == 0 );

    // We expect that it is a multiple of 6 vertex (because we expect to add quads)
    wxASSERT( (aTriangleContainer->GetVertexSize() % 6 ) == 0 );

    // We expect that there are normals with same size as vertex
    wxASSERT( aTriangleContainer->GetNormalsSize() == aTriangleContainer->GetVertexSize() );

    if( ( aTriangleContainer->GetVertexSize() > 0 )
      && ( ( aTriangleContainer->GetVertexSize() % 3 ) == 0 )
      && ( ( aTriangleContainer->GetVertexSize() % 6 ) == 0 )
      && ( aTriangleContainer->GetNormalsSize() == aTriangleContainer->GetVertexSize() ) )
    {
        const GLuint listIdx = glGenLists( 1 );

        if( glIsList( listIdx ) )
        {
            glDisableClientState( GL_TEXTURE_COORD_ARRAY );
            glDisableClientState( GL_COLOR_ARRAY );
            glEnableClientState( GL_NORMAL_ARRAY );
            glEnableClientState( GL_VERTEX_ARRAY );
            glVertexPointer( 3, GL_FLOAT, 0, aTriangleContainer->GetVertexPointer() );
            glNormalPointer( GL_FLOAT, 0, aTriangleContainer->GetNormalsPointer() );

            glNewList( listIdx, GL_COMPILE );

            setBlendfunction();

            glDrawArrays( GL_TRIANGLES, 0, aTriangleContainer->GetVertexSize() );

            glDisable( GL_BLEND );
            glEndList();

            glDisableClientState( GL_VERTEX_ARRAY );
            glDisableClientState( GL_NORMAL_ARRAY );

            return listIdx;
        }
    }

    return 0;
}


void OPENGL_RENDER_LIST::endTransformation() const
{
    if( m_haveTransformation )
    {
        glPopMatrix();
    }
}


void OPENGL_RENDER_LIST::setBlendfunction() const
{
    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
}


void OPENGL_RENDER_LIST::beginTransformation() const
{
    if( m_haveTransformation )
    {
        glPushMatrix();
        glTranslatef( 0.0f, 0.0f, m_zPositionTransformation );
        glScalef( 1.0f, 1.0f, m_zScaleTransformation );
    }
}
