From http://www.jwz.org/xscreensaver/xscreensaver-5.38.tar.gz
[xscreensaver] / hacks / glx / vigilance.c
1 /* vigilance, Copyright (c) 2017 Jamie Zawinski <jwz@jwz.org>
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  * Draws surveillance cameras, taking an interest in their surroundings.
12  */
13
14 #define DEFAULTS        "*delay:        20000       \n" \
15                         "*count:        5           \n" \
16                         "*showFPS:      False       \n" \
17                         "*wireframe:    False       \n" \
18                         "*bodyColor:    #666666"   "\n" \
19                         "*capColor:     #FFFFFF"   "\n" \
20                         "*hingeColor:   #444444"   "\n" \
21                         "*mountColor:   #444444"   "\n" \
22                         "*lensColor:    #000000"   "\n" \
23                         "*groundColor:  #004400"   "\n" \
24
25 # define free_camera 0
26 # define release_camera 0
27 #undef countof
28 #define countof(x) (sizeof((x))/sizeof((*x)))
29
30 #define DEF_SPEED       "1.0"
31 #define DEF_CAMERA_SIZE "1.0"
32
33 #include "xlockmore.h"
34 #include "gltrackball.h"
35 #include "xpm-ximage.h"
36 #include "normals.h"
37
38 #include <ctype.h>
39
40 #ifdef USE_GL /* whole file */
41
42 #undef ABS
43 #define ABS(x) ((x)<0?-(x):(x))
44 #undef MAX
45 #define MAX(A,B) ((A)>(B)?(A):(B))
46 #undef MIN
47 #define MIN(A,B) ((A)<(B)?(A):(B))
48 #undef BELLRAND
49 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
50
51 #include "gllist.h"
52
53 extern const struct gllist
54   *seccam_body, *seccam_cap, *seccam_hinge, *seccam_pipe, *seccam_lens;
55 static struct gllist *ground = 0;
56
57 static const struct gllist * const *all_objs[] = {
58   &seccam_body, &seccam_cap, &seccam_hinge, &seccam_pipe, &seccam_lens,
59   (const struct gllist * const *) &ground
60 };
61
62 #define CAMERA_BODY     0
63 #define CAMERA_CAP      1
64 #define CAMERA_HINGE    2
65 #define CAMERA_MOUNT    3
66 #define CAMERA_LENS     4
67 #define GROUND          5
68
69 #define BEAM_ZOFF 0.185 /* distance from origin to lens in model */
70
71 typedef enum { IDLE, WARM_UP, ZOT, COOL_DOWN } camera_state;
72
73
74 typedef struct {
75   XYZ pos;
76   GLfloat facing;               /* rotation around vertical axis, degrees */
77   GLfloat pitch;                /* front/back tilt, degrees */
78   GLfloat velocity;             /* most recent angular velocity, degrees */
79   long focus_id;                /* pedestrian or camera of interest */
80   XYZ focus;                    /* point of interest */
81   camera_state state;
82   GLfloat tick;
83 } camera;
84
85 typedef struct pedestrian pedestrian;
86 struct pedestrian {
87   long id;
88   XYZ pos;
89   GLfloat length;
90   GLfloat frequency, amplitude;
91   GLfloat ratio;
92   GLfloat speed;
93   pedestrian *next;
94 };
95
96 typedef struct {
97   GLXContext *glx_context;
98   trackball_state *user_trackball;
99   Bool button_down_p;
100
101   GLuint *dlists;
102   GLfloat component_colors[countof(all_objs)][4];
103
104   int ncameras;
105   camera *cameras;
106   pedestrian *pedestrians;
107 } camera_configuration;
108
109 static camera_configuration *bps = NULL;
110
111 static GLfloat speed_arg;
112 #ifdef DEBUG
113 static int debug_p;
114 #endif
115
116 static XrmOptionDescRec opts[] = {
117   { "-speed",      ".speed",     XrmoptionSepArg, 0 },
118 #ifdef DEBUG
119   {"-debug",       ".debug",     XrmoptionNoArg, "True" },
120   {"+debug",       ".debug",     XrmoptionNoArg, "False" },
121 #endif
122 };
123
124 static argtype vars[] = {
125   {&speed_arg,   "speed",      "Speed",     DEF_SPEED,      t_Float},
126 #ifdef DEBUG
127   {&debug_p,     "debug",      "Debug",     "False",        t_Bool},
128 #endif
129 };
130
131 ENTRYPOINT ModeSpecOpt camera_opts = {
132   countof(opts), opts, countof(vars), vars, NULL};
133
134
135 ENTRYPOINT void
136 reshape_camera (ModeInfo *mi, int width, int height)
137 {
138   GLfloat h = (GLfloat) height / (GLfloat) width;
139   int y = 0;
140
141   if (width > height * 5) {   /* tiny window: show middle */
142     height = width * 9/16;
143     y = -height/2;
144     h = height / (GLfloat) width;
145   }
146
147   glViewport (0, y, width, height);
148   glMatrixMode(GL_PROJECTION);
149   glLoadIdentity();
150   gluPerspective (30.0, 1/h, 1.0, 200);
151   glMatrixMode(GL_MODELVIEW);
152   glLoadIdentity();
153   gluLookAt( 0, 0, 30,
154              0, 0, 0,
155              0, 1, 0);
156
157 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
158   {
159     int o = (int) current_device_rotation();
160     if (o != 0 && o != 180 && o != -180)
161       glScalef (1/h, 1/h, 1/h);
162   }
163 # endif
164
165   glClear(GL_COLOR_BUFFER_BIT);
166 }
167
168
169 ENTRYPOINT Bool
170 camera_handle_event (ModeInfo *mi, XEvent *event)
171 {
172   camera_configuration *bp = &bps[MI_SCREEN(mi)];
173
174   if (gltrackball_event_handler (event, bp->user_trackball,
175                                  MI_WIDTH (mi), MI_HEIGHT (mi),
176                                  &bp->button_down_p))
177     return True;
178   else if (event->xany.type == KeyPress)
179     {
180       KeySym keysym;
181       char c = 0;
182       XLookupString (&event->xkey, &c, 1, &keysym, 0);
183       if (c == ' ' || c == '\t')
184         {
185           int i;
186           if (bp->cameras[0].state == IDLE && bp->pedestrians)
187             for (i = 0; i < bp->ncameras; i++)
188               {
189                 bp->cameras[i].state = WARM_UP;
190                 bp->cameras[i].tick  = 1.0;
191               }
192           return True;
193         }
194     }
195
196   return False;
197 }
198
199
200 static int draw_ground (ModeInfo *, GLfloat color[4]);
201
202 static void
203 parse_color (ModeInfo *mi, char *key, GLfloat color[4])
204 {
205   XColor xcolor;
206   char *string = get_string_resource (mi->dpy, key, "CameraColor");
207   if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
208     {
209       fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
210                key, string);
211       exit (1);
212     }
213
214   color[0] = xcolor.red   / 65536.0;
215   color[1] = xcolor.green / 65536.0;
216   color[2] = xcolor.blue  / 65536.0;
217   color[3] = 1;
218 }
219
220
221 ENTRYPOINT void 
222 init_camera (ModeInfo *mi)
223 {
224   camera_configuration *bp;
225   int wire = MI_IS_WIREFRAME(mi);
226   int i;
227   MI_INIT (mi, bps);
228
229   bp = &bps[MI_SCREEN(mi)];
230
231   bp->glx_context = init_GL(mi);
232
233   reshape_camera (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
234
235   glShadeModel(GL_SMOOTH);
236
237   glEnable(GL_DEPTH_TEST);
238   glEnable(GL_NORMALIZE);
239   glEnable(GL_CULL_FACE);
240
241   if (!wire)
242     {
243       GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
244       GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
245       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
246       GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
247
248       glEnable(GL_LIGHTING);
249       glEnable(GL_LIGHT0);
250       glEnable(GL_DEPTH_TEST);
251       glEnable(GL_CULL_FACE);
252
253       glLightfv(GL_LIGHT0, GL_POSITION, pos);
254       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
255       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
256       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
257     }
258
259   bp->user_trackball = gltrackball_init (False);
260
261   bp->dlists = (GLuint *) calloc (countof(all_objs)+1, sizeof(GLuint));
262   for (i = 0; i < countof(all_objs); i++)
263     bp->dlists[i] = glGenLists (1);
264
265   for (i = 0; i < countof(all_objs); i++)
266     {
267       const struct gllist *gll = *all_objs[i];
268       char *key = 0;
269       GLfloat spec1[4] = {1.00, 1.00, 1.00, 1.0};
270       GLfloat spec2[4] = {0.40, 0.40, 0.70, 1.0};
271       GLfloat *spec = spec1;
272       GLfloat shiny = 20;
273
274       glNewList (bp->dlists[i], GL_COMPILE);
275
276       glMatrixMode(GL_MODELVIEW);
277       glPushMatrix();
278       glMatrixMode(GL_TEXTURE);
279       glPushMatrix();
280       glMatrixMode(GL_MODELVIEW);
281
282       glRotatef (-90, 1, 0, 0);
283       glRotatef (180, 0, 0, 1);
284       glScalef (6, 6, 6);
285
286       glBindTexture (GL_TEXTURE_2D, 0);
287
288       switch (i) {
289       case CAMERA_BODY:  key = "bodyColor";   break;
290       case CAMERA_CAP:   key = "capColor";    break;
291       case CAMERA_HINGE: key = "hingeColor";  break;
292       case CAMERA_MOUNT: key = "mountColor";  break;
293       case CAMERA_LENS:  key = "lensColor";   spec = spec2; break;
294       case GROUND:       key = "groundColor"; spec = spec2; shiny = 128; break;
295       default: abort(); break;
296       }
297
298       parse_color (mi, key, bp->component_colors[i]);
299
300       glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  spec);
301       glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
302
303       switch (i) {
304       case GROUND:
305         if (! ground)
306           ground = (struct gllist *) calloc (1, sizeof(*ground));
307         ground->points = draw_ground (mi, bp->component_colors[i]);
308         break;
309       default:
310         renderList (gll, wire);
311         /* glColor3f (1, 1, 1); renderListNormals (gll, 100, True); */
312         /* glColor3f (1, 1, 0); renderListNormals (gll, 100, False); */
313         break;
314       }
315
316       glMatrixMode(GL_TEXTURE);
317       glPopMatrix();
318       glMatrixMode(GL_MODELVIEW);
319       glPopMatrix();
320
321       glEndList ();
322     }
323
324   bp->ncameras = MI_COUNT(mi);
325   if (bp->ncameras <= 0) bp->ncameras = 1;
326   bp->cameras = (camera *) calloc (bp->ncameras, sizeof (camera));
327
328   {
329     GLfloat range = (MI_COUNT(mi) <= 2) ? 4 : 5.5;
330     GLfloat extent;
331     GLfloat spacing = range / bp->ncameras;
332     if (spacing < 0.7) spacing = 0.7;
333     extent = spacing * (bp->ncameras - 1);
334     for (i = 0; i < bp->ncameras; i++)
335       {
336         camera *c = &bp->cameras[i];
337         c->state = IDLE;
338         c->pos.x = i*spacing - extent/2;
339         c->pos.z += 0.7;
340         if (spacing < 1.6)
341           c->pos.z = (i & 1 ? 1.1 : -0.3);
342         c->focus.x = c->pos.x;
343         c->focus.y = c->pos.y + 1;
344         c->focus.z = c->pos.z + BEAM_ZOFF;
345         c->pitch   = -50;
346       }
347   }
348
349
350 # ifdef DEBUG
351   if (!debug_p)
352 # endif
353     /* Let's tilt the floor a little. */
354     gltrackball_reset (bp->user_trackball,
355                        -0.70 + frand(1.58),
356                        -0.30 + frand(0.40));
357 }
358
359
360 static XYZ
361 normalize (XYZ p)
362 {
363   GLfloat d = sqrt(p.x*p.x + p.y*p.y * p.z*p.z);
364   if (d < 0.0000001)
365     p.x = p.y = p.z = 0;
366   else
367     {
368       p.x /= d;
369       p.y /= d;
370       p.z /= d;
371     }
372     
373   return p;
374 }
375
376
377 static GLfloat
378 vector_angle (XYZ a, XYZ b)
379 {
380   double La = sqrt (a.x*a.x + a.y*a.y + a.z*a.z);
381   double Lb = sqrt (b.x*b.x + b.y*b.y + b.z*b.z);
382   double cc, angle;
383
384   if (La == 0 || Lb == 0) return 0;
385   if (a.x == b.x && a.y == b.y && a.z == b.z) return 0;
386
387   /* dot product of two vectors is defined as:
388        La * Lb * cos(angle between vectors)
389      and is also defined as:
390        ax*bx + ay*by + az*bz
391      so:
392       La * Lb * cos(angle) = ax*bx + ay*by + az*bz
393       cos(angle)  = (ax*bx + ay*by + az*bz) / (La * Lb)
394       angle = acos ((ax*bx + ay*by + az*bz) / (La * Lb));
395   */
396   cc = (a.x*b.x + a.y*b.y + a.z*b.z) / (La * Lb);
397   if (cc > 1) cc = 1;  /* avoid fp rounding error (1.000001 => sqrt error) */
398   angle = acos (cc);
399
400   return (angle);
401 }
402
403
404 static int
405 draw_component (ModeInfo *mi, int i, GLfloat color[4])
406 {
407   camera_configuration *bp = &bps[MI_SCREEN(mi)];
408   if (! color)
409     color = bp->component_colors[i];
410   glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
411   glCallList (bp->dlists[i]);
412   return (*all_objs[i])->points / 3;
413 }
414
415
416 static int
417 draw_double_component (ModeInfo *mi, int i)
418 {
419   int count = draw_component (mi, i, 0);
420
421   glFrontFace(GL_CCW);
422   glScalef (1, 1, -1);
423   count += draw_component (mi, i, 0);
424   glScalef (1, 1, -1);
425   glFrontFace(GL_CW);
426   return count;
427 }
428
429
430 static int
431 draw_ray (ModeInfo *mi, camera *c)
432 {
433   int wire = MI_IS_WIREFRAME(mi);
434   int count = 0;
435   GLfloat length = 10000;
436   GLfloat thickness = 0.08;
437   int i;
438
439   glPushMatrix();
440   glTranslatef (c->pos.x, c->pos.y, c->pos.z + BEAM_ZOFF);
441   glRotatef (-c->facing, 0, 0, 1);
442   glRotatef ( c->pitch,  1, 0, 0);
443   glRotatef (frand(90),  0, 1, 0);
444   glScalef (thickness, length, thickness);
445   glDisable (GL_LIGHTING);
446
447   for (i = 0; i < 5; i++)
448     {
449       glColor4f (1, 0, 0, 0.1 + (i * 0.18));
450       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
451       glVertex3f (0, 0, -0.5); glVertex3f (0, 0,  0.5);
452       glVertex3f (0, 1,  0.5); glVertex3f (0, 1, -0.5);
453       glEnd();
454       count++;
455       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
456       glVertex3f (-0.5, 0, 0); glVertex3f ( 0.5, 0, 0);
457       glVertex3f ( 0.5, 1, 0); glVertex3f (-0.5, 1, 0);
458       glEnd();
459       count++;
460       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
461       glVertex3f (0, 1, -0.5); glVertex3f (0, 1,  0.5);
462       glVertex3f (0, 0,  0.5); glVertex3f (0, 0, -0.5);
463       glEnd();
464       count++;
465       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
466       glVertex3f (-0.5, 1, 0); glVertex3f ( 0.5, 1, 0);
467       glVertex3f ( 0.5, 0, 0); glVertex3f (-0.5, 0, 0);
468       glEnd();
469       count++;
470       glScalef (0.7, 1, 0.7);
471     }
472   if (!MI_IS_WIREFRAME(mi))
473     glEnable (GL_LIGHTING);
474   glPopMatrix();
475   return count;
476 }
477
478
479 static int
480 draw_camera_1 (ModeInfo *mi, camera *c)
481 {
482   int count = 0;
483   GLfloat scale = 0.01;
484   GLfloat color[4] = { 1, 0, 0, 1 };
485   glPushMatrix();
486
487   glTranslatef (c->pos.x, c->pos.y, c->pos.z);
488   glScalef (scale, scale, scale);
489
490   glRotatef (90, 1, 0, 0);
491   glRotatef (-90, 0, 1, 0);
492
493   count += draw_double_component (mi, CAMERA_MOUNT);
494
495   glRotatef (-c->facing, 0, 1, 0);
496   glRotatef (-c->pitch,  0, 0, 1);
497
498   count += draw_double_component (mi, CAMERA_HINGE);
499
500   if (c->state == WARM_UP)
501     {
502       if (c->tick < 0.2)
503         glTranslatef ((0.2 - c->tick) / (scale * 3), 0, 0);
504     }
505
506   if (c->state == ZOT)
507     {
508       glTranslatef ((0.005 - frand(0.01)) / scale,
509                     (0.005 - frand(0.01)) / scale,
510                     (0.005 - frand(0.01)) / scale);
511     }
512
513   count += draw_double_component (mi, CAMERA_BODY);
514
515   if (c->state == ZOT)
516     {
517       glTranslatef ((0.005 - frand(0.01)) / scale,
518                     (0.005 - frand(0.01)) / scale,
519                     (0.005 - frand(0.01)) / scale);
520     }
521
522   count += draw_double_component (mi, CAMERA_CAP);
523
524   switch (c->state) {
525   case IDLE:      break;
526   case WARM_UP:   color[0] = 1.0 - c->tick; break;
527   case ZOT:       color[0] = 1.0;           break;
528   case COOL_DOWN: color[0] = c->tick;       break;
529   default: abort(); break;
530   }
531
532   count += draw_component (mi, CAMERA_LENS, 
533                            (c->state == IDLE ? 0 : color));
534
535 # ifdef DEBUG
536   if (debug_p && c->state != ZOT)
537     {
538       glDisable (GL_LIGHTING);
539       glColor3f (1, 1, 0);
540       glBegin (GL_LINES);
541       glVertex3f (0, BEAM_ZOFF / scale, 0);
542       glVertex3f (-100 / scale, BEAM_ZOFF / scale, 0);
543       glEnd();
544       if (!MI_IS_WIREFRAME(mi))
545         glEnable (GL_LIGHTING);
546     }
547 # endif
548
549   glPopMatrix();
550
551   return count;
552 }
553
554
555 /* The position this pedestrian would appear at during the given ratio
556    through its life cycle.
557  */
558 static XYZ
559 pedestrian_position (pedestrian *p, GLfloat ratio)
560 {
561   XYZ pos = p->pos;
562   if (p->speed < 0)
563     ratio = 1-ratio;
564   pos.x += p->length * ratio;
565   pos.z += sin (M_PI * ratio * p->frequency * 2) * p->amplitude
566     + p->amplitude/2;
567   return pos;
568 }
569
570
571 static int
572 draw_pedestrian (ModeInfo *mi, pedestrian *p)
573 {
574   int count = 0;
575 # ifdef DEBUG
576   if (debug_p)
577     {
578       GLfloat r;
579       GLfloat step = 0.001;
580       glDisable (GL_LIGHTING);
581       glColor3f (0, 0, 1);
582
583       glBegin (GL_LINE_STRIP);
584       glVertex3f (p->pos.x, p->pos.y, p->pos.z + p->amplitude);
585       glVertex3f (p->pos.x, p->pos.y, 0);
586       glVertex3f (p->pos.x + p->length, p->pos.y, 0);
587       glVertex3f (p->pos.x + p->length, p->pos.y, p->pos.z + p->amplitude);
588       glEnd();
589
590       glBegin (GL_LINE_STRIP);
591       for (r = 0; r <= 1; r += step)
592         {
593           XYZ pos = pedestrian_position (p, r);
594           glVertex3f (pos.x, pos.y, pos.z);
595           count++;
596           if (p->ratio >= r && p->ratio < r + step)
597             {
598               glVertex3f (pos.x, pos.y, pos.z - p->amplitude);
599               glVertex3f (pos.x, pos.y, pos.z + p->amplitude);
600               glVertex3f (pos.x, pos.y, pos.z);
601               count++;
602             }
603         }
604       glEnd();
605       if (!MI_IS_WIREFRAME(mi))
606         glEnable (GL_LIGHTING);
607     }
608 # endif
609   return count;
610 }
611
612
613 /* Start someone walking through the scene.
614  */
615 static void
616 add_pedestrian (ModeInfo *mi)
617 {
618   camera_configuration *bp = &bps[MI_SCREEN(mi)];
619   pedestrian *p = (pedestrian *) calloc (1, sizeof(*p));
620   static int id = 100;
621   p->id = id++;
622   p->length = 35;
623   p->ratio = 0;
624   p->pos.x = -p->length/2;
625   p->pos.y = 3 + frand(10);
626   p->pos.z = -1.5 + frand(4) + ((random() % 10) ? 0 : frand(8));
627   p->frequency = 4 + frand(4);
628   p->amplitude = 0.1 + ((random() % 10) ? BELLRAND(0.45) : BELLRAND(1.5));
629   p->ratio = 0;
630   p->speed = ((4 + frand(4) + ((random() % 10) ? 0 : frand(10)))
631               * (random() & 1 ? 1 : -1)
632               * speed_arg);
633
634   if (bp->pedestrians)
635     {
636       pedestrian *p2;   /* add it to the end */
637       for (p2 = bp->pedestrians; p2->next; p2 = p2->next)
638         ;
639       p2->next = p;
640     }
641   else
642     {
643       p->next = bp->pedestrians;
644       bp->pedestrians = p;
645     }
646 }
647
648
649 static void
650 free_pedestrian (ModeInfo *mi, pedestrian *p)
651 {
652   camera_configuration *bp = &bps[MI_SCREEN(mi)];
653   Bool ok = False;
654   if (!p) abort();
655   if (p == bp->pedestrians)
656     {
657       bp->pedestrians = p->next;
658       ok = True;
659     }
660   else
661     {
662       pedestrian *p0;
663       for (p0 = bp->pedestrians; p0; p0 = p0->next)
664         if (p0->next == p)
665           {
666             p0->next = p0->next ? p0->next->next : 0;
667             ok = True;
668             break;
669           }
670     }
671   if (!ok) abort();
672   p->next = 0;
673   free (p);
674 }
675
676
677 static void
678 tick_pedestrian (ModeInfo *mi, pedestrian *p)
679 {
680   p->ratio += 0.001 * (p->speed > 0 ? p->speed : -p->speed);
681   if (p->ratio >= 1)
682     free_pedestrian (mi, p);
683 }
684
685
686 /* Extract the position of the thing this camera would like to look at.
687  */
688 static void
689 set_camera_focus (ModeInfo *mi, camera *c)
690 {
691   camera_configuration *bp = &bps[MI_SCREEN(mi)];
692
693   if (c->focus_id > 0)          /* Look at pedestrian with id N */
694     {
695       long id = c->focus_id;
696       pedestrian *p;
697       for (p = bp->pedestrians; p; p = p->next)
698         if (p->id == id)
699           break;
700       if (p)
701         c->focus = pedestrian_position (p, p->ratio);
702       else
703         c->focus_id = 0;  /* that pedestrian has escaped */
704     }
705   else if (c->focus_id < 0)     /* Look at camera -N-1 */
706     {
707       long n = -c->focus_id - 1;
708       if (bp->ncameras > n)
709         c->focus = bp->cameras[n].pos;
710     }
711 }
712
713
714 static void
715 tick_camera (ModeInfo *mi, camera *c)
716 {
717   camera_configuration *bp = &bps[MI_SCREEN(mi)];
718
719   GLfloat X, Y, Z;
720   set_camera_focus (mi, c);
721
722   X = c->focus.x - c->pos.x;
723   Y = c->focus.y - c->pos.y;
724   Z = c->focus.z - c->pos.z - BEAM_ZOFF;
725
726   if (X != 0 || Y != 0)
727     {
728       GLfloat ofacing = c->facing;
729       GLfloat opitch  = c->pitch;
730
731       GLfloat target_facing = atan2 (X, Y) * (180 / M_PI);
732       GLfloat target_pitch  = atan2 (Z, sqrt(X*X + Y*Y)) * (180 / M_PI);
733
734       /* Adjust the current velocity.
735          If we are nearing the target, slow down (but don't stop).
736          Otherwise, speed up (but don't break the speed limit).
737        */
738       {
739         GLfloat accel = 0.5 * speed_arg;
740         GLfloat decel_range = 20;
741         GLfloat max_velocity = 5 * speed_arg;
742         GLfloat close_enough = 0.5 * speed_arg;
743
744         GLfloat dx = target_facing - c->facing;
745         GLfloat dy = target_pitch  - c->pitch;
746         GLfloat angular_distance = sqrt (dx*dx + dy*dy);
747
748         /* Split the velocity into vx and vy components.  This isn't
749            quite right since this treats them as Cartesian rather than
750            polar, but it's close enough.
751          */
752         GLfloat r = (dx == 0) ? 1 : ABS(dy) / ABS(dx);
753         GLfloat vx = 1-r;
754         GLfloat vy = r;
755
756         if (angular_distance < decel_range)  /* Nearing target, slow down */
757           {
758             c->velocity -= accel;
759             if (c->velocity <= 0)
760               c->velocity = accel;
761           }
762         else
763           {
764             c->velocity += accel;
765             if (c->velocity > max_velocity)
766               c->velocity = max_velocity;
767           }
768
769         /* Don't overshoot */
770         if (vx > ABS(dx)) vx = ABS(dx);
771         if (vy > ABS(dy)) vy = ABS(dy);
772
773
774         /* Rotate toward target by current angular velocity. */
775         c->facing += vx * c->velocity * (target_facing > c->facing ? 1 : -1);
776         c->pitch  += vy * c->velocity * (target_pitch  > c->pitch  ? 1 : -1);
777
778         /* If we are pointed *really close* to the target, just lock on,
779            to avoid twitchy overshoot rounding errors.
780          */
781         if (angular_distance < close_enough)
782           {
783             c->facing = target_facing;
784             c->pitch  = target_pitch;
785           }
786
787         /* Constrain to hinge's range of motion */
788         c->facing = MAX (-90, MIN (90, c->facing));
789         c->pitch  = MAX (-90, MIN (55, c->pitch));
790
791         /* After this motion, our prevailing velocity (for next time)
792            is the angular distance we actually moved.
793          */
794         dx = c->facing - ofacing;
795         dy = c->pitch  - opitch;
796         c->velocity = sqrt (dx*dx + dy*dy);
797       }
798     }
799
800 # ifdef DEBUG
801   if (debug_p && 1)
802     /* Mark the point on which this camera is focusing. */
803     {
804       glPushMatrix();
805       glDisable (GL_LIGHTING);
806       glColor3f(0.3, 0.3, 0.3);
807       glBegin (GL_LINES);
808       glVertex3f (c->pos.x,   c->pos.y,   c->pos.z + BEAM_ZOFF);
809       glVertex3f (c->focus.x, c->focus.y, c->focus.z);
810       glEnd();
811       glColor3f(1,1,1);
812       glBegin (GL_LINES);
813       glVertex3f (c->focus.x-0.25, c->focus.y, c->focus.z);
814       glVertex3f (c->focus.x+0.25, c->focus.y, c->focus.z);
815       glVertex3f (c->focus.x, c->focus.y-0.25, c->focus.z);
816       glVertex3f (c->focus.x, c->focus.y+0.25, c->focus.z);
817       glVertex3f (c->focus.x, c->focus.y, c->focus.z-0.25);
818       glVertex3f (c->focus.x, c->focus.y, c->focus.z+0.25);
819       glEnd();
820       if (!MI_IS_WIREFRAME(mi))
821         glEnable (GL_LIGHTING);
822       glPopMatrix();
823     }
824 # endif
825
826   /* If this camera is looking at another camera, get shy and look away
827      if the target sees us looking.
828    */
829   if (c->focus_id < 0)
830     {
831       camera *c2 = &bp->cameras[-1 - c->focus_id];
832       XYZ a, b;
833       GLfloat aa = c->facing / (180 / M_PI);
834       GLfloat bb = c->pitch  / (180 / M_PI);
835       GLfloat angle;
836
837       a.y = cos(aa)* cos(bb);
838       a.x = sin(aa)* cos(bb);
839       a.z = sin(bb);
840
841       aa = c2->facing / (180 / M_PI);
842       bb = c2->pitch  / (180 / M_PI);
843       b.y = cos(aa)* cos(bb);
844       b.x = sin(aa)* cos(bb);
845       b.z = sin(bb);
846
847       angle = vector_angle (normalize(a), normalize(b)) * (180 / M_PI);
848
849       if (angle > 100)
850         {
851           c->focus_id = 0;
852           /* Look "away" which means in the same direction. */
853           c->focus.x = c->pos.x + (c2->focus.x - c2->pos.x);
854           c->focus.y = c->pos.y + (c2->focus.y - c2->pos.y);
855           c->focus.z = c->pos.z + (c2->focus.z - c2->pos.z);
856           /* Look at either the sky or the ground.*/
857           c->focus.z = c->focus.x * (random() & 1 ? 1 : -1) * 5;
858           c->velocity = c2->velocity * 3;
859         }
860     }
861
862
863   /* Randomly pick some other things to look at.
864    */
865
866   if (c->state == IDLE &&
867       (c->focus_id <= 0
868        ? !(random() % (int) MAX (1, (50 / speed_arg)))
869        : !(random() % (int) MAX (1, (1000 / speed_arg)))))
870     {
871
872       /* Shiny! Start paying attention to something else. */
873
874       if ((bp->ncameras > 1 && !(random() % 20)))       /* look at a camera */
875         {
876           int which = random() % 4;
877           long i;
878           for (i = 0; i < bp->ncameras; i++)
879             if (c == &bp->cameras[i])
880               break;
881
882           /* Look at cameras 1 or 2 away in each direction, but not farther.
883              Since we arrange them in 2 staggered lines, those are the only
884              four that are in line of sight.
885            */
886           if (i >= 2 && which == 0)
887             which = i-2;
888           else if (i >= 1 && which == 1)
889             which = i-1;
890           else if (i < bp->ncameras-2 && which == 2)
891             which = i+2;
892           else if (i == bp->ncameras-1)
893             which = i-1;
894           else /* (i < bp->ncameras-2 && which == 3) */
895             which = i+1;
896
897           c->focus_id = -1 - which;
898         }
899       else                                      /* look at a pedestrian */
900         {
901           int n = 0;
902           pedestrian *p;
903           for (p = bp->pedestrians; p; p = p->next)
904             n++;
905           if (n > 0)
906             {
907               n = random() % n;
908               p = bp->pedestrians;
909               while (n > 0 && p)
910                 p = p->next;
911               if (p)
912                 c->focus_id = p->id;
913             }
914         }
915     }
916
917   /* Run the animation */
918
919   if (c->state != IDLE)
920     {
921       pedestrian *p = bp->pedestrians; /* first one */
922       if (p) c->focus_id = p->id;
923
924       switch (c->state) {
925       case WARM_UP:   c->tick -= 0.01 * speed_arg; break;
926       case ZOT:       c->tick -= 0.006 * speed_arg; 
927         if (p) p->speed *= 0.995;  /* target takes 1d6 HP damage */
928         break;
929       case COOL_DOWN: c->tick -= 0.02 * speed_arg; break;
930       default: abort(); break;
931       }
932
933       if (c->tick <= 0)
934         {
935           c->tick = 1.0;
936           switch (c->state) {
937           case WARM_UP:   c->state = ZOT; break;
938           case ZOT:       c->state = COOL_DOWN;
939             c->focus_id = 0;
940             if (p) p->ratio = 1.0;  /* threat eliminated */
941             break;
942           case COOL_DOWN: c->state = IDLE; break;
943           default: abort(); break;
944           }
945         }
946     }
947
948   if (bp->cameras[0].state == IDLE &&
949       bp->pedestrians &&
950       bp->pedestrians[0].ratio < 0.3 &&
951       !(random() % MAX (1, (int) (50000 / speed_arg))))
952     {
953       /* CASE NIGHTMARE RED detected, engage SCORPION STARE by authority
954          of MAGINOT BLUE STARS. */
955       int i;
956       for (i = 0; i < bp->ncameras; i++)
957         {
958           bp->cameras[i].state = WARM_UP;
959           bp->cameras[i].tick  = 1.0;
960         }
961     }
962 }
963
964
965 static int
966 draw_ground (ModeInfo *mi, GLfloat color[4])
967 {
968   int wire = MI_IS_WIREFRAME(mi);
969   GLfloat i, j, k, h;
970
971   /* When using fog, iOS apparently doesn't like lines or quads that are
972      really long, and extend very far outside of the scene. Maybe?  If the
973      length of the line (cells * cell_size) is greater than 25 or so, lines
974      that are oriented steeply away from the viewer tend to disappear
975      (whether implemented as GL_LINES or as GL_QUADS).
976
977      So we do a bunch of smaller grids instead of one big one.
978   */
979   int cells = 20;
980   GLfloat cell_size = 0.4;
981   int points = 0;
982   int gridsw = 10;
983   int gridsh = 2;
984
985   glPushMatrix();
986
987   if (!wire)
988     {
989       GLfloat fog_color[4] = { 0, 0, 0, 1 };
990
991       glLineWidth (2);
992       glEnable (GL_LINE_SMOOTH);
993       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
994       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
995       glEnable (GL_BLEND);
996
997       glFogi (GL_FOG_MODE, GL_EXP2);
998       glFogfv (GL_FOG_COLOR, fog_color);
999       glFogf (GL_FOG_DENSITY, 0.017);
1000       glFogf (GL_FOG_START, -cells/2 * cell_size * gridsh);
1001       glEnable (GL_FOG);
1002     }
1003
1004   glColor4fv (color);
1005   glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
1006
1007   glTranslatef (-cells * gridsw * cell_size / 2, 0, 0);
1008   for (h = 0; h < 2; h++)
1009     {
1010       glPushMatrix();
1011       glTranslatef (0, cells * cell_size / 2, 0);
1012       for (j = 0; j < gridsh; j++)
1013         {
1014           glPushMatrix();
1015           for (k = 0; k < gridsw; k++)
1016             {
1017               glBegin (GL_LINES);
1018               for (i = -cells/2; i < cells/2; i++)
1019                 {
1020                   GLfloat a = i * cell_size;
1021                   GLfloat b = cells/2 * cell_size;
1022                   glVertex3f (a, -b, 0); glVertex3f (a, b, 0); points++;
1023                   glVertex3f (-b, a, 0); glVertex3f (b, a, 0); points++;
1024                 }
1025               glEnd();
1026               glTranslatef (cells * cell_size, 0, 0);
1027             }
1028           glPopMatrix();
1029           glTranslatef (0, cells * cell_size, 0);
1030         }
1031       glPopMatrix();
1032       glRotatef (90, 1, 0, 0);
1033     }
1034
1035   if (!wire)
1036     glDisable (GL_LINE_SMOOTH);
1037
1038   glPopMatrix();
1039
1040   return points;
1041 }
1042
1043
1044 ENTRYPOINT void
1045 draw_camera (ModeInfo *mi)
1046 {
1047   camera_configuration *bp = &bps[MI_SCREEN(mi)];
1048   Display *dpy = MI_DISPLAY(mi);
1049   Window window = MI_WINDOW(mi);
1050   GLfloat camera_size;
1051   int i;
1052
1053   if (!bp->glx_context)
1054     return;
1055
1056   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1057
1058   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1059
1060   glPushMatrix ();
1061
1062 # ifdef HAVE_MOBILE
1063   glRotatef (current_device_rotation(), 0, 0, 1);  /* right side up */
1064 # endif
1065
1066   gltrackball_rotate (bp->user_trackball);
1067
1068 # ifdef HAVE_MOBILE
1069   {
1070     GLfloat s = 0.6;
1071     glScalef (s, s, s);
1072   }
1073 # endif
1074
1075 # ifdef DEBUG
1076   if (debug_p)
1077     {
1078       GLfloat s = 0.2;
1079       glScalef (s, s, s);
1080       glRotatef (30, 0, 1, 0);
1081       glRotatef (15, 1, 0, 0);
1082       glTranslatef (0, 0, -80);
1083     }
1084 # endif
1085
1086   mi->polygon_count = 0;
1087
1088   camera_size = 5;
1089
1090   if (MI_COUNT(mi) <= 2)    /* re-frame the scene a little bit */
1091     glTranslatef (0, -1, 7);
1092   if (MI_COUNT(mi) >= 20)
1093     glTranslatef (0, -1.5, -5);
1094   if (MI_COUNT(mi) >= 40)
1095     glTranslatef (0, 2, -15);
1096
1097   glScalef (camera_size, camera_size, camera_size);
1098
1099   /* +Z is toward sky; +X is toward the right along the back wall;
1100      +Y is toward the viewer. */
1101   glRotatef (-90, 1, 0, 0);
1102   glScalef (1, -1, 1);
1103
1104   glPushMatrix();
1105   glScalef (1/camera_size, 1/camera_size, 1/camera_size);
1106   glTranslatef (0, -2.38, -8);  /* Move the ground down and back */
1107   glCallList (bp->dlists[GROUND]);
1108   mi->polygon_count += ground->points;
1109
1110   glPopMatrix();
1111
1112   {
1113     pedestrian *p, *p2;
1114     for (p = bp->pedestrians, p2 = p ? p->next : 0;
1115          p;
1116          p = p2, p2 = p2 ? p2->next : 0)
1117       {
1118         mi->polygon_count += draw_pedestrian (mi, p);
1119         tick_pedestrian (mi, p);  /* might free p */
1120       }
1121
1122     if (!bp->pedestrians || !(random() % MAX (1, (int) (200 / speed_arg))))
1123       add_pedestrian (mi);
1124   }
1125
1126   for (i = 0; i < bp->ncameras; i++)
1127     {
1128       camera *c = &bp->cameras[i];
1129       mi->polygon_count += draw_camera_1 (mi, c);
1130       tick_camera (mi, c);
1131     }
1132
1133   for (i = 0; i < bp->ncameras; i++)
1134     {
1135       camera *c = &bp->cameras[i];
1136       if (c->state == ZOT)  /* Do this last, for alpha blending */
1137         mi->polygon_count += draw_ray (mi, c);
1138     }
1139
1140   glPopMatrix ();
1141
1142   if (mi->fps_p) do_fps (mi);
1143   glFinish();
1144
1145   glXSwapBuffers(dpy, window);
1146 }
1147
1148 XSCREENSAVER_MODULE_2 ("Vigilance", vigilance, camera)
1149
1150 #endif /* USE_GL */