/////////////////////////////////////////////////////////////////////////////
// Name:        3d_canvas.cpp
/////////////////////////////////////////////////////////////////////////////


#ifdef __GNUG__
#pragma implementation
#pragma interface
#endif

#include "fctsys.h"
#include "trigo.h"

#include "wx/image.h"

#if !wxUSE_GLCANVAS
#error Please set wxUSE_GLCANVAS to 1 in setup.h.
#endif

#include "wx/dataobj.h"
#include "wx/clipbrd.h"

#include "fctsys.h"
#include "common.h"
#include "id.h"

#include "3d_viewer.h"
#include "trackball.h"

/* Tool and button Bitmaps */
#define XPM_3D_MAIN
#include "bitmaps3d.h"
#include "bitmaps.h"

enum onrclick_id
{
	ID_POPUP_3D_VIEW_START = 2000,
	ID_POPUP_ZOOMIN,
	ID_POPUP_ZOOMOUT,
	ID_POPUP_VIEW_XPOS,
	ID_POPUP_VIEW_XNEG,
	ID_POPUP_VIEW_YPOS,
	ID_POPUP_VIEW_YNEG,
	ID_POPUP_VIEW_ZPOS,
	ID_POPUP_VIEW_ZNEG,
	ID_POPUP_MOVE3D_LEFT,
	ID_POPUP_MOVE3D_RIGHT,
	ID_POPUP_MOVE3D_UP,
	ID_POPUP_MOVE3D_DOWN,
	ID_POPUP_3D_VIEW_END
};


/*
 * Pcb3D_GLCanvas implementation
 */

BEGIN_EVENT_TABLE(Pcb3D_GLCanvas, wxGLCanvas)
    EVT_SIZE(Pcb3D_GLCanvas::OnSize)
    EVT_PAINT(Pcb3D_GLCanvas::OnPaint)
    EVT_CHAR(Pcb3D_GLCanvas::OnChar)
    EVT_MOUSE_EVENTS(Pcb3D_GLCanvas::OnMouseEvent)
    EVT_ERASE_BACKGROUND(Pcb3D_GLCanvas::OnEraseBackground)
	EVT_MENU_RANGE(ID_POPUP_3D_VIEW_START, ID_POPUP_3D_VIEW_END,
		Pcb3D_GLCanvas::OnPopUpMenu)
END_EVENT_TABLE()

/*************************************************************************/
Pcb3D_GLCanvas::Pcb3D_GLCanvas(WinEDA3D_DrawFrame *parent, wxWindowID id,
			int* gl_attrib):
  wxGLCanvas(parent, id, wxPoint(-1,-1), wxSize(-1,-1), 0, wxT("Pcb3D_glcanvas"), gl_attrib)
/*************************************************************************/
{
	m_init = FALSE;
	m_gllist = 0;
	m_Parent = parent;
	DisplayStatus();
}


/*************************************/
Pcb3D_GLCanvas::~Pcb3D_GLCanvas(void)
/*************************************/
{
	ClearLists();
}

/*************************************/
void Pcb3D_GLCanvas::ClearLists(void)
/*************************************/
{
	if( m_gllist > 0 )
		glDeleteLists(m_gllist, 1);
	m_init = FALSE;
	m_gllist = 0;
}

/*********************************************/
void Pcb3D_GLCanvas::OnChar(wxKeyEvent& event)
/*********************************************/
{
	SetView3D(event.GetKeyCode());
	event.Skip();
}

