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