]> git.mxchange.org Git - flightgear.git/blob - src/GUI/gui.cxx
Updates from Mark Vallevand.
[flightgear.git] / src / GUI / gui.cxx
1 /**************************************************************************
2  * gui.cxx
3  *
4  * Written 1998 by Durk Talsma, started Juni, 1998.  For the flight gear
5  * project.
6  *
7  * Additional mouse supported added by David Megginson, 1999.
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License as
11  * published by the Free Software Foundation; either version 2 of the
12  * License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22  *
23  * $Id$
24  **************************************************************************/
25
26
27 #ifdef HAVE_CONFIG_H
28 #  include <config.h>
29 #endif
30
31 #include <Include/compiler.h>
32
33 #ifdef FG_MATH_EXCEPTION_CLASH
34 #  include <math.h>
35 #endif
36
37 #ifdef HAVE_WINDOWS_H
38 #  include <windows.h>
39 #endif
40
41 #include <GL/glut.h>
42 #include <XGL/xgl.h>
43
44 #if defined(FX) && defined(XMESA)
45 #  include <GL/xmesa.h>
46 #endif
47
48 #include STL_STRING
49
50 #include <stdlib.h>
51 #include <string.h>
52
53 #include <Include/general.hxx>
54 #include <Include/fg_constants.h>
55 #include <Debug/logstream.hxx>
56 #include <Aircraft/aircraft.hxx>
57 #include <Airports/simple.hxx>
58 #include <Cockpit/panel.hxx>
59 #include <Controls/controls.hxx>
60 #include <FDM/flight.hxx>
61 #include <Main/options.hxx>
62 #include <Main/fg_init.hxx>
63 #include <Main/views.hxx>
64 #include <Misc/fgpath.hxx>
65 #include <Network/network.h>
66 #include <Screen/screen-dump.hxx>
67 #include <Time/fg_time.hxx>
68
69 #if defined( WIN32 ) && !defined( __CYGWIN__ )
70 #  include <Screen/win32-printer.h>
71 #  include <Screen/GlBitmaps.h>
72 #endif
73
74 /*
75  * trackball.h
76  * A virtual trackball implementation
77  * Written by Gavin Bell for Silicon Graphics, November 1988.
78  */
79 /*
80  * Pass the x and y coordinates of the last and current positions of
81  * the mouse, scaled so they are from (-1.0 ... 1.0).
82  *
83  * The resulting rotation is returned as a quaternion rotation in the
84  * first paramater.
85  */
86 void
87 trackball(float q[4], float p1x, float p1y, float p2x, float p2y);
88
89 /*
90  * Given two quaternions, add them together to get a third quaternion.
91  * Adding quaternions to get a compound rotation is analagous to adding
92  * translations to get a compound translation.  When incrementally
93  * adding rotations, the first argument here should be the new
94
95   * rotation, the second and third the total rotation (which will be
96  * over-written with the resulting new total rotation).
97  */
98 void
99 add_quats(float *q1, float *q2, float *dest);
100
101 /*
102  * A useful function, builds a rotation matrix in Matrix based on
103  * given quaternion.
104  */
105 void
106 build_rotmatrix(float m[4][4], float q[4]);
107
108 /*
109  * This function computes a quaternion based on an axis (defined by
110  * the given vector) and an angle about which to rotate.  The angle is
111  * expressed in radians.  The result is put into the third argument.
112  */
113 void
114 axis_to_quat(float a[3], float phi, float q[4]);
115
116
117 #include "gui.h"
118
119 FG_USING_STD(string);
120
121 #ifndef FG_HAVE_NATIVE_SGI_COMPILERS
122 FG_USING_STD(cout);
123 #endif
124
125 #if defined(WIN32) || defined(__CYGWIN32__)
126 #define WIN32_CURSOR_TWEAKS
127 #elif (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 9)
128 #define X_CURSOR_TWEAKS
129 #endif
130
131 // hack, should come from an include someplace
132 extern void fgInitVisuals( void );
133 extern void fgReshape( int width, int height );
134 extern void fgRenderFrame( void );
135
136 puFont guiFnt = 0;
137 fntTexFont *guiFntHandle = 0;
138
139 static puMenuBar    *mainMenuBar = 0;
140 //static puButton     *hideMenuButton = 0;
141
142 static puDialogBox  *dialogBox = 0;
143 static puFrame      *dialogFrame = 0;
144 static puText       *dialogBoxMessage = 0;
145 static puOneShot    *dialogBoxOkButton = 0;
146
147
148 static puDialogBox  *YNdialogBox = 0;
149 static puFrame      *YNdialogFrame = 0;
150 static puText       *YNdialogBoxMessage = 0;
151 static puOneShot    *YNdialogBoxOkButton = 0;
152 static puOneShot    *YNdialogBoxNoButton = 0;
153
154 static char msg_OK[]     = "OK";
155 static char msg_NO[]     = "NO";
156 static char msg_YES[]    = "YES";
157 static char msg_CANCEL[] = "Cancel";
158 static char msg_RESET[]  = "Reset";
159
160 char *gui_msg_OK;     // "OK"
161 char *gui_msg_NO;     // "NO"
162 char *gui_msg_YES;    // "YES"
163 char *gui_msg_CANCEL; // "CANCEL"
164 char *gui_msg_RESET;  // "RESET"
165
166 static char global_dialog_string[256];
167
168 // from autopilot.cxx
169 extern void NewAltitude( puObject *cb );
170 extern void NewHeading( puObject *cb );
171 extern void fgAPAdjust( puObject * );
172 extern void NewTgtAirport( puObject *cb );
173 bool fgAPTerrainFollowEnabled( void );
174 bool fgAPAltitudeEnabled( void );
175 bool fgAPHeadingEnabled( void );
176 bool fgAPWayPointEnabled( void );
177 bool fgAPAutoThrottleEnabled( void );
178
179 // from cockpit.cxx
180 extern void fgLatLonFormatToggle( puObject *);
181
182 /* --------------------------------------------------------------------
183 Mouse stuff
184 ---------------------------------------------------------------------*/
185
186 static int _mX = 0;
187 static int _mY = 0;
188 static int _savedX = 0;
189 static int _savedY = 0;
190 static int last_buttons = 0 ;
191 static int mouse_active = 0;
192 static int menu_on = 0;
193 static int mouse_joystick_control = 0;
194
195 //static time_t mouse_off_time;
196 //static int mouse_timed_out;
197
198 // to allow returning to previous view
199 // on second left click in MOUSE_VIEW mode
200 // This has file scope so that it can be reset
201 // if the little rodent is moved  NHV
202 static  int _mVtoggle;
203
204 // we break up the glutGetModifiers return mask
205 // once per loop and stash what we need in these
206 static int glut_active_shift;
207 static int glut_active_ctrl;
208 static int glut_active_alt;
209
210 static float lastquat[4];
211 static float curquat[4];
212 static float _quat0[4];
213 float quat_mat[4][4];
214
215 // uncomment this for view to exactly follow mouse in MOUSE_VIEW mode
216 // else smooth out the view panning to .01 radian per frame
217 // see view_offset smoothing mechanism in main.cxx
218 #define NO_SMOOTH_MOUSE_VIEW
219
220 // uncomment following to
221 #define RESET_VIEW_ON_LEAVING_MOUSE_VIEW
222
223 /* --------------------------------------------------------------------
224 Support for mouse as control yoke (david@megginson.com)
225
226 - right button toggles between pointer and yoke
227 - horizontal drag with no buttons moves ailerons
228 - vertical drag with no buttons moves elevators
229 - horizontal drag with left button moves brakes (left=on)
230 - vertical drag with left button moves throttle (up=more)
231 - horizontal drag with middle button moves rudder
232 - vertical drag with middle button moves trim
233
234 For the *_sensitivity variables, a lower number means more sensitive.
235
236 TODO: figure out how to keep pointer from leaving window in yoke mode.
237 TODO: add thresholds and null zones
238 TODO: sensitivity should be configurable at user option.
239 TODO: allow differential braking (this will be useful if FlightGear
240       ever supports tail-draggers like the DC-3)
241 ---------------------------------------------------------------------*/
242
243 typedef enum {
244     MOUSE_POINTER,
245     MOUSE_YOKE,
246     MOUSE_VIEW
247 } MouseMode;
248
249 MouseMode mouse_mode = MOUSE_POINTER;
250
251 static double aileron_sensitivity = 1.0/500.0;
252 static double elevator_sensitivity = 1.0/500.0;
253 static double brake_sensitivity = 1.0/250.0;
254 static double throttle_sensitivity = 1.0/250.0;
255 static double rudder_sensitivity = 1.0/500.0;
256 static double trim_sensitivity = 1.0/1000.0;
257
258 static inline void Quat0( void ) {
259     curquat[0] = _quat0[0];
260     curquat[1] = _quat0[1];
261     curquat[2] = _quat0[2];
262     curquat[3] = _quat0[3];
263 }
264
265 static inline int left_button( void ) {
266     return( last_buttons & (1 << GLUT_LEFT_BUTTON) );
267 }
268
269 static inline int middle_button( void ) {
270     return( last_buttons & (1 << GLUT_MIDDLE_BUTTON) );
271 }
272
273 static inline int right_button( void ) {
274     return( last_buttons & (1 << GLUT_RIGHT_BUTTON) );
275 }
276
277 static inline void TurnCursorOn( void )
278 {
279     mouse_active = ~0;
280 #if defined(WIN32_CURSOR_TWEAKS)
281     switch (mouse_mode) {
282         case MOUSE_POINTER:
283             glutSetCursor(GLUT_CURSOR_INHERIT);
284             break;
285         case MOUSE_YOKE:
286             glutSetCursor(GLUT_CURSOR_CROSSHAIR);
287             break;
288         case MOUSE_VIEW:
289             glutSetCursor(GLUT_CURSOR_LEFT_RIGHT);
290             break;
291     }
292 #endif
293 #if defined(X_CURSOR_TWEAKS)
294     glutWarpPointer( current_view.get_winWidth()/2, current_view.get_winHeight()/2);
295 #endif
296 }
297
298 static inline void TurnCursorOff( void )
299 {
300     mouse_active = 0;
301 #if defined(WIN32_CURSOR_TWEAKS)
302     glutSetCursor(GLUT_CURSOR_NONE);
303 #elif defined(X_CURSOR_TWEAKS)
304     glutWarpPointer( current_view.get_winWidth(), current_view.get_winHeight());
305 #endif
306 }
307
308 void maybeToggleMouse( void )
309 {
310 #if defined(WIN32_CURSOR_TWEAKS)
311     static int first_time = ~0;
312     static int mouse_changed = 0;
313
314     if ( first_time ) {
315         if(!mouse_active) {
316             mouse_changed = ~mouse_changed;
317             TurnCursorOn();
318         }
319     } else {
320         if( mouse_mode != MOUSE_POINTER )
321             return;
322         if( mouse_changed ) {
323             mouse_changed = ~mouse_changed;
324             if(mouse_active) {
325                 TurnCursorOff();
326             }
327         }
328     }
329     first_time = ~first_time;
330 #endif // #ifdef WIN32
331 }
332
333 // Call with FALSE to init and TRUE to restore
334 void BusyCursor( int restore )
335 {
336     static GLenum cursor = (GLenum) 0;
337     if( restore ) {
338         glutSetCursor(cursor);
339     } else {
340         cursor = (GLenum) glutGet( (GLenum) GLUT_WINDOW_CURSOR );
341 #if defined(WIN32_CURSOR_TWEAKS)
342         TurnCursorOn();
343 #endif
344         glutSetCursor( GLUT_CURSOR_WAIT );
345     }
346 }
347
348 int guiGetMouseButton(void)
349 {
350     return last_buttons;
351 }
352
353 void guiGetMouse(int *x, int *y)
354 {
355     *x = _mX;
356     *y = _mY;
357 };
358
359 void guiMotionFunc ( int x, int y )
360 {
361     int ww, wh, need_warp = 0;
362     float W, H;
363     double offset;
364 //  FGTime *t = FGTime::cur_time_params;
365 //  if( mouse_timed_out ) {
366 //      if( t->get_cur_time() > mouse_off_time ) {
367 //          moused_timed_out = 0;
368 //          TurnCursorOn();
369 //          glutPostRedisplay () ;
370 //      }
371 //  }
372
373     if (mouse_mode == MOUSE_POINTER) {
374         puMouse ( x, y ) ;
375         glutPostRedisplay () ;
376     } else {
377         if( x == _mX && y == _mY)
378             return;
379         
380         // reset left click MOUSE_VIEW toggle feature
381         _mVtoggle = 0;
382         
383         ww = current_view.get_winWidth();
384         wh = current_view.get_winHeight();
385         
386         switch (mouse_mode) {
387             case MOUSE_YOKE:
388                 if( !mouse_joystick_control ) {
389                     mouse_joystick_control = 1;
390                     current_options.set_control_mode( fgOPTIONS::FG_MOUSE );
391                 } else {
392                     if ( left_button() ) {
393                         offset = (_mX - x) * brake_sensitivity;
394                         controls.move_brake(FGControls::ALL_WHEELS, offset);
395                         offset = (_mY - y) * throttle_sensitivity;
396                         controls.move_throttle(FGControls::ALL_ENGINES, offset);
397                     } else if ( right_button() ) {
398                         if( !fgAPHeadingEnabled() ) {
399                             offset = (x - _mX) * rudder_sensitivity;
400                             controls.move_rudder(offset);
401                         }
402                         if( !fgAPAltitudeEnabled() ) {
403                             offset = (_mY - y) * trim_sensitivity;
404                             controls.move_elevator_trim(offset);
405                         }
406                     } else {
407                         if( !fgAPHeadingEnabled() ) {
408                             offset = (x - _mX) * aileron_sensitivity;
409                             controls.move_aileron(offset);
410                         }
411                         if( !fgAPAltitudeEnabled() ) {
412                             offset = (_mY - y) * elevator_sensitivity;
413                             controls.move_elevator(offset);
414                         }
415                     }
416                 }
417                 // Keep the mouse in the window.
418                 if (x < 5 || x > ww-5 || y < 5 || y > wh-5) {
419                     x = ww / 2;
420                     y = wh / 2;
421                     need_warp = 1;
422                 }
423                 break;
424                 
425             case MOUSE_VIEW:
426                 if( y <= 0 ) {
427 #define CONTRAINED_MOUSE_VIEW_Y
428 #ifdef CONTRAINED_MOUSE_VIEW_Y
429                     y = 1;
430 #else
431                     y = wh-2;
432 #endif // CONTRAINED_MOUSE_VIEW_Y
433                     need_warp = 1;
434                 } else if( y >= wh-1) {
435 #ifdef CONTRAINED_MOUSE_VIEW_Y
436                     y = wh-2;
437 #else
438                     y = 1;
439 #endif // CONTRAINED_MOUSE_VIEW_Y
440                     need_warp = 1;
441                 }
442                 // wrap MOUSE_VIEW mode cursor x position
443                 if ( x <= 0 ) {
444                     need_warp = 1;
445                     x = ww-2;
446                 } else if ( x >= ww-1 ) {
447                     need_warp = 1;
448                     x = 1;
449                 }
450                 // try to get FG_PI movement in each half of screen
451                 // do spherical pan
452                 W = ww;
453                 H = wh;
454                 if( middle_button() ) {
455                     trackball(lastquat,
456                               (2.0f * _mX - W) / W,
457                               0, //(H - 2.0f * y) / H,         // 3
458                               (2.0f * x - W) / W,
459                               0 //(H - 2.0f * _mY) / H       // 1
460                              );
461                     x = _mX;
462                     y = _mY;
463                     need_warp = 1;
464                 } else {
465                     trackball(lastquat,
466                               0, //(2.0f * _mX - W) / W,  // 0
467                               (H - 2.0f * y) / H,         // 3
468                               0, //(2.0f * x - W) / W,    // 2
469                               (H - 2.0f * _mY) / H        // 1 
470                              );
471                 }
472                 add_quats(lastquat, curquat, curquat);
473                 build_rotmatrix(quat_mat, curquat);
474                 
475                 // do horizontal pan
476                 // this could be done in above quat
477                 // but requires redoing view pipeline
478                 offset = current_view.get_goal_view_offset();
479                 offset += ((_mX - x) * FG_2PI / W );
480                 while (offset < 0.0) {
481                     offset += FG_2PI;
482                 }
483                 while (offset > FG_2PI) {
484                     offset -= FG_2PI;
485                 }
486                 current_view.set_goal_view_offset(offset);
487 #ifdef NO_SMOOTH_MOUSE_VIEW
488                 current_view.set_view_offset(offset);
489 #endif
490                 break;
491             
492             default:
493                 break;
494         }
495     }
496     if( need_warp)
497         glutWarpPointer(x, y);
498     
499     // Record the new mouse position.
500     _mX = x;
501     _mY = y;
502 }
503
504
505 void guiMouseFunc(int button, int updown, int x, int y)
506 {
507     int glutModifiers;
508
509     // private MOUSE_VIEW state variables
510     // to allow alternate left clicks in MOUSE_VIEW mode
511     // to toggle between current offsets and straight ahead
512     // uses _mVtoggle
513     static int _mVx, _mVy, _Vx, _Vy;
514     static float _quat[4];
515     static double _view_offset;
516     
517     // general purpose variables
518     double offset;
519             
520     glutModifiers = glutGetModifiers();
521     glut_active_shift = glutModifiers & GLUT_ACTIVE_SHIFT;
522     glut_active_ctrl  = glutModifiers & GLUT_ACTIVE_CTRL; 
523     glut_active_alt   = glutModifiers & GLUT_ACTIVE_ALT;
524     
525     // Was the left button pressed?
526     if (updown == GLUT_DOWN ) {
527         if( button == GLUT_LEFT_BUTTON)
528         {
529             switch (mouse_mode) {
530                 case MOUSE_POINTER:
531                     break;
532                 case MOUSE_YOKE:
533                     break;
534                 case MOUSE_VIEW:
535                     if(_mVtoggle) {
536                         // resume previous view offsets
537                         _mX = _mVx;
538                         _mY = _mVy;
539                         x = _Vx;
540                         y = _Vy;
541                         curquat[0] = _quat[0];
542                         curquat[1] = _quat[1];
543                         curquat[2] = _quat[2];
544                         curquat[3] = _quat[3];
545                         current_view.set_goal_view_offset(_view_offset);
546 #ifdef NO_SMOOTH_MOUSE_VIEW
547                         current_view.set_view_offset(_view_offset);
548 #endif
549                     } else {
550                         // center view
551                         _mVx = _mX;
552                         _mVy = _mY;
553                         _Vx = x;
554                         _Vy = y;
555                         _quat[0] = curquat[0];
556                         _quat[1] = curquat[1];
557                         _quat[2] = curquat[2];
558                         _quat[3] = curquat[3];
559                         x = current_view.get_winWidth()/2;
560                         y = current_view.get_winHeight()/2;
561                         Quat0();
562                         _view_offset = current_view.get_goal_view_offset();
563                         current_view.set_goal_view_offset(0.0);
564 #ifdef NO_SMOOTH_MOUSE_VIEW
565                         current_view.set_view_offset(0.0);
566 #endif
567                     }
568                     glutWarpPointer( x , y);
569                     build_rotmatrix(quat_mat, curquat);
570                     _mVtoggle = ~_mVtoggle;
571                     break;
572             }
573         }else if ( button == GLUT_RIGHT_BUTTON) {
574             switch (mouse_mode) {
575                 case MOUSE_POINTER:
576                     mouse_mode = MOUSE_YOKE;
577                     mouse_joystick_control = 0;
578                     _savedX = x;
579                     _savedY = y;
580                     // start with zero point in center of screen
581                     _mX = current_view.get_winWidth()/2;
582                     _mY = current_view.get_winHeight()/2;
583                     
584                     // try to have the MOUSE_YOKE position
585                     // reflect the current stick position
586                     offset = controls.get_aileron();
587                     x = _mX - (int)(offset * aileron_sensitivity);
588                     offset = controls.get_elevator();
589                     y = _mY - (int)(offset * elevator_sensitivity);
590                     
591                     glutSetCursor(GLUT_CURSOR_CROSSHAIR);
592                     FG_LOG( FG_INPUT, FG_INFO, "Mouse in yoke mode" );
593                     break;
594                     
595                 case MOUSE_YOKE:
596                     mouse_mode = MOUSE_VIEW;
597                     current_options.set_control_mode( fgOPTIONS::FG_JOYSTICK );
598                     x = current_view.get_winWidth()/2;
599                     y = current_view.get_winHeight()/2;
600                     _mVtoggle = 0;
601                     Quat0();
602                     build_rotmatrix(quat_mat, curquat);
603                     glutSetCursor(GLUT_CURSOR_LEFT_RIGHT);
604                     FG_LOG( FG_INPUT, FG_INFO, "Mouse in view mode" );
605                     break;
606                     
607                 case MOUSE_VIEW:
608                     mouse_mode = MOUSE_POINTER;
609                     x = _savedX;
610                     y = _savedY;
611 #ifdef RESET_VIEW_ON_LEAVING_MOUSE_VIEW
612                     Quat0();
613                     build_rotmatrix(quat_mat, curquat);
614                     current_view.set_goal_view_offset(0.0);
615 #ifdef NO_SMOOTH_MOUSE_VIEW
616                     current_view.set_view_offset(0.0);
617 #endif
618 #endif      // RESET_VIEW_ON_LEAVING_MOUSE_VIEW
619                     glutSetCursor(GLUT_CURSOR_INHERIT);
620                     
621                     if(!menu_on)
622                         TurnCursorOff();
623                     
624                     FG_LOG( FG_INPUT, FG_INFO, "Mouse in pointer mode" );
625                     break;
626             }     
627             glutWarpPointer( x, y );
628         } // END RIGHT BUTTON
629     } // END UPDOWN == GLUT_DOWN
630     
631     // Note which button is pressed.
632     if ( updown == GLUT_DOWN ) {
633         last_buttons |=  ( 1 << button ) ;
634     } else {
635         last_buttons &= ~( 1 << button ) ;
636     }
637     
638     // If we're in pointer mode, let PUI
639     // know what's going on.
640     if (mouse_mode == MOUSE_POINTER) {
641         puMouse (button, updown, x,y);
642     }
643     
644     // Register the new position (if it
645     // hasn't been registered already).
646     _mX = x;
647     _mY = y;
648     
649     glutPostRedisplay ();
650 }
651
652 /* ================ General Purpose Functions ================ */
653
654 // Intercept the Escape Key
655 void ConfirmExitDialog(void)
656 {
657     FG_PUSH_PUI_DIALOG( YNdialogBox );
658 }
659
660 // General Purpose Message Box
661 void mkDialog (const char *txt)
662 {
663     strncpy(global_dialog_string, txt, 256);
664     dialogBoxMessage->setLabel(global_dialog_string);
665     FG_PUSH_PUI_DIALOG( dialogBox );
666 }
667
668 // Repair any damage done to the Panel by other Gui Items
669 void guiFixPanel( void )
670 {
671     int toggle_pause;
672
673     if ( current_options.get_panel_status() ) {
674         FGView *v = &current_view;
675         FGTime *t = FGTime::cur_time_params;
676
677         if( (toggle_pause = !t->getPause()) )
678             t->togglePauseMode();
679
680         // this seems to be the only way to do this :-(
681         // problem is the viewport has been mucked with
682         xglViewport(0, 0 , (GLint)(v->winWidth), (GLint)(v->winHeight) );
683         FGPanel::OurPanel->ReInit(0, 0, 1024, 768);
684
685         if(toggle_pause)
686             t->togglePauseMode();
687     }
688 }
689
690 // Toggle the Menu and Mouse display state
691 void guiToggleMenu(void)
692 {
693     if( menu_on ) {
694         // printf("Hiding Menu\n");
695         mainMenuBar->hide  ();
696 #if defined(WIN32_CURSOR_TWEAKS)
697         if( mouse_mode == MOUSE_POINTER )
698             TurnCursorOff();
699 #endif // #ifdef WIN32_CURSOR_TWEAKS
700     } else {
701         // printf("Showing Menu\n");
702         mainMenuBar->reveal();
703 #ifdef WIN32
704         TurnCursorOn();
705 #endif // #ifdef WIN32
706     }
707     menu_on = ~menu_on;
708 }
709     
710 /* -----------------------------------------------------------------------
711 the Gui callback functions 
712 ____________________________________________________________________*/
713
714 void reInit(puObject *cb)
715 {
716     BusyCursor(0);
717     Quat0();
718     build_rotmatrix(quat_mat, curquat);
719     fgReInitSubsystems();
720     BusyCursor(1);
721 }
722         
723 // This is the accessor function
724 void guiTogglePanel(puObject *cb)
725 {
726     current_options.toggle_panel();
727 }
728     
729 //void MenuHideMenuCb(puObject *cb)
730 void hideMenuCb (puObject *cb)
731 {
732     guiToggleMenu();
733 }
734
735 void goodBye(puObject *)
736 {
737     // FG_LOG( FG_INPUT, FG_ALERT,
738     //      "Program exiting normally at user request." );
739     cout << "Program exiting normally at user request." << endl;
740
741     //  if(gps_bug)
742     //      fclose(gps_bug);
743
744     exit(-1);
745 }
746
747
748 void goAwayCb (puObject *me)
749 {
750     FG_POP_PUI_DIALOG( dialogBox );
751 }
752
753 void mkDialogInit (void)
754 {
755     //  printf("mkDialogInit\n");
756     int x = (current_options.get_xsize()/2 - 400/2);
757     int y = (current_options.get_ysize()/2 - 100/2);
758     dialogBox = new puDialogBox (x, y); // 150, 50
759     {
760         dialogFrame = new puFrame (0,0,400,100);
761         dialogBoxMessage  =  new puText         (10, 70);
762         dialogBoxMessage  -> setLabel           ("");
763         dialogBoxOkButton =  new puOneShot      (180, 10, 240, 50);
764         dialogBoxOkButton -> setLegend          (gui_msg_OK);
765         dialogBoxOkButton -> makeReturnDefault  (TRUE );
766         dialogBoxOkButton -> setCallback        (goAwayCb);
767     }
768     FG_FINALIZE_PUI_DIALOG( dialogBox );
769 }
770
771 void MayBeGoodBye(puObject *)
772 {
773     ConfirmExitDialog(); 
774 }
775
776 void goAwayYesNoCb(puObject *me)
777 {
778     FG_POP_PUI_DIALOG( YNdialogBox);
779 }
780
781 void ConfirmExitDialogInit(void)
782 {
783     char msg[] = "Really Quit";
784     char *s;
785
786     //  printf("ConfirmExitDialogInit\n");
787     int len = 200 - puGetStringWidth( puGetDefaultLabelFont(), msg )/2;
788
789     int x = (current_options.get_xsize()/2 - 400/2);
790     int y = (current_options.get_ysize()/2 - 100/2);
791         
792     YNdialogBox = new puDialogBox (x, y); // 150, 50
793     //  YNdialogBox = new puDialogBox (150, 50);
794     {
795         YNdialogFrame = new puFrame (0,0,400, 100);
796         
797         YNdialogBoxMessage  =  new puText         (len, 70);
798         YNdialogBoxMessage  -> setDefaultValue    (msg);
799         YNdialogBoxMessage  -> getDefaultValue    (&s);
800         YNdialogBoxMessage  -> setLabel           (s);
801         
802         YNdialogBoxOkButton =  new puOneShot      (100, 10, 160, 50);
803         YNdialogBoxOkButton -> setLegend          (gui_msg_OK);
804         YNdialogBoxOkButton -> setCallback        (goodBye);
805         
806         YNdialogBoxNoButton =  new puOneShot      (240, 10, 300, 50);
807         YNdialogBoxNoButton -> setLegend          (gui_msg_NO);
808         //      YNdialogBoxNoButton -> makeReturnDefault  (TRUE );
809         YNdialogBoxNoButton -> setCallback        (goAwayYesNoCb);
810     }
811     FG_FINALIZE_PUI_DIALOG( YNdialogBox );
812 }
813
814 void notCb (puObject *)
815 {
816     mkDialog ("This function isn't implemented yet");
817 }
818
819 void helpCb (puObject *)
820 {
821     string command;
822         
823 #if defined(FX) && !defined(WIN32)
824 #  if defined(XMESA_FX_FULLSCREEN) && defined(XMESA_FX_WINDOW)
825     if ( global_fullscreen ) {
826         global_fullscreen = false;
827         XMesaSetFXmode( XMESA_FX_WINDOW );
828     }
829 #  endif
830 #endif
831         
832 #if !defined(WIN32)
833     string url = "http://www.flightgear.org/Docs/InstallGuide/getstart.html";
834         
835     if ( system("xwininfo -name Netscape > /dev/null 2>&1") == 0 ) {
836         command = "netscape -remote \"openURL(" + url + ")\" &";
837     } else {
838         command = "netscape " + url + " &";
839     }
840 #else
841     command = "webrun.bat";
842 #endif
843         
844     system( command.c_str() );
845     //string text = "Help started in netscape window.";
846
847     //mkDialog (text.c_str());
848     mkDialog ("Help started in netscape window.");
849 }
850
851 #if defined( WIN32 ) && !defined( __CYGWIN__)
852
853 static void rotateView( double roll, double pitch, double yaw )
854 {
855         // rotate view
856 }
857
858 static GLBitmap *b1 = NULL;
859 extern FGInterface cur_view_fdm;
860 GLubyte *hiResScreenCapture( int multiplier )
861 {
862         float oldfov = current_options.get_fov();
863         float fov = oldfov / multiplier;
864         FGView *v = &current_view;
865         current_options.set_fov(fov);
866         v->force_update_fov_math();
867     fgInitVisuals();
868     int cur_width = current_view.get_winWidth( );
869     int cur_height = current_view.get_winHeight( );
870         if (b1) delete( b1 );
871         // New empty (mostly) bitmap
872         b1 = new GlBitmap( GL_RGB, 1, 1, (unsigned char *)"123" );
873         int x,y;
874         for ( y = 0; y < multiplier; y++ )
875         {
876                 for ( x = 0; x < multiplier; x++ )
877                 {
878                         fgReshape( cur_width, cur_height );
879                         // pan to tile
880                         rotateView( 0, (y*fov)-((multiplier-1)*fov/2), (x*fov)-((multiplier-1)*fov/2) );
881                         fgRenderFrame();
882                         // restore view
883                         GlBitmap b2;
884                         b1->copyBitmap( &b2, cur_width*x, cur_height*y );
885                 }
886         }
887         current_view.UpdateViewParams(cur_view_fdm);
888         current_options.set_fov(oldfov);
889         v->force_update_fov_math();
890         return b1->getBitmap();
891 }
892 #endif
893
894
895 #if defined( WIN32 ) && !defined( __CYGWIN__)
896 // win32 print screen function
897 void printScreen ( puObject *obj ) {
898     bool show_pu_cursor = false;
899     TurnCursorOff();
900     if ( !puCursorIsHidden() ) {
901         show_pu_cursor = true;
902         puHideCursor();
903     }
904     BusyCursor( 0 );
905     mainMenuBar->hide();
906
907     CGlPrinter p( CGlPrinter::PRINT_BITMAP );
908     int cur_width = current_view.get_winWidth( );
909     int cur_height = current_view.get_winHeight( );
910     p.Begin( "FlightGear", cur_width*3, cur_height*3 );
911         p.End( hiResScreenCapture(3) );
912
913     if( menu_on ) {
914         mainMenuBar->reveal();
915     }
916     BusyCursor(1);
917     if ( show_pu_cursor ) {
918         puShowCursor();
919     }
920     TurnCursorOn();
921 }
922 #endif // #ifdef WIN32
923
924
925 void dumpSnapShot ( puObject *obj ) {
926     fgDumpSnapShot();
927 }
928
929
930 // do a screen snap shot
931 void fgDumpSnapShot () {
932     bool show_pu_cursor = false;
933
934     mainMenuBar->hide();
935     TurnCursorOff();
936     if ( !puCursorIsHidden() ) {
937         show_pu_cursor = true;
938         puHideCursor();
939     }
940
941     fgInitVisuals();
942     fgReshape( current_options.get_xsize(), current_options.get_ysize() );
943
944     // we need two render frames here to clear the menu and cursor
945     // ... not sure why but doing an extra fgFenderFrame() shoulnd't
946     // hurt anything
947     fgRenderFrame();
948     fgRenderFrame();
949
950     my_glDumpWindow( "fgfs-screen.ppm", 
951                      current_options.get_xsize(), 
952                      current_options.get_ysize() );
953     
954     mkDialog ("Snap shot saved to fgfs-screen.ppm");
955
956     if ( show_pu_cursor ) {
957         puShowCursor();
958     }
959
960     TurnCursorOn();
961     if( menu_on ) {
962         mainMenuBar->reveal();
963     }
964
965 }
966
967
968 /// The beginnings of teleportation :-)
969 //  Needs cleaning up but works
970 //  These statics should disapear when this is a class
971 static puDialogBox     *AptDialog = 0;
972 static puFrame         *AptDialogFrame = 0;
973 static puText          *AptDialogMessage = 0;
974 static puInput         *AptDialogInput = 0;
975
976 static char NewAirportId[16];
977 static char NewAirportLabel[] = "Enter New Airport ID"; 
978
979 static puOneShot       *AptDialogOkButton = 0;
980 static puOneShot       *AptDialogCancelButton = 0;
981 static puOneShot       *AptDialogResetButton = 0;
982
983 void AptDialog_Cancel(puObject *)
984 {
985     FG_POP_PUI_DIALOG( AptDialog );
986 }
987
988 void AptDialog_OK (puObject *)
989 {
990         fgAIRPORTS airports;
991         fgAIRPORT a;
992     
993     FGTime *t = FGTime::cur_time_params;
994     int PauseMode = t->getPause();
995     if(!PauseMode)
996         t->togglePauseMode();
997
998     char *s;
999     AptDialogInput->getValue(&s);
1000     string AptId(s);
1001
1002         cout << "AptDialog_OK " << AptId << " " << AptId.length() << endl;
1003     
1004     AptDialog_Cancel( NULL );
1005     
1006     if ( AptId.length() ) {
1007         // set initial position from airport id
1008         FG_LOG( FG_GENERAL, FG_INFO,
1009                 "Attempting to set starting position from airport code "
1010                 << AptId );
1011
1012         airports.load("apt_simple");
1013         if ( airports.search( AptId, &a ) )
1014         {
1015             current_options.set_airport_id( AptId.c_str() );
1016             BusyCursor(0);
1017             fgReInitSubsystems();
1018             BusyCursor(1);
1019         } else {
1020             AptId  += " not in database.";
1021             mkDialog(AptId.c_str());
1022         }
1023     }
1024     if( PauseMode != t->getPause() )
1025         t->togglePauseMode();
1026 }
1027
1028 void AptDialog_Reset(puObject *)
1029 {
1030     //  strncpy( NewAirportId, current_options.get_airport_id().c_str(), 16 );
1031     sprintf( NewAirportId, "%s", current_options.get_airport_id().c_str() );
1032     AptDialogInput->setValue ( NewAirportId );
1033     AptDialogInput->setCursor( 0 ) ;
1034 }
1035
1036 void NewAirport(puObject *cb)
1037 {
1038     //  strncpy( NewAirportId, current_options.get_airport_id().c_str(), 16 );
1039     sprintf( NewAirportId, "%s", current_options.get_airport_id().c_str() );
1040 //      cout << "NewAirport " << NewAirportId << endl;
1041     AptDialogInput->setValue( NewAirportId );
1042
1043     FG_PUSH_PUI_DIALOG( AptDialog );
1044 }
1045
1046 static void NewAirportInit(void)
1047 {
1048     sprintf( NewAirportId, "%s", current_options.get_airport_id().c_str() );
1049     int len = 150 - puGetStringWidth( puGetDefaultLabelFont(),
1050                                       NewAirportLabel ) / 2;
1051
1052     AptDialog = new puDialogBox (150, 50);
1053     {
1054         AptDialogFrame   = new puFrame           (0,0,350, 150);
1055         AptDialogMessage = new puText            (len, 110);
1056         AptDialogMessage ->    setLabel          (NewAirportLabel);
1057
1058         AptDialogInput   = new puInput           (50, 70, 300, 100);
1059         AptDialogInput   ->    setValue          (NewAirportId);
1060         AptDialogInput   ->    acceptInput();
1061
1062         AptDialogOkButton     =  new puOneShot   (50, 10, 110, 50);
1063         AptDialogOkButton     ->     setLegend   (gui_msg_OK);
1064         AptDialogOkButton     ->     setCallback (AptDialog_OK);
1065         AptDialogOkButton     ->     makeReturnDefault(TRUE);
1066
1067         AptDialogCancelButton =  new puOneShot   (140, 10, 210, 50);
1068         AptDialogCancelButton ->     setLegend   (gui_msg_CANCEL);
1069         AptDialogCancelButton ->     setCallback (AptDialog_Cancel);
1070
1071         AptDialogResetButton  =  new puOneShot   (240, 10, 300, 50);
1072         AptDialogResetButton  ->     setLegend   (gui_msg_RESET);
1073         AptDialogResetButton  ->     setCallback (AptDialog_Reset);
1074     }
1075         cout << "NewAirportInit " << NewAirportId << endl;
1076     FG_FINALIZE_PUI_DIALOG( AptDialog );
1077 }
1078
1079 /// The beginnings of networking :-)
1080 //  Needs cleaning up but works
1081 //  These statics should disapear when this is a class
1082 static puDialogBox     *NetIdDialog = 0;
1083 static puFrame         *NetIdDialogFrame = 0;
1084 static puText          *NetIdDialogMessage = 0;
1085 static puInput         *NetIdDialogInput = 0;
1086
1087 static char NewNetId[16];
1088 static char NewNetIdLabel[] = "Enter New Callsign"; 
1089
1090 static puOneShot       *NetIdDialogOkButton = 0;
1091 static puOneShot       *NetIdDialogCancelButton = 0;
1092
1093 void NetIdDialog_Cancel(puObject *)
1094 {
1095     FG_POP_PUI_DIALOG( NetIdDialog );
1096 }
1097
1098 void NetIdDialog_OK (puObject *)
1099 {
1100     string NetId;
1101     
1102     FGTime *t = FGTime::cur_time_params;
1103     int PauseMode = t->getPause();
1104     if(!PauseMode)
1105         t->togglePauseMode();
1106 /*  
1107    The following needs some cleanup because 
1108    "string options.NetId" and "char *net_callsign" 
1109 */
1110     NetIdDialogInput->getValue(&net_callsign);
1111     NetId = net_callsign;
1112     
1113     NetIdDialog_Cancel( NULL );
1114     current_options.set_net_id( NetId.c_str() );
1115 /* Entering a callsign indicates : user wants Net HUD Info */
1116     net_hud_display = 1;
1117
1118     if( PauseMode != t->getPause() )
1119         t->togglePauseMode();
1120 }
1121
1122 void NewCallSign(puObject *cb)
1123 {
1124     sprintf( NewNetId, "%s", current_options.get_net_id().c_str() );
1125     NetIdDialogInput->setValue( NewNetId );
1126
1127     FG_PUSH_PUI_DIALOG( NetIdDialog );
1128 }
1129
1130 static void NewNetIdInit(void)
1131 {
1132     sprintf( NewNetId, "%s", current_options.get_net_id().c_str() );
1133     int len = 150 - puGetStringWidth( puGetDefaultLabelFont(),
1134                                       NewNetIdLabel ) / 2;
1135
1136     NetIdDialog = new puDialogBox (150, 50);
1137     {
1138         NetIdDialogFrame   = new puFrame           (0,0,350, 150);
1139         NetIdDialogMessage = new puText            (len, 110);
1140         NetIdDialogMessage ->    setLabel          (NewNetIdLabel);
1141
1142         NetIdDialogInput   = new puInput           (50, 70, 300, 100);
1143         NetIdDialogInput   ->    setValue          (NewNetId);
1144         NetIdDialogInput   ->    acceptInput();
1145
1146         NetIdDialogOkButton     =  new puOneShot   (50, 10, 110, 50);
1147         NetIdDialogOkButton     ->     setLegend   (gui_msg_OK);
1148         NetIdDialogOkButton     ->     setCallback (NetIdDialog_OK);
1149         NetIdDialogOkButton     ->     makeReturnDefault(TRUE);
1150
1151         NetIdDialogCancelButton =  new puOneShot   (240, 10, 300, 50);
1152         NetIdDialogCancelButton ->     setLegend   (gui_msg_CANCEL);
1153         NetIdDialogCancelButton ->     setCallback (NetIdDialog_Cancel);
1154
1155     }
1156     FG_FINALIZE_PUI_DIALOG( NetIdDialog );
1157 }
1158
1159 static void net_display_toggle( puObject *cb)
1160 {
1161         net_hud_display = (net_hud_display) ? 0 : 1;
1162         printf("Toggle net_hud_display : %d\n", net_hud_display);
1163 }
1164
1165 static void net_blaster_toggle( puObject *cb)
1166 {
1167         net_blast_toggle = (net_blast_toggle) ? 0 : -1;
1168         printf("Toggle net_blast : %d\n", net_blast_toggle);
1169 }
1170
1171 /***************  End Networking  **************/
1172
1173
1174
1175 /* -----------------------------------------------------------------------
1176 The menu stuff 
1177 ---------------------------------------------------------------------*/
1178 char *fileSubmenu               [] = {
1179     "Exit", /* "Close", "---------", */
1180 #if defined( WIN32 ) && !defined( __CYGWIN__)
1181     "Print",
1182 #endif
1183     "Snap Shot",
1184     /* "---------", "Save", */ 
1185     "Reset", NULL
1186 };
1187 puCallback fileSubmenuCb        [] = {
1188     MayBeGoodBye, /* hideMenuCb, NULL, */
1189 #if defined( WIN32 ) && !defined( __CYGWIN__)
1190     printScreen, 
1191 #endif
1192     /* NULL, notCb, */
1193     dumpSnapShot,
1194     reInit, NULL
1195 };
1196
1197 /*
1198 char *editSubmenu               [] = {
1199     "Edit text", NULL
1200 };
1201 puCallback editSubmenuCb        [] = {
1202     notCb, NULL
1203 };
1204 */
1205
1206 char *viewSubmenu               [] = {
1207     /* "Cockpit View > ", "View >","------------", */ "Toggle Panel...", NULL
1208 };
1209 puCallback viewSubmenuCb        [] = {
1210     /* notCb, notCb, NULL, guiTogglePanel, */ NULL
1211 };
1212
1213 char *aircraftSubmenu           [] = {
1214     "Autopilot", "Heading", "Altitude", "Navigation", "Airport", 
1215     /* "Communication", */ NULL
1216 };
1217 puCallback aircraftSubmenuCb    [] = {
1218     fgAPAdjust, NewHeading, NewAltitude, fgLatLonFormatToggle, NewTgtAirport, 
1219     /* notCb, */ NULL
1220 };
1221
1222 char *environmentSubmenu        [] = {
1223     "Airport", /* "Terrain", "Weather", */ NULL
1224 };
1225 puCallback environmentSubmenuCb [] = {
1226     NewAirport, /* notCb, notCb, */ NULL
1227 };
1228
1229 /*
1230 char *optionsSubmenu            [] = {
1231     "Preferences", "Realism & Reliablity...", NULL
1232 };
1233 puCallback optionsSubmenuCb     [] = {
1234     notCb, notCb, NULL
1235 };
1236 */
1237
1238 #ifdef FG_NETWORK_OLK
1239 char *networkSubmenu            [] = {
1240     /* "Unregister from FGD ", "Send MSG to All", "Send MSG", "Show Pilots", */
1241     /* "Register to FGD", */
1242     /* "Scan for Deamons", */ "Enter Callsign", /* "Display Netinfos", */
1243     "Toggle Display",
1244     "Hyper Blast", NULL
1245 };
1246 puCallback networkSubmenuCb     [] = {
1247     /* notCb, notCb, notCb, notCb, notCb, notCb, */ NewCallSign, /* notCb, */
1248     net_display_toggle, net_blaster_toggle, NULL
1249 };
1250 #endif
1251
1252 char *helpSubmenu               [] = {
1253     /* "About...", */ "Help", NULL
1254 };
1255 puCallback helpSubmenuCb        [] = {
1256     /* notCb, */ helpCb, NULL
1257 };
1258
1259
1260 /* -------------------------------------------------------------------------
1261 init the gui
1262 _____________________________________________________________________*/
1263
1264
1265 void guiInit()
1266 {
1267     char *mesa_win_state;
1268
1269     // Initialize PUI
1270     puInit();
1271     puSetDefaultStyle         ( PUSTYLE_SMALL_BEVELLED ); //PUSTYLE_DEFAULT
1272     puSetDefaultColourScheme  (0.8, 0.8, 0.8, 0.4);
1273
1274     // Initialize our GLOBAL GUI STRINGS
1275     gui_msg_OK     = msg_OK;     // "OK"
1276     gui_msg_NO     = msg_NO;     // "NO"
1277     gui_msg_YES    = msg_YES;    // "YES"
1278     gui_msg_CANCEL = msg_CANCEL; // "CANCEL"
1279     gui_msg_RESET  = msg_RESET;  // "RESET"
1280
1281     // Next check home directory
1282     FGPath fntpath;
1283     char* envp = ::getenv( "FG_FONTS" );
1284     if ( envp != NULL ) {
1285         fntpath.set( envp );
1286     } else {
1287         fntpath.set( current_options.get_fg_root() );
1288         fntpath.append( "Fonts" );
1289     }
1290
1291     // Install our fast fonts
1292     fntpath.append( "typewriter.txf" );
1293     guiFntHandle = new fntTexFont ;
1294     guiFntHandle -> load ( (char *)fntpath.c_str() ) ;
1295     puFont GuiFont ( guiFntHandle, 15 ) ;
1296     puSetDefaultFonts( GuiFont, GuiFont ) ;
1297     guiFnt = puGetDefaultLabelFont();
1298   
1299     if ( current_options.get_mouse_pointer() == 0 ) {
1300         // no preference specified for mouse pointer, attempt to autodetect...
1301         // Determine if we need to render the cursor, or if the windowing
1302         // system will do it.  First test if we are rendering with glide.
1303         if ( strstr ( general.get_glRenderer(), "Glide" ) ) {
1304             // Test for the MESA_GLX_FX env variable
1305             if ( (mesa_win_state = getenv( "MESA_GLX_FX" )) != NULL) {
1306                 // test if we are fullscreen mesa/glide
1307                 if ( (mesa_win_state[0] == 'f') ||
1308                      (mesa_win_state[0] == 'F') ) {
1309                     puShowCursor ();
1310                 }
1311             }
1312         }
1313         mouse_active = ~mouse_active;
1314     } else if ( current_options.get_mouse_pointer() == 1 ) {
1315         // don't show pointer
1316     } else if ( current_options.get_mouse_pointer() == 2 ) {
1317         // force showing pointer
1318         puShowCursor();
1319         mouse_active = ~mouse_active;
1320     }
1321         
1322     // MOUSE_VIEW mode stuff
1323     trackball(_quat0, 0.0, 0.0, 0.0, 0.0);  
1324     Quat0();
1325     build_rotmatrix(quat_mat, curquat);
1326
1327     // Set up our Dialog Boxes
1328     ConfirmExitDialogInit();
1329     NewAirportInit();
1330 #ifdef FG_NETWORK_OLK
1331     NewNetIdInit();
1332 #endif
1333     mkDialogInit();
1334     
1335     // Make the menu bar
1336     mainMenuBar = new puMenuBar ();
1337     mainMenuBar -> add_submenu ("File", fileSubmenu, fileSubmenuCb);
1338     // mainMenuBar -> add_submenu ("Edit", editSubmenu, editSubmenuCb);
1339     mainMenuBar -> add_submenu ("View", viewSubmenu, viewSubmenuCb);
1340     mainMenuBar -> add_submenu ("Aircraft", aircraftSubmenu, aircraftSubmenuCb);
1341     mainMenuBar -> add_submenu ("Environment", environmentSubmenu, environmentSubmenuCb);
1342     // mainMenuBar -> add_submenu ("Options", optionsSubmenu, optionsSubmenuCb);
1343 #ifdef FG_NETWORK_OLK
1344     mainMenuBar -> add_submenu ("Network", networkSubmenu, networkSubmenuCb);
1345 #endif
1346     mainMenuBar -> add_submenu ("Help", helpSubmenu, helpSubmenuCb);
1347     mainMenuBar-> close ();
1348     // Set up menu bar toggle
1349     menu_on = ~0;
1350 }
1351
1352
1353
1354 /*
1355  * Trackball code:
1356  *
1357  * Implementation of a virtual trackball.
1358  * Implemented by Gavin Bell, lots of ideas from Thant Tessman and
1359  *   the August '88 issue of Siggraph's "Computer Graphics," pp. 121-129.
1360  *
1361  * Vector manip code:
1362  *
1363  * Original code from:
1364  * David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul Haeberli
1365  *
1366  * Much mucking with by:
1367  * Gavin Bell
1368  */
1369 #if defined(_WIN32) && !defined( __CYGWIN32__ )
1370 #pragma warning (disable:4244)          /* disable bogus conversion warnings */
1371 #endif
1372 #include <math.h>
1373 #include <stdio.h>
1374 //#include "trackball.h"
1375
1376 /*
1377  * This size should really be based on the distance from the center of
1378  * rotation to the point on the object underneath the mouse.  That
1379  * point would then track the mouse as closely as possible.  This is a
1380  * simple example, though, so that is left as an Exercise for the
1381  * Programmer.
1382  */
1383 #define TRACKBALLSIZE  (0.8f)
1384 #define SQRT(x) sqrt(x)
1385
1386 /*
1387  * Local function prototypes (not defined in trackball.h)
1388  */
1389 static float tb_project_to_sphere(float, float, float);
1390 static void normalize_quat(float [4]);
1391
1392 static void
1393 vzero(float *v)
1394 {
1395     v[0] = 0.0;
1396     v[1] = 0.0;
1397     v[2] = 0.0;
1398 }
1399
1400 static void
1401 vset(float *v, float x, float y, float z)
1402 {
1403     v[0] = x;
1404     v[1] = y;
1405     v[2] = z;
1406 }
1407
1408 static void
1409 vsub(const float *src1, const float *src2, float *dst)
1410 {
1411     dst[0] = src1[0] - src2[0];
1412     dst[1] = src1[1] - src2[1];
1413     dst[2] = src1[2] - src2[2];
1414 }
1415
1416 static void
1417 vcopy(const float *v1, float *v2)
1418 {
1419     register int i;
1420     for (i = 0 ; i < 3 ; i++)
1421         v2[i] = v1[i];
1422 }
1423
1424 static void
1425 vcross(const float *v1, const float *v2, float *cross)
1426 {
1427     float temp[3];
1428
1429     temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
1430     temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
1431     temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
1432     vcopy(temp, cross);
1433 }
1434
1435 static float
1436 vlength(const float *v)
1437 {
1438     float tmp = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
1439     return SQRT(tmp);
1440 }
1441
1442 static void
1443 vscale(float *v, float div)
1444 {
1445     v[0] *= div;
1446     v[1] *= div;
1447     v[2] *= div;
1448 }
1449
1450 static void
1451 vnormal(float *v)
1452 {
1453     vscale(v,1.0/vlength(v));
1454 }
1455
1456 static float
1457 vdot(const float *v1, const float *v2)
1458 {
1459     return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
1460 }
1461
1462 static void
1463 vadd(const float *src1, const float *src2, float *dst)
1464 {
1465     dst[0] = src1[0] + src2[0];
1466     dst[1] = src1[1] + src2[1];
1467     dst[2] = src1[2] + src2[2];
1468 }
1469
1470 /*
1471  *  Given an axis and angle, compute quaternion.
1472  */
1473 void
1474 axis_to_quat(float a[3], float phi, float q[4])
1475 {
1476     double sinphi2, cosphi2;
1477     double phi2 = phi/2.0;
1478     sinphi2 = sin(phi2);
1479     cosphi2 = cos(phi2);
1480     vnormal(a);
1481     vcopy(a,q);
1482     vscale(q,sinphi2);
1483     q[3] = cosphi2;
1484 }
1485
1486 /*
1487  * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet
1488  * if we are away from the center of the sphere.
1489  */
1490 static float
1491 tb_project_to_sphere(float r, float x, float y)
1492 {
1493     float d, t, z, tmp;
1494
1495     tmp = x*x + y*y;
1496     d = SQRT(tmp);
1497     if (d < r * 0.70710678118654752440) {    /* Inside sphere */
1498         tmp = r*r - d*d;
1499         z = SQRT(tmp);
1500     } else {           /* On hyperbola */
1501         t = r / 1.41421356237309504880;
1502         z = t*t / d;
1503     }
1504     return z;
1505 }
1506
1507 /*
1508  * Quaternions always obey:  a^2 + b^2 + c^2 + d^2 = 1.0
1509  * If they don't add up to 1.0, dividing by their magnitued will
1510  * renormalize them.
1511  *
1512  * Note: See the following for more information on quaternions:
1513  *
1514  * - Shoemake, K., Animating rotation with quaternion curves, Computer
1515  *   Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985.
1516  * - Pletinckx, D., Quaternion calculus as a basic tool in computer
1517  *   graphics, The Visual Computer 5, 2-13, 1989.
1518  */
1519 static void
1520 normalize_quat(float q[4])
1521 {
1522     int i;
1523     float mag, tmp;
1524
1525     tmp = q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3];
1526     mag = 1.0 / SQRT(tmp);
1527     for (i = 0; i < 4; i++)
1528         q[i] *= mag;
1529 }
1530
1531 /*
1532  * Ok, simulate a track-ball.  Project the points onto the virtual
1533  * trackball, then figure out the axis of rotation, which is the cross
1534  * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0)
1535  * Note:  This is a deformed trackball-- is a trackball in the center,
1536  * but is deformed into a hyperbolic sheet of rotation away from the
1537  * center.  This particular function was chosen after trying out
1538  * several variations.
1539  *
1540  * It is assumed that the arguments to this routine are in the range
1541  * (-1.0 ... 1.0)
1542  */
1543 void
1544 trackball(float q[4], float p1x, float p1y, float p2x, float p2y)
1545 {
1546     float a[3]; /* Axis of rotation */
1547     float phi;  /* how much to rotate about axis */
1548     float p1[3], p2[3], d[3];
1549     float t;
1550
1551     if (p1x == p2x && p1y == p2y) {
1552         /* Zero rotation */
1553         vzero(q);
1554         q[3] = 1.0;
1555         return;
1556     }
1557
1558     /*
1559      * First, figure out z-coordinates for projection of P1 and P2 to
1560      * deformed sphere
1561      */
1562     vset(p1,p1x,p1y,tb_project_to_sphere(TRACKBALLSIZE,p1x,p1y));
1563     vset(p2,p2x,p2y,tb_project_to_sphere(TRACKBALLSIZE,p2x,p2y));
1564
1565     /*
1566      *  Now, we want the cross product of P1 and P2
1567      */
1568     vcross(p2,p1,a);
1569
1570     /*
1571      *  Figure out how much to rotate around that axis.
1572      */
1573     vsub(p1,p2,d);
1574     t = vlength(d) / (2.0*TRACKBALLSIZE);
1575
1576     /*
1577      * Avoid problems with out-of-control values...
1578      */
1579     if (t > 1.0) t = 1.0;
1580     if (t < -1.0) t = -1.0;
1581     phi = 2.0 * asin(t);
1582
1583     axis_to_quat(a,phi,q);
1584 }
1585
1586 /*
1587  * Given two rotations, e1 and e2, expressed as quaternion rotations,
1588  * figure out the equivalent single rotation and stuff it into dest.
1589  *
1590  * This routine also normalizes the result every RENORMCOUNT times it is
1591  * called, to keep error from creeping in.
1592  *
1593  * NOTE: This routine is written so that q1 or q2 may be the same
1594  * as dest (or each other).
1595  */
1596
1597 #define RENORMCOUNT 97
1598
1599 void
1600 add_quats(float q1[4], float q2[4], float dest[4])
1601 {
1602     static int count=0;
1603     float t1[4], t2[4], t3[4];
1604     float tf[4];
1605
1606 #if 0
1607 printf("q1 = %f %f %f %f\n", q1[0], q1[1], q1[2], q1[3]);
1608 printf("q2 = %f %f %f %f\n", q2[0], q2[1], q2[2], q2[3]);
1609 #endif
1610
1611     vcopy(q1,t1);
1612     vscale(t1,q2[3]);
1613
1614     vcopy(q2,t2);
1615     vscale(t2,q1[3]);
1616
1617     vcross(q2,q1,t3);
1618     vadd(t1,t2,tf);
1619     vadd(t3,tf,tf);
1620     tf[3] = q1[3] * q2[3] - vdot(q1,q2);
1621
1622 #if 0
1623 printf("tf = %f %f %f %f\n", tf[0], tf[1], tf[2], tf[3]);
1624 #endif
1625
1626     dest[0] = tf[0];
1627     dest[1] = tf[1];
1628     dest[2] = tf[2];
1629     dest[3] = tf[3];
1630
1631     if (++count > RENORMCOUNT) {
1632         count = 0;
1633         normalize_quat(dest);
1634     }
1635 }
1636
1637 /*
1638  * Build a rotation matrix, given a quaternion rotation.
1639  *
1640  */
1641 void
1642 build_rotmatrix(float m[4][4], float q[4])
1643 {
1644 //#define TRANSPOSED_QUAT
1645 #ifndef TRANSPOSED_QUAT
1646     m[0][0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]);
1647     m[0][1] = 2.0 * (q[0] * q[1] - q[2] * q[3]);
1648     m[0][2] = 2.0 * (q[2] * q[0] + q[1] * q[3]);
1649     m[0][3] = 0.0;
1650
1651     m[1][0] = 2.0 * (q[0] * q[1] + q[2] * q[3]);
1652     m[1][1]= 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]);
1653     m[1][2] = 2.0 * (q[1] * q[2] - q[0] * q[3]);
1654     m[1][3] = 0.0;
1655
1656     m[2][0] = 2.0 * (q[2] * q[0] - q[1] * q[3]);
1657     m[2][1] = 2.0 * (q[1] * q[2] + q[0] * q[3]);
1658     m[2][2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]);
1659     
1660     m[2][3] = 0.0;
1661     m[3][0] = 0.0;
1662     m[3][1] = 0.0;
1663     m[3][2] = 0.0;
1664     m[3][3] = 1.0;
1665 #else //  TRANSPOSED_QUAT
1666     m[0][0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]);
1667     m[0][1] = 2.0 * (q[0] * q[1] + q[2] * q[3]);
1668     m[0][2] = 2.0 * (q[2] * q[0] - q[1] * q[3]);
1669     m[0][3] = 0.0;
1670
1671     m[1][0] = 2.0 * (q[0] * q[1] - q[2] * q[3]);
1672     m[1][1] = 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]);
1673     m[1][2] = 2.0 * (q[1] * q[2] + q[0] * q[3]);
1674     m[1][3] = 0.0;
1675
1676     m[2][0] = 2.0 * (q[2] * q[0] + q[1] * q[3]);
1677     m[2][1] = 2.0 * (q[1] * q[2] - q[0] * q[3]);
1678     m[2][2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]);
1679     m[2][3] = 0.0;
1680     
1681     m[3][0] = 0.0;
1682     m[3][1] = 0.0;
1683     m[3][2] = 0.0;
1684     m[3][3] = 1.0;
1685 #endif // 0
1686 }
1687