#include <fctsys.h>
#include <kicad_string.h>
#include <wxPcbStruct.h>
#include <drawtxt.h>
#include <trigo.h>
#include <appl_wxstruct.h>
#include <3d_struct.h>
#include <macros.h>

#include <pcbnew.h>

#include <class_board.h>
#include <class_module.h>
#include <class_track.h>
#include <class_edge_mod.h>
#include <class_pcb_text.h>

#include <vector>
#include <cmath>


#define SEGM_COUNT_PER_360 32   // Number of segments to approximate a circle per segments

/* helper function:
 * some characters cannot be used in names,
 * this function change them to "_"
 */
static void ChangeIllegalCharacters( wxString & aFileName, bool aDirSepIsIllegal );


/* the dialog to create VRML files, derived from DIALOG_EXPORT_3DFILE_BASE,
 * created by wxFormBuilder
 */
#include <dialog_export_3Dfiles_base.h> // the wxFormBuilder header file

#define OPTKEY_OUTPUT_UNIT wxT("VrmlExportUnit" )
#define OPTKEY_3DFILES_OPT wxT("VrmlExport3DShapeFilesOpt" )

class DIALOG_EXPORT_3DFILE : public DIALOG_EXPORT_3DFILE_BASE
{
private:
    PCB_EDIT_FRAME* m_parent;
    wxConfig* m_config;
    int m_unitsOpt;          // to remember last option
    int m_3DFilesOpt;        // to remember last option

    void OnCancelClick( wxCommandEvent& event ){ EndModal( wxID_CANCEL ); }
    void OnOkClick( wxCommandEvent& event ){ EndModal( wxID_OK ); }

public:
    DIALOG_EXPORT_3DFILE( PCB_EDIT_FRAME* parent ) :
        DIALOG_EXPORT_3DFILE_BASE( parent )
    {
        m_parent = parent;
        m_config = wxGetApp().GetSettings();
        SetFocus();
        m_config->Read( OPTKEY_OUTPUT_UNIT, &m_unitsOpt );
        m_config->Read( OPTKEY_3DFILES_OPT, &m_3DFilesOpt );
        m_rbSelectUnits->SetSelection(m_unitsOpt);
        m_rb3DFilesOption->SetSelection(m_3DFilesOpt);
        GetSizer()->SetSizeHints( this );
        Centre();
    }
    ~DIALOG_EXPORT_3DFILE()
    {
        m_unitsOpt = GetUnits( );
        m_3DFilesOpt = Get3DFilesOption( );
        m_config->Write( OPTKEY_OUTPUT_UNIT, m_unitsOpt );
        m_config->Write( OPTKEY_3DFILES_OPT, m_3DFilesOpt );
    };

    void SetSubdir( const wxString & aDir )
    {
        m_SubdirNameCtrl->SetValue( aDir);
    }

    wxString GetSubdir( )
    {
        return m_SubdirNameCtrl->GetValue( );
    }

    wxFilePickerCtrl* FilePicker()
    {
        return m_filePicker;
    }

    int GetUnits( )
    {
        return m_unitsOpt = m_rbSelectUnits->GetSelection();
    }

    int Get3DFilesOption( )
    {
        return m_3DFilesOpt = m_rb3DFilesOption->GetSelection();
    }
};

// I use this a lot...
static const double PI2 = M_PI / 2;

// Absolutely not optimized triangle bag :D
struct VRMLPt
{
    double x, y, z;
};
struct FlatPt
{
    FlatPt( double _x = 0, double _y = 0 ) : x( _x ), y( _y )
    { }
    double x, y;
};
struct Triangle
{
    Triangle( double x1, double y1, double z1,
              double x2, double y2, double z2,
              double x3, double y3, double z3 )
    {
        p1.x = x1; p1.y = y1; p1.z = z1;
        p2.x = x2; p2.y = y2; p2.z = z2;
        p3.x = x3; p3.y = y3; p3.z = z3;
    }
    Triangle() { }
    VRMLPt p1, p2, p3;
};
typedef std::vector<Triangle> TriangleBag;

// A flat triangle fan
struct FlatFan
{
    FlatPt              c;
    std::vector<FlatPt> pts;
    void                add( double x, double y )
    {
        pts.push_back( FlatPt( x, y ) );
    }
    void bag( int layer, bool close = true );
};

// A flat quad ring
struct FlatRing
{
    std::vector<FlatPt> inner;
    std::vector<FlatPt> outer;
    void                add_inner( double x, double y )
    {
        inner.push_back( FlatPt( x, y ) );
    }

    void add_outer( double x, double y )
    {
        outer.push_back( FlatPt( x, y ) );
    }

    void bag( int layer, bool close = true );
};

// A vertical quad loop
struct VLoop
{
    std::vector<FlatPt> pts;
    double              z_top, z_bottom;
    void                add( double x, double y )
    {
        pts.push_back( FlatPt( x, y ) );
    }

    void bag( TriangleBag& triangles, bool close = true );
};

// The bags for all the layers
static TriangleBag layer_triangles[LAYER_COUNT];
static TriangleBag via_triangles[4];
static double      layer_z[LAYER_COUNT];

static void bag_flat_triangle( int layer, //{{{
                               double x1, double y1,
                               double x2, double y2,
                               double x3, double y3 )
{
    double z = layer_z[layer];

    layer_triangles[layer].push_back( Triangle( x1, y1, z, x2, y2, z, x3, y3, z ) );
}


void FlatFan::bag( int layer, bool close ) //{{{
{
    unsigned i;

    for( i = 0; i < pts.size() - 1; i++ )
        bag_flat_triangle( layer, c.x, c.y, pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y );

    if( close )
        bag_flat_triangle( layer, c.x, c.y, pts[i].x, pts[i].y, pts[0].x, pts[0].y );
}


