r474: no message
[ctsim.git] / src / graph3dview.cpp
1 /*****************************************************************************
2 ** FILE IDENTIFICATION
3 **
4 **   Name:          graph3dview.cpp
5 **   Purpose:       3d graph view classes
6 **   Programmer:    Kevin Rosenberg
7 **   Date Started:  Jan 2001
8 **
9 **  This is part of the CTSim program
10 **  Copyright (c) 1983-2001 Kevin Rosenberg
11 **
12 **  $Id: graph3dview.cpp,v 1.3 2001/01/30 13:47:46 kevin Exp $
13 **
14 **  This program is free software; you can redistribute it and/or modify
15 **  it under the terms of the GNU General Public License (version 2) as
16 **  published by the Free Software Foundation.
17 **
18 **  This program is distributed in the hope that it will be useful,
19 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
20 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 **  GNU General Public License for more details.
22 **
23 **  You should have received a copy of the GNU General Public License
24 **  along with this program; if not, write to the Free Software
25 **  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26 ******************************************************************************/
27
28
29 #ifdef __GNUG__
30 #pragma implementation
31 #pragma interface
32 #endif
33
34 // For compilers that support precompilation, includes "wx.h".
35 #include "wx/wxprec.h"
36
37 #ifdef __BORLANDC__
38 #pragma hdrstop
39 #endif
40
41 #ifndef WX_PRECOMP
42 #include "wx/wx.h"
43 #endif
44
45 #if !wxUSE_GLCANVAS
46 #error Please set wxUSE_GLCANVAS to 1 in setup.h.
47 #endif
48
49 #include "wx/timer.h"
50 #include "wx/glcanvas.h"
51
52 #include <GL/gl.h>
53 #include <GL/glu.h>
54
55 #include "ct.h"
56 #include "ctsim.h"
57 #include "docs.h"
58 #include "views.h"
59 #include "dialogs.h"
60 #include "dlgprojections.h"
61 #include "dlgreconstruct.h"
62 #include "backprojectors.h"
63 #include "reconstruct.h"
64 #include "timer.h"
65
66 #if defined(MSVC) || HAVE_SSTREAM
67 #include <sstream>
68 #else
69 #include <sstream_subst>
70 #endif
71
72
73 //***********************************************************************
74 // Function: CalculateVectorNormal
75 //
76 // Purpose: Given three points of a 3D plane, this function calculates
77 //          the normal vector of that plane.
78 //
79 // Parameters:
80 //     fVert1[]   == array for 1st point (3 elements are x, y, and z).
81 //     fVert2[]   == array for 2nd point (3 elements are x, y, and z).
82 //     fVert3[]   == array for 3rd point (3 elements are x, y, and z).
83 //
84 // Returns:
85 //     fNormalX   == X vector for the normal vector
86 //     fNormalY   == Y vector for the normal vector
87 //     fNormalZ   == Z vector for the normal vector
88 //*************************************************************************
89
90
91 GLvoid CalculateVectorNormal (GLfloat fVert1[], GLfloat fVert2[], 
92                               GLfloat fVert3[], GLfloat *fNormalX,
93                               GLfloat *fNormalY, GLfloat *fNormalZ)
94 {
95   GLfloat Qx = fVert2[0] - fVert1[0];
96   GLfloat Qy = fVert2[1] - fVert1[1];
97   GLfloat Qz = fVert2[2] - fVert1[2];
98   GLfloat Px = fVert3[0] - fVert1[0];
99   GLfloat Py = fVert3[1] - fVert1[1];
100   GLfloat Pz = fVert3[2] - fVert1[2];
101   
102   *fNormalX = Py*Qz - Pz*Qy;
103   *fNormalY = Pz*Qx - Px*Qz;
104   *fNormalZ = Px*Qy - Py*Qx;
105   
106
107
108 IMPLEMENT_DYNAMIC_CLASS(Graph3dFileView, wxView)
109
110 BEGIN_EVENT_TABLE(Graph3dFileView, wxView)
111 EVT_MENU(IFMENU_FILE_PROPERTIES, Graph3dFileView::OnProperties)
112 END_EVENT_TABLE()
113
114 Graph3dFileView::Graph3dFileView ()
115 : m_pFileMenu(NULL)
116 {
117   m_bUseVertexArrays = GL_FALSE;
118   m_bDoubleBuffer = GL_TRUE;
119   m_bSmooth = GL_TRUE;
120   m_bLighting = GL_TRUE;
121   m_dXRotate = 0;
122   m_dYRotate = 0;
123 }
124
125 Graph3dFileView::~Graph3dFileView()
126 {
127   GetDocumentManager()->FileHistoryRemoveMenu (m_pFileMenu);
128   GetDocumentManager()->ActivateView(this, FALSE, TRUE);
129 }
130
131 bool 
132 Graph3dFileView::OnCreate (wxDocument *doc, long WXUNUSED(flags) )
133 {
134   m_pFrame = CreateChildFrame(doc, this);
135   (m_pFrame);
136   
137   int width, height;
138   m_pFrame->GetClientSize (&width, &height);
139   m_pFrame->SetTitle("Graph3dFileView");
140   m_pCanvas = CreateCanvas (m_pFrame);
141   
142   m_pFrame->Show(TRUE);
143   m_pCanvas->SetCurrent();
144   
145   InitGL();
146   
147   int x, y;  // X requires a forced resize
148   m_pFrame->GetSize(&x, &y);
149   m_pFrame->SetSize(-1, -1, x, y);
150   m_pFrame->SetFocus();
151   m_pFrame->Show(true);
152   Activate(true);
153   
154   return true;
155
156
157 Graph3dFileCanvas* 
158 Graph3dFileView::CreateCanvas (wxFrame* parent)
159 {
160   Graph3dFileCanvas* pCanvas;
161   int width, height;
162   parent->GetClientSize (&width, &height);
163   
164 #ifdef __WXMSW__
165   int *gl_attrib = NULL;
166 #else
167   int gl_attrib[20] = { GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1,
168     GLX_BLUE_SIZE, 1, GLX_DEPTH_SIZE, 1, GLX_DOUBLEBUFFER, None };
169 #endif
170   
171   if(! m_bDoubleBuffer) {
172 #ifdef __WXGTK__
173     gl_attrib[9] = None;
174 #endif
175   }
176   
177   pCanvas = new Graph3dFileCanvas (this, parent, wxPoint(0, 0), wxSize(200, 200), 0, gl_attrib);
178   
179   pCanvas->SetBackgroundColour(*wxWHITE);
180   pCanvas->Clear();
181   
182   return pCanvas;
183 }
184
185
186
187 void
188 Graph3dFileView::DrawSurface()
189 {
190   int nVertices = GetDocument()->m_nVertices;
191   glTripleFloat* pVertices = GetDocument()->m_pVertices;
192   glTripleFloat* pNormals = GetDocument()->m_pNormals;
193
194 #ifdef GL_EXT_vertex_array
195   if (m_bUseVertexArrays) {
196     glDrawArraysEXT( GL_TRIANGLE_STRIP, 0, nVertices );
197   }
198   else {
199 #endif
200         double edge = 1.;       
201         unsigned int nx = GetDocument()->m_nx;
202   unsigned int ny = GetDocument()->m_ny;
203   const ImageFileArray v = GetDocument()->m_array;
204   if (nx == 0 || ny == 0 || ! v)
205     return;
206
207   double dMin = v[0][0];
208   double dMax = dMin;
209   unsigned int ix;
210   for (ix = 0; ix < nx; ix++)
211     for (unsigned int iy = 0; iy < ny; iy++)
212       if (v[ix][iy] < dMin)
213         dMin = v[ix][iy];
214       else if (v[ix][iy] > dMax)
215         dMax = v[ix][iy];
216
217   double actOffset = dMin;
218   double actScale = 0.3 * sqrt(nx*nx+ny*ny) / (dMax - dMin);
219
220   glRotatef( m_dYRotate, 0.0, 0.0, 1.0 );
221   glRotatef( m_dXRotate, 1.0, 0.0, 0.0 );
222   glTranslatef (-static_cast<double>(nx) / 2, -static_cast<double>(ny) / 2, 0);
223
224 //      glNewList(opnListNum++,GL_COMPILE);             
225                 for (ix = 0; ix < nx-1; ix++) {                 
226                         for (unsigned int iy = 0; iy < ny-1; iy++) {                    
227                                 
228           float p1[3], p2[3], p3[3], p4[3];
229               float n1[3], n2[3], n3[3], n4[3];
230                                 glBegin(GL_LINE_LOOP);
231                                         
232                                 p1[0] = ix;  p1[1] = actScale * (v[ix][iy] + actOffset); p1[2] = iy;
233                                 p2[0] = ix+1; p2[1] = actScale * (v[ix+1][iy] + actOffset); p2[2] = iy; 
234                                 p3[0] = ix+1; p3[1] = actScale * (v[ix+1][iy+1] + actOffset); p3[2] = iy;
235                                 p4[0] = ix;  p4[1] = actScale * (v[ix][iy+1] + actOffset); p4[2] = iy;
236                                                                                         
237                                 n1[0] = -(p2[1] - p1[1])*(p3[2] - p1[2]) + (p2[2] - p1[2])*(p3[1] - p2[1]);
238                                 n1[1] = -(p2[2] - p1[2])*(p3[0] - p2[0]) + (p2[0] - p1[0])*(p3[2] - p2[2]);
239                                 n1[2] = -(p2[0] - p1[0])*(p3[1] - p2[1]) + (p2[1] - p1[1])*(p3[0] - p2[0]);
240                                         
241                                 glVertex3fv(p1); glNormal3fv(n1);                                       
242                                 glVertex3fv(p2); glNormal3fv(n1);                                       
243                                 glVertex3fv(p3); glNormal3fv(n1);                                       
244                                 glVertex3fv(p4); glNormal3fv(n1);                                                                                                                                               
245         glEnd();                                
246                         }                       
247                         
248                 }
249         glEndList();
250                 
251 #ifdef GL_EXT_vertex_array
252   }
253 #endif
254 }
255
256
257 void
258 Graph3dFileView::OnProperties (wxCommandEvent& event)
259 {
260     std::ostringstream os;
261     *theApp->getLog() << ">>>>\n" << os.str().c_str() << "<<<<\n";
262     wxMessageDialog dialogMsg (getFrameForChild(), os.str().c_str(), "Imagefile Properties", wxOK | wxICON_INFORMATION);
263     dialogMsg.ShowModal();
264 }
265
266
267
268 void 
269 Graph3dFileView::OnDraw (wxDC* dc)
270 {
271 #ifndef __WXMOTIF__
272   if (! m_pCanvas->GetContext()) return;
273 #endif
274   
275   Draw();
276   m_pCanvas->SwapBuffers();
277 }
278
279
280 void 
281 Graph3dFileView::Draw ()
282 {
283   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
284   glPushMatrix();
285   DrawSurface();
286   
287   glPopMatrix();
288   glFlush();
289 }
290
291
292 void
293 Graph3dFileView::InitMaterials()
294 {
295   static float ambient[] = {0.1, 0.1, 0.1, 1.0};
296   static float diffuse[] = {0.5, 1.0, 1.0, 1.0};
297   static float position0[] = {0.0, 0.0, 20.0, 0.0};
298   static float position1[] = {0.0, 0.0, -20.0, 0.0};
299   static float front_mat_shininess[] = {60.0};
300   static float front_mat_specular[] = {0.2, 0.2, 0.2, 1.0};
301   static float front_mat_diffuse[] = {0.5, 0.28, 0.38, 1.0};
302   /*
303   static float back_mat_shininess[] = {60.0};
304   static float back_mat_specular[] = {0.5, 0.5, 0.2, 1.0};
305   static float back_mat_diffuse[] = {1.0, 1.0, 0.2, 1.0};
306   */
307   static float lmodel_ambient[] = {1.0, 1.0, 1.0, 1.0};
308   static float lmodel_twoside[] = {GL_FALSE};
309   
310   glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
311   glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
312   glLightfv(GL_LIGHT0, GL_POSITION, position0);
313   glEnable(GL_LIGHT0);
314   
315   glLightfv(GL_LIGHT1, GL_AMBIENT, ambient);
316   glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse);
317   glLightfv(GL_LIGHT1, GL_POSITION, position1);
318   glEnable(GL_LIGHT1);
319   
320   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
321   glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
322   glEnable(GL_LIGHTING);
323   
324   glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, front_mat_shininess);
325   glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, front_mat_specular);
326   glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, front_mat_diffuse);
327 }
328
329
330 void 
331 Graph3dFileView::InitGL ()
332 {
333   glClearColor(0.0, 0.0, 0.0, 0.0);
334   
335   glShadeModel(GL_SMOOTH);
336   glEnable(GL_DEPTH_TEST);
337   
338   InitMaterials();
339   
340   glMatrixMode(GL_PROJECTION);
341   glLoadIdentity();
342   glOrtho (-300, 300, -300, 300, 200, -200);
343   glMatrixMode(GL_MODELVIEW);
344   glLoadIdentity();
345  
346 }
347
348 void 
349 Graph3dFileView::OnUpdate (wxView *WXUNUSED(sender), wxObject *WXUNUSED(hint) )
350 {
351   int nVertices = GetDocument()->m_nVertices;
352   glTripleFloat* pVertices = GetDocument()->m_pVertices;
353   glTripleFloat* pNormals = GetDocument()->m_pNormals;
354
355 #if 0    
356   const ImageFile& rIF = GetDocument()->getImageFile();
357   ImageFileArrayConst v = rIF.getArray();
358   int nx = rIF.nx();
359   int ny = rIF.ny();
360   if (v != NULL && nx != 0 && ny != 0) {
361     unsigned char* imageData = new unsigned char [nx * ny * 3];
362     for (int ix = 0; ix < nx; ix++) {
363       for (int iy = 0; iy < ny; iy++) {
364         double scaleValue = ((v[ix][iy] - m_dMinPixel) / scaleWidth) * 255;
365         int intensity = static_cast<int>(scaleValue + 0.5);
366         intensity = clamp (intensity, 0, 255);
367         int baseAddr = ((ny - 1 - iy) * nx + ix) * 3;
368         imageData[baseAddr] = imageData[baseAddr+1] = imageData[baseAddr+2] = intensity;
369       }
370     }
371     wxImage image (nx, ny, imageData, true);
372     m_bitmap = image.ConvertToBitmap();
373     delete imageData;
374     int xSize = nx;
375     int ySize = ny;
376     ySize = clamp (ySize, 0, 800);
377     m_pFrame->SetClientSize (xSize, ySize);
378     m_pCanvas->SetScrollbars(20, 20, nx/20, ny/20);
379     m_pCanvas->SetBackgroundColour(*wxWHITE);
380   } 
381 #endif
382   
383 #ifdef GL_EXT_vertex_array
384   if (m_bUseVertexArrays) {
385     //  glVertexPointerEXT( 3, GL_FLOAT, 0, nVertices, pVertices );
386     //  glNormalPointerEXT( GL_FLOAT, 0, nVertices, pNormals );
387     glEnable( GL_VERTEX_ARRAY_EXT );
388     glEnable( GL_NORMAL_ARRAY_EXT );
389   }
390 #endif
391   if (m_pCanvas)
392     m_pCanvas->Refresh();
393 }
394
395 bool 
396 Graph3dFileView::OnClose (bool deleteWindow)
397 {
398   if (! GetDocument() || ! GetDocument()->Close())
399     return false;
400   
401   Activate (false);
402   if (m_pCanvas) {
403     m_pCanvas->setView(NULL);
404     m_pCanvas = NULL;
405   }
406   wxString s(theApp->GetAppName());
407   if (m_pFrame)
408     m_pFrame->SetTitle(s);
409   
410   SetFrame(NULL);
411   
412   if (deleteWindow) {
413     m_pFrame->Destroy();
414     m_pFrame = NULL;
415     if (GetDocument() && GetDocument()->getBadFileOpen())
416       ::wxYield();  // wxWindows bug workaround
417   }
418   
419   return true;
420 }
421
422 #if CTSIM_MDI
423 wxDocMDIChildFrame*
424 #else
425 wxDocChildFrame*
426 #endif
427 Graph3dFileView::CreateChildFrame (wxDocument *doc, wxView *view)
428 {
429 #if CTSIM_MDI
430   wxDocMDIChildFrame* subframe = new wxDocMDIChildFrame (doc, view, theApp->getMainFrame(), -1, "Graph3dFile Frame", wxPoint(-1, -1), wxSize(0, 0), wxDEFAULT_FRAME_STYLE);
431 #else
432   wxDocChildFrame* subframe = new wxDocChildFrame (doc, view, theApp->getMainFrame(), -1, "Graph3dFile Frame", wxPoint(-1, -1), wxSize(0, 0), wxDEFAULT_FRAME_STYLE);
433 #endif
434   theApp->setIconForFrame (subframe);
435   
436   m_pFileMenu = new wxMenu;
437   
438   m_pFileMenu->Append(MAINMENU_FILE_CREATE_PHANTOM, "Cr&eate Phantom...\tCtrl-P");
439   m_pFileMenu->Append(MAINMENU_FILE_CREATE_FILTER, "Create &Filter...\tCtrl-F");
440   m_pFileMenu->Append(wxID_OPEN, "&Open...\tCtrl-O");
441   m_pFileMenu->Append(wxID_SAVE, "&Save\tCtrl-S");
442   m_pFileMenu->Append(wxID_SAVEAS, "Save &As...");
443   m_pFileMenu->Append(wxID_CLOSE, "&Close\tCtrl-W");
444   
445   m_pFileMenu->AppendSeparator();
446   m_pFileMenu->Append(IFMENU_FILE_PROPERTIES, "P&roperties");
447   
448   m_pFileMenu->AppendSeparator();
449   m_pFileMenu->Append(wxID_PRINT, "&Print...");
450   m_pFileMenu->Append(wxID_PRINT_SETUP, "Print &Setup...");
451   m_pFileMenu->Append(wxID_PREVIEW, "Print Preview");
452 #ifdef CTSIM_MDI
453   m_pFileMenu->AppendSeparator();
454   m_pFileMenu->Append(MAINMENU_FILE_EXIT, "E&xit");
455 #endif
456   GetDocumentManager()->FileHistoryAddFilesToMenu(m_pFileMenu);
457   GetDocumentManager()->FileHistoryUseMenu(m_pFileMenu);
458   
459   wxMenu *help_menu = new wxMenu;
460   help_menu->Append(MAINMENU_HELP_CONTENTS, "&Contents\tF1");
461   help_menu->Append(MAINMENU_HELP_TOPICS, "&Topics\tCtrl-H");
462   help_menu->Append(MAINMENU_HELP_ABOUT, "&About");
463   
464   wxMenuBar *menu_bar = new wxMenuBar;
465   
466   menu_bar->Append(m_pFileMenu, "&File");
467   menu_bar->Append(help_menu, "&Help");
468   
469   subframe->SetMenuBar(menu_bar);
470   
471   subframe->Centre(wxBOTH);
472   
473   wxAcceleratorEntry accelEntries[10];
474   accelEntries[0].Set (wxACCEL_CTRL, static_cast<int>('O'), wxID_OPEN);
475   accelEntries[1].Set (wxACCEL_CTRL, static_cast<int>('S'), wxID_SAVE);
476   accelEntries[2].Set (wxACCEL_CTRL, static_cast<int>('W'), wxID_CLOSE);
477   accelEntries[3].Set (wxACCEL_CTRL, static_cast<int>('H'), MAINMENU_HELP_TOPICS);
478   accelEntries[4].Set (wxACCEL_CTRL, static_cast<int>('P'), MAINMENU_FILE_CREATE_PHANTOM);
479   accelEntries[5].Set (wxACCEL_CTRL, static_cast<int>('F'), MAINMENU_FILE_CREATE_FILTER);
480   accelEntries[6].Set (wxACCEL_NORMAL, WXK_F1, MAINMENU_HELP_CONTENTS);
481   accelEntries[7].Set (wxACCEL_CTRL, static_cast<int>('A'), IFMENU_VIEW_SCALE_AUTO);
482   accelEntries[8].Set (wxACCEL_CTRL, static_cast<int>('U'), IFMENU_VIEW_SCALE_FULL);
483   accelEntries[9].Set (wxACCEL_CTRL, static_cast<int>('E'), IFMENU_VIEW_SCALE_MINMAX);
484   wxAcceleratorTable accelTable (10, accelEntries);
485   subframe->SetAcceleratorTable (accelTable);
486   
487   return subframe;
488 }
489
490
491
492 BEGIN_EVENT_TABLE(Graph3dFileCanvas, wxGLCanvas)
493 EVT_SIZE(Graph3dFileCanvas::OnSize)
494 EVT_PAINT(Graph3dFileCanvas::OnPaint)
495 EVT_CHAR(Graph3dFileCanvas::OnChar)
496 EVT_MOUSE_EVENTS(Graph3dFileCanvas::OnMouseEvent)
497 EVT_ERASE_BACKGROUND(Graph3dFileCanvas::OnEraseBackground)
498 END_EVENT_TABLE()
499
500
501
502
503 Graph3dFileCanvas::Graph3dFileCanvas (Graph3dFileView* view, wxWindow *parent, const wxPoint& pos, 
504                                       const wxSize& size, long style, int* gl_attrib):
505 wxGLCanvas (parent, -1, pos, size, style, _T("Graph3dCanvas"), gl_attrib), m_pView(view)
506 {
507   parent->Show(TRUE);
508   SetCurrent();
509   /* Make sure server supports the vertex array extension */
510   char* extensions = (char *) glGetString( GL_EXTENSIONS );
511   if (!extensions || !strstr( extensions, "GL_EXT_vertex_array" )) {
512     m_pView->m_bUseVertexArrays = GL_FALSE;
513   }
514 }
515
516
517 Graph3dFileCanvas::~Graph3dFileCanvas(void)
518 {
519   m_pView = NULL;
520 }
521
522 void 
523 Graph3dFileCanvas::OnDraw(wxDC& dc)
524 {
525   if (m_pView)
526     m_pView->OnDraw(& dc);
527 }
528
529 void 
530 Graph3dFileCanvas::OnSize(wxSizeEvent& event)
531 {
532 #ifndef __WXMOTIF__
533   if (!GetContext()) return;
534 #endif
535   
536   SetCurrent();
537   int width, height;
538   GetClientSize (&width, &height);
539   Reshape (width, height);
540 }
541
542 void 
543 Graph3dFileCanvas::OnChar(wxKeyEvent& event)
544 {
545   if (! m_pView)
546     return;
547   
548   switch(event.KeyCode()) {
549   case WXK_LEFT:
550         m_pView->m_dYRotate -= 15.0;
551     break;
552   case WXK_RIGHT:
553     m_pView->m_dYRotate += 15.0;
554     break;
555   case WXK_UP:
556     m_pView->m_dXRotate += 15.0;
557     break;
558   case WXK_DOWN:
559     m_pView->m_dXRotate -= 15.0;
560     break;
561   case 's': case 'S':
562     m_pView->m_bSmooth = !m_pView->m_bSmooth;
563     if (m_pView->m_bSmooth) {
564       glShadeModel(GL_SMOOTH);
565     } else {
566       glShadeModel(GL_FLAT);
567     }
568     break;
569   case 'l': case 'L':
570     m_pView->m_bLighting = !m_pView->m_bLighting;
571     if (m_pView->m_bLighting) {
572       glEnable(GL_LIGHTING);
573     } else {
574       glDisable(GL_LIGHTING);
575     }
576     break;
577   default:
578     {
579       event.Skip();
580       return;
581     }
582   }
583   
584   Refresh (false);
585 }
586
587 void
588 Graph3dFileCanvas::Reshape (int width, int height)
589 {
590   glViewport (0, 0, (GLint)width, (GLint)height);
591 }
592
593
594 void 
595 Graph3dFileCanvas::OnMouseEvent(wxMouseEvent& event)
596 {
597   static int dragging = 0;
598   static float last_x, last_y;
599   
600   if(event.LeftIsDown()) {
601     if(!dragging) {
602       dragging = 1;
603     } else {
604       m_pView->m_dXRotate += (event.GetX() - last_x)*1.0;
605       m_pView->m_dYRotate += (event.GetY() - last_y)*1.0;
606       Refresh(FALSE);
607     }
608     last_x = event.GetX();
609     last_y = event.GetY();
610   } else
611     dragging = 0;
612 }
613
614 void 
615 Graph3dFileCanvas::OnEraseBackground(wxEraseEvent& event)
616 {
617   // Do nothing: avoid flashing.
618 }
619
620
621
622