/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 1992-2012 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 3d_draw.cpp
*/

#include <fctsys.h>
#include <common.h>
#include <trigo.h>
#include <pcbstruct.h>
#include <drawtxt.h>
#include <layers_id_colors_and_visibility.h>

#include <class_board.h>
#include <class_module.h>
#include <class_track.h>
#include <class_edge_mod.h>
#include <class_zone.h>
#include <class_drawsegment.h>
#include <class_pcb_text.h>
#include <colors_selection.h>
#include <convert_basic_shapes_to_polygon.h>

#include <3d_viewer.h>
#include <3d_canvas.h>
#include <info3d_visu.h>
#include <trackball.h>
#include <3d_draw_basic_functions.h>

// Imported function:
extern void SetGLColor( int color );
extern void Set_Object_Data( std::vector< S3D_VERTEX >& aVertices, double aBiuTo3DUnits );
extern void CheckGLError();


 /* returns true if aLayer should be displayed, false otherwise
  */
static bool    Is3DLayerEnabled( int aLayer );

 /* returns the Z orientation parmeter 1.0 or -1.0 for aLayer
  * Z orientation is 1.0 for all layers but "back" layers:
  *  LAYER_N_BACK , ADHESIVE_N_BACK, SOLDERPASTE_N_BACK ), SILKSCREEN_N_BACK
  * used to calculate the Z orientation parmeter for glNormal3f
  */
static GLfloat  Get3DLayer_Z_Orientation( int aLayer );

void EDA_3D_CANVAS::Redraw( bool finish )
{
    // SwapBuffer requires the window to be shown before calling
    if( !IsShown() )
        return;

    SetCurrent( *m_glRC );

    // Set the OpenGL viewport according to the client size of this canvas.
    // This is done here rather than in a wxSizeEvent handler because our
    // OpenGL rendering context (and thus viewport setting) is used with
    // multiple canvases: If we updated the viewport in the wxSizeEvent
    // handler, changing the size of one canvas causes a viewport setting that
    // is wrong when next another canvas is repainted.
    const wxSize ClientSize = GetClientSize();

    // *MUST* be called *after*  SetCurrent( ):
    glViewport( 0, 0, ClientSize.x, ClientSize.y );

    InitGL();

    glMatrixMode( GL_MODELVIEW );    // position viewer
    // transformations
    GLfloat mat[4][4];

    // Translate motion first, so rotations don't mess up the orientation...
    glTranslatef( m_draw3dOffset.x, m_draw3dOffset.y, 0.0F );

    build_rotmatrix( mat, g_Parm_3D_Visu.m_Quat );
    glMultMatrixf( &mat[0][0] );

    glRotatef( g_Parm_3D_Visu.m_Rot[0], 1.0, 0.0, 0.0 );
    glRotatef( g_Parm_3D_Visu.m_Rot[1], 0.0, 1.0, 0.0 );
    glRotatef( g_Parm_3D_Visu.m_Rot[2], 0.0, 0.0, 1.0 );

    if( m_gllist )
    {
        glCallList( m_gllist );
    }
    else
    {
        CreateDrawGL_List();
    }

    glFlush();
    if( finish )
        glFinish();

    SwapBuffers();
}