static void bag_flat_quad( int layer, //{{{
                           double x1, double y1,
                           double x2, double y2,
                           double x3, double y3,
                           double x4, double y4 )
{
    bag_flat_triangle( layer, x1, y1, x3, y3, x2, y2 );
    bag_flat_triangle( layer, x2, y2, x3, y3, x4, y4 );
}


void FlatRing::bag( int layer, bool close ) //{{{
{
    unsigned i;

    for( i = 0; i < inner.size() - 1; i++ )
        bag_flat_quad( layer,
                       inner[i].x, inner[i].y,
                       outer[i].x, outer[i].y,
                       inner[i + 1].x, inner[i + 1].y,
                       outer[i + 1].x, outer[i + 1].y );

    if( close )
        bag_flat_quad( layer,
                       inner[i].x, inner[i].y,
                       outer[i].x, outer[i].y,
                       inner[0].x, inner[0].y,
                       outer[0].x, outer[0].y );
}


static void bag_vquad( TriangleBag& triangles, //{{{
                       double x1, double y1, double x2, double y2,
                       double z1, double z2 )
{
    triangles.push_back( Triangle( x1, y1, z1,
                                   x2, y2, z1,
                                   x2, y2, z2 ) );
    triangles.push_back( Triangle( x1, y1, z1,
                                   x2, y2, z2,
                                   x1, y1, z2 ) );
}


void VLoop::bag( TriangleBag& triangles, bool close ) //{{{
{
    unsigned i;

    for( i = 0; i < pts.size() - 1; i++ )
        bag_vquad( triangles, pts[i].x, pts[i].y,
                   pts[i + 1].x, pts[i + 1].y,
                   z_top, z_bottom );

    if( close )
        bag_vquad( triangles, pts[i].x, pts[i].y,
                   pts[0].x, pts[0].y,
                   z_top, z_bottom );
}


static void write_triangle_bag( FILE* output_file, int color_index, //{{{
                                const TriangleBag& triangles )
{
    /* A lot of nodes are not required, but blender sometimes chokes
     * without them */
    static const char* shape_boiler[] =
    {
        "Transform {\n",
        "  translation 0.0 0.0 0.0\n",
        "  rotation 1.0 0.0 0.0 0.0\n",
        "  scale 1.0 1.0 1.0\n",
        "  children [\n",
        "    Group {\n",
        "      children [\n",
        "        Shape {\n",
        "          appearance Appearance {\n",
        "            material Material {\n",
        0,                                          // Material marker
        "              ambientIntensity 0.8\n",
        "              transparency 0.2\n",
        "              shininess 0.2\n",
        "            }\n",
        "          }\n",
        "          geometry IndexedFaceSet {\n",
        "            solid TRUE\n",
        "            coord Coordinate {\n",
        "              point [\n",
        0,                                          // Coordinates marker
        "              ]\n",
        "            }\n",
        "            coordIndex [\n",
        0,                                          // Index marker
        "            ]\n",
        "          }\n",
        "        }\n",
        "      ]\n",
        "    }\n",
        "  ]\n",
        "}\n",
        0 // End marker
    };
    int marker_found = 0, lineno = 0;

    while( marker_found < 4 )
    {
        if( shape_boiler[lineno] )
            fputs( shape_boiler[lineno], output_file );
        else
        {
            marker_found++;
            switch( marker_found )
            {
            case 1: // Material marker
                fprintf( output_file,
                         "              diffuseColor %g %g %g\n",
                         (double) ColorRefs[color_index].m_Red / 255.0,
                         (double) ColorRefs[color_index].m_Green / 255.0,
                         (double) ColorRefs[color_index].m_Blue / 255.0 );
                fprintf( output_file,
                         "              specularColor %g %g %g\n",
                         (double) ColorRefs[color_index].m_Red / 255.0,
                         (double) ColorRefs[color_index].m_Green / 255.0,
                         (double) ColorRefs[color_index].m_Blue / 255.0 );
                fprintf( output_file,
                         "              emissiveColor %g %g %g\n",
                         (double) ColorRefs[color_index].m_Red / 255.0,
                         (double) ColorRefs[color_index].m_Green / 255.0,
                         (double) ColorRefs[color_index].m_Blue / 255.0 );
                break;

            case 2:
            {
                // Coordinates marker
                for( TriangleBag::const_iterator i = triangles.begin();
                     i != triangles.end();
                     i++ )
                {
                    fprintf( output_file, "%g %g %g\n",
                             i->p1.x, -i->p1.y, i->p1.z );
                    fprintf( output_file, "%g %g %g\n",
                             i->p2.x, -i->p2.y, i->p2.z );
                    fprintf( output_file, "%g %g %g\n",
                             i->p3.x, -i->p3.y, i->p3.z );
                }
            }
            break;

            case 3:
            {
                // Index marker
                // OK, that's sick ...
                int j = 0;
                for( TriangleBag::const_iterator i = triangles.begin();
                     i != triangles.end();
                     i++ )
                {
                    fprintf( output_file, "%d %d %d -1\n", j, j + 1, j + 2 );
                    j += 3;
                }
            }
            break;

            default:
                break;
            }
        }
        lineno++;
    }
}


