/*
 * 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  clayer_triangles.cpp
 * @brief
 */


#include "clayer_triangles.h"
#include <wx/debug.h>   // For the wxASSERT
#include <mutex>
#include <thread>
#include <atomic>


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


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

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


CLAYER_TRIANGLES::~CLAYER_TRIANGLES()
{
    delete m_layer_top_segment_ends;
    m_layer_top_segment_ends = 0;

    delete m_layer_top_triangles;
    m_layer_top_triangles = 0;

    delete m_layer_middle_contourns_quads;
    m_layer_middle_contourns_quads = 0;

    delete m_layer_bot_triangles;
    m_layer_bot_triangles = 0;

    delete m_layer_bot_segment_ends;
    m_layer_bot_segment_ends = 0;
}


void CLAYER_TRIANGLES::AddToMiddleContourns( const std::vector< SFVEC2F > &aContournPoints,
                                             float zBot,
                                             float zTop,
                                             bool aInvertFaceDirection )
{
    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];

            {
                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 CLAYER_TRIANGLES::AddToMiddleContourns( const SHAPE_LINE_CHAIN &outlinePath,
                                             float zBot,
                                             float zTop,
                                             double aBiuTo3Du,
                                             bool aInvertFaceDirection )
{
    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 );
}


void CLAYER_TRIANGLES::AddToMiddleContourns( const SHAPE_POLY_SET &aPolySet,
                                             float zBot,
                                             float zTop,
                                             double aBiuTo3Du,
                                             bool aInvertFaceDirection )
{
    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 );

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


CLAYERS_OGL_DISP_LISTS::CLAYERS_OGL_DISP_LISTS( const CLAYER_TRIANGLES &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;
}


CLAYERS_OGL_DISP_LISTS::~CLAYERS_OGL_DISP_LISTS()
{
    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 CLAYERS_OGL_DISP_LISTS::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 CLAYERS_OGL_DISP_LISTS::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 CLAYERS_OGL_DISP_LISTS::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 CLAYERS_OGL_DISP_LISTS::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 CLAYERS_OGL_DISP_LISTS::DrawMiddle() const
{
    beginTransformation();

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

    endTransformation();
}


void CLAYERS_OGL_DISP_LISTS::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 CLAYERS_OGL_DISP_LISTS::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 CLAYERS_OGL_DISP_LISTS::DrawAllCameraCulledSubtractLayer(
        const CLAYERS_OGL_DISP_LISTS *aLayerToSubtractA,
        const CLAYERS_OGL_DISP_LISTS *aLayerToSubtractB,
        bool aDrawMiddle ) 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( !m_draw_it_transparent )
    {
        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( !m_draw_it_transparent )
    {
        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();

        // It will not render the middle contours of the layer.
        // It is used with vias and holes (copper vias and to subtract solder
        // mask holes). But since in the vias, it will draw a cylinder
        // and in soldermask it doesn't need to draw the contour.
        // so it is not used the middle part of B
//        if( aLayerToSubtractB )
//            aLayerToSubtractB->DrawMiddle();
    }

    glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE );

    glCullFace( GL_BACK );
    glDisable( GL_STENCIL_TEST );

/*
    if( m_draw_it_transparent )
    {
        glEnable(GL_DEPTH_TEST);
        glDepthMask(GL_TRUE);
    }*/
}


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

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


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


GLuint CLAYERS_OGL_DISP_LISTS::generate_top_or_bot_seg_ends(
        const CLAYER_TRIANGLE_CONTAINER *aTriangleContainer,
        bool aIsNormalUp,
        GLuint aTextureId ) const
{
    wxASSERT( aTriangleContainer != NULL );

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

            setBlendfunction();

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

            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 CLAYERS_OGL_DISP_LISTS::generate_top_or_bot_triangles(
        const CLAYER_TRIANGLE_CONTAINER *aTriangleContainer,
        bool aIsNormalUp ) const
{
    wxASSERT( aTriangleContainer != NULL );

    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 CLAYERS_OGL_DISP_LISTS::generate_middle_triangles(
        const CLAYER_TRIANGLE_CONTAINER *aTriangleContainer ) const
{
    wxASSERT( aTriangleContainer != NULL );

    // 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 CLAYERS_OGL_DISP_LISTS::endTransformation() const
{
    if( m_haveTransformation )
    {
        glPopMatrix();
    }
}


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


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