GLuint EDA_3D_CANVAS::CreateDrawGL_List()
{
    PCB_BASE_FRAME* pcbframe = Parent()->Parent();
    BOARD* pcb = pcbframe->GetBoard();

    wxBusyCursor         dummy;

    m_gllist = glGenLists( 1 );

    // Build 3D board parameters:
    g_Parm_3D_Visu.InitSettings( pcb );

    glNewList( m_gllist, GL_COMPILE_AND_EXECUTE );

    glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );

    // draw axis
    if (g_Parm_3D_Visu.m_DrawFlags[g_Parm_3D_Visu.FL_AXIS])
    {
        glEnable( GL_COLOR_MATERIAL );
        SetGLColor( WHITE );
        glBegin( GL_LINES );
        glNormal3f( 0.0f, 0.0f, 1.0f );     // Normal is Z axis
        glVertex3f( 0.0f, 0.0f, 0.0f );
        glVertex3f( 1.0f, 0.0f, 0.0f );     // X axis
        glVertex3f( 0.0f, 0.0f, 0.0f );
        glVertex3f( 0.0f, -1.0f, 0.0f );    // Y axis
        glNormal3f( 1.0f, 0.0f, 0.0f );     // Normal is Y axis
        glVertex3f( 0.0f, 0.0f, 0.0f );
        glVertex3f( 0.0f, 0.0f, 0.3f );     // Z axis
        glEnd();
    }

    // move the board in order to draw it with its center at 0,0 3D coordinates
    glTranslatef( -g_Parm_3D_Visu.m_BoardPos.x * g_Parm_3D_Visu.m_BiuTo3Dunits,
                  -g_Parm_3D_Visu.m_BoardPos.y * g_Parm_3D_Visu.m_BiuTo3Dunits,
                  0.0F );

    // draw tracks and vias :
    for( TRACK* track = pcb->m_Track; track != NULL; track = track->Next() )
    {
        if( track->Type() == PCB_VIA_T )
            Draw3D_Via( (SEGVIA*) track );
        else
        {
            int    layer = track->GetLayer();

            if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( layer ) )
                Draw3D_Track( track );
        }
    }

    if (g_Parm_3D_Visu.m_DrawFlags[g_Parm_3D_Visu.FL_ZONE])
    {
        for( int ii = 0; ii < pcb->GetAreaCount(); ii++ )
        {
            int layer = pcb->GetArea( ii )->GetLayer();

            if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( layer )  )
                Draw3D_Zone( pcb->GetArea( ii ) );
        }
    }


    // Draw epoxy limits: TODO

    // draw graphic items
    EDA_ITEM* PtStruct;

    for( PtStruct = pcb->m_Drawings;  PtStruct != NULL;  PtStruct = PtStruct->Next() )
    {
        switch( PtStruct->Type() )
        {
        case PCB_LINE_T:
        {
            DRAWSEGMENT* segment = (DRAWSEGMENT*) PtStruct;
            if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( segment->GetLayer() ) )
                Draw3D_DrawSegment( segment );
        }
            break;

        case PCB_TEXT_T:
        {
            TEXTE_PCB* text = (TEXTE_PCB*) PtStruct;
            if( Is3DLayerEnabled( text->GetLayer() ) )
                Draw3D_DrawText( text );
        }
            break;

        default:
            break;
        }
    }

    // draw footprints
    MODULE* Module = pcb->m_Modules;

    for( ; Module != NULL; Module = Module->Next() )
        Module->Draw3D( this );

    // Draw grid
    if( g_Parm_3D_Visu.m_DrawFlags[g_Parm_3D_Visu.FL_GRID] )
    DrawGrid( g_Parm_3D_Visu.m_3D_Grid );

    glEndList();

    // Test for errors
    CheckGLError();

    return m_gllist;
}

/* Draw a zone (solid copper areas in aZone)
 */
void EDA_3D_CANVAS::Draw3D_Zone( ZONE_CONTAINER* aZone )
{
    int layer = aZone->GetLayer();
    int color = g_ColorsSettings.GetLayerColor( layer );
    int thickness = g_Parm_3D_Visu.GetLayerObjectThicknessBIU( layer );

    if( layer == LAST_COPPER_LAYER )
        layer = g_Parm_3D_Visu.m_CopperLayersCount - 1;

    int zpos = g_Parm_3D_Visu.GetLayerZcoordBIU( layer );

    SetGLColor( color );
    glNormal3f( 0.0, 0.0, Get3DLayer_Z_Orientation( layer ) );

    if( aZone->m_FillMode == 0 )
    {
        // solid polygons only are used to fill areas
        if( aZone->GetFilledPolysList().size() > 3 )
        {
            Draw3D_SolidHorizontalPolyPolygons( aZone->GetFilledPolysList(),
                                  g_Parm_3D_Visu.GetLayerZcoordBIU( layer ),
                                  thickness, g_Parm_3D_Visu.m_BiuTo3Dunits );
        }
    }
    else
    {
        // segments are used to fill areas
        for( unsigned iseg = 0; iseg < aZone->m_FillSegmList.size(); iseg++ )
            Draw3D_SolidSegment( aZone->m_FillSegmList[iseg].m_Start,
                                 aZone->m_FillSegmList[iseg].m_End,
                                 aZone->m_ZoneMinThickness, thickness, zpos,
                                 g_Parm_3D_Visu.m_BiuTo3Dunits );
    }

    // Draw copper area outlines
    std::vector<CPolyPt> polysList = aZone->GetFilledPolysList();

    if( polysList.size() == 0 )
        return;

    if( aZone->m_ZoneMinThickness <= 1 )
        return;

    int      imax = polysList.size() - 1;
    CPolyPt* firstcorner = &polysList[0];
    CPolyPt* begincorner = firstcorner;

    for( int ic = 1; ic <= imax; ic++ )
    {
        CPolyPt* endcorner = &polysList[ic];

        if( begincorner->m_utility == 0 )
        {
            // Draw only basic outlines, not extra segments
            wxPoint start( begincorner->x, begincorner->y  );
            wxPoint end( endcorner->x, endcorner->y );
            Draw3D_SolidSegment( start, end,
                                 aZone->m_ZoneMinThickness, thickness, zpos,
                                 g_Parm_3D_Visu.m_BiuTo3Dunits );
        }

        if( (endcorner->end_contour) || (ic == imax) )
        {
            // the last corner of a filled area is found: draw it
            if( endcorner->m_utility == 0 )
            {
                // Draw only basic outlines, not extra segments
                wxPoint start( endcorner->x, endcorner->y );
                wxPoint end( firstcorner->x, firstcorner->y );
                Draw3D_SolidSegment( start, end,
                                     aZone->m_ZoneMinThickness, thickness, zpos,
                                     g_Parm_3D_Visu.m_BiuTo3Dunits );
            }

            ic++;

            if( ic < imax - 1 )
                begincorner = firstcorner = &polysList[ic];
        }
        else
        {
            begincorner = endcorner;
        }
    }
}