static void compute_layer_Zs( BOARD* pcb ) //{{{
{
    int    copper_layers = pcb->GetCopperLayerCount( );

    // We call it 'layer' thickness, but it's the whole board thickness!
    double board_thickness = pcb->GetDesignSettings().GetBoardThickness();
    double half_thickness  = board_thickness / 2;

    // Compute each layer's Z value, more or less like the 3d view
    for( int i = 0; i <= LAYER_N_FRONT; i++ )
    {
        if( i < copper_layers )
            layer_z[i] = board_thickness * i / (copper_layers - 1) - half_thickness;
        else
            layer_z[i] = half_thickness; // The component layer...
    }

    /* To avoid rounding interference, we apply an epsilon to each
     * successive layer */
    const double epsilon_z = 1 * IU_PER_MILS; // That's 1 mils, about 1/50 mm
    layer_z[SOLDERPASTE_N_BACK]  = -half_thickness - epsilon_z * 4;
    layer_z[ADHESIVE_N_BACK]     = -half_thickness - epsilon_z * 3;
    layer_z[SILKSCREEN_N_BACK]   = -half_thickness - epsilon_z * 2;
    layer_z[SOLDERMASK_N_BACK]   = -half_thickness - epsilon_z;
    layer_z[SOLDERMASK_N_FRONT]  = half_thickness + epsilon_z;
    layer_z[SILKSCREEN_N_FRONT]  = half_thickness + epsilon_z * 2;
    layer_z[ADHESIVE_N_FRONT]    = half_thickness + epsilon_z * 3;
    layer_z[SOLDERPASTE_N_FRONT] = half_thickness + epsilon_z * 4;
    layer_z[DRAW_N]    = half_thickness + epsilon_z * 5;
    layer_z[COMMENT_N] = half_thickness + epsilon_z * 6;
    layer_z[ECO1_N]    = half_thickness + epsilon_z * 7;
    layer_z[ECO2_N]    = half_thickness + epsilon_z * 8;
    layer_z[EDGE_N]    = 0;
}


static void export_vrml_line( int layer, double startx, double starty, //{{{
                              double endx, double endy, double width, int divisions )
{
    double  r     = width / 2;
    double  angle = atan2( endy - starty, endx - startx );
    double  alpha;
    FlatFan fan;

    // Output the 'bone' as a triangle fan, this is the fan centre
    fan.c.x = (startx + endx) / 2;
    fan.c.y = (starty + endy) / 2;

    // The 'end' side cap
    for( alpha = angle - PI2; alpha < angle + PI2; alpha += PI2 / divisions )
        fan.add( endx + r * cos( alpha ), endy + r * sin( alpha ) );

    alpha = angle + PI2;
    fan.add( endx + r * cos( alpha ), endy + r * sin( alpha ) );
    // The 'start' side cap
    for( alpha = angle + PI2; alpha < angle + 3 * PI2; alpha += PI2 / divisions )
        fan.add( startx + r * cos( alpha ), starty + r * sin( alpha ) );

    alpha = angle + 3 * PI2;
    fan.add( startx + r * cos( alpha ), starty + r * sin( alpha ) );
    // Export the fan
    fan.bag( layer );
}


static void export_vrml_circle( int layer, double startx, double starty,
                                double endx, double endy, double width )
{
    double   hole, radius;
    FlatRing ring;

    radius = hypot( startx - endx, starty - endy ) + ( width / 2);
    hole  = radius - width;

    for( double alpha = 0; alpha < M_PI * 2; alpha += M_PI * 2 / SEGM_COUNT_PER_360 )
    {
        ring.add_inner( startx + hole * cos( alpha ), starty + hole * sin( alpha ) );
        ring.add_outer( startx + radius * cos( alpha ), starty + radius * sin( alpha ) );
    }

    ring.bag( layer );
}


static void export_vrml_slot( TriangleBag& triangles, //{{{
                              int top_layer, int bottom_layer, double xc, double yc,
                              double dx, double dy, int orient )
{
    double capx, capy; // Cap center
    VLoop  loop;
    int divisions = SEGM_COUNT_PER_360 / 2;

    loop.z_top    = layer_z[top_layer];
    loop.z_bottom = layer_z[bottom_layer];
    double angle = orient / 1800.0 * M_PI;

    if( dy > dx )
    {
        EXCHG( dx, dy );
        angle += PI2;
    }

    // The exchange above means that cutter radius is alvays dy/2
    double r = dy / 2;
    double alpha;
    // The first side cap
    capx = xc + cos( angle ) * dx / 2;
    capy = yc + sin( angle ) * dx / 2;

    for( alpha = angle - PI2; alpha < angle + PI2; alpha += PI2 / divisions )
        loop.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );

    alpha = angle + PI2;
    loop.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );

    // The other side cap
    capx = xc - cos( angle ) * dx / 2;
    capy = yc - sin( angle ) * dx / 2;

    for( alpha = angle + PI2; alpha < angle + 3 * PI2; alpha += PI2 / divisions )
        loop.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );

    alpha = angle + 3 * PI2;
    loop.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
    loop.bag( triangles );
}


static void export_vrml_hole( TriangleBag& triangles, int top_layer, int bottom_layer,
                              double xc, double yc, double hole )
{
    VLoop loop;

    loop.z_top    = layer_z[top_layer];
    loop.z_bottom = layer_z[bottom_layer];

    for( double alpha = 0; alpha < M_PI * 2; alpha += M_PI * 2 / SEGM_COUNT_PER_360 )
        loop.add( xc + cos( alpha ) * hole, yc + sin( alpha ) * hole );

    loop.bag( triangles );
}


