http://www.jwz.org/xscreensaver/xscreensaver-5.09.tar.gz
[xscreensaver] / hacks / glx / sonar.c
1 /* sonar, Copyright (c) 1998-2008 Jamie Zawinski and Stephen Martin
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* Created in Apr 1998 by Stephen Martin <smartin@vanderfleet-martin.net>
13  * for the  RedHat Screensaver Contest
14  * Heavily hacked by jwz ever since.
15  * Rewritten in OpenGL by jwz, Aug 2008.
16  *
17  * This is an implementation of a general purpose reporting tool in the
18  * format of a Sonar display. It is designed such that a sensor is read
19  * on every movement of a sweep arm and the results of that sensor are
20  * displayed on the screen. The location of the display points (targets) on the
21  * screen are determined by the current localtion of the sweep and a distance
22  * value associated with the target. 
23  *
24  * Currently the only two sensors that are implemented are the simulator
25  * (the default) and the ping sensor. The simulator randomly creates a set
26  * of bogies that move around on the scope while the ping sensor can be
27  * used to display hosts on your network.
28  *
29  * The ping code is only compiled in if you define HAVE_ICMP or HAVE_ICMPHDR,
30  * because, unfortunately, different systems have different ways of creating
31  * these sorts of packets.
32  *
33  * In order to use the ping sensor on most systems, this program must be
34  * installed as setuid root, so that it can create an ICMP RAW socket.  Root
35  * privileges are disavowed shortly after startup (just after connecting to
36  * the X server and reading the resource database) so this is *believed* to
37  * be a safe thing to do, but it is usually recommended that you have as few
38  * setuid programs around as possible, on general principles.
39  *
40  * It is not necessary to make it setuid on MacOS systems, because on those
41  * systems, unprivileged programs can ping by using ICMP DGRAM sockets
42  * instead of ICMP RAW.
43  *
44  * It should be easy to extend this code to support other sorts of sensors.
45  * Some ideas:
46  *   - search the output of "netstat" for the list of hosts to ping;
47  *   - plot the contents of /proc/interrupts;
48  *   - plot the process table, by process size, cpu usage, or total time;
49  *   - plot the logged on users by idle time or cpu usage.
50  *
51  */
52
53 #define DEF_FONT "-*-lucidatypewriter-bold-r-normal-*-*-480-*-*-*-*-iso8859-1"
54 #define DEF_SPEED        "1.0"
55 #define DEF_SWEEP_SIZE   "0.3"
56 #define DEF_FONT_SIZE    "12"
57 #define DEF_TEAM_A_NAME  "F18"
58 #define DEF_TEAM_B_NAME  "MIG"
59 #define DEF_TEAM_A_COUNT "4"
60 #define DEF_TEAM_B_COUNT "4"
61 #define DEF_PING         "default"
62 #define DEF_PING_TIMEOUT "3000"
63 #define DEF_RESOLVE      "True"
64 #define DEF_TIMES        "True"
65 #define DEF_WOBBLE       "True"
66 #define DEF_DEBUG        "False"
67
68 #define DEFAULTS        "*delay:        30000       \n" \
69                         "*font:       " DEF_FONT   "\n" \
70                         "*showFPS:      False       \n" \
71                         "*wireframe:    False       \n" \
72
73
74 # define refresh_sonar 0
75 #undef countof
76 #define countof(x) (sizeof((x))/sizeof((*x)))
77
78 #ifdef HAVE_UNISTD_H
79 # include <unistd.h>   /* for setuid() */
80 #endif
81
82 #include "xlockmore.h"
83 #include "sonar.h"
84 #include "gltrackball.h"
85 #include "rotator.h"
86 #include "texfont.h"
87 #include <ctype.h>
88
89 #ifdef USE_GL /* whole file */
90
91 typedef struct {
92   double x,y,z;
93 } XYZ;
94
95 typedef struct {
96   GLXContext *glx_context;
97   trackball_state *trackball;
98   rotator *rot;
99   Bool button_down_p;
100
101   double start_time;
102   GLfloat sweep_offset;
103
104   GLuint screen_list, grid_list, sweep_list, table_list;
105   int screen_polys, grid_polys, sweep_polys, table_polys;
106   GLfloat sweep_th;
107   GLfloat line_thickness;
108
109   texture_font_data *texfont;
110
111   sonar_sensor_data *ssd;
112   char *error;
113
114   sonar_bogie *displayed;       /* on screen and fading */
115   sonar_bogie *pending;         /* returned by sensor, not yet on screen */
116
117 } sonar_configuration;
118
119 static sonar_configuration *sps = NULL;
120
121 static GLfloat speed;
122 static GLfloat sweep_size;
123 static GLfloat font_size;
124 static Bool resolve_p;
125 static Bool times_p;
126 static Bool wobble_p;
127 static Bool debug_p;
128
129 static char *team_a_name;
130 static char *team_b_name;
131 static int team_a_count;
132 static int team_b_count;
133 static int ping_timeout;
134 static char *ping_arg;
135
136 static XrmOptionDescRec opts[] = {
137   { "-speed",        ".speed",       XrmoptionSepArg, 0 },
138   { "-sweep-size",   ".sweepSize",   XrmoptionSepArg, 0 },
139   { "-font-size",    ".fontSize",    XrmoptionSepArg, 0 },
140   { "-team-a-name",  ".teamAName",   XrmoptionSepArg, 0 },
141   { "-team-b-name",  ".teamBName",   XrmoptionSepArg, 0 },
142   { "-team-a-count", ".teamACount",  XrmoptionSepArg, 0 },
143   { "-team-b-count", ".teamBCount",  XrmoptionSepArg, 0 },
144   { "-ping",         ".ping",        XrmoptionSepArg, 0 },
145   { "-ping-timeout", ".pingTimeout", XrmoptionSepArg, 0 },
146   { "-dns",          ".resolve",     XrmoptionNoArg, "True" },
147   { "+dns",          ".resolve",     XrmoptionNoArg, "False" },
148   { "-times",        ".times",       XrmoptionNoArg, "True" },
149   { "+times",        ".times",       XrmoptionNoArg, "False" },
150   { "-wobble",       ".wobble",      XrmoptionNoArg, "True" },
151   { "+wobble",       ".wobble",      XrmoptionNoArg, "False" },
152   { "-debug",        ".debug",       XrmoptionNoArg, "True" },
153 };
154
155 static argtype vars[] = {
156   {&speed,        "speed",       "Speed",       DEF_SPEED,        t_Float},
157   {&sweep_size,   "sweepSize",   "SweepSize",   DEF_SWEEP_SIZE,   t_Float},
158   {&font_size,    "fontSize",    "FontSize",    DEF_FONT_SIZE,    t_Float},
159   {&team_a_name,  "teamAName",   "TeamName",    DEF_TEAM_A_NAME,  t_String},
160   {&team_b_name,  "teamBName",   "TeamName",    DEF_TEAM_B_NAME,  t_String},
161   {&team_a_count, "teamACount",  "TeamCount",   DEF_TEAM_A_COUNT, t_Int},
162   {&team_b_count, "teamBCount",  "TeamCount",   DEF_TEAM_A_COUNT, t_Int},
163   {&ping_arg,     "ping",        "Ping",        DEF_PING,         t_String},
164   {&ping_timeout, "pingTimeout", "PingTimeout", DEF_PING_TIMEOUT, t_Int},
165   {&resolve_p,    "resolve",     "Resolve",     DEF_RESOLVE,      t_Bool},
166   {&times_p,      "times",       "Times",       DEF_TIMES,        t_Bool},
167   {&wobble_p,     "wobble",      "Wobble",      DEF_WOBBLE,       t_Bool},
168   {&debug_p,      "debug",       "Debug",       DEF_DEBUG,        t_Bool},
169 };
170
171 ENTRYPOINT ModeSpecOpt sonar_opts = {countof(opts), opts, countof(vars), vars, NULL};
172
173
174 static int
175 draw_screen (ModeInfo *mi, Bool mesh_p, Bool sweep_p)
176 {
177   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
178   int wire = MI_IS_WIREFRAME(mi);
179   int polys = 0;
180   int i;
181   int th_steps, r_steps, r_skip, th_skip, th_skip2, outer_r;
182   GLfloat curvature = M_PI * 0.4;
183   GLfloat r0, r1, z0, z1, zoff;
184   XYZ *ring;
185
186   static const GLfloat glass[4]  = {0.0, 0.4, 0.0, 0.5};
187   static const GLfloat lines[4]  = {0.0, 0.7, 0.0, 0.5};
188   static const GLfloat sweepc[4] = {0.2, 1.0, 0.2, 0.5};
189   static const GLfloat spec[4]   = {1.0, 1.0, 1.0, 1.0};
190   static const GLfloat shiny     = 20.0;
191
192   if (wire && !(mesh_p || sweep_p)) return 0;
193
194   glPushAttrib (GL_ENABLE_BIT);
195   glDisable (GL_TEXTURE_2D);
196
197   glFrontFace (GL_CCW);
198   th_steps = 36 * 4;    /* must be a multiple of th_skip2 divisor */
199   r_steps = 40;
200   r_skip = 1;
201   th_skip = 1;
202   th_skip2 = 1;
203   outer_r = 0;
204
205   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
206   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
207   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mesh_p ? lines : glass);
208   if (wire) glColor3fv (lines);
209
210   if (mesh_p) 
211     {
212       th_skip  = th_steps / 12;
213       th_skip2 = th_steps / 36;
214       r_skip = r_steps / 3;
215       outer_r = r_steps * 0.93;
216
217       if (! wire)
218         glLineWidth (sp->line_thickness);
219     }
220
221   ring = (XYZ *) calloc (th_steps, sizeof(*ring));
222
223   for (i = 0; i < th_steps; i++)
224     {
225       double a = M_PI * 2 * i / th_steps;
226       ring[i].x = cos(a);
227       ring[i].y = sin(a);
228     }
229
230   /* place the bottom of the disc on the xy plane. */
231   zoff = cos (curvature/2 * (M_PI/2)) / 2;
232
233   for (i = r_steps; i > 0; i--)
234     {
235       int j0, j1;
236
237       r0 = i     / (GLfloat) r_steps;
238       r1 = (i+1) / (GLfloat) r_steps;
239
240       if (r1 > 1) r1 = 1; /* avoid asin lossage */
241
242       z0 = cos (curvature/2 * asin (r0)) / 2 - zoff;
243       z1 = cos (curvature/2 * asin (r1)) / 2 - zoff;
244
245       glBegin(wire || mesh_p ? GL_LINES : GL_QUAD_STRIP);
246       for (j0 = 0; j0 <= th_steps; j0++)
247         {
248           if (mesh_p && 
249               (i < outer_r
250                ? (j0 % th_skip != 0)
251                : (j0 % th_skip2 != 0)))
252             continue;
253
254           if (sweep_p)
255             {
256               GLfloat color[4];
257               GLfloat r = 1 - (j0 / (GLfloat) (th_steps * sweep_size));
258 #if 0
259               color[0] = glass[0] + (sweepc[0] - glass[0]) * r;
260               color[1] = glass[1] + (sweepc[1] - glass[1]) * r;
261               color[2] = glass[2] + (sweepc[2] - glass[2]) * r;
262               color[3] = glass[3];
263 #else
264               color[0] = sweepc[0];
265               color[1] = sweepc[1];
266               color[2] = sweepc[2];
267               color[3] = r;
268 #endif
269               glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
270             }
271
272           j1 = j0 % th_steps;
273           glNormal3f (r0 * ring[j1].x, r0 * ring[j1].y, z0);
274           glVertex3f (r0 * ring[j1].x, r0 * ring[j1].y, z0);
275           glNormal3f (r1 * ring[j1].x, r1 * ring[j1].y, z1);
276           glVertex3f (r1 * ring[j1].x, r1 * ring[j1].y, z1);
277           polys++;
278
279           if (sweep_p && j0 >= th_steps * sweep_size)
280             break;
281           if (sweep_p && wire)
282             break;
283         }
284       glEnd();
285
286       if (mesh_p &&
287           (i == outer_r ||
288            i == r_steps ||
289            (i % r_skip == 0 &&
290             i < r_steps - r_skip)))
291         {
292           glBegin(GL_LINE_LOOP);
293           for (j0 = 0; j0 < th_steps; j0++)
294             {
295               glNormal3f (r0 * ring[j0].x, r0 * ring[j0].y, z0);
296               glVertex3f (r0 * ring[j0].x, r0 * ring[j0].y, z0);
297               polys++;
298             }
299           glEnd();
300         }
301     }
302
303   /* one more polygon for the middle */
304   if (!wire && !sweep_p)
305     {
306       glBegin(wire || mesh_p ? GL_LINE_LOOP : GL_POLYGON);
307       glNormal3f (0, 0, 1);
308       for (i = 0; i < th_steps; i++)
309         {
310           glNormal3f (r0 * ring[i].x, r0 * ring[i].y, z0);
311           glVertex3f (r0 * ring[i].x, r0 * ring[i].y, z0);
312         }
313       polys++;
314       glEnd();
315     }
316
317   glPopAttrib();
318   free (ring);
319
320   return polys;
321 }
322
323
324 static int
325 draw_text (ModeInfo *mi, const char *string, GLfloat r, GLfloat th, 
326            GLfloat ttl, GLfloat size)
327 {
328   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
329   int wire = MI_IS_WIREFRAME(mi);
330   int polys = 0;
331   GLfloat font_scale = 0.001 * (size > 0 ? size : font_size) / 14.0;
332   int lines = 0, max_w = 0, lh = 0;
333   char *string2 = strdup (string);
334   char *token = string2;
335   char *line;
336   GLfloat color[4];
337
338   if (size <= 0)   /* if size not specified, draw in yellow with alpha */
339     {
340       color[0] = 1;
341       color[1] = 1;
342       color[2] = 0;
343       color[3] = (ttl / (M_PI * 2)) * 1.2;
344       if (color[3] > 1) color[3] = 1;
345
346       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
347       if (wire)
348         glColor3f (color[0]*color[3], color[1]*color[3], color[2]*color[3]);
349     }
350
351   while ((line = strtok (token, "\r\n")))
352     {
353       int w = texture_string_width (sp->texfont, line, &lh);
354       if (w > max_w) max_w = w;
355       lines++;
356       token = 0;
357     }
358
359   glPushMatrix();
360   glTranslatef (r * cos (th), r * sin(th), 0);
361   glScalef (font_scale, font_scale, font_scale);
362
363   if (size <= 0)                /* Draw the dot */
364     {
365       GLfloat s = font_size * 1.7;
366       glDisable (GL_TEXTURE_2D);
367       glFrontFace (GL_CW);
368       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
369       glVertex3f (0, s, 0);
370       glVertex3f (s, s, 0);
371       glVertex3f (s, 0, 0);
372       glVertex3f (0, 0, 0);
373       glEnd();
374       glTranslatef (-max_w/2, -lh, 0);
375     }
376   else
377     glTranslatef (-max_w/2, -lh/2, 0);
378
379   /* draw each line, centered */
380   if (! wire) glEnable (GL_TEXTURE_2D);
381   free (string2);
382   string2 = strdup (string);
383   token = string2;
384   while ((line = strtok (token, "\r\n")))
385     {
386       int w = texture_string_width (sp->texfont, line, 0);
387       glPushMatrix();
388       glTranslatef ((max_w-w)/2, 0, 0);
389
390       if (wire)
391         {
392           glBegin (GL_LINE_LOOP);
393           glVertex3f (0, 0, 0);
394           glVertex3f (w, 0, 0);
395           glVertex3f (w, lh, 0);
396           glVertex3f (0, lh, 0);
397           glEnd();
398         }
399       else
400         {
401           glFrontFace (GL_CW);
402           print_texture_string (sp->texfont, line);
403         }
404       glPopMatrix();
405       glTranslatef (0, -lh, 0);
406       polys++;
407       token = 0;
408     }
409   glPopMatrix();
410
411   free (string2);
412
413   if (! wire) glEnable (GL_DEPTH_TEST);
414
415   return polys;
416 }
417
418
419 /* There's a disc with a hole in it around the screen, to act as a mask
420    preventing slightly off-screen bogies from showing up.  This clips 'em.
421  */
422 static int
423 draw_table (ModeInfo *mi)
424 {
425   /*sonar_configuration *sp = &sps[MI_SCREEN(mi)];*/
426   int wire = MI_IS_WIREFRAME(mi);
427   int polys = 0;
428   int i;
429   int th_steps = 36 * 4;    /* same as in draw_screen */
430
431   static const GLfloat color[4]  = {0.0, 0.0, 0.0, 1.0};
432   static const GLfloat text[4]   = {0.15, 0.15, 0.15, 1.0};
433   static const GLfloat spec[4]   = {0.0, 0.0, 0.0, 1.0};
434   static const GLfloat shiny     = 0.0;
435
436   if (wire) return 0;
437
438   glPushAttrib (GL_ENABLE_BIT);
439   glDisable (GL_TEXTURE_2D);
440
441   glMaterialfv (GL_FRONT, GL_SPECULAR,  spec);
442   glMateriali  (GL_FRONT, GL_SHININESS, shiny);
443   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
444
445   glFrontFace (GL_CCW);
446   glBegin(wire ? GL_LINES : GL_QUAD_STRIP);
447   glNormal3f (0, 0, 1);
448   for (i = 0; i <= th_steps; i++)
449     {
450       double a = M_PI * 2 * i / th_steps;
451       double x = cos(a);
452       double y = sin(a);
453       glVertex3f (x, y, 0);
454       glVertex3f (x*10, y*10, 0);
455       polys++;
456     }
457   glEnd();
458   glPopAttrib();
459
460   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, text);
461   glTranslatef (0, 0, 0.01);
462   for (i = 0; i < 360; i += 10)
463     {
464       char buf[10];
465       GLfloat a = M_PI/2 - (i / 180.0 * M_PI);
466       sprintf (buf, "%d", i);
467       polys += draw_text (mi, buf, 1.07, a, 0, 10.0);
468     }
469
470   return polys;
471 }
472
473
474 static int
475 draw_bogies (ModeInfo *mi)
476 {
477   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
478   int polys = 0;
479   sonar_bogie *b;
480
481   for (b = sp->displayed; b; b = b->next)
482     {
483       char *s = (char *) 
484         malloc (strlen (b->name) + (b->desc ? strlen(b->desc) : 0) + 3);
485       strcpy (s, b->name);
486       if (b->desc)
487         {
488           strcat (s, "\n");
489           strcat (s, b->desc);
490         }
491       polys += draw_text (mi, s, b->r, b->th, b->ttl, -1);
492       free (s);
493
494       /* Move *very slightly* forward so that the text is not all in the
495          same plane: this prevents flickering with overlapping text as
496          the textures fight for priority. */
497       glTranslatef(0, 0, 0.00002);
498     }
499
500   return polys;
501 }
502
503
504 /* called from sonar-sim.c and sonar-icmp.c */
505 sonar_bogie *
506 copy_bogie (sonar_sensor_data *ssd, const sonar_bogie *b)
507 {
508   sonar_bogie *b2 = (sonar_bogie *) calloc (1, sizeof(*b2));
509   b2->name = strdup (b->name);
510   b2->desc = b->desc ? strdup (b->desc) : 0;
511   b2->r    = b->r;
512   b2->th   = b->th;
513   b2->ttl  = b->ttl;
514   /* does not copy b->closure */
515
516   /* Take this opportunity to normalize 'th' to the range [0-2pi). */
517   while (b2->th < 0)       b2->th += M_PI*2;
518   while (b2->th >= M_PI*2) b2->th -= M_PI*2;
519
520   return b2;
521 }
522
523
524 /* called from sonar-icmp.c */
525 void
526 free_bogie (sonar_sensor_data *ssd, sonar_bogie *b)
527 {
528   if (b->closure)
529     ssd->free_bogie_cb (ssd, b->closure);
530   free (b->name);
531   if (b->desc) free (b->desc);
532   free (b);
533 }
534
535 /* removes it from the list and frees it
536  */
537 static void
538 delete_bogie (sonar_sensor_data *ssd, sonar_bogie *b,
539               sonar_bogie **from_list)
540 {
541   sonar_bogie *ob, *prev;
542   for (prev = 0, ob = *from_list; ob; prev = ob, ob = ob->next)
543     if (ob == b)
544       {
545         if (prev)
546           prev->next = b->next;
547         else
548           (*from_list) = b->next;
549         free_bogie (ssd, b);
550         break;
551       }
552 }
553
554
555 /* copies the bogie and adds it to the list.
556    if there's another bogie there with the same name, frees that one.
557  */
558 static void
559 copy_and_insert_bogie (sonar_sensor_data *ssd, sonar_bogie *b,
560                        sonar_bogie **to_list)
561 {
562   sonar_bogie *ob, *prev;
563   if (!b) abort();
564   for (prev = 0, ob = *to_list; ob; prev = ob, ob = ob->next)
565     {
566       if (ob == b) abort();   /* this will end badly */
567       if (!strcmp (ob->name, b->name))  /* match! */
568         {
569           delete_bogie (ssd, ob, to_list);
570           break;
571         }
572     }
573
574   b = copy_bogie (ssd, b);
575   b->next = *to_list;
576   *to_list = b;
577 }
578
579
580 static void
581 update_sensor_data (sonar_configuration *sp)
582 {
583   sonar_bogie *new_list = sp->ssd->scan_cb (sp->ssd);
584   sonar_bogie *b2;
585
586   /* If a bogie exists in 'new_list' but not 'pending', add it.
587      If a bogie exists in both, update it in 'pending'.
588    */
589   for (b2 = new_list; b2; b2 = b2->next)
590     {
591       if (debug_p > 2)
592         fprintf (stderr, "%s:   updated: %s (%5.2f %5.2f %5.2f)\n", 
593                  progname, b2->name, b2->r, b2->th, b2->ttl);
594       copy_and_insert_bogie (sp->ssd, b2, &sp->pending);
595     }
596   if (debug_p > 2) fprintf (stderr, "\n");
597 }
598
599
600 /* Returns whether the given angle lies between two other angles.
601    When those angles cross 0, it assumes the wedge is the smaller one.
602    That is: 5 lies between 10 and 350 degrees (a 20 degree wedge).
603  */
604 static Bool
605 point_in_wedge (GLfloat th, GLfloat low, GLfloat high)
606 {
607   if (low < high)
608     return (th > low && th <= high);
609   else
610     return (th <= high || th > low);
611 }
612
613
614 /* Returns the current time in seconds as a double.
615  */
616 static double
617 double_time (void)
618 {
619   struct timeval now;
620 # ifdef GETTIMEOFDAY_TWO_ARGS
621   struct timezone tzp;
622   gettimeofday(&now, &tzp);
623 # else
624   gettimeofday(&now);
625 # endif
626
627   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
628 }
629
630
631 static void
632 sweep (sonar_configuration *sp)
633 {
634   sonar_bogie *b;
635
636   /* Move the sweep forward (clockwise).
637    */
638   GLfloat prev_sweep, this_sweep, tick;
639   GLfloat cycle_secs = 30 / speed;  /* default to one cycle every N seconds */
640   this_sweep = ((cycle_secs - fmod (double_time() - sp->start_time +
641                                     sp->sweep_offset,
642                                     cycle_secs))
643                 / cycle_secs
644                 * M_PI * 2);
645   prev_sweep = sp->sweep_th;
646   tick = prev_sweep - this_sweep;
647   while (tick < 0) tick += M_PI*2;
648
649   sp->sweep_th = this_sweep;
650
651   if (this_sweep < 0 || this_sweep >= M_PI*2) abort();
652   if (prev_sweep < 0)  /* skip first time */
653     return;
654
655   if (tick < 0 || tick >= M_PI*2) abort();
656
657
658   /* Go through the 'pending' sensor data, find those bogies who are
659      just now being swept, and move them from 'pending' to 'displayed'.
660      (Leave bogies that have not yet been swept alone: we'll get to
661      them when the sweep moves forward.)
662    */
663   b = sp->pending;
664   while (b)
665     {
666       sonar_bogie *next = b->next;
667       if (point_in_wedge (b->th, this_sweep, prev_sweep))
668         {
669           if (debug_p > 1) {
670             time_t t = time((time_t *) 0);
671             fprintf (stderr,
672                      "%s: sweep hit: %02d:%02d: %s: (%5.2f %5.2f %5.2f;"
673                      " th=[%.2f < %.2f <= %.2f])\n", 
674                      progname,
675                      (int) (t / 60) % 60, (int) t % 60,
676                      b->name, b->r, b->th, b->ttl,
677                      this_sweep, b->th, prev_sweep);
678           }
679           b->ttl = M_PI * 2.1;
680           copy_and_insert_bogie (sp->ssd, b, &sp->displayed);
681           delete_bogie (sp->ssd, b, &sp->pending);
682         }
683       b = next;
684     }
685
686
687   /* Update TTL on all currently-displayed bogies; delete the dead.
688
689      Request sensor updates on the ones just now being swept.
690
691      Any updates go into 'pending' and might not show up until
692      the next time the sweep comes around.  This is to prevent
693      already-drawn bogies from jumping to a new position without
694      having faded out first.
695   */
696   b = sp->displayed;
697   while (b)
698     {
699       sonar_bogie *next = b->next;
700       b->ttl -= tick;
701
702       if (b->ttl <= 0)
703         {
704           if (debug_p > 1)
705             fprintf (stderr, "%s: TTL expired: %s (%5.2f %5.2f %5.2f)\n",
706                      progname, b->name, b->r, b->th, b->ttl);
707           delete_bogie (sp->ssd, b, &sp->displayed);
708         }
709       b = next;
710     }
711
712   update_sensor_data (sp);
713 }
714
715
716 static void
717 draw_startup_blurb (ModeInfo *mi)
718 {
719   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
720   const char *msg = (sp->error ? sp->error : "Resolving hosts...");
721   static const GLfloat color[4] = {0, 1, 0, 1};
722
723   if (!sp->error && ping_arg && !strcmp (ping_arg, "simulation"))
724     return;  /* don't bother */
725
726   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
727   glTranslatef (0, 0, 0.3);
728   draw_text (mi, msg, 0, 0, 0, 30.0);
729
730   /* only leave error message up for N seconds */
731   if (sp->error &&
732       sp->start_time + 4 < double_time())
733     {
734       free (sp->error);
735       sp->error = 0;
736     }
737 }
738
739
740 /* Window management, etc
741  */
742 ENTRYPOINT void
743 reshape_sonar (ModeInfo *mi, int width, int height)
744 {
745   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
746   GLfloat h = (GLfloat) height / (GLfloat) width;
747
748   glViewport (0, 0, (GLint) width, (GLint) height);
749
750   glMatrixMode(GL_PROJECTION);
751   glLoadIdentity();
752   gluPerspective (30.0, 1/h, 1.0, 100.0);
753
754   glMatrixMode(GL_MODELVIEW);
755   glLoadIdentity();
756   gluLookAt( 0.0, 0.0, 30.0,
757              0.0, 0.0, 0.0,
758              0.0, 1.0, 0.0);
759
760   glClear(GL_COLOR_BUFFER_BIT);
761
762   sp->line_thickness = (MI_IS_WIREFRAME (mi) ? 1 : MAX (1, height / 300.0));
763 }
764
765
766 ENTRYPOINT Bool
767 sonar_handle_event (ModeInfo *mi, XEvent *event)
768 {
769   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
770
771   if (event->xany.type == ButtonPress &&
772       event->xbutton.button == Button1)
773     {
774       sp->button_down_p = True;
775       gltrackball_start (sp->trackball,
776                          event->xbutton.x, event->xbutton.y,
777                          MI_WIDTH (mi), MI_HEIGHT (mi));
778       return True;
779     }
780   else if (event->xany.type == ButtonRelease &&
781            event->xbutton.button == Button1)
782     {
783       sp->button_down_p = False;
784       return True;
785     }
786   else if (event->xany.type == ButtonPress &&
787            (event->xbutton.button == Button4 ||
788             event->xbutton.button == Button5 ||
789             event->xbutton.button == Button6 ||
790             event->xbutton.button == Button7))
791     {
792       gltrackball_mousewheel (sp->trackball, event->xbutton.button, 10,
793                               !!event->xbutton.state);
794       return True;
795     }
796   else if (event->xany.type == MotionNotify &&
797            sp->button_down_p)
798     {
799       gltrackball_track (sp->trackball,
800                          event->xmotion.x, event->xmotion.y,
801                          MI_WIDTH (mi), MI_HEIGHT (mi));
802       return True;
803     }
804
805   return False;
806 }
807
808
809 ENTRYPOINT void 
810 init_sonar (ModeInfo *mi)
811 {
812   sonar_configuration *sp;
813   int wire = MI_IS_WIREFRAME(mi);
814
815   if (!sps) {
816     sps = (sonar_configuration *)
817       calloc (MI_NUM_SCREENS(mi), sizeof (sonar_configuration));
818     if (!sps) {
819       fprintf(stderr, "%s: out of memory\n", progname);
820       exit(1);
821     }
822   }
823   sp = &sps[MI_SCREEN(mi)];
824   sp->glx_context = init_GL(mi);
825
826   reshape_sonar (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
827
828   if (!wire)
829     {
830       GLfloat pos[4] = {0.05, 0.07, 1.00, 0.0};
831       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
832       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
833       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
834
835       glEnable(GL_TEXTURE_2D);
836       glEnable(GL_LIGHTING);
837       glEnable(GL_LIGHT0);
838       glEnable(GL_CULL_FACE);
839       glEnable(GL_DEPTH_TEST);
840       glEnable(GL_NORMALIZE);
841       glEnable(GL_LINE_SMOOTH);
842       glEnable(GL_BLEND);
843       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
844       glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
845       glShadeModel(GL_SMOOTH);
846
847       glLightfv(GL_LIGHT0, GL_POSITION, pos);
848       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
849       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
850       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
851     }
852
853   sp->trackball = gltrackball_init ();
854   sp->rot = make_rotator (0, 0, 0, 0, speed * 0.003, True);
855
856   sp->texfont = load_texture_font (MI_DISPLAY(mi), "font");
857   check_gl_error ("loading font");
858
859   sp->table_list = glGenLists (1);
860   glNewList (sp->table_list, GL_COMPILE);
861   sp->table_polys = draw_table (mi);
862   glEndList ();
863
864   sp->screen_list = glGenLists (1);
865   glNewList (sp->screen_list, GL_COMPILE);
866   sp->screen_polys = draw_screen (mi, False, False);
867   glEndList ();
868
869   sp->grid_list = glGenLists (1);
870   glNewList (sp->grid_list, GL_COMPILE);
871   sp->grid_polys = draw_screen (mi, True,  False);
872   glEndList ();
873
874   sp->sweep_list = glGenLists (1);
875   glNewList (sp->sweep_list, GL_COMPILE);
876   sp->sweep_polys = draw_screen (mi, False, True);
877   glEndList ();
878
879   sp->start_time = double_time ();
880   sp->sweep_offset = random() % 60;
881   sp->sweep_th = -1;
882 }
883
884
885 static void
886 init_sensor (ModeInfo *mi)
887 {
888   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
889
890   if (sp->ssd) abort();
891
892   if (!ping_arg || !*ping_arg ||
893       !strcmp(ping_arg, "default") ||
894       !!strcmp (ping_arg, "simulation"))
895     sp->ssd = init_ping (MI_DISPLAY (mi), &sp->error, ping_arg,
896                          ping_timeout, resolve_p, times_p, debug_p);
897
898   /* Disavow privs.  This was already done in init_ping(), but
899      we might not have called that at all, so do it again. */
900   setuid(getuid());
901
902   if (!sp->ssd)
903     sp->ssd = init_simulation (MI_DISPLAY (mi), &sp->error,
904                                team_a_name, team_b_name,
905                                team_a_count, team_b_count,
906                                debug_p);
907   if (!sp->ssd)
908     abort();
909 }
910
911
912 ENTRYPOINT void
913 draw_sonar (ModeInfo *mi)
914 {
915   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
916   Display *dpy = MI_DISPLAY(mi);
917   Window window = MI_WINDOW(mi);
918
919   if (!sp->glx_context)
920     return;
921
922   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context));
923
924   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
925
926   glPushMatrix ();
927   { GLfloat s = 7; glScalef (s,s,s); }
928
929   gltrackball_rotate (sp->trackball);
930
931   if (wobble_p)
932     {
933       double x, y, z;
934       double max = 40;
935       get_position (sp->rot, &x, &y, &z, !sp->button_down_p);
936       glRotatef (max/2 - x*max, 1, 0, 0);
937       glRotatef (max/2 - z*max, 0, 1, 0);
938     }
939
940   mi->polygon_count = 0;
941
942   glPushMatrix();                                       /* table */
943   glCallList (sp->table_list);
944   mi->polygon_count += sp->table_polys;
945   glPopMatrix();
946
947   glPushMatrix();                                       /* text */
948   glTranslatef (0, 0, -0.01);
949   mi->polygon_count += draw_bogies (mi);
950   glPopMatrix();
951
952   glCallList (sp->screen_list);                         /* glass */
953   mi->polygon_count += sp->screen_polys;
954
955   glTranslatef (0, 0, 0.004);                           /* sweep */
956   glPushMatrix();
957   glRotatef ((sp->sweep_th * 180 / M_PI), 0, 0, 1);
958   if (sp->sweep_th >= 0)
959     glCallList (sp->sweep_list);
960   mi->polygon_count += sp->sweep_polys;
961   glPopMatrix();
962
963   glTranslatef (0, 0, 0.004);                           /* grid */
964   glCallList (sp->grid_list);
965   mi->polygon_count += sp->screen_polys;
966
967   if (! sp->ssd || sp->error)
968     draw_startup_blurb(mi);
969
970   glPopMatrix ();
971
972   if (mi->fps_p) do_fps (mi);
973   glFinish();
974
975   glXSwapBuffers(dpy, window);
976
977   if (! sp->ssd)
978     /* Just starting up.  "Resolving hosts" text printed.  Go stall. */
979     init_sensor (mi);
980   else
981     sweep (sp);
982 }
983
984 ENTRYPOINT void
985 release_sonar (ModeInfo *mi)
986 {
987 #if 0
988   sonar_configuration *sp = &sps[MI_SCREEN(mi)];
989   sonar_bogie *b = sp->displayed;
990   while (b)
991     {
992       sonar_bogie *next = b->next;
993       free_bogie (sp->ssd, b);
994       b = next;
995     }
996   sp->displayed = 0;
997
998   b = sp->pending;
999   while (b)
1000     {
1001       sonar_bogie *next = b->next;
1002       free_bogie (sp->ssd, b);
1003       b = next;
1004     }
1005   sp->pending = 0;
1006
1007   sp->ssd->free_data_cb (sp->ssd, sp->ssd->closure);
1008   free (sp->ssd);
1009   sp->ssd = 0;
1010 #endif
1011 }
1012
1013 XSCREENSAVER_MODULE ("Sonar", sonar)
1014
1015 #endif /* USE_GL */