]> git.mxchange.org Git - flightgear.git/blob - src/GUI/gui.cxx
15e6fd2e439f996096e336c8cb883dcea7404246
[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( CGlPrinter::READ_BITMAP );
865     int cur_width = current_view.get_winWidth( );
866     int cur_height = current_view.get_winHeight( );
867     p.Begin( "FlightGear" );
868     fgInitVisuals();
869     fgReshape( cur_width, cur_height );
870     //fgReshape( p.GetHorzRes(), p.GetVertRes() );
871     fgRenderFrame();
872     fgRenderFrame();
873     p.End();
874
875     if( menu_on ) {
876         mainMenuBar->reveal();
877     }
878     BusyCursor(1);
879     if ( show_pu_cursor ) {
880         puShowCursor();
881     }
882     TurnCursorOn();
883 }
884 #endif // #ifdef WIN32
885
886
887 void dumpSnapShot ( puObject *obj ) {
888     fgDumpSnapShot();
889 }
890
891
892 // do a screen snap shot
893 void fgDumpSnapShot () {
894     bool show_pu_cursor = false;
895
896     mainMenuBar->hide();
897     TurnCursorOff();
898     if ( !puCursorIsHidden() ) {
899         show_pu_cursor = true;
900         puHideCursor();
901     }
902
903     fgInitVisuals();
904     fgReshape( current_options.get_xsize(), current_options.get_ysize() );
905
906     // we need two render frames here to clear the menu and cursor
907     // ... not sure why but doing an extra fgFenderFrame() shoulnd't
908     // hurt anything
909     fgRenderFrame();
910     fgRenderFrame();
911
912     my_glDumpWindow( "fgfs-screen.ppm", 
913                      current_options.get_xsize(), 
914                      current_options.get_ysize() );
915     
916     mkDialog ("Snap shot saved to fgfs-screen.ppm");
917
918     if ( show_pu_cursor ) {
919         puShowCursor();
920     }
921
922     TurnCursorOn();
923     if( menu_on ) {
924         mainMenuBar->reveal();
925     }
926
927 }
928
929
930 /// The beginnings of teleportation :-)
931 //  Needs cleaning up but works
932 //  These statics should disapear when this is a class
933 static puDialogBox     *AptDialog = 0;
934 static puFrame         *AptDialogFrame = 0;
935 static puText          *AptDialogMessage = 0;
936 static puInput         *AptDialogInput = 0;
937
938 static char NewAirportId[16];
939 static char NewAirportLabel[] = "Enter New Airport ID"; 
940
941 static puOneShot       *AptDialogOkButton = 0;
942 static puOneShot       *AptDialogCancelButton = 0;
943 static puOneShot       *AptDialogResetButton = 0;
944
945 void AptDialog_Cancel(puObject *)
946 {
947     FG_POP_PUI_DIALOG( AptDialog );
948 }
949
950 void AptDialog_OK (puObject *)
951 {
952         fgAIRPORTS airports;
953         fgAIRPORT a;
954     
955     FGTime *t = FGTime::cur_time_params;
956     int PauseMode = t->getPause();
957     if(!PauseMode)
958         t->togglePauseMode();
959
960     char *s;
961     AptDialogInput->getValue(&s);
962     string AptId(s);
963
964         cout << "AptDialog_OK " << AptId << " " << AptId.length() << endl;
965     
966     AptDialog_Cancel( NULL );
967     
968     if ( AptId.length() ) {
969         // set initial position from airport id
970         FG_LOG( FG_GENERAL, FG_INFO,
971                 "Attempting to set starting position from airport code "
972                 << AptId );
973
974         airports.load("apt_simple");
975         if ( airports.search( AptId, &a ) )
976         {
977             current_options.set_airport_id( AptId.c_str() );
978             BusyCursor(0);
979             fgReInitSubsystems();
980             BusyCursor(1);
981         } else {
982             AptId  += " not in database.";
983             mkDialog(AptId.c_str());
984         }
985     }
986     if( PauseMode != t->getPause() )
987         t->togglePauseMode();
988 }
989
990 void AptDialog_Reset(puObject *)
991 {
992     //  strncpy( NewAirportId, current_options.get_airport_id().c_str(), 16 );
993     sprintf( NewAirportId, "%s", current_options.get_airport_id().c_str() );
994     AptDialogInput->setValue ( NewAirportId );
995     AptDialogInput->setCursor( 0 ) ;
996 }
997
998 void NewAirport(puObject *cb)
999 {
1000     //  strncpy( NewAirportId, current_options.get_airport_id().c_str(), 16 );
1001     sprintf( NewAirportId, "%s", current_options.get_airport_id().c_str() );
1002 //      cout << "NewAirport " << NewAirportId << endl;
1003     AptDialogInput->setValue( NewAirportId );
1004
1005     FG_PUSH_PUI_DIALOG( AptDialog );
1006 }
1007
1008 static void NewAirportInit(void)
1009 {
1010     sprintf( NewAirportId, "%s", current_options.get_airport_id().c_str() );
1011     int len = 150 - puGetStringWidth( puGetDefaultLabelFont(),
1012                                       NewAirportLabel ) / 2;
1013
1014     AptDialog = new puDialogBox (150, 50);
1015     {
1016         AptDialogFrame   = new puFrame           (0,0,350, 150);
1017         AptDialogMessage = new puText            (len, 110);
1018         AptDialogMessage ->    setLabel          (NewAirportLabel);
1019
1020         AptDialogInput   = new puInput           (50, 70, 300, 100);
1021         AptDialogInput   ->    setValue          (NewAirportId);
1022         AptDialogInput   ->    acceptInput();
1023
1024         AptDialogOkButton     =  new puOneShot   (50, 10, 110, 50);
1025         AptDialogOkButton     ->     setLegend   (gui_msg_OK);
1026         AptDialogOkButton     ->     setCallback (AptDialog_OK);
1027         AptDialogOkButton     ->     makeReturnDefault(TRUE);
1028
1029         AptDialogCancelButton =  new puOneShot   (140, 10, 210, 50);
1030         AptDialogCancelButton ->     setLegend   (gui_msg_CANCEL);
1031         AptDialogCancelButton ->     setCallback (AptDialog_Cancel);
1032
1033         AptDialogResetButton  =  new puOneShot   (240, 10, 300, 50);
1034         AptDialogResetButton  ->     setLegend   (gui_msg_RESET);
1035         AptDialogResetButton  ->     setCallback (AptDialog_Reset);
1036     }
1037         cout << "NewAirportInit " << NewAirportId << endl;
1038     FG_FINALIZE_PUI_DIALOG( AptDialog );
1039 }
1040
1041 /// The beginnings of networking :-)
1042 //  Needs cleaning up but works
1043 //  These statics should disapear when this is a class
1044 static puDialogBox     *NetIdDialog = 0;
1045 static puFrame         *NetIdDialogFrame = 0;
1046 static puText          *NetIdDialogMessage = 0;
1047 static puInput         *NetIdDialogInput = 0;
1048
1049 static char NewNetId[16];
1050 static char NewNetIdLabel[] = "Enter New Callsign"; 
1051
1052 static puOneShot       *NetIdDialogOkButton = 0;
1053 static puOneShot       *NetIdDialogCancelButton = 0;
1054
1055 void NetIdDialog_Cancel(puObject *)
1056 {
1057     FG_POP_PUI_DIALOG( NetIdDialog );
1058 }
1059
1060 void NetIdDialog_OK (puObject *)
1061 {
1062     string NetId;
1063     
1064     FGTime *t = FGTime::cur_time_params;
1065     int PauseMode = t->getPause();
1066     if(!PauseMode)
1067         t->togglePauseMode();
1068 /*  
1069    The following needs some cleanup because 
1070    "string options.NetId" and "char *net_callsign" 
1071 */
1072     NetIdDialogInput->getValue(&net_callsign);
1073     NetId = net_callsign;
1074     
1075     NetIdDialog_Cancel( NULL );
1076     current_options.set_net_id( NetId.c_str() );
1077 /* Entering a callsign indicates : user wants Net HUD Info */
1078     net_hud_display = 1;
1079
1080     if( PauseMode != t->getPause() )
1081         t->togglePauseMode();
1082 }
1083
1084 void NewCallSign(puObject *cb)
1085 {
1086     sprintf( NewNetId, "%s", current_options.get_net_id().c_str() );
1087     NetIdDialogInput->setValue( NewNetId );
1088
1089     FG_PUSH_PUI_DIALOG( NetIdDialog );
1090 }
1091
1092 static void NewNetIdInit(void)
1093 {
1094     sprintf( NewNetId, "%s", current_options.get_net_id().c_str() );
1095     int len = 150 - puGetStringWidth( puGetDefaultLabelFont(),
1096                                       NewNetIdLabel ) / 2;
1097
1098     NetIdDialog = new puDialogBox (150, 50);
1099     {
1100         NetIdDialogFrame   = new puFrame           (0,0,350, 150);
1101         NetIdDialogMessage = new puText            (len, 110);
1102         NetIdDialogMessage ->    setLabel          (NewNetIdLabel);
1103
1104         NetIdDialogInput   = new puInput           (50, 70, 300, 100);
1105         NetIdDialogInput   ->    setValue          (NewNetId);
1106         NetIdDialogInput   ->    acceptInput();
1107
1108         NetIdDialogOkButton     =  new puOneShot   (50, 10, 110, 50);
1109         NetIdDialogOkButton     ->     setLegend   (gui_msg_OK);
1110         NetIdDialogOkButton     ->     setCallback (NetIdDialog_OK);
1111         NetIdDialogOkButton     ->     makeReturnDefault(TRUE);
1112
1113         NetIdDialogCancelButton =  new puOneShot   (240, 10, 300, 50);
1114         NetIdDialogCancelButton ->     setLegend   (gui_msg_CANCEL);
1115         NetIdDialogCancelButton ->     setCallback (NetIdDialog_Cancel);
1116
1117     }
1118     FG_FINALIZE_PUI_DIALOG( NetIdDialog );
1119 }
1120
1121 static void net_display_toggle( puObject *cb)
1122 {
1123         net_hud_display = (net_hud_display) ? 0 : 1;
1124         printf("Toggle net_hud_display : %d\n", net_hud_display);
1125 }
1126
1127 static void net_blaster_toggle( puObject *cb)
1128 {
1129         net_blast_toggle = (net_blast_toggle) ? 0 : -1;
1130         printf("Toggle net_blast : %d\n", net_blast_toggle);
1131 }
1132
1133 /***************  End Networking  **************/
1134
1135
1136
1137 /* -----------------------------------------------------------------------
1138 The menu stuff 
1139 ---------------------------------------------------------------------*/
1140 char *fileSubmenu               [] = {
1141     "Exit", /* "Close", "---------", */
1142 #if defined( WIN32 ) && !defined( __CYGWIN__)
1143     "Print",
1144 #endif
1145     "Snap Shot",
1146     /* "---------", "Save", */ 
1147     "Reset", NULL
1148 };
1149 puCallback fileSubmenuCb        [] = {
1150     MayBeGoodBye, /* hideMenuCb, NULL, */
1151 #if defined( WIN32 ) && !defined( __CYGWIN__)
1152     printScreen, 
1153 #endif
1154     /* NULL, notCb, */
1155     dumpSnapShot,
1156     reInit, NULL
1157 };
1158
1159 /*
1160 char *editSubmenu               [] = {
1161     "Edit text", NULL
1162 };
1163 puCallback editSubmenuCb        [] = {
1164     notCb, NULL
1165 };
1166 */
1167
1168 char *viewSubmenu               [] = {
1169     /* "Cockpit View > ", "View >","------------", */ "Toggle Panel...", NULL
1170 };
1171 puCallback viewSubmenuCb        [] = {
1172     /* notCb, notCb, NULL, guiTogglePanel, */ NULL
1173 };
1174
1175 char *aircraftSubmenu           [] = {
1176     "Autopilot", "Heading", "Altitude", "Navigation", "Airport", 
1177     /* "Communication", */ NULL
1178 };
1179 puCallback aircraftSubmenuCb    [] = {
1180     fgAPAdjust, NewHeading, NewAltitude, fgLatLonFormatToggle, NewTgtAirport, 
1181     /* notCb, */ NULL
1182 };
1183
1184 char *environmentSubmenu        [] = {
1185     "Airport", /* "Terrain", "Weather", */ NULL
1186 };
1187 puCallback environmentSubmenuCb [] = {
1188     NewAirport, /* notCb, notCb, */ NULL
1189 };
1190
1191 /*
1192 char *optionsSubmenu            [] = {
1193     "Preferences", "Realism & Reliablity...", NULL
1194 };
1195 puCallback optionsSubmenuCb     [] = {
1196     notCb, notCb, NULL
1197 };
1198 */
1199
1200 #ifdef FG_NETWORK_OLK
1201 char *networkSubmenu            [] = {
1202     /* "Unregister from FGD ", "Send MSG to All", "Send MSG", "Show Pilots", */
1203     /* "Register to FGD", */
1204     /* "Scan for Deamons", */ "Enter Callsign", /* "Display Netinfos", */
1205     "Toggle Display",
1206     "Hyper Blast", NULL
1207 };
1208 puCallback networkSubmenuCb     [] = {
1209     /* notCb, notCb, notCb, notCb, notCb, notCb, */ NewCallSign, /* notCb, */
1210     net_display_toggle, net_blaster_toggle, NULL
1211 };
1212 #endif
1213
1214 char *helpSubmenu               [] = {
1215     /* "About...", */ "Help", NULL
1216 };
1217 puCallback helpSubmenuCb        [] = {
1218     /* notCb, */ helpCb, NULL
1219 };
1220
1221
1222 /* -------------------------------------------------------------------------
1223 init the gui
1224 _____________________________________________________________________*/
1225
1226
1227 void guiInit()
1228 {
1229     char *mesa_win_state;
1230
1231     // Initialize PUI
1232     puInit();
1233     puSetDefaultStyle         ( PUSTYLE_SMALL_BEVELLED ); //PUSTYLE_DEFAULT
1234     puSetDefaultColourScheme  (0.8, 0.8, 0.8, 0.4);
1235
1236     // Initialize our GLOBAL GUI STRINGS
1237     gui_msg_OK     = msg_OK;     // "OK"
1238     gui_msg_NO     = msg_NO;     // "NO"
1239     gui_msg_YES    = msg_YES;    // "YES"
1240     gui_msg_CANCEL = msg_CANCEL; // "CANCEL"
1241     gui_msg_RESET  = msg_RESET;  // "RESET"
1242
1243     // Next check home directory
1244     FGPath fntpath;
1245     char* envp = ::getenv( "FG_FONTS" );
1246     if ( envp != NULL ) {
1247         fntpath.set( envp );
1248     } else {
1249         fntpath.set( current_options.get_fg_root() );
1250         fntpath.append( "Fonts" );
1251     }
1252
1253     // Install our fast fonts
1254     fntpath.append( "typewriter.txf" );
1255     guiFntHandle = new fntTexFont ;
1256     guiFntHandle -> load ( (char *)fntpath.c_str() ) ;
1257     puFont GuiFont ( guiFntHandle, 15 ) ;
1258     puSetDefaultFonts( GuiFont, GuiFont ) ;
1259     guiFnt = puGetDefaultLabelFont();
1260   
1261     if ( current_options.get_mouse_pointer() == 0 ) {
1262         // no preference specified for mouse pointer, attempt to autodetect...
1263         // Determine if we need to render the cursor, or if the windowing
1264         // system will do it.  First test if we are rendering with glide.
1265         if ( strstr ( general.get_glRenderer(), "Glide" ) ) {
1266             // Test for the MESA_GLX_FX env variable
1267             if ( (mesa_win_state = getenv( "MESA_GLX_FX" )) != NULL) {
1268                 // test if we are fullscreen mesa/glide
1269                 if ( (mesa_win_state[0] == 'f') ||
1270                      (mesa_win_state[0] == 'F') ) {
1271                     puShowCursor ();
1272                 }
1273             }
1274         }
1275         mouse_active = ~mouse_active;
1276     } else if ( current_options.get_mouse_pointer() == 1 ) {
1277         // don't show pointer
1278     } else if ( current_options.get_mouse_pointer() == 2 ) {
1279         // force showing pointer
1280         puShowCursor();
1281         mouse_active = ~mouse_active;
1282     }
1283         
1284     // MOUSE_VIEW mode stuff
1285     trackball(_quat0, 0.0, 0.0, 0.0, 0.0);  
1286     Quat0();
1287     build_rotmatrix(quat_mat, curquat);
1288
1289     // Set up our Dialog Boxes
1290     ConfirmExitDialogInit();
1291     NewAirportInit();
1292 #ifdef FG_NETWORK_OLK
1293     NewNetIdInit();
1294 #endif
1295     mkDialogInit();
1296     
1297     // Make the menu bar
1298     mainMenuBar = new puMenuBar ();
1299     mainMenuBar -> add_submenu ("File", fileSubmenu, fileSubmenuCb);
1300     // mainMenuBar -> add_submenu ("Edit", editSubmenu, editSubmenuCb);
1301     mainMenuBar -> add_submenu ("View", viewSubmenu, viewSubmenuCb);
1302     mainMenuBar -> add_submenu ("Aircraft", aircraftSubmenu, aircraftSubmenuCb);
1303     mainMenuBar -> add_submenu ("Environment", environmentSubmenu, environmentSubmenuCb);
1304     // mainMenuBar -> add_submenu ("Options", optionsSubmenu, optionsSubmenuCb);
1305 #ifdef FG_NETWORK_OLK
1306     mainMenuBar -> add_submenu ("Network", networkSubmenu, networkSubmenuCb);
1307 #endif
1308     mainMenuBar -> add_submenu ("Help", helpSubmenu, helpSubmenuCb);
1309     mainMenuBar-> close ();
1310     // Set up menu bar toggle
1311     menu_on = ~0;
1312 }
1313
1314
1315
1316 /*
1317  * Trackball code:
1318  *
1319  * Implementation of a virtual trackball.
1320  * Implemented by Gavin Bell, lots of ideas from Thant Tessman and
1321  *   the August '88 issue of Siggraph's "Computer Graphics," pp. 121-129.
1322  *
1323  * Vector manip code:
1324  *
1325  * Original code from:
1326  * David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul Haeberli
1327  *
1328  * Much mucking with by:
1329  * Gavin Bell
1330  */
1331 #if defined(_WIN32) && !defined( __CYGWIN32__ )
1332 #pragma warning (disable:4244)          /* disable bogus conversion warnings */
1333 #endif
1334 #include <math.h>
1335 #include <stdio.h>
1336 //#include "trackball.h"
1337
1338 /*
1339  * This size should really be based on the distance from the center of
1340  * rotation to the point on the object underneath the mouse.  That
1341  * point would then track the mouse as closely as possible.  This is a
1342  * simple example, though, so that is left as an Exercise for the
1343  * Programmer.
1344  */
1345 #define TRACKBALLSIZE  (0.8f)
1346 #define SQRT(x) sqrt(x)
1347
1348 /*
1349  * Local function prototypes (not defined in trackball.h)
1350  */
1351 static float tb_project_to_sphere(float, float, float);
1352 static void normalize_quat(float [4]);
1353
1354 static void
1355 vzero(float *v)
1356 {
1357     v[0] = 0.0;
1358     v[1] = 0.0;
1359     v[2] = 0.0;
1360 }
1361
1362 static void
1363 vset(float *v, float x, float y, float z)
1364 {
1365     v[0] = x;
1366     v[1] = y;
1367     v[2] = z;
1368 }
1369
1370 static void
1371 vsub(const float *src1, const float *src2, float *dst)
1372 {
1373     dst[0] = src1[0] - src2[0];
1374     dst[1] = src1[1] - src2[1];
1375     dst[2] = src1[2] - src2[2];
1376 }
1377
1378 static void
1379 vcopy(const float *v1, float *v2)
1380 {
1381     register int i;
1382     for (i = 0 ; i < 3 ; i++)
1383         v2[i] = v1[i];
1384 }
1385
1386 static void
1387 vcross(const float *v1, const float *v2, float *cross)
1388 {
1389     float temp[3];
1390
1391     temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
1392     temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
1393     temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
1394     vcopy(temp, cross);
1395 }
1396
1397 static float
1398 vlength(const float *v)
1399 {
1400     float tmp = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
1401     return SQRT(tmp);
1402 }
1403
1404 static void
1405 vscale(float *v, float div)
1406 {
1407     v[0] *= div;
1408     v[1] *= div;
1409     v[2] *= div;
1410 }
1411
1412 static void
1413 vnormal(float *v)
1414 {
1415     vscale(v,1.0/vlength(v));
1416 }
1417
1418 static float
1419 vdot(const float *v1, const float *v2)
1420 {
1421     return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
1422 }
1423
1424 static void
1425 vadd(const float *src1, const float *src2, float *dst)
1426 {
1427     dst[0] = src1[0] + src2[0];
1428     dst[1] = src1[1] + src2[1];
1429     dst[2] = src1[2] + src2[2];
1430 }
1431
1432 /*
1433  *  Given an axis and angle, compute quaternion.
1434  */
1435 void
1436 axis_to_quat(float a[3], float phi, float q[4])
1437 {
1438     double sinphi2, cosphi2;
1439     double phi2 = phi/2.0;
1440     sinphi2 = sin(phi2);
1441     cosphi2 = cos(phi2);
1442     vnormal(a);
1443     vcopy(a,q);
1444     vscale(q,sinphi2);
1445     q[3] = cosphi2;
1446 }
1447
1448 /*
1449  * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet
1450  * if we are away from the center of the sphere.
1451  */
1452 static float
1453 tb_project_to_sphere(float r, float x, float y)
1454 {
1455     float d, t, z, tmp;
1456
1457     tmp = x*x + y*y;
1458     d = SQRT(tmp);
1459     if (d < r * 0.70710678118654752440) {    /* Inside sphere */
1460         tmp = r*r - d*d;
1461         z = SQRT(tmp);
1462     } else {           /* On hyperbola */
1463         t = r / 1.41421356237309504880;
1464         z = t*t / d;
1465     }
1466     return z;
1467 }
1468
1469 /*
1470  * Quaternions always obey:  a^2 + b^2 + c^2 + d^2 = 1.0
1471  * If they don't add up to 1.0, dividing by their magnitued will
1472  * renormalize them.
1473  *
1474  * Note: See the following for more information on quaternions:
1475  *
1476  * - Shoemake, K., Animating rotation with quaternion curves, Computer
1477  *   Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985.
1478  * - Pletinckx, D., Quaternion calculus as a basic tool in computer
1479  *   graphics, The Visual Computer 5, 2-13, 1989.
1480  */
1481 static void
1482 normalize_quat(float q[4])
1483 {
1484     int i;
1485     float mag, tmp;
1486
1487     tmp = q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3];
1488     mag = 1.0 / SQRT(tmp);
1489     for (i = 0; i < 4; i++)
1490         q[i] *= mag;
1491 }
1492
1493 /*
1494  * Ok, simulate a track-ball.  Project the points onto the virtual
1495  * trackball, then figure out the axis of rotation, which is the cross
1496  * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0)
1497  * Note:  This is a deformed trackball-- is a trackball in the center,
1498  * but is deformed into a hyperbolic sheet of rotation away from the
1499  * center.  This particular function was chosen after trying out
1500  * several variations.
1501  *
1502  * It is assumed that the arguments to this routine are in the range
1503  * (-1.0 ... 1.0)
1504  */
1505 void
1506 trackball(float q[4], float p1x, float p1y, float p2x, float p2y)
1507 {
1508     float a[3]; /* Axis of rotation */
1509     float phi;  /* how much to rotate about axis */
1510     float p1[3], p2[3], d[3];
1511     float t;
1512
1513     if (p1x == p2x && p1y == p2y) {
1514         /* Zero rotation */
1515         vzero(q);
1516         q[3] = 1.0;
1517         return;
1518     }
1519
1520     /*
1521      * First, figure out z-coordinates for projection of P1 and P2 to
1522      * deformed sphere
1523      */
1524     vset(p1,p1x,p1y,tb_project_to_sphere(TRACKBALLSIZE,p1x,p1y));
1525     vset(p2,p2x,p2y,tb_project_to_sphere(TRACKBALLSIZE,p2x,p2y));
1526
1527     /*
1528      *  Now, we want the cross product of P1 and P2
1529      */
1530     vcross(p2,p1,a);
1531
1532     /*
1533      *  Figure out how much to rotate around that axis.
1534      */
1535     vsub(p1,p2,d);
1536     t = vlength(d) / (2.0*TRACKBALLSIZE);
1537
1538     /*
1539      * Avoid problems with out-of-control values...
1540      */
1541     if (t > 1.0) t = 1.0;
1542     if (t < -1.0) t = -1.0;
1543     phi = 2.0 * asin(t);
1544
1545     axis_to_quat(a,phi,q);
1546 }
1547
1548 /*
1549  * Given two rotations, e1 and e2, expressed as quaternion rotations,
1550  * figure out the equivalent single rotation and stuff it into dest.
1551  *
1552  * This routine also normalizes the result every RENORMCOUNT times it is
1553  * called, to keep error from creeping in.
1554  *
1555  * NOTE: This routine is written so that q1 or q2 may be the same
1556  * as dest (or each other).
1557  */
1558
1559 #define RENORMCOUNT 97
1560
1561 void
1562 add_quats(float q1[4], float q2[4], float dest[4])
1563 {
1564     static int count=0;
1565     float t1[4], t2[4], t3[4];
1566     float tf[4];
1567
1568 #if 0
1569 printf("q1 = %f %f %f %f\n", q1[0], q1[1], q1[2], q1[3]);
1570 printf("q2 = %f %f %f %f\n", q2[0], q2[1], q2[2], q2[3]);
1571 #endif
1572
1573     vcopy(q1,t1);
1574     vscale(t1,q2[3]);
1575
1576     vcopy(q2,t2);
1577     vscale(t2,q1[3]);
1578
1579     vcross(q2,q1,t3);
1580     vadd(t1,t2,tf);
1581     vadd(t3,tf,tf);
1582     tf[3] = q1[3] * q2[3] - vdot(q1,q2);
1583
1584 #if 0
1585 printf("tf = %f %f %f %f\n", tf[0], tf[1], tf[2], tf[3]);
1586 #endif
1587
1588     dest[0] = tf[0];
1589     dest[1] = tf[1];
1590     dest[2] = tf[2];
1591     dest[3] = tf[3];
1592
1593     if (++count > RENORMCOUNT) {
1594         count = 0;
1595         normalize_quat(dest);
1596     }
1597 }
1598
1599 /*
1600  * Build a rotation matrix, given a quaternion rotation.
1601  *
1602  */
1603 void
1604 build_rotmatrix(float m[4][4], float q[4])
1605 {
1606 //#define TRANSPOSED_QUAT
1607 #ifndef TRANSPOSED_QUAT
1608     m[0][0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]);
1609     m[0][1] = 2.0 * (q[0] * q[1] - q[2] * q[3]);
1610     m[0][2] = 2.0 * (q[2] * q[0] + q[1] * q[3]);
1611     m[0][3] = 0.0;
1612
1613     m[1][0] = 2.0 * (q[0] * q[1] + q[2] * q[3]);
1614     m[1][1]= 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]);
1615     m[1][2] = 2.0 * (q[1] * q[2] - q[0] * q[3]);
1616     m[1][3] = 0.0;
1617
1618     m[2][0] = 2.0 * (q[2] * q[0] - q[1] * q[3]);
1619     m[2][1] = 2.0 * (q[1] * q[2] + q[0] * q[3]);
1620     m[2][2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]);
1621     
1622     m[2][3] = 0.0;
1623     m[3][0] = 0.0;
1624     m[3][1] = 0.0;
1625     m[3][2] = 0.0;
1626     m[3][3] = 1.0;
1627 #else //  TRANSPOSED_QUAT
1628     m[0][0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]);
1629     m[0][1] = 2.0 * (q[0] * q[1] + q[2] * q[3]);
1630     m[0][2] = 2.0 * (q[2] * q[0] - q[1] * q[3]);
1631     m[0][3] = 0.0;
1632
1633     m[1][0] = 2.0 * (q[0] * q[1] - q[2] * q[3]);
1634     m[1][1] = 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]);
1635     m[1][2] = 2.0 * (q[1] * q[2] + q[0] * q[3]);
1636     m[1][3] = 0.0;
1637
1638     m[2][0] = 2.0 * (q[2] * q[0] + q[1] * q[3]);
1639     m[2][1] = 2.0 * (q[1] * q[2] - q[0] * q[3]);
1640     m[2][2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]);
1641     m[2][3] = 0.0;
1642     
1643     m[3][0] = 0.0;
1644     m[3][1] = 0.0;
1645     m[3][2] = 0.0;
1646     m[3][3] = 1.0;
1647 #endif // 0
1648 }
1649