static void export_vrml_oval_pad( int layer, double xc, double yc,
                                  double dx, double dy, int orient )
{
    double  capx, capy; // Cap center
    FlatFan fan;

    fan.c.x = xc;
    fan.c.y = yc;
    double angle = orient / 1800.0 * M_PI;
    int divisions = SEGM_COUNT_PER_360 / 2;

    if( dy > dx )
    {
        EXCHG( dx, dy );
        angle += PI2;
    }

    // The exchange above means that cutter radius is alvays dy/2
    double r = dy / 2;
    double alpha;

    // The first side cap
    capx = xc + cos( angle ) * dx / 2;
    capy = yc + sin( angle ) * dx / 2;

    for( alpha = angle - PI2; alpha < angle + PI2; alpha += PI2 / divisions )
        fan.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );

    alpha = angle + PI2;
    fan.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
    // The other side cap
    capx = xc - cos( angle ) * dx / 2;
    capy = yc - sin( angle ) * dx / 2;

    for( alpha = angle + PI2; alpha < angle + 3 * PI2; alpha += PI2 / divisions )
        fan.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );

    alpha = angle + 3 * PI2;
    fan.add( capx + r * cos( alpha ), capy + r * sin( alpha ) );
    fan.bag( layer );
}


static void export_vrml_arc( int layer, double centerx, double centery,
                             double arc_startx, double arc_starty,
                             double width, double arc_angle )
{
    FlatRing ring;
    double   start_angle = atan2( arc_starty - centery, arc_startx - centerx );

    int count = KiROUND( arc_angle / 360.0 * SEGM_COUNT_PER_360 );
    if( count < 0 )
        count = -count;
    if( count == 0 )
        count = 1;
    double divisions = arc_angle*M_PI/180.0 / count;

    double outer_radius = hypot( arc_starty - centery, arc_startx - centerx )
                          + ( width / 2);
    double inner_radius  = outer_radius - width;

    double alpha = 0;
    for( int ii = 0; ii <= count; alpha += divisions, ii++ )
    {
        double angle_rot = start_angle + alpha;
        ring.add_inner( centerx + cos( angle_rot ) * inner_radius,
                        centery + sin( angle_rot ) * inner_radius );
        ring.add_outer( centerx + cos( angle_rot ) * outer_radius,
                        centery + sin( angle_rot ) * outer_radius );
    }

    ring.bag( layer, false );
}

static void export_vrml_varc( TriangleBag& triangles,
                              int top_layer, int bottom_layer,
                              double centerx, double centery,
                              double arc_startx, double arc_starty,
                              double arc_angle )
{
    VLoop loop;

    loop.z_top    = layer_z[top_layer];
    loop.z_bottom = layer_z[bottom_layer];

    double start_angle = atan2( arc_starty - centery, arc_startx - centerx );
    double radius = hypot( arc_starty - centery, arc_startx - centerx );

    int count = KiROUND( arc_angle / 360.0 * SEGM_COUNT_PER_360 );
    if( count < 0 )
        count = -count;
    if( count == 0 )
        count = 1;
    double divisions = arc_angle*M_PI/180.0 / count;

    double alpha = 0;
    for( int ii = 0; ii <= count; alpha += divisions, ii++ )
    {
        double angle_rot = start_angle + alpha;
        loop.add( centerx + cos( angle_rot ) * radius, centery + sin( angle_rot ) * radius );
    }

    loop.bag( triangles );
}


static void export_vrml_drawsegment( DRAWSEGMENT* drawseg ) //{{{
{
    int    layer = drawseg->GetLayer();
    double w     = drawseg->GetWidth();
    double x     = drawseg->GetStart().x;
    double y     = drawseg->GetStart().y;
    double xf    = drawseg->GetEnd().x;
    double yf    = drawseg->GetEnd().y;

    // Items on the edge layer are high, not thick
    if( layer == EDGE_N )
    {
        switch( drawseg->GetShape() )
        {
        // There is a special 'varc' primitive for this
        case S_ARC:
            export_vrml_varc( layer_triangles[layer],
                              FIRST_COPPER_LAYER, LAST_COPPER_LAYER,
                              x, y, xf, yf, drawseg->GetAngle()/10 );
            break;

        // Circles on edge are usually important holes
        case S_CIRCLE:
            export_vrml_hole( layer_triangles[layer],
                              FIRST_COPPER_LAYER, LAST_COPPER_LAYER, x, y,
                              hypot( xf - x, yf - y ) / 2 );
            break;

        default:
        {
            // Simply a quad
            double z_top    = layer_z[FIRST_COPPER_LAYER];
            double z_bottom = layer_z[LAST_COPPER_LAYER];
            bag_vquad( layer_triangles[layer], x, y, xf, yf, z_top, z_bottom );
            break;
        }
        }
    }
    else
    {
        switch( drawseg->GetShape() )
        {
        case S_ARC:
            export_vrml_arc( layer,
                             (double) drawseg->GetCenter().x,
                             (double) drawseg->GetCenter().y,
                             (double) drawseg->GetArcStart().x,
                             (double) drawseg->GetArcStart().y,
                             w, drawseg->GetAngle()/10 );
            break;

        case S_CIRCLE:
            export_vrml_circle( layer, x, y, xf, yf, w );
            break;

        default:
            export_vrml_line( layer, x, y, xf, yf, w, 1 );
            break;
        }
    }
}


/* C++ doesn't have closures and neither continuation forms... this is
 * for coupling the vrml_text_callback with the common parameters */

static int s_text_layer;
static int s_text_width;
static void vrml_text_callback( int x0, int y0, int xf, int yf )
{
    export_vrml_line( s_text_layer, x0, y0, xf, yf, s_text_width, 1 );
}