// draw a 3D grid: an horizontal grid (XY plane and Z = 0,
// and a vertical grid (XZ plane and Y = 0)
void EDA_3D_CANVAS::DrawGrid( double aGriSizeMM )
{
    double zpos = 0.0;
    int gridcolor = DARKGRAY;           // Color of grid lines
    int gridcolor_marker = LIGHTGRAY;   // Color of grid lines every 5 lines
    double scale = g_Parm_3D_Visu.m_BiuTo3Dunits;

    glNormal3f( 0.0, 0.0, 1.0 );

    wxSize brd_size = g_Parm_3D_Visu.m_BoardSize;
    wxPoint brd_center_pos = g_Parm_3D_Visu.m_BoardPos;
    NEGATE( brd_center_pos.y );

    int xsize  = std::max( brd_size.x, Millimeter2iu( 100 ) );
    int ysize  = std::max( brd_size.y, Millimeter2iu( 100 ) );

    // Grid limits, in 3D units
    double xmin = (brd_center_pos.x - xsize/2) * scale;
    double xmax = (brd_center_pos.x + xsize/2) * scale;
    double ymin = (brd_center_pos.y - ysize/2) * scale;
    double ymax = (brd_center_pos.y + ysize/2) * scale;
    double zmin = Millimeter2iu( -50 ) * scale;
    double zmax = Millimeter2iu( 100 ) * scale;

    // Draw horizontal grid centered on 3D origin (center of the board)
    for( int ii = 0; ; ii++ )
    {
        if( (ii % 5) )
            SetGLColor( gridcolor );
        else
            SetGLColor( gridcolor_marker );

        int delta = KiROUND( ii * aGriSizeMM * IU_PER_MM );

        if( delta <= xsize/2 )    // Draw grid lines parallel to X axis
        {
            glBegin(GL_LINES);
            glVertex3f( (brd_center_pos.x + delta) * scale, -ymin, zpos );
            glVertex3f( (brd_center_pos.x + delta) * scale, -ymax, zpos );
            glEnd();

            if( ii != 0 )
            {
                glBegin(GL_LINES);
                glVertex3f( (brd_center_pos.x - delta) * scale, -ymin, zpos );
                glVertex3f( (brd_center_pos.x - delta) * scale, -ymax, zpos );
                glEnd();
            }
        }

        if( delta <= ysize/2 )    // Draw grid lines parallel to Y axis
        {
            glBegin(GL_LINES);
            glVertex3f( xmin, -(brd_center_pos.y + delta) * scale, zpos );
            glVertex3f( xmax, -(brd_center_pos.y + delta) * scale, zpos );
            glEnd();
            if( ii != 0 )
            {
                glBegin(GL_LINES);
                glVertex3f( xmin, -(brd_center_pos.y - delta) * scale, zpos );
                glVertex3f( xmax, -(brd_center_pos.y - delta) * scale, zpos );
                glEnd();
            }
        }

        if( ( delta > ysize/2 ) && ( delta > xsize/2 ) )
            break;
    }

    // Draw vertical grid n Z axis
    glNormal3f( 0.0, -1.0, 0.0 );

    // Draw vertical grid lines (parallel to Z axis)
    for( int ii = 0; ; ii++ )
    {
        if( (ii % 5) )
            SetGLColor( gridcolor );
        else
            SetGLColor( gridcolor_marker );

        double delta = ii * aGriSizeMM * IU_PER_MM;

        glBegin(GL_LINES);
        glVertex3f( (brd_center_pos.x + delta) * scale, -brd_center_pos.y * scale, zmin );
        glVertex3f( (brd_center_pos.x + delta) * scale, -brd_center_pos.y * scale, zmax );
        glEnd();

        if( ii != 0 )
        {
            glBegin(GL_LINES);
            glVertex3f( (brd_center_pos.x - delta) * scale, -brd_center_pos.y * scale, zmin );
            glVertex3f( (brd_center_pos.x - delta) * scale, -brd_center_pos.y * scale, zmax );
            glEnd();
        }

        if( delta > xsize/2 )
            break;
    }

    // Draw horizontal grid lines on Z axis
    for( int ii = 0; ; ii++ )
    {
        if( (ii % 5) )
            SetGLColor( gridcolor );
        else
            SetGLColor( gridcolor_marker );
        double delta = ii * aGriSizeMM * IU_PER_MM  * scale;

        if( delta <= zmax )
        {   // Draw grid lines on Z axis (positive Z axis coordinates)
            glBegin(GL_LINES);
            glVertex3f(xmin, -brd_center_pos.y * scale, delta);
            glVertex3f(xmax, -brd_center_pos.y * scale, delta);
            glEnd();
        }

        if( delta <= -zmin && ( ii != 0 ) )
        {   // Draw grid lines on Z axis (negative Z axis coordinates)
            glBegin(GL_LINES);
            glVertex3f(xmin, -brd_center_pos.y * scale, -delta);
            glVertex3f(xmax, -brd_center_pos.y * scale, -delta);
            glEnd();
        }

        if( ( delta > zmax ) && ( delta > -zmin ) )
            break;
    }

}