/*********************************************/
void Pcb3D_GLCanvas::SetView3D(int keycode)
/*********************************************/
{
int ii;
double delta_move = 0.7 * g_Parm_3D_Visu.m_Zoom;

    switch(keycode)
	{
		case WXK_LEFT:
			g_Draw3d_dx -= delta_move;
			break;
		case WXK_RIGHT:
			g_Draw3d_dx += delta_move;
			break;
		case WXK_UP:
			g_Draw3d_dy += delta_move;
			break;
		case WXK_DOWN:
			g_Draw3d_dy -= delta_move;
			break;

		case WXK_HOME:
			g_Parm_3D_Visu.m_Zoom = 1.0;
			g_Draw3d_dx = g_Draw3d_dy = 0;
			trackball( g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
		break;

		case WXK_END:
			break;

		case WXK_F1:
			g_Parm_3D_Visu.m_Zoom /= 1.4;
			if ( g_Parm_3D_Visu.m_Zoom <= 0.01)
				g_Parm_3D_Visu.m_Zoom = 0.01;
			break;

		case WXK_F2:
			g_Parm_3D_Visu.m_Zoom *= 1.4;
			break;

		case '+':
			break;

		case '-':
			break;

		case 'r':
		case 'R':
			g_Draw3d_dx = g_Draw3d_dy = 0;
			for ( ii = 0; ii < 4; ii++ )
				g_Parm_3D_Visu.m_Rot[ii] = 0.0;
			trackball(g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
			break;

		case 'x':
			for ( ii = 0; ii < 4; ii++ )
				g_Parm_3D_Visu.m_Rot[ii] = 0.0;
			trackball(g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
			g_Parm_3D_Visu.m_ROTZ = -90;
			g_Parm_3D_Visu.m_ROTX = -90;
			break;

		case 'X':
			for ( ii = 0; ii < 4; ii++ )
				g_Parm_3D_Visu.m_Rot[ii] = 0.0;
			trackball(g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
			g_Parm_3D_Visu.m_ROTZ = 90;
			g_Parm_3D_Visu.m_ROTX = -90;
			break;

		case 'y':
			for ( ii = 0; ii < 4; ii++ )
				g_Parm_3D_Visu.m_Rot[ii] = 0.0;
			trackball(g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
			g_Parm_3D_Visu.m_ROTX = -90;
			break;

		case 'Y':
			for ( ii = 0; ii < 4; ii++ )
				g_Parm_3D_Visu.m_Rot[ii] = 0.0;
			trackball( g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
			g_Parm_3D_Visu.m_ROTX = -90;
			g_Parm_3D_Visu.m_ROTZ = -180;
			break;

		case 'z':
			for ( ii = 0; ii < 4; ii++ )
				g_Parm_3D_Visu.m_Rot[ii] = 0.0;
			trackball(g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
			break;

		case 'Z':
			for ( ii = 0; ii < 4; ii++ )
				g_Parm_3D_Visu.m_Rot[ii] = 0.0;
			trackball(g_Parm_3D_Visu.m_Quat, 0.0, 0.0, 0.0, 0.0 );
			g_Parm_3D_Visu.m_ROTX = -180;
			break;

		default:
			return;
		}

	DisplayStatus();
    Refresh(FALSE);
}

/********************************************************/
void Pcb3D_GLCanvas::OnMouseEvent(wxMouseEvent& event)
/********************************************************/
{
wxSize size(GetClientSize());
float spin_quat[4];


	if( event.RightDown() )
		{
		OnRightClick(event); return;
		}

	if ( event.m_wheelRotation )
	{
        if( event.ShiftDown() ) {
            if ( event.GetWheelRotation() < 0 ) {
               /* up */
               SetView3D(WXK_UP);
            } else {
               /* down */
               SetView3D(WXK_DOWN);
           }
        } else if( event.ControlDown() ) {
            if ( event.GetWheelRotation() > 0 ) {
                /* right */
                SetView3D(WXK_RIGHT);
            } else {
                /* left */
                SetView3D(WXK_LEFT);
            }
        }
        else {
            if ( event.GetWheelRotation() > 0 )
            {
                g_Parm_3D_Visu.m_Zoom /= 1.4;
                if ( g_Parm_3D_Visu.m_Zoom <= 0.01)
                    g_Parm_3D_Visu.m_Zoom = 0.01;
            }
            else g_Parm_3D_Visu.m_Zoom *= 1.4;
            DisplayStatus();
            Refresh(FALSE);
        }
 	}

    if (event.Dragging())
    {
        /* drag in progress, simulate trackball */
        trackball(spin_quat,
          (2.0*g_Parm_3D_Visu.m_Beginx - size.x) / size.x,
          (size.y - 2.0*g_Parm_3D_Visu.m_Beginy) / size.y,
          (     2.0*event.GetX() - size.x) / size.x,
          (    size.y - 2.0*event.GetY()) / size.y);

        add_quats( spin_quat, g_Parm_3D_Visu.m_Quat, g_Parm_3D_Visu.m_Quat );

        /* orientation has changed, redraw mesh */
		DisplayStatus();
		Refresh(FALSE);
    }

    g_Parm_3D_Visu.m_Beginx = event.GetX();
    g_Parm_3D_Visu.m_Beginy = event.GetY();
}

/*******************************************************/
void Pcb3D_GLCanvas::OnRightClick(wxMouseEvent& event)
/*******************************************************/
/* Construit et affiche un menu Popup lorsque on actionne le bouton droit
	de la souris
*/
{
wxPoint pos;
wxMenu PopUpMenu;

	pos.x = event.GetX(); pos.y = event.GetY();
	wxMenuItem *item = new wxMenuItem(&PopUpMenu, ID_POPUP_ZOOMIN,
					 _("Zoom +"));
	item->SetBitmap(zoom_in_xpm);
	PopUpMenu.Append(item);

	item = new wxMenuItem(&PopUpMenu, ID_POPUP_ZOOMOUT,
					 _("Zoom -"));
	item->SetBitmap(zoom_out_xpm);
	PopUpMenu.Append(item);

	PopUpMenu.AppendSeparator();
	item = new wxMenuItem(&PopUpMenu, ID_POPUP_VIEW_ZPOS,
					 _("Top View"));
	item->SetBitmap(axis3d_top_xpm);
	PopUpMenu.Append(item);

	item = new wxMenuItem(&PopUpMenu, ID_POPUP_VIEW_ZNEG,
					 _("Bottom View"));
	item->SetBitmap(axis3d_bottom_xpm);
	PopUpMenu.Append(item);

	PopUpMenu.AppendSeparator();
	item = new wxMenuItem(&PopUpMenu, ID_POPUP_VIEW_XPOS,
					 _("Right View"));
	item->SetBitmap(axis3d_right_xpm);
	PopUpMenu.Append(item);

	item = new wxMenuItem(&PopUpMenu, ID_POPUP_VIEW_XNEG,
					 _("Left View"));
	item->SetBitmap(axis3d_left_xpm);
	PopUpMenu.Append(item);


	PopUpMenu.AppendSeparator();
	item = new wxMenuItem(&PopUpMenu, ID_POPUP_VIEW_YPOS,
					 _("Front View"));
	item->SetBitmap(axis3d_front_xpm);
	PopUpMenu.Append(item);

	item = new wxMenuItem(&PopUpMenu, ID_POPUP_VIEW_YNEG,
					 _("Back View"));
	item->SetBitmap(axis3d_back_xpm);
	PopUpMenu.Append(item);

	PopUpMenu.AppendSeparator();
	item = new wxMenuItem(&PopUpMenu, ID_POPUP_MOVE3D_LEFT,
					 _("Move left <-"));
	item->SetBitmap(left_xpm);
	PopUpMenu.Append(item);

	item = new wxMenuItem(&PopUpMenu, ID_POPUP_MOVE3D_RIGHT,
					 _("Move right ->"));
	item->SetBitmap(right_xpm);
	PopUpMenu.Append(item);

	item = new wxMenuItem(&PopUpMenu, ID_POPUP_MOVE3D_UP,
					 _("Move Up ^"));
	item->SetBitmap(up_xpm);
	PopUpMenu.Append(item);

	item = new wxMenuItem(&PopUpMenu, ID_POPUP_MOVE3D_DOWN,
					 _("Move Down"));
	item->SetBitmap(down_xpm);
	PopUpMenu.Append(item);

	PopupMenu( &PopUpMenu, pos);
}

/*******************************************************/
void Pcb3D_GLCanvas::OnPopUpMenu(wxCommandEvent & event)
/*******************************************************/
{
int key = 0;

	switch( event.GetId() )
	{
		case ID_POPUP_ZOOMIN:
			key = WXK_F1;
			break;

		case ID_POPUP_ZOOMOUT:
			key = WXK_F2;
			break;

		case ID_POPUP_VIEW_XPOS:
			key = 'x';
			break;

		case ID_POPUP_VIEW_XNEG:
			key = 'X';
			break;

		case ID_POPUP_VIEW_YPOS:
			key = 'y';
			break;

		case ID_POPUP_VIEW_YNEG:
			key = 'Y';
			break;

		case ID_POPUP_VIEW_ZPOS:
			key = 'z';
			break;

		case ID_POPUP_VIEW_ZNEG:
			key = 'Z';
			break;

		case ID_POPUP_MOVE3D_LEFT:
			key = WXK_LEFT;
			break;
		
		case ID_POPUP_MOVE3D_RIGHT:
			key = WXK_RIGHT;
			break;
		
		case ID_POPUP_MOVE3D_UP:
			key = WXK_UP;
			break;
		
		case ID_POPUP_MOVE3D_DOWN:
			key = WXK_DOWN;
			break;

		default:
			return;
	}
	SetView3D(key);
}


/***************************************/
void Pcb3D_GLCanvas::DisplayStatus(void)
/***************************************/
{
wxString msg;
	msg.Printf(wxT("dx %3.2f"), g_Draw3d_dx);
	m_Parent->SetStatusText(msg,1);

	msg.Printf(wxT("dy %3.2f"), g_Draw3d_dy);
	m_Parent->SetStatusText(msg,2);

	msg.Printf(wxT("View: %3.1f"), 45 * g_Parm_3D_Visu.m_Zoom);
	m_Parent->SetStatusText(msg,3);
}


/*************************************************/
void Pcb3D_GLCanvas::OnPaint( wxPaintEvent& event )
/*************************************************/
{
   wxPaintDC dc(this);

#ifndef __WXMOTIF__
    if (!GetContext()) return;
#endif
	Redraw();
	event.Skip();
}


/**********************************************/
void Pcb3D_GLCanvas::OnSize(wxSizeEvent& event)
/**********************************************/
{
    // this is also necessary to update the context on some platforms
    wxGLCanvas::OnSize(event);

    // set GL viewport (not called by wxGLCanvas::OnSize on all platforms...)
    int w, h;
    GetClientSize(&w, &h);
#ifndef __WXMOTIF__
    if (GetContext())
#endif
    {
        SetCurrent();
        glViewport(0, 0, (GLint) w, (GLint) h);
    }
}

/***********************************************************/
void Pcb3D_GLCanvas::OnEraseBackground(wxEraseEvent& event)
/***********************************************************/
{
  // Do nothing, to avoid flashing.
}


/****************************/
void Pcb3D_GLCanvas::InitGL()
/****************************/
/* Int parametres generaux pour OPENGL
*/
{
wxSize size = GetClientSize();

    if (! m_init)
	{
		m_init = TRUE;
		g_Parm_3D_Visu.m_Zoom = 1.0;
		ZBottom = 1.0; ZTop = 10.0;
	}

    SetCurrent();

    /* set viewing projection */
double ratio_HV = (double) size.x / size.y;	// Ratio largeur /hauteur de la fenetre d'affichage
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

#define MAX_VIEW_ANGLE 	160.0/45.0
	if ( g_Parm_3D_Visu.m_Zoom > MAX_VIEW_ANGLE)
			g_Parm_3D_Visu.m_Zoom = MAX_VIEW_ANGLE;
    gluPerspective( 45.0 * g_Parm_3D_Visu.m_Zoom, ratio_HV, 1 ,10 );

//	glFrustum(-1., 1.1F, -1.1F, 1.1F, ZBottom, ZTop);

    /* position viewer */
    glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
    glTranslatef(0.0F, 0.0F, - ( ZBottom +ZTop) / 2);

    /* clear color and depth buffers */
    glClearColor( g_Parm_3D_Visu.m_BgColor.m_Red,
		g_Parm_3D_Visu.m_BgColor.m_Green,
		g_Parm_3D_Visu.m_BgColor.m_Blue, 1 );
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	/* Setup light souces: */
	SetLights();


    glDisable(GL_CULL_FACE);	// show back faces

    glEnable(GL_DEPTH_TEST);	// Enable z-buferring

    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);

	/* speedups */
    glEnable(GL_DITHER);
	glShadeModel(GL_SMOOTH);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
    glHint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST);
	
	/* blend */
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}


/***********************************/
void Pcb3D_GLCanvas::SetLights(void)
/***********************************/
/* Init sources lumineuses pour OPENGL
*/
{
double light;
GLfloat light_color[4];

	SetCurrent();

    /* set viewing projection */
	light_color[3] = 1.0;
	GLfloat Z_axis_pos[4]   = { 0.0, 0.0, 3.0, 0.0 };
	GLfloat lowZ_axis_pos[4]   = { 0.0, 0.0, -3.0, 0.5 };

    /* activate light */
	light = 1.0;
	light_color[0] = light_color[1] = light_color[2]= light;
    glLightfv(GL_LIGHT0, GL_POSITION, Z_axis_pos);
    glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_color);
	light = 0.3;
	light_color[0] = light_color[1] = light_color[2] = light;
	glLightfv(GL_LIGHT1, GL_POSITION, lowZ_axis_pos);
    glLightfv(GL_LIGHT1, GL_DIFFUSE,  light_color);
	glEnable(GL_LIGHT0);	// White spot on Z axis
    glEnable(GL_LIGHT1);	// White spot on Z axis ( bottom)
    glEnable(GL_LIGHTING);
}


/**********************************************************/
void Pcb3D_GLCanvas::TakeScreenshot(wxCommandEvent & event)
/**********************************************************/
/* Create a Screenshot of the current 3D view.
	Output file format is png or jpeg, or image is copied on clipboard
*/
{
wxString FullFileName;
wxString file_ext, mask;
bool fmt_is_jpeg = FALSE;

	if ( event.GetId() == ID_MENU_SCREENCOPY_JPEG ) fmt_is_jpeg = TRUE;
	if ( event.GetId() != ID_TOOL_SCREENCOPY_TOCLIBBOARD )
	{
		file_ext = fmt_is_jpeg ? wxT(".jpg") : wxT(".png";)
		mask = wxT("*") + file_ext;
		FullFileName = m_Parent->m_Parent->GetScreen()->m_FileName;
		ChangeFileNameExt(FullFileName,file_ext);

		FullFileName =
			EDA_FileSelector( _("3D Image filename:"),
					wxEmptyString,		/* Chemin par defaut */
					FullFileName,		/* nom fichier par defaut */
					file_ext,			/* extension par defaut */
					mask,				/* Masque d'affichage */
					this,
					wxFD_SAVE,
					TRUE
					);
		if ( FullFileName.IsEmpty() ) return;
	}
	
	Redraw(true);
	wxSize image_size = GetClientSize();
	wxClientDC dc(this);
	wxBitmap bitmap(image_size.x, image_size.y );
	wxMemoryDC memdc;
    memdc.SelectObject( bitmap );
	memdc.Blit(0, 0, image_size.x, image_size.y, &dc, 0, 0);
    memdc.SelectObject( wxNullBitmap );

	if ( event.GetId() == ID_TOOL_SCREENCOPY_TOCLIBBOARD )
	{
    wxBitmapDataObject *dobjBmp = new wxBitmapDataObject;
		dobjBmp->SetBitmap(bitmap);
		if (wxTheClipboard->Open())
		{
			if ( !wxTheClipboard->SetData(dobjBmp) )
				wxLogError( _T("Failed to copy image to clipboard"));
			wxTheClipboard->Flush();	/* the data on clipboard
					will stay available after the application exits */
			wxTheClipboard->Close();
		}
	}

	else
	{
		wxImage image = bitmap.ConvertToImage();

		if ( !image.SaveFile( FullFileName,
				fmt_is_jpeg ? wxBITMAP_TYPE_JPEG : wxBITMAP_TYPE_PNG))
			wxLogError(wxT("Can't save file"));

		image.Destroy();
	}
}