static void export_vrml_pcbtext( TEXTE_PCB* text )
{
    // Coupling by globals! Ewwww...
    s_text_layer = text->GetLayer();
    s_text_width = text->m_Thickness;

    wxSize size = text->m_Size;
    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, BLACK,
                             txt, text->GetOrientation(), size,
                             text->m_HJustify, text->m_VJustify,
                             text->m_Thickness, text->m_Italic,
                             true,
                             vrml_text_callback );
            pos += offset;
        }

        delete (list);
    }
    else
    {
        DrawGraphicText( NULL, NULL, text->m_Pos, BLACK,
                         text->m_Text, text->GetOrientation(), size,
                         text->m_HJustify, text->m_VJustify,
                         text->m_Thickness, text->m_Italic,
                         true,
                         vrml_text_callback );
    }
}


static void export_vrml_drawings( BOARD* pcb ) //{{{
{
    // draw graphic items
    for( EDA_ITEM* drawing = pcb->m_Drawings;  drawing != 0;  drawing = drawing->Next() )
    {
        switch( drawing->Type() )
        {
        case PCB_LINE_T:
            export_vrml_drawsegment( (DRAWSEGMENT*) drawing );
            break;

        case PCB_TEXT_T:
            export_vrml_pcbtext( (TEXTE_PCB*) drawing );
            break;

        default:
            break;
        }
    }
}


static void export_round_padstack( BOARD* pcb, double x, double y, double r, //{{{
                                   int bottom_layer, int top_layer )
{
    int copper_layers = pcb->GetCopperLayerCount( );

    for( int layer = bottom_layer; layer < copper_layers; layer++ )
    {
        // The last layer is always the component one, unless it's single face
        if( (layer > FIRST_COPPER_LAYER) && (layer == copper_layers - 1) )
            layer = LAST_COPPER_LAYER;

        if( layer <= top_layer )
            export_vrml_circle( layer, x, y, x + r / 2, y, r );
    }
}


static void export_vrml_via( BOARD* pcb, SEGVIA* via ) //{{{
{
    double x, y, r, hole;
    int    top_layer, bottom_layer;

    r    = via->m_Width / 2;
    hole = via->GetDrillValue() / 2;
    x    = via->m_Start.x;
    y    = via->m_Start.y;
    via->ReturnLayerPair( &top_layer, &bottom_layer );

    // Export the via padstack
    export_round_padstack( pcb, x, y, r, bottom_layer, top_layer );

    // Drill a hole
    export_vrml_hole( via_triangles[via->m_Shape], top_layer, bottom_layer, x, y, hole );
}


static void export_vrml_tracks( BOARD* pcb ) //{{{
{
    for( TRACK* track = pcb->m_Track; track != NULL; track = track->Next() )
    {
        if( track->Type() == PCB_VIA_T )
            export_vrml_via( pcb, (SEGVIA*) track );
        else
            export_vrml_line( track->GetLayer(), track->m_Start.x, track->m_Start.y,
                              track->m_End.x, track->m_End.y, track->m_Width, 4 );
    }
}


/* not used? @todo complete
static void export_vrml_zones( BOARD* pcb )
{
    // Export fill segments
    for( SEGZONE* segzone = pcb->m_Zone;
        segzone != 0;
        segzone = segzone->Next() )
    {
        // Fill tracks are exported with low subdivisions
        if( segzone->Type() == PCB_ZONE_T )
            export_vrml_line( segzone->GetLayer(), segzone->m_Start.x, segzone->m_Start.y,
                              segzone->m_End.x, segzone->m_End.y, segzone->m_Width, 1 );
    }

    // Export zone outlines
    for( int i = 0; i < pcb->GetAreaCount(); i++ )
    {
        ZONE_CONTAINER* zone = pcb->GetArea( i );

        if( ( zone->m_FilledPolysList.size() == 0 )
           ||( zone->m_ZoneMinThickness <= 1 ) )
            continue;

        int width = zone->m_ZoneMinThickness;
        if( width > 0 )
        {
            int      imax  = zone->m_FilledPolysList.size() - 1;
            int      layer = zone->GetLayer();
            CPolyPt* firstcorner = &zone->m_FilledPolysList[0];
            CPolyPt* begincorner = firstcorner;

            // I'm not really positive about what he's doing here...
            for( int ic = 1; ic <= imax; ic++ )
            {
                CPolyPt* endcorner = &zone->m_FilledPolysList[ic];
                if( begincorner->utility == 0 ) // Draw only basic outlines, not extra segments
                    export_vrml_line( layer, begincorner->x, begincorner->y,
                                      endcorner->x, endcorner->y, width, 1 );
                if( (endcorner->end_contour) || (ic == imax) )  // the last corner of a filled area is found: draw it
                {
                    if( endcorner->utility == 0 )               // Draw only basic outlines, not extra segments
                        export_vrml_line( layer, endcorner->x, endcorner->y,
                                          firstcorner->x, firstcorner->y, width, 1 );
                    ic++;

                    // A new contour?
                    if( ic < imax - 1 )
                        begincorner = firstcorner = &zone->m_FilledPolysList[ic];
                }
                else
                    begincorner = endcorner;
            }
        }
    }
}
*/

static void export_vrml_text_module( TEXTE_MODULE* module ) //{{{
{
    if( module->IsVisible() )
    {
        wxSize size = module->m_Size;

        if( module->m_Mirror )
            NEGATE( size.x ); // Text is mirrored

        s_text_layer = module->GetLayer();
        s_text_width = module->m_Thickness;
        DrawGraphicText( NULL, NULL, module->m_Pos, BLACK,
                         module->m_Text, module->GetDrawRotation(), size,
                         module->m_HJustify, module->m_VJustify,
                         module->m_Thickness, module->m_Italic,
                         true,
                         vrml_text_callback );
    }
}


