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