void EDA_3D_CANVAS::Draw3D_Track( TRACK* aTrack )
{
    int layer = aTrack->GetLayer();
    int color = g_ColorsSettings.GetLayerColor( layer );
    int thickness = g_Parm_3D_Visu.GetCopperThicknessBIU();

    if( layer == LAST_COPPER_LAYER )
        layer = g_Parm_3D_Visu.m_CopperLayersCount - 1;

    int zpos = g_Parm_3D_Visu.GetLayerZcoordBIU( layer );

    SetGLColor( color );
    glNormal3f( 0.0, 0.0, Get3DLayer_Z_Orientation( layer ) );

    Draw3D_SolidSegment( aTrack->m_Start, aTrack->m_End,
                         aTrack->m_Width, thickness, zpos,
                         g_Parm_3D_Visu.m_BiuTo3Dunits );
}

void EDA_3D_CANVAS::Draw3D_Via( SEGVIA* via )
{
    int    layer, top_layer, bottom_layer;
    int    color;
    double biu_to_3Dunits = g_Parm_3D_Visu.m_BiuTo3Dunits ;

    int outer_radius = via->m_Width / 2;
    int inner_radius = via->GetDrillValue() / 2;
    int thickness = g_Parm_3D_Visu.GetCopperThicknessBIU();

    via->ReturnLayerPair( &top_layer, &bottom_layer );

    // Drawing horizontal thick rings:
    for( layer = bottom_layer; layer < g_Parm_3D_Visu.m_CopperLayersCount; layer++ )
    {
        int zpos = g_Parm_3D_Visu.GetLayerZcoordBIU( layer );

        if( layer < g_Parm_3D_Visu.m_CopperLayersCount - 1 )
        {
            if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( layer ) == false )
                continue;

            color = g_ColorsSettings.GetLayerColor( layer );
        }
        else
        {
            if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( LAYER_N_FRONT ) == false )
                continue;

            color = g_ColorsSettings.GetLayerColor( LAYER_N_FRONT );
        }

        SetGLColor( color );
        glNormal3f( 0.0, 0.0, Get3DLayer_Z_Orientation( layer ) );

        Draw3D_ZaxisCylinder( via->m_Start, (outer_radius + inner_radius)/2,
                                  thickness, outer_radius - inner_radius,
                                  zpos, biu_to_3Dunits );
        if( layer >= top_layer )
            break;
    }

    // Drawing via hole:
    color = g_ColorsSettings.GetItemColor( VIAS_VISIBLE + via->m_Shape );
    SetGLColor( color );
    int height = g_Parm_3D_Visu.GetLayerZcoordBIU(top_layer) -
                 g_Parm_3D_Visu.GetLayerZcoordBIU( bottom_layer );
    int zpos = g_Parm_3D_Visu.GetLayerZcoordBIU(bottom_layer) + thickness/2;

    Draw3D_ZaxisCylinder( via->m_Start, inner_radius + thickness/2, height,
                          thickness, zpos, biu_to_3Dunits );
}


void EDA_3D_CANVAS::Draw3D_DrawSegment( DRAWSEGMENT* segment )
{
    int layer = segment->GetLayer();
    int color = g_ColorsSettings.GetLayerColor( layer );
    int thickness = g_Parm_3D_Visu.GetLayerObjectThicknessBIU( layer );

    SetGLColor( color );

    if( layer == EDGE_N )
    {
        for( layer = 0; layer < g_Parm_3D_Visu.m_CopperLayersCount; layer++ )
        {
            glNormal3f( 0.0, 0.0, Get3DLayer_Z_Orientation( layer ) );
            int zpos = g_Parm_3D_Visu.GetLayerZcoordBIU(layer);

            switch( segment->GetShape() )
            {
            case S_ARC:
                Draw3D_ArcSegment( segment->GetCenter(), segment->GetArcStart(),
                                   segment->GetAngle(), segment->GetWidth(), thickness,
                                   zpos, g_Parm_3D_Visu.m_BiuTo3Dunits );
                break;

            case S_CIRCLE:
                {
                int radius = KiROUND( hypot( double(segment->GetStart().x - segment->GetEnd().x),
                                             double(segment->GetStart().y - segment->GetEnd().y) )
                                    );
                Draw3D_ZaxisCylinder( segment->GetStart(), radius,
                                      thickness, segment->GetWidth(),
                                      zpos, g_Parm_3D_Visu.m_BiuTo3Dunits );
                }
                break;

            default:
                Draw3D_SolidSegment( segment->GetStart(), segment->GetEnd(),
                                    segment->GetWidth(), thickness,
                                    zpos, g_Parm_3D_Visu.m_BiuTo3Dunits );
                break;
            }
        }
    }
    else
    {
        glNormal3f( 0.0, 0.0, Get3DLayer_Z_Orientation( layer ) );
        int zpos = g_Parm_3D_Visu.GetLayerZcoordBIU(layer);

        if( Is3DLayerEnabled( layer ) )
        {
            switch( segment->GetShape() )
            {
            case S_ARC:
                Draw3D_ArcSegment( segment->GetCenter(), segment->GetArcStart(),
                                   segment->GetAngle(), segment->GetWidth(), thickness,
                                   zpos, g_Parm_3D_Visu.m_BiuTo3Dunits );
                break;

            case S_CIRCLE:
            {
                int radius = KiROUND( hypot( double(segment->GetStart().x - segment->GetEnd().x),
                                             double(segment->GetStart().y - segment->GetEnd().y) )
                                    );
                Draw3D_ZaxisCylinder( segment->GetStart(), radius,
                                      thickness, segment->GetWidth(),
                                      zpos, g_Parm_3D_Visu.m_BiuTo3Dunits );
            }
                break;

            default:
                Draw3D_SolidSegment( segment->GetStart(), segment->GetEnd(),
                                    segment->GetWidth(), thickness,
                                    zpos, g_Parm_3D_Visu.m_BiuTo3Dunits );
                break;
            }
        }
    }
}


// These variables are used in Draw3dTextSegm.
// But Draw3dTextSegm is a call back function, so we cannot send them as arguments,
// so they are static.
int s_Text3DWidth, s_Text3DZPos, s_thickness;

// This is a call back function, used by DrawGraphicText to draw the 3D text shape:
static void Draw3dTextSegm( int x0, int y0, int xf, int yf )
{
    Draw3D_SolidSegment( wxPoint( x0, y0), wxPoint( xf, yf ),
                        s_Text3DWidth, s_thickness, s_Text3DZPos,
                        g_Parm_3D_Visu.m_BiuTo3Dunits );
}


void EDA_3D_CANVAS::Draw3D_DrawText( TEXTE_PCB* text )
{
    int layer = text->GetLayer();
    int color = g_ColorsSettings.GetLayerColor( layer );

    SetGLColor( color );
    s_Text3DZPos  = g_Parm_3D_Visu.GetLayerZcoordBIU( layer );
    s_Text3DWidth = text->GetThickness();
    glNormal3f( 0.0, 0.0, Get3DLayer_Z_Orientation( layer ) );
    wxSize size = text->m_Size;
    s_thickness = g_Parm_3D_Visu.GetLayerObjectThicknessBIU( layer );

    if( text->m_Mirror )
        NEGATE( size.x );

    if( text->m_MultilineAllowed )
    {
        wxPoint        pos  = text->m_Pos;
        wxArrayString* list = wxStringSplit( text->m_Text, '\n' );
        wxPoint        offset;

        offset.y = text->GetInterline();

        RotatePoint( &offset, text->GetOrientation() );

        for( unsigned i = 0; i<list->Count(); i++ )
        {
            wxString txt = list->Item( i );
            DrawGraphicText( NULL, NULL, pos, (EDA_COLOR_T) color,
                             txt, text->GetOrientation(), size,
                             text->m_HJustify, text->m_VJustify,
                             text->GetThickness(), text->m_Italic,
                             true, Draw3dTextSegm );
            pos += offset;
        }

        delete list;
    }
    else
    {
        DrawGraphicText( NULL, NULL, text->m_Pos, (EDA_COLOR_T) color,
                         text->m_Text, text->GetOrientation(), size,
                         text->m_HJustify, text->m_VJustify,
                         text->GetThickness(), text->m_Italic,
                         true,
                         Draw3dTextSegm );
    }
}


void MODULE::Draw3D( EDA_3D_CANVAS* glcanvas )
{
    D_PAD* pad = m_Pads;

    // Draw pads
    for( ; pad != NULL; pad = pad->Next() )
        pad->Draw3D( glcanvas );

    // Draw module shape: 3D shape if exists (or module outlines if not exists)
    S3D_MASTER* Struct3D  = m_3D_Drawings;
    bool        As3dShape = false;

    if( g_Parm_3D_Visu.m_DrawFlags[g_Parm_3D_Visu.FL_MODULE] )
    {
        double zpos;
        if( IsFlipped() )
            zpos = g_Parm_3D_Visu.GetModulesZcoord3DIU( true );
        else
            zpos = g_Parm_3D_Visu.GetModulesZcoord3DIU( false );

        glPushMatrix();

        glTranslatef( m_Pos.x * g_Parm_3D_Visu.m_BiuTo3Dunits,
                      -m_Pos.y * g_Parm_3D_Visu.m_BiuTo3Dunits,
                      zpos );

        if( m_Orient )
        {
            glRotatef( (double) m_Orient / 10, 0.0, 0.0, 1.0 );
        }

        if( IsFlipped() )
        {
            glRotatef( 180.0, 0.0, 1.0, 0.0 );
            glRotatef( 180.0, 0.0, 0.0, 1.0 );
        }

        for( ; Struct3D != NULL; Struct3D = Struct3D->Next() )
        {
            if( !Struct3D->m_Shape3DName.IsEmpty() )
            {
                As3dShape = true;
                Struct3D->ReadData();
            }
        }

        glPopMatrix();
    }

    EDA_ITEM* Struct = m_Drawings;
    for( ; Struct != NULL; Struct = Struct->Next() )
    {
        switch( Struct->Type() )
        {
        case PCB_MODULE_TEXT_T:
            break;

        case PCB_MODULE_EDGE_T:
        {
            EDGE_MODULE* edge = (EDGE_MODULE*) Struct;

            // Draw module edges when no 3d shape exists.
            // Always draw pcb edges.
            if( !As3dShape || edge->GetLayer() == EDGE_N )
                edge->Draw3D( glcanvas );
        }
        break;

        default:
            break;
        }
    }
}


void EDGE_MODULE::Draw3D( EDA_3D_CANVAS* glcanvas )
{
    if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( m_Layer ) == false )
        return;

    int color = g_ColorsSettings.GetLayerColor( m_Layer );
    SetGLColor( color );

    // for outline shape = S_POLYGON:
    // We must compute true coordinates from m_PolyPoints
    // which are relative to module position and module orientation = 0
    std::vector<CPolyPt> polycorners;

    if( m_Shape == S_POLYGON )
    {
        polycorners.reserve( m_PolyPoints.size() );
        MODULE* module = (MODULE*) m_Parent;

        CPolyPt corner;
        for( unsigned ii = 0; ii < m_PolyPoints.size(); ii++ )
        {
            corner.x = m_PolyPoints[ii].x;
            corner.y = m_PolyPoints[ii].y;

            RotatePoint( &corner.x, &corner.y, module->GetOrientation() );
            if( module )
            {
                corner.x += module->m_Pos.x;
                corner.y += module->m_Pos.y;
            }
            polycorners.push_back( corner );
        }

        polycorners.back().end_contour = true;
    }

    if( m_Layer == EDGE_N )
    {
        for( int layer = 0; layer < g_Parm_3D_Visu.m_CopperLayersCount; layer++ )
        {
            glNormal3f( 0.0, 0.0, Get3DLayer_Z_Orientation( layer ) );
            int zpos = g_Parm_3D_Visu.GetLayerZcoordBIU( layer );
            int thickness = g_Parm_3D_Visu.GetLayerObjectThicknessBIU( m_Layer );

            switch( m_Shape )
            {
            case S_SEGMENT:
                Draw3D_SolidSegment( m_Start, m_End, m_Width,
                                     thickness, zpos,
                                     g_Parm_3D_Visu.m_BiuTo3Dunits );
                break;

            case S_CIRCLE:
            {
               int radius = KiROUND( hypot( double(m_Start.x - m_End.x),
                                             double(m_Start.y - m_End.y) )
                                    );
                Draw3D_ZaxisCylinder( m_Start, radius,
                                      thickness, GetWidth(),
                                      zpos, g_Parm_3D_Visu.m_BiuTo3Dunits );
            }
                break;

            case S_ARC:
                Draw3D_ArcSegment( GetCenter(), GetArcStart(),
                                   GetAngle(), GetWidth(), thickness,
                                   zpos, g_Parm_3D_Visu.m_BiuTo3Dunits );
                break;

            case S_POLYGON:
                Draw3D_SolidHorizontalPolyPolygons( polycorners, zpos, thickness,
                                                    g_Parm_3D_Visu.m_BiuTo3Dunits);
                break;

            default:
                D( printf( "Error: Shape nr %d not implemented!\n", m_Shape ); )
                break;
            }
        }
    }
    else
    {
        int thickness = g_Parm_3D_Visu.GetLayerObjectThicknessBIU( m_Layer );
        glNormal3f( 0.0, 0.0, Get3DLayer_Z_Orientation( m_Layer ) );
        int zpos = g_Parm_3D_Visu.GetLayerZcoordBIU(m_Layer);

        switch( m_Shape )
        {
        case S_SEGMENT:
                Draw3D_SolidSegment( m_Start, m_End, m_Width,
                                     thickness, zpos,
                                     g_Parm_3D_Visu.m_BiuTo3Dunits );
            break;

        case S_CIRCLE:
        {
            int radius = KiROUND( hypot( double(m_Start.x - m_End.x),
                                         double(m_Start.y - m_End.y) )
                                );
            Draw3D_ZaxisCylinder( m_Start, radius,
                                  thickness, GetWidth(),
                                  zpos, g_Parm_3D_Visu.m_BiuTo3Dunits );
        }
            break;

        case S_ARC:
            Draw3D_ArcSegment( GetCenter(), GetArcStart(),
                               GetAngle(), GetWidth(), thickness,
                               zpos, g_Parm_3D_Visu.m_BiuTo3Dunits );
            break;

        case S_POLYGON:
            Draw3D_SolidHorizontalPolyPolygons( polycorners, zpos, thickness,
                                                g_Parm_3D_Visu.m_BiuTo3Dunits );
            break;

        default:
            D( printf( "Error: Shape nr %d not implemented!\n", m_Shape ); )
            break;
        }
    }
}


// Draw 3D pads.
void D_PAD::Draw3D( EDA_3D_CANVAS* glcanvas )
{
    double scale = g_Parm_3D_Visu.m_BiuTo3Dunits;

    // Calculate the center of the pad shape.
    wxPoint shape_pos = ReturnShapePos();

    int height = g_Parm_3D_Visu.GetLayerZcoordBIU(LAYER_N_FRONT) -
                    g_Parm_3D_Visu.GetLayerZcoordBIU(LAYER_N_BACK);
    int thickness = g_Parm_3D_Visu.GetCopperThicknessBIU();

    // Store here the points to approximate hole by segments
    std::vector <CPolyPt> holecornersBuffer;
    const int slice = 12;   // number of segments to approximate a circle

    // Draw the pad hole
    bool hasHole = m_Drill.x && m_Drill.y;

    if( hasHole )
    {
        SetGLColor( DARKGRAY );
        int holeZpoz = g_Parm_3D_Visu.GetLayerZcoordBIU(LAYER_N_BACK) + thickness/2;
        int holeHeight = height - thickness;

        if( m_Drill.x == m_Drill.y )    // usual round hole
        {
            Draw3D_ZaxisCylinder( m_Pos,  (m_Drill.x + thickness) / 2, holeHeight,
                                  thickness, holeZpoz, scale );
            TransformCircleToPolygon( holecornersBuffer, m_Pos, m_Drill.x/2, slice );
        }
        else    // Oblong hole
        {
            wxPoint ends_offset;
            int width;

            if( m_Drill.x > m_Drill.y )    // Horizontal oval
            {
                ends_offset.x = ( m_Drill.x - m_Drill.y ) / 2;
                width = m_Drill.y;
            }
            else    // Vertical oval
            {
                ends_offset.y = ( m_Drill.y - m_Drill.x ) / 2;
                width = m_Drill.x;
            }

            RotatePoint( &ends_offset, m_Orient );

            wxPoint start  = m_Pos + ends_offset;
            wxPoint end  = m_Pos - ends_offset;
            int hole_radius = ( width + thickness ) / 2;

            // Prepare the shape creation
            TransformRoundedEndsSegmentToPolygon( holecornersBuffer, start, end, slice, width );

            // Draw the hole
            Draw3D_ZaxisOblongCylinder( start, end, hole_radius, holeHeight,
                                        thickness, holeZpoz, scale );
        }
    }

    glNormal3f( 0.0, 0.0, 1.0 ); // Normal is Z axis

    int nlmax = g_Parm_3D_Visu.m_CopperLayersCount - 1;

    // Store here the points to approximate pad shape by segments
    std::vector<CPolyPt> polyPadShape;

    switch( GetShape() )
    {
    case PAD_CIRCLE:
        for( int layer = FIRST_COPPER_LAYER; layer <= LAST_COPPER_LAYER; layer++ )
        {
            if( layer && (layer == nlmax) )
                layer = LAYER_N_FRONT;

            if( !IsOnLayer( layer ) )
                continue;

            if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( layer ) == false )
                continue;

            SetGLColor( g_ColorsSettings.GetLayerColor( layer ) );
            int zpos = g_Parm_3D_Visu.GetLayerZcoordBIU( layer );
            int ring_radius = (m_Size.x + m_Drill.x) / 4;
            if( thickness == 0 )
                glNormal3f( 0.0, 0.0, Get3DLayer_Z_Orientation( layer ) );

            Draw3D_ZaxisCylinder(shape_pos, ring_radius,
                                  thickness, ( m_Size.x - m_Drill.x) / 2,
                                  zpos - (thickness/2), scale );
            }

        break;

    case PAD_OVAL:
        {
        wxPoint ends_offset;
        int width;
        if( m_Size.x > m_Size.y ) // Horizontal ellipse
        {
            ends_offset.x = ( m_Size.x - m_Size.y ) / 2;
            width = m_Size.y;
        }
        else // Vertical ellipse
        {
            ends_offset.y = ( m_Size.y - m_Size.x ) / 2;
            width = m_Size.x;
        }

        RotatePoint( &ends_offset, m_Orient );
        wxPoint start  = shape_pos + ends_offset;
        wxPoint end  = shape_pos - ends_offset;
        TransformRoundedEndsSegmentToPolygon( polyPadShape, start, end, slice, width );
        if( hasHole )
            polyPadShape.insert( polyPadShape.end(), holecornersBuffer.begin(), holecornersBuffer.end() );
        }
        break;

    case PAD_RECT:
    case PAD_TRAPEZOID:
        {
        wxPoint coord[5];
        BuildPadPolygon( coord, wxSize(0,0), m_Orient );
        for( int ii = 0; ii < 4; ii ++ )
        {
            CPolyPt pt( coord[ii].x + shape_pos.x, coord[ii].y+ shape_pos.y );
            polyPadShape.push_back( pt );
        }
        polyPadShape.back().end_contour = true;

        if( hasHole )
            polyPadShape.insert( polyPadShape.end(), holecornersBuffer.begin(), holecornersBuffer.end() );
        }
    break;

    default:
        break;
    }

    if( polyPadShape.size() )
    {
        for( int layer = FIRST_COPPER_LAYER; layer <= LAST_COPPER_LAYER; layer++ )
        {
            if( layer && (layer == nlmax) )
                layer = LAYER_N_FRONT;

            if( !IsOnLayer( layer ) )
                continue;

            if( g_Parm_3D_Visu.m_BoardSettings->IsLayerVisible( layer ) == false )
                continue;

            SetGLColor( g_ColorsSettings.GetLayerColor( layer ) );

            if( thickness == 0 )
                glNormal3f( 0.0, 0.0, Get3DLayer_Z_Orientation( layer ) );

            // If not hole: draw a single polygon
            int zpos = g_Parm_3D_Visu.GetLayerZcoordBIU( layer );
            if( hasHole )
            {
                Draw3D_SolidHorizontalPolygonWithHoles( polyPadShape, zpos,
                                thickness, g_Parm_3D_Visu.m_BiuTo3Dunits );
            }

            else
            {
                Draw3D_SolidHorizontalPolyPolygons( polyPadShape, zpos,
                                          thickness, g_Parm_3D_Visu.m_BiuTo3Dunits );
            }
        }
    }

}

bool Is3DLayerEnabled( int aLayer )
{
    int flg = -1;
    // see if layer needs to be shown
    // check the flags
    switch (aLayer)
    {
        case DRAW_N:
            flg=g_Parm_3D_Visu.FL_DRAWINGS;
            break;

        case COMMENT_N:
            flg=g_Parm_3D_Visu.FL_COMMENTS;
            break;

        case ECO1_N:
            flg=g_Parm_3D_Visu.FL_ECO1;
            break;

        case ECO2_N:
            flg=g_Parm_3D_Visu.FL_ECO2;
            break;
    }
    // the layer was not a layer with a flag, so show it
    if( flg < 0 )
        return true;

    // if the layer has a flag, return the flag
    return g_Parm_3D_Visu.m_DrawFlags[flg];
}


GLfloat Get3DLayer_Z_Orientation( int aLayer )
{
    double nZ;

    nZ = 1.0;

    if( ( aLayer == LAYER_N_BACK )
       || ( aLayer == ADHESIVE_N_BACK )
       || ( aLayer == SOLDERPASTE_N_BACK )
       || ( aLayer == SILKSCREEN_N_BACK )
       || ( aLayer == SOLDERMASK_N_BACK ) )
        nZ = -1.0;

    return nZ;
}