static void export_vrml_edge_module( EDGE_MODULE* aOutline ) //{{{
{
    int    layer = aOutline->GetLayer();
    double x     = aOutline->GetStart().x;
    double y     = aOutline->GetStart().y;
    double xf    = aOutline->GetEnd().x;
    double yf    = aOutline->GetEnd().y;
    double w     = aOutline->GetWidth();

    switch( aOutline->GetShape() )
    {
    case S_ARC:
        export_vrml_arc( layer, x, y, xf, yf, w, aOutline->GetAngle()/10 );
        break;

    case S_CIRCLE:
        export_vrml_circle( layer, x, y, xf, yf, w );
        break;

    default:
        export_vrml_line( layer, x, y, xf, yf, w, 1 );
        break;
    }
}


static void export_vrml_pad( BOARD* pcb, D_PAD* aPad ) //{{{
{
    double hole_drill_w = (double) aPad->GetDrillSize().x / 2;
    double hole_drill_h = (double) aPad->GetDrillSize().y / 2;
    double hole_drill   = MIN( hole_drill_w, hole_drill_h );
    double hole_x = aPad->GetPosition().x;
    double hole_y = aPad->GetPosition().y;

    // Export the hole on the edge layer
    if( hole_drill > 0 )
    {
        if( aPad->GetDrillShape() == PAD_OVAL )
        {
            // Oblong hole (slot)
            export_vrml_slot( layer_triangles[EDGE_N],
                              FIRST_COPPER_LAYER, LAST_COPPER_LAYER,
                              hole_x, hole_y, hole_drill_w, hole_drill_h, aPad->GetOrientation() );
        }
        else
        {
            // Drill a round hole
            export_vrml_hole( layer_triangles[EDGE_N],
                              FIRST_COPPER_LAYER, LAST_COPPER_LAYER,
                              hole_x, hole_y, hole_drill );
        }
    }

    // The pad proper, on the selected layers
    int         layer_mask    = aPad->GetLayerMask();
    int         copper_layers = pcb->GetCopperLayerCount( );

    // The (maybe offseted) pad position
    wxPoint     pad_pos   = aPad->ReturnShapePos();
    double      pad_x     = pad_pos.x;
    double      pad_y     = pad_pos.y;
    wxSize      pad_delta = aPad->GetDelta();

    double      pad_dx    = pad_delta.x / 2;
    double      pad_dy    = pad_delta.y / 2;

    double      pad_w     = aPad->GetSize().x / 2;
    double      pad_h     = aPad->GetSize().y / 2;

    for( int layer = FIRST_COPPER_LAYER; layer < copper_layers; layer++ )
    {
        // The last layer is always the component one, unless it's single face
        if( (layer > FIRST_COPPER_LAYER) && (layer == copper_layers - 1) )
            layer = LAST_COPPER_LAYER;

        if( layer_mask & (1 << layer) )
        {
            // OK, the pad is on this layer, export it
            switch( aPad->GetShape() )
            {
            case PAD_CIRCLE:
                export_vrml_circle( layer, pad_x, pad_y,
                                    pad_x + pad_w / 2, pad_y, pad_w );
                break;

            case PAD_OVAL:
                export_vrml_oval_pad( layer, pad_x, pad_y,
                                      pad_w * 2, pad_h * 2, aPad->GetOrientation() );
                break;

            case PAD_RECT:
                // Just to be sure :D
                pad_dx = 0;
                pad_dy = 0;

            case PAD_TRAPEZOID:
                {
                    int coord[8] =
                    {
                        KiROUND(-pad_w - pad_dy), KiROUND(+pad_h + pad_dx),
                        KiROUND(-pad_w + pad_dy), KiROUND(-pad_h - pad_dx),
                        KiROUND(+pad_w - pad_dy), KiROUND(+pad_h - pad_dx),
                        KiROUND(+pad_w + pad_dy), KiROUND(-pad_h + pad_dx),
                    };

                    for( int i = 0; i < 4; i++ )
                    {
                        RotatePoint( &coord[i * 2], &coord[i * 2 + 1], aPad->GetOrientation() );
                        coord[i * 2]     += KiROUND( pad_x );
                        coord[i * 2 + 1] += KiROUND( pad_y );
                    }

                    bag_flat_quad( layer, coord[0], coord[1],
                                   coord[2], coord[3],
                                   coord[4], coord[5],
                                   coord[6], coord[7] );
                }
                break;

            default:
                ;
            }
        }
    }
}


// From axis/rot to quaternion
static void build_quat( double x, double y, double z, double a, double q[4] )
{
    double sina = sin( a / 2 );

    q[0] = x * sina;
    q[1] = y * sina;
    q[2] = z * sina;
    q[3] = cos( a / 2 );
}


// From quaternion to axis/rot
static void from_quat( double q[4], double rot[4] )
{
    rot[3] = acos( q[3] ) * 2;

    for( int i = 0; i < 3; i++ )
    {
        rot[i] = q[i] / sin( rot[3] / 2 );
    }
}


// Quaternion composition
static void compose_quat( double q1[4], double q2[4], double qr[4] )
{
    double tmp[4];

    tmp[0] = q2[3] *q1[0] + q2[0] *q1[3] + q2[1] *q1[2] - q2[2] *q1[1];
    tmp[1] = q2[3] *q1[1] + q2[1] *q1[3] + q2[2] *q1[0] - q2[0] *q1[2];
    tmp[2] = q2[3] *q1[2] + q2[2] *q1[3] + q2[0] *q1[1] - q2[1] *q1[0];
    tmp[3] = q2[3] *q1[3] - q2[0] *q1[0] - q2[1] *q1[1] - q2[2] *q1[2];
    qr[0]  = tmp[0]; qr[1] = tmp[1];
    qr[2]  = tmp[2]; qr[3] = tmp[3];
}


static void export_vrml_module( BOARD* aPcb, MODULE* aModule,
                                FILE* aOutputFile, double aScalingFactor,
                                bool aExport3DFiles, const wxString & a3D_Subdir )
{
    // Reference and value
    export_vrml_text_module( aModule->m_Reference );
    export_vrml_text_module( aModule->m_Value );

    // Export module edges
    for( EDA_ITEM* item = aModule->m_Drawings;  item != NULL;  item = item->Next() )
    {
        switch( item->Type() )
        {
        case PCB_MODULE_TEXT_T:
            export_vrml_text_module( dynamic_cast<TEXTE_MODULE*>(item) );
            break;

        case PCB_MODULE_EDGE_T:
            export_vrml_edge_module( dynamic_cast<EDGE_MODULE*>(item) );
            break;

        default:
            break;
        }
    }

    // Export pads
    for( D_PAD* pad = aModule->m_Pads;  pad; pad = pad->Next() )
        export_vrml_pad( aPcb, pad );

    bool isFlipped = aModule->GetLayer() == LAYER_N_BACK;

    // Export the object VRML model(s)
    for( S3D_MASTER* vrmlm = aModule->m_3D_Drawings; vrmlm != 0; vrmlm = vrmlm->Next() )
    {
        wxString fname = vrmlm->m_Shape3DName;

        if( fname.IsEmpty() )
            continue;

        if( ! wxFileName::FileExists( fname ) )
        {
            wxFileName fn = fname;
            fname = wxGetApp().FindLibraryPath( fn );

            if( fname.IsEmpty() )   // keep "short" name if full filemane not found
                fname = vrmlm->m_Shape3DName;
        }

        fname.Replace(wxT("\\"), wxT("/" ) );
        wxString source_fname = fname;

        if( aExport3DFiles )    // Change illegal characters in short filename
        {
            ChangeIllegalCharacters( fname, true );
            fname = a3D_Subdir + wxT("/") + fname;

            if( !wxFileExists( fname ) )
                wxCopyFile( source_fname, fname );
        }

        /* Calculate 3D shape rotation:
         * this is the rotation parameters, with an additional 180 deg rotation
         * for footprints that are flipped
         * When flipped, axis rotation is the horizontal axis (X axis)
         */
        double rotx = - vrmlm->m_MatRotation.x;
        double roty = - vrmlm->m_MatRotation.y;
        double rotz = - vrmlm->m_MatRotation.z;

        if ( isFlipped )
        {
            rotx += 180.0;
            NEGATE(roty);
            NEGATE(rotz);
        }

        // Do some quaternion munching
        double q1[4], q2[4], rot[4];
        build_quat( 1, 0, 0, rotx / 180.0 * M_PI, q1 );
        build_quat( 0, 1, 0, roty / 180.0 * M_PI, q2 );
        compose_quat( q1, q2, q1 );
        build_quat( 0, 0, 1, rotz / 180.0 * M_PI, q2 );
        compose_quat( q1, q2, q1 );
        // Note here aModule->GetOrientation() is in 0.1 degrees,
        // so module rotation is aModule->GetOrientation() / 1800.0
        build_quat( 0, 0, 1, aModule->GetOrientation() / 1800.0 * M_PI, q2 );
        compose_quat( q1, q2, q1 );
        from_quat( q1, rot );

        fprintf( aOutputFile, "Transform {\n" );
        // A null rotation would fail the acos!
        if( rot[3] != 0.0 )
        {
            fprintf( aOutputFile, "  rotation %g %g %g %g\n", rot[0], rot[1], rot[2], rot[3] );
        }

        fprintf( aOutputFile, "  scale %g %g %g\n",
                 vrmlm->m_MatScale.x * aScalingFactor,
                 vrmlm->m_MatScale.y * aScalingFactor,
                 vrmlm->m_MatScale.z * aScalingFactor );

        // adjust 3D shape offset position (offset is given in inch)
        #define UNITS_3D_TO_PCB_UNITS (IU_PER_MILS*1000)
        int offsetx = KiROUND( vrmlm->m_MatPosition.x * UNITS_3D_TO_PCB_UNITS );
        int offsety = KiROUND( vrmlm->m_MatPosition.y * UNITS_3D_TO_PCB_UNITS );
        double offsetz = vrmlm->m_MatPosition.z * UNITS_3D_TO_PCB_UNITS;

        if ( isFlipped )
            NEGATE(offsetz);
        else    // In normal mode, Y axis is reversed in Pcbnew.
            NEGATE(offsety);

        RotatePoint(&offsetx, &offsety, aModule->GetOrientation());

        fprintf( aOutputFile, "  translation %g %g %g\n",
                 (double) (offsetx + aModule->m_Pos.x),
                 - (double)(offsety + aModule->m_Pos.y),    // Y axis is reversed in Pcbnew
                 offsetz + layer_z[aModule->GetLayer()] );
        fprintf( aOutputFile,
                 "  children [\n    Inline {\n      url \"%s\"\n    } ]\n",
                 TO_UTF8( fname ) );
        fprintf( aOutputFile, "  }\n" );
    }
}


static void write_and_empty_triangle_bag( FILE* output_file, TriangleBag& triangles, int color )
{
    if( !triangles.empty() )
    {
        write_triangle_bag( output_file, color, triangles );
        triangles.clear( );
    }
}


/**
 * Function OnExportVRML
 * will export the current BOARD to a VRML file.
 */
void PCB_EDIT_FRAME::OnExportVRML( wxCommandEvent& event )
{
    wxFileName fn;
    static wxString subDirFor3Dshapes = wxT("shapes3D");
    double scaleList[3] = { 1.0, 25.4, 25.4/1000 };

    // Build default file name
    wxString ext = wxT( "wrl" );
    fn = GetBoard()->GetFileName();
    fn.SetExt( ext );

    DIALOG_EXPORT_3DFILE dlg( this );
    dlg.FilePicker()->SetPath( fn.GetFullPath() );
    dlg.SetSubdir( subDirFor3Dshapes );

    if( dlg.ShowModal() != wxID_OK )
        return;

    double scale = scaleList[dlg.GetUnits( )];     // final scale export
    bool export3DFiles = dlg.Get3DFilesOption( ) == 0;

wxBusyCursor dummy;

    wxString fullFilename = dlg.FilePicker()->GetPath();
    subDirFor3Dshapes = dlg.GetSubdir();

    if( ! wxDirExists( subDirFor3Dshapes ) )
        wxMkdir( subDirFor3Dshapes );

    if( ! ExportVRML_File( fullFilename, scale, export3DFiles, subDirFor3Dshapes ) )
    {
        wxString msg = _( "Unable to create " ) + fullFilename;
        wxMessageBox( msg );
        return;
    }
}

/**
 * Function ExportVRML_File
 * Creates the file(s) exporting current BOARD to a VRML file.
 * @param aFullFileName = the full filename of the file to create
 * @param aScale = the general scaling factor. 1.0 to export in inch
 * @param aExport3DFiles = true to copy 3D shapes in the subdir a3D_Subdir
 * @param a3D_Subdir = sub directory where 3D shapes files are copied
 * used only when aExport3DFiles == true
 * @return true if Ok.
 */
/* When copying 3D shapes files, the new filename is build from
 * the full path name, changing the separators by underscore.
 * this is needed because files with the same shortname can exist in different directories
 */
bool PCB_EDIT_FRAME::ExportVRML_File( const wxString & aFullFileName,
                                      double aScale, bool aExport3DFiles,
                                      const wxString & a3D_Subdir )
{
    wxString   msg;
    FILE*      output_file;
    BOARD*     pcb = GetBoard();

    output_file = wxFopen( aFullFileName, wxT( "wt" ) );
    if( output_file == NULL )
        return false;

    // Switch the locale to standard C (needed to print floating point numbers like 1.3)
    SetLocaleTo_C_standard();

    // Begin with the usual VRML boilerplate
    wxString name = aFullFileName;

    name.Replace(wxT("\\"), wxT("/" ) );
    ChangeIllegalCharacters( name, false );
    fprintf( output_file, "#VRML V2.0 utf8\n"
                          "WorldInfo {\n"
                          "  title \"%s - Generated by Pcbnew\"\n"
                          "}\n", TO_UTF8( name ) );

    /* The would be in decimils and not in meters, as the standard wants.
     * It is trivial to embed everything in a transform node to
     * fix it. For example here we build the world in inches...
    */

    /* scaling factor to convert internal units (decimils) to inches
    */
    double board_scaling_factor = 0.0001;

    /* auxiliary scale to export to a different scale.
     */
    double general_scaling_factor = board_scaling_factor * aScale;
    fprintf( output_file, "Transform {\n" );
    fprintf( output_file, "  scale %g %g %g\n",
             general_scaling_factor, general_scaling_factor, general_scaling_factor );

    /* Define the translation to have the board centre to the 2D axis origin
     * more easy for rotations...
     */
    EDA_RECT bbbox = pcb->ComputeBoundingBox();

    double dx = board_scaling_factor * bbbox.Centre().x * aScale;
    double dy = board_scaling_factor * bbbox.Centre().y * aScale;

    fprintf( output_file, "  translation %g %g 0.0\n", -dx, dy );

    fprintf( output_file, "  children [\n" );

    /* scaling factor to convert 3D models to board units (decimils)
     * Usually we use Wings3D to create thems.
     * One can consider the 3D units is 0.1 inch
     * So the scaling factor from 0.1 inch to board units
     * is 0.1 / board_scaling_factor
     */

    double wrml_3D_models_scaling_factor = 0.1 / board_scaling_factor;
    // Preliminary computation: the z value for each layer
    compute_layer_Zs( pcb );

    // Drawing and text on the board, and edges which are special
    export_vrml_drawings( pcb );

    // Export vias and trackage
    export_vrml_tracks( pcb );

    // Export zone fills
/* TODO    export_vrml_zones(pcb);
*/

    // Export footprints
    for( MODULE* module = pcb->m_Modules; module != 0; module = module->Next() )
        export_vrml_module( pcb, module, output_file,
                            wrml_3D_models_scaling_factor,
                            aExport3DFiles, a3D_Subdir );

    /* Output the bagged triangles for each layer
     * Each layer will be a separate shape */
    for( int layer = 0; layer < LAYER_COUNT; layer++ )
        write_and_empty_triangle_bag( output_file,
                                      layer_triangles[layer],
                                      pcb->GetLayerColor(layer) );

    // Same thing for the via layers
    for( int i = 0; i < 4; i++ )
        write_and_empty_triangle_bag( output_file,
                                      via_triangles[i],
                                      pcb->GetVisibleElementColor( VIAS_VISIBLE + i ) );

    // Close the outer 'transform' node
    fputs( "]\n}\n", output_file );

    // End of work
    fclose( output_file );
    SetLocaleTo_Default();       // revert to the current  locale

    return true;
}

/*
 * some characters cannot be used in filenames,
 * this function change them to "_"
 */
static void ChangeIllegalCharacters( wxString & aFileName, bool aDirSepIsIllegal )
{
    if( aDirSepIsIllegal )
        aFileName.Replace(wxT("/"), wxT("_" ) );

    aFileName.Replace(wxT(" "), wxT("_" ) );
    aFileName.Replace(wxT(":"), wxT("_" ) );
}
