summaryrefslogtreecommitdiffstats
path: root/hacks/glx/vigilance.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/glx/vigilance.c')
-rw-r--r--hacks/glx/vigilance.c1150
1 files changed, 1150 insertions, 0 deletions
diff --git a/hacks/glx/vigilance.c b/hacks/glx/vigilance.c
new file mode 100644
index 0000000..05e19ed
--- /dev/null
+++ b/hacks/glx/vigilance.c
@@ -0,0 +1,1150 @@
+/* vigilance, Copyright (c) 2017-2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ * Draws surveillance cameras, taking an interest in their surroundings.
+ */
+
+#define DEFAULTS "*delay: 20000 \n" \
+ "*count: 5 \n" \
+ "*showFPS: False \n" \
+ "*wireframe: False \n" \
+ "*bodyColor: #666666" "\n" \
+ "*capColor: #FFFFFF" "\n" \
+ "*hingeColor: #444444" "\n" \
+ "*mountColor: #444444" "\n" \
+ "*lensColor: #000000" "\n" \
+ "*groundColor: #004400" "\n" \
+
+# define free_camera 0
+# define release_camera 0
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#define DEF_SPEED "1.0"
+#define DEF_CAMERA_SIZE "1.0"
+
+#include "xlockmore.h"
+#include "gltrackball.h"
+#include "ximage-loader.h"
+#include "normals.h"
+
+#include <ctype.h>
+
+#ifdef USE_GL /* whole file */
+
+#undef ABS
+#define ABS(x) ((x)<0?-(x):(x))
+#undef MAX
+#define MAX(A,B) ((A)>(B)?(A):(B))
+#undef MIN
+#define MIN(A,B) ((A)<(B)?(A):(B))
+#undef BELLRAND
+#define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
+
+#include "gllist.h"
+
+extern const struct gllist
+ *seccam_body, *seccam_cap, *seccam_hinge, *seccam_pipe, *seccam_lens;
+static struct gllist *ground = 0;
+
+static const struct gllist * const *all_objs[] = {
+ &seccam_body, &seccam_cap, &seccam_hinge, &seccam_pipe, &seccam_lens,
+ (const struct gllist * const *) &ground
+};
+
+#define CAMERA_BODY 0
+#define CAMERA_CAP 1
+#define CAMERA_HINGE 2
+#define CAMERA_MOUNT 3
+#define CAMERA_LENS 4
+#define GROUND 5
+
+#define BEAM_ZOFF 0.185 /* distance from origin to lens in model */
+
+typedef enum { IDLE, WARM_UP, ZOT, COOL_DOWN } camera_state;
+
+
+typedef struct {
+ XYZ pos;
+ GLfloat facing; /* rotation around vertical axis, degrees */
+ GLfloat pitch; /* front/back tilt, degrees */
+ GLfloat velocity; /* most recent angular velocity, degrees */
+ long focus_id; /* pedestrian or camera of interest */
+ XYZ focus; /* point of interest */
+ camera_state state;
+ GLfloat tick;
+} camera;
+
+typedef struct pedestrian pedestrian;
+struct pedestrian {
+ long id;
+ XYZ pos;
+ GLfloat length;
+ GLfloat frequency, amplitude;
+ GLfloat ratio;
+ GLfloat speed;
+ pedestrian *next;
+};
+
+typedef struct {
+ GLXContext *glx_context;
+ trackball_state *user_trackball;
+ Bool button_down_p;
+
+ GLuint *dlists;
+ GLfloat component_colors[countof(all_objs)][4];
+
+ int ncameras;
+ camera *cameras;
+ pedestrian *pedestrians;
+} camera_configuration;
+
+static camera_configuration *bps = NULL;
+
+static GLfloat speed_arg;
+#ifdef DEBUG
+static int debug_p;
+#endif
+
+static XrmOptionDescRec opts[] = {
+ { "-speed", ".speed", XrmoptionSepArg, 0 },
+#ifdef DEBUG
+ {"-debug", ".debug", XrmoptionNoArg, "True" },
+ {"+debug", ".debug", XrmoptionNoArg, "False" },
+#endif
+};
+
+static argtype vars[] = {
+ {&speed_arg, "speed", "Speed", DEF_SPEED, t_Float},
+#ifdef DEBUG
+ {&debug_p, "debug", "Debug", "False", t_Bool},
+#endif
+};
+
+ENTRYPOINT ModeSpecOpt camera_opts = {
+ countof(opts), opts, countof(vars), vars, NULL};
+
+
+ENTRYPOINT void
+reshape_camera (ModeInfo *mi, int width, int height)
+{
+ GLfloat h = (GLfloat) height / (GLfloat) width;
+ int y = 0;
+
+ if (width > height * 5) { /* tiny window: show middle */
+ height = width * 9/16;
+ y = -height/2;
+ h = height / (GLfloat) width;
+ }
+
+ glViewport (0, y, width, height);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ gluPerspective (30.0, 1/h, 1.0, 200);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ gluLookAt( 0, 0, 30,
+ 0, 0, 0,
+ 0, 1, 0);
+
+# ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
+ {
+ int o = (int) current_device_rotation();
+ if (o != 0 && o != 180 && o != -180)
+ glScalef (1/h, 1/h, 1/h);
+ }
+# endif
+
+ glClear(GL_COLOR_BUFFER_BIT);
+}
+
+
+ENTRYPOINT Bool
+camera_handle_event (ModeInfo *mi, XEvent *event)
+{
+ camera_configuration *bp = &bps[MI_SCREEN(mi)];
+
+ if (gltrackball_event_handler (event, bp->user_trackball,
+ MI_WIDTH (mi), MI_HEIGHT (mi),
+ &bp->button_down_p))
+ return True;
+ else if (event->xany.type == KeyPress)
+ {
+ KeySym keysym;
+ char c = 0;
+ XLookupString (&event->xkey, &c, 1, &keysym, 0);
+ if (c == ' ' || c == '\t')
+ {
+ int i;
+ if (bp->cameras[0].state == IDLE && bp->pedestrians)
+ for (i = 0; i < bp->ncameras; i++)
+ {
+ bp->cameras[i].state = WARM_UP;
+ bp->cameras[i].tick = 1.0;
+ }
+ return True;
+ }
+ }
+
+ return False;
+}
+
+
+static int draw_ground (ModeInfo *, GLfloat color[4]);
+
+static void
+parse_color (ModeInfo *mi, char *key, GLfloat color[4])
+{
+ XColor xcolor;
+ char *string = get_string_resource (mi->dpy, key, "CameraColor");
+ if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
+ {
+ fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
+ key, string);
+ exit (1);
+ }
+
+ color[0] = xcolor.red / 65536.0;
+ color[1] = xcolor.green / 65536.0;
+ color[2] = xcolor.blue / 65536.0;
+ color[3] = 1;
+}
+
+
+ENTRYPOINT void
+init_camera (ModeInfo *mi)
+{
+ camera_configuration *bp;
+ int wire = MI_IS_WIREFRAME(mi);
+ int i;
+ MI_INIT (mi, bps);
+
+ bp = &bps[MI_SCREEN(mi)];
+
+ bp->glx_context = init_GL(mi);
+
+ reshape_camera (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+
+ glShadeModel(GL_SMOOTH);
+
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_NORMALIZE);
+ glEnable(GL_CULL_FACE);
+
+ if (!wire)
+ {
+ GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
+ GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
+ GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
+ GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
+
+ glEnable(GL_LIGHTING);
+ glEnable(GL_LIGHT0);
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_CULL_FACE);
+
+ glLightfv(GL_LIGHT0, GL_POSITION, pos);
+ glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
+ glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
+ glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
+ }
+
+ bp->user_trackball = gltrackball_init (False);
+
+ bp->dlists = (GLuint *) calloc (countof(all_objs)+1, sizeof(GLuint));
+ for (i = 0; i < countof(all_objs); i++)
+ bp->dlists[i] = glGenLists (1);
+
+ for (i = 0; i < countof(all_objs); i++)
+ {
+ const struct gllist *gll = *all_objs[i];
+ char *key = 0;
+ GLfloat spec1[4] = {1.00, 1.00, 1.00, 1.0};
+ GLfloat spec2[4] = {0.40, 0.40, 0.70, 1.0};
+ GLfloat *spec = spec1;
+ GLfloat shiny = 20;
+
+ glNewList (bp->dlists[i], GL_COMPILE);
+
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ glMatrixMode(GL_TEXTURE);
+ glPushMatrix();
+ glMatrixMode(GL_MODELVIEW);
+
+ glRotatef (-90, 1, 0, 0);
+ glRotatef (180, 0, 0, 1);
+ glScalef (6, 6, 6);
+
+ glBindTexture (GL_TEXTURE_2D, 0);
+
+ switch (i) {
+ case CAMERA_BODY: key = "bodyColor"; break;
+ case CAMERA_CAP: key = "capColor"; break;
+ case CAMERA_HINGE: key = "hingeColor"; break;
+ case CAMERA_MOUNT: key = "mountColor"; break;
+ case CAMERA_LENS: key = "lensColor"; spec = spec2; break;
+ case GROUND: key = "groundColor"; spec = spec2; shiny = 128; break;
+ default: abort(); break;
+ }
+
+ parse_color (mi, key, bp->component_colors[i]);
+
+ glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec);
+ glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
+
+ switch (i) {
+ case GROUND:
+ if (! ground)
+ ground = (struct gllist *) calloc (1, sizeof(*ground));
+ ground->points = draw_ground (mi, bp->component_colors[i]);
+ break;
+ default:
+ renderList (gll, wire);
+ /* glColor3f (1, 1, 1); renderListNormals (gll, 100, True); */
+ /* glColor3f (1, 1, 0); renderListNormals (gll, 100, False); */
+ break;
+ }
+
+ glMatrixMode(GL_TEXTURE);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+
+ glEndList ();
+ }
+
+ bp->ncameras = MI_COUNT(mi);
+ if (bp->ncameras <= 0) bp->ncameras = 1;
+ bp->cameras = (camera *) calloc (bp->ncameras, sizeof (camera));
+
+ {
+ GLfloat range = (MI_COUNT(mi) <= 2) ? 4 : 5.5;
+ GLfloat extent;
+ GLfloat spacing = range / bp->ncameras;
+ if (spacing < 0.7) spacing = 0.7;
+ extent = spacing * (bp->ncameras - 1);
+ for (i = 0; i < bp->ncameras; i++)
+ {
+ camera *c = &bp->cameras[i];
+ c->state = IDLE;
+ c->pos.x = i*spacing - extent/2;
+ c->pos.z += 0.7;
+ if (spacing < 1.6)
+ c->pos.z = (i & 1 ? 1.1 : -0.3);
+ c->focus.x = c->pos.x;
+ c->focus.y = c->pos.y + 1;
+ c->focus.z = c->pos.z + BEAM_ZOFF;
+ c->pitch = -50;
+ }
+ }
+
+
+# ifdef DEBUG
+ if (!debug_p)
+# endif
+ /* Let's tilt the floor a little. */
+ gltrackball_reset (bp->user_trackball,
+ -0.70 + frand(1.58),
+ -0.30 + frand(0.40));
+}
+
+
+static XYZ
+normalize (XYZ p)
+{
+ GLfloat d = sqrt(p.x*p.x + p.y*p.y * p.z*p.z);
+ if (d < 0.0000001)
+ p.x = p.y = p.z = 0;
+ else
+ {
+ p.x /= d;
+ p.y /= d;
+ p.z /= d;
+ }
+
+ return p;
+}
+
+
+static GLfloat
+vector_angle (XYZ a, XYZ b)
+{
+ double La = sqrt (a.x*a.x + a.y*a.y + a.z*a.z);
+ double Lb = sqrt (b.x*b.x + b.y*b.y + b.z*b.z);
+ double cc, angle;
+
+ if (La == 0 || Lb == 0) return 0;
+ if (a.x == b.x && a.y == b.y && a.z == b.z) return 0;
+
+ /* dot product of two vectors is defined as:
+ La * Lb * cos(angle between vectors)
+ and is also defined as:
+ ax*bx + ay*by + az*bz
+ so:
+ La * Lb * cos(angle) = ax*bx + ay*by + az*bz
+ cos(angle) = (ax*bx + ay*by + az*bz) / (La * Lb)
+ angle = acos ((ax*bx + ay*by + az*bz) / (La * Lb));
+ */
+ cc = (a.x*b.x + a.y*b.y + a.z*b.z) / (La * Lb);
+ if (cc > 1) cc = 1; /* avoid fp rounding error (1.000001 => sqrt error) */
+ angle = acos (cc);
+
+ return (angle);
+}
+
+
+static int
+draw_component (ModeInfo *mi, int i, GLfloat color[4])
+{
+ camera_configuration *bp = &bps[MI_SCREEN(mi)];
+ if (! color)
+ color = bp->component_colors[i];
+ glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
+ glCallList (bp->dlists[i]);
+ return (*all_objs[i])->points / 3;
+}
+
+
+static int
+draw_double_component (ModeInfo *mi, int i)
+{
+ int count = draw_component (mi, i, 0);
+
+ glFrontFace(GL_CCW);
+ glScalef (1, 1, -1);
+ count += draw_component (mi, i, 0);
+ glScalef (1, 1, -1);
+ glFrontFace(GL_CW);
+ return count;
+}
+
+
+static int
+draw_ray (ModeInfo *mi, camera *c)
+{
+ int wire = MI_IS_WIREFRAME(mi);
+ int count = 0;
+ GLfloat length = 10000;
+ GLfloat thickness = 0.08;
+ int i;
+
+ glPushMatrix();
+ glTranslatef (c->pos.x, c->pos.y, c->pos.z + BEAM_ZOFF);
+ glRotatef (-c->facing, 0, 0, 1);
+ glRotatef ( c->pitch, 1, 0, 0);
+ glRotatef (frand(90), 0, 1, 0);
+ glScalef (thickness, length, thickness);
+ glDisable (GL_LIGHTING);
+
+ for (i = 0; i < 5; i++)
+ {
+ glColor4f (1, 0, 0, 0.1 + (i * 0.18));
+ glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
+ glVertex3f (0, 0, -0.5); glVertex3f (0, 0, 0.5);
+ glVertex3f (0, 1, 0.5); glVertex3f (0, 1, -0.5);
+ glEnd();
+ count++;
+ glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
+ glVertex3f (-0.5, 0, 0); glVertex3f ( 0.5, 0, 0);
+ glVertex3f ( 0.5, 1, 0); glVertex3f (-0.5, 1, 0);
+ glEnd();
+ count++;
+ glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
+ glVertex3f (0, 1, -0.5); glVertex3f (0, 1, 0.5);
+ glVertex3f (0, 0, 0.5); glVertex3f (0, 0, -0.5);
+ glEnd();
+ count++;
+ glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
+ glVertex3f (-0.5, 1, 0); glVertex3f ( 0.5, 1, 0);
+ glVertex3f ( 0.5, 0, 0); glVertex3f (-0.5, 0, 0);
+ glEnd();
+ count++;
+ glScalef (0.7, 1, 0.7);
+ }
+ if (!MI_IS_WIREFRAME(mi))
+ glEnable (GL_LIGHTING);
+ glPopMatrix();
+ return count;
+}
+
+
+static int
+draw_camera_1 (ModeInfo *mi, camera *c)
+{
+ int count = 0;
+ GLfloat scale = 0.01;
+ GLfloat color[4] = { 1, 0, 0, 1 };
+ glPushMatrix();
+
+ glTranslatef (c->pos.x, c->pos.y, c->pos.z);
+ glScalef (scale, scale, scale);
+
+ glRotatef (90, 1, 0, 0);
+ glRotatef (-90, 0, 1, 0);
+
+ count += draw_double_component (mi, CAMERA_MOUNT);
+
+ glRotatef (-c->facing, 0, 1, 0);
+ glRotatef (-c->pitch, 0, 0, 1);
+
+ count += draw_double_component (mi, CAMERA_HINGE);
+
+ if (c->state == WARM_UP)
+ {
+ if (c->tick < 0.2)
+ glTranslatef ((0.2 - c->tick) / (scale * 3), 0, 0);
+ }
+
+ if (c->state == ZOT)
+ {
+ glTranslatef ((0.005 - frand(0.01)) / scale,
+ (0.005 - frand(0.01)) / scale,
+ (0.005 - frand(0.01)) / scale);
+ }
+
+ count += draw_double_component (mi, CAMERA_BODY);
+
+ if (c->state == ZOT)
+ {
+ glTranslatef ((0.005 - frand(0.01)) / scale,
+ (0.005 - frand(0.01)) / scale,
+ (0.005 - frand(0.01)) / scale);
+ }
+
+ count += draw_double_component (mi, CAMERA_CAP);
+
+ switch (c->state) {
+ case IDLE: break;
+ case WARM_UP: color[0] = 1.0 - c->tick; break;
+ case ZOT: color[0] = 1.0; break;
+ case COOL_DOWN: color[0] = c->tick; break;
+ default: abort(); break;
+ }
+
+ count += draw_component (mi, CAMERA_LENS,
+ (c->state == IDLE ? 0 : color));
+
+# ifdef DEBUG
+ if (debug_p && c->state != ZOT)
+ {
+ glDisable (GL_LIGHTING);
+ glColor3f (1, 1, 0);
+ glBegin (GL_LINES);
+ glVertex3f (0, BEAM_ZOFF / scale, 0);
+ glVertex3f (-100 / scale, BEAM_ZOFF / scale, 0);
+ glEnd();
+ if (!MI_IS_WIREFRAME(mi))
+ glEnable (GL_LIGHTING);
+ }
+# endif
+
+ glPopMatrix();
+
+ return count;
+}
+
+
+/* The position this pedestrian would appear at during the given ratio
+ through its life cycle.
+ */
+static XYZ
+pedestrian_position (pedestrian *p, GLfloat ratio)
+{
+ XYZ pos = p->pos;
+ if (p->speed < 0)
+ ratio = 1-ratio;
+ pos.x += p->length * ratio;
+ pos.z += sin (M_PI * ratio * p->frequency * 2) * p->amplitude
+ + p->amplitude/2;
+ return pos;
+}
+
+
+static int
+draw_pedestrian (ModeInfo *mi, pedestrian *p)
+{
+ int count = 0;
+# ifdef DEBUG
+ if (debug_p)
+ {
+ GLfloat r;
+ GLfloat step = 0.001;
+ glDisable (GL_LIGHTING);
+ glColor3f (0, 0, 1);
+
+ glBegin (GL_LINE_STRIP);
+ glVertex3f (p->pos.x, p->pos.y, p->pos.z + p->amplitude);
+ glVertex3f (p->pos.x, p->pos.y, 0);
+ glVertex3f (p->pos.x + p->length, p->pos.y, 0);
+ glVertex3f (p->pos.x + p->length, p->pos.y, p->pos.z + p->amplitude);
+ glEnd();
+
+ glBegin (GL_LINE_STRIP);
+ for (r = 0; r <= 1; r += step)
+ {
+ XYZ pos = pedestrian_position (p, r);
+ glVertex3f (pos.x, pos.y, pos.z);
+ count++;
+ if (p->ratio >= r && p->ratio < r + step)
+ {
+ glVertex3f (pos.x, pos.y, pos.z - p->amplitude);
+ glVertex3f (pos.x, pos.y, pos.z + p->amplitude);
+ glVertex3f (pos.x, pos.y, pos.z);
+ count++;
+ }
+ }
+ glEnd();
+ if (!MI_IS_WIREFRAME(mi))
+ glEnable (GL_LIGHTING);
+ }
+# endif
+ return count;
+}
+
+
+/* Start someone walking through the scene.
+ */
+static void
+add_pedestrian (ModeInfo *mi)
+{
+ camera_configuration *bp = &bps[MI_SCREEN(mi)];
+ pedestrian *p = (pedestrian *) calloc (1, sizeof(*p));
+ static int id = 100;
+ p->id = id++;
+ p->length = 35;
+ p->ratio = 0;
+ p->pos.x = -p->length/2;
+ p->pos.y = 3 + frand(10);
+ p->pos.z = -1.5 + frand(4) + ((random() % 10) ? 0 : frand(8));
+ p->frequency = 4 + frand(4);
+ p->amplitude = 0.1 + ((random() % 10) ? BELLRAND(0.45) : BELLRAND(1.5));
+ p->ratio = 0;
+ p->speed = ((4 + frand(4) + ((random() % 10) ? 0 : frand(10)))
+ * (random() & 1 ? 1 : -1)
+ * speed_arg);
+
+ if (bp->pedestrians)
+ {
+ pedestrian *p2; /* add it to the end */
+ for (p2 = bp->pedestrians; p2->next; p2 = p2->next)
+ ;
+ p2->next = p;
+ }
+ else
+ {
+ p->next = bp->pedestrians;
+ bp->pedestrians = p;
+ }
+}
+
+
+static void
+free_pedestrian (ModeInfo *mi, pedestrian *p)
+{
+ camera_configuration *bp = &bps[MI_SCREEN(mi)];
+ Bool ok = False;
+ if (!p) abort();
+ if (p == bp->pedestrians)
+ {
+ bp->pedestrians = p->next;
+ ok = True;
+ }
+ else
+ {
+ pedestrian *p0;
+ for (p0 = bp->pedestrians; p0; p0 = p0->next)
+ if (p0->next == p)
+ {
+ p0->next = p0->next ? p0->next->next : 0;
+ ok = True;
+ break;
+ }
+ }
+ if (!ok) abort();
+ p->next = 0;
+ free (p);
+}
+
+
+static void
+tick_pedestrian (ModeInfo *mi, pedestrian *p)
+{
+ p->ratio += 0.001 * (p->speed > 0 ? p->speed : -p->speed);
+ if (p->ratio >= 1)
+ free_pedestrian (mi, p);
+}
+
+
+/* Extract the position of the thing this camera would like to look at.
+ */
+static void
+set_camera_focus (ModeInfo *mi, camera *c)
+{
+ camera_configuration *bp = &bps[MI_SCREEN(mi)];
+
+ if (c->focus_id > 0) /* Look at pedestrian with id N */
+ {
+ long id = c->focus_id;
+ pedestrian *p;
+ for (p = bp->pedestrians; p; p = p->next)
+ if (p->id == id)
+ break;
+ if (p)
+ c->focus = pedestrian_position (p, p->ratio);
+ else
+ c->focus_id = 0; /* that pedestrian has escaped */
+ }
+ else if (c->focus_id < 0) /* Look at camera -N-1 */
+ {
+ long n = -c->focus_id - 1;
+ if (bp->ncameras > n)
+ c->focus = bp->cameras[n].pos;
+ }
+}
+
+
+static void
+tick_camera (ModeInfo *mi, camera *c)
+{
+ camera_configuration *bp = &bps[MI_SCREEN(mi)];
+
+ GLfloat X, Y, Z;
+ set_camera_focus (mi, c);
+
+ X = c->focus.x - c->pos.x;
+ Y = c->focus.y - c->pos.y;
+ Z = c->focus.z - c->pos.z - BEAM_ZOFF;
+
+ if (X != 0 || Y != 0)
+ {
+ GLfloat ofacing = c->facing;
+ GLfloat opitch = c->pitch;
+
+ GLfloat target_facing = atan2 (X, Y) * (180 / M_PI);
+ GLfloat target_pitch = atan2 (Z, sqrt(X*X + Y*Y)) * (180 / M_PI);
+
+ /* Adjust the current velocity.
+ If we are nearing the target, slow down (but don't stop).
+ Otherwise, speed up (but don't break the speed limit).
+ */
+ {
+ GLfloat accel = 0.5 * speed_arg;
+ GLfloat decel_range = 20;
+ GLfloat max_velocity = 5 * speed_arg;
+ GLfloat close_enough = 0.5 * speed_arg;
+
+ GLfloat dx = target_facing - c->facing;
+ GLfloat dy = target_pitch - c->pitch;
+ GLfloat angular_distance = sqrt (dx*dx + dy*dy);
+
+ /* Split the velocity into vx and vy components. This isn't
+ quite right since this treats them as Cartesian rather than
+ polar, but it's close enough.
+ */
+ GLfloat r = (dx == 0) ? 1 : ABS(dy) / ABS(dx);
+ GLfloat vx = 1-r;
+ GLfloat vy = r;
+
+ if (angular_distance < decel_range) /* Nearing target, slow down */
+ {
+ c->velocity -= accel;
+ if (c->velocity <= 0)
+ c->velocity = accel;
+ }
+ else
+ {
+ c->velocity += accel;
+ if (c->velocity > max_velocity)
+ c->velocity = max_velocity;
+ }
+
+ /* Don't overshoot */
+ if (vx > ABS(dx)) vx = ABS(dx);
+ if (vy > ABS(dy)) vy = ABS(dy);
+
+
+ /* Rotate toward target by current angular velocity. */
+ c->facing += vx * c->velocity * (target_facing > c->facing ? 1 : -1);
+ c->pitch += vy * c->velocity * (target_pitch > c->pitch ? 1 : -1);
+
+ /* If we are pointed *really close* to the target, just lock on,
+ to avoid twitchy overshoot rounding errors.
+ */
+ if (angular_distance < close_enough)
+ {
+ c->facing = target_facing;
+ c->pitch = target_pitch;
+ }
+
+ /* Constrain to hinge's range of motion */
+ c->facing = MAX (-90, MIN (90, c->facing));
+ c->pitch = MAX (-90, MIN (55, c->pitch));
+
+ /* After this motion, our prevailing velocity (for next time)
+ is the angular distance we actually moved.
+ */
+ dx = c->facing - ofacing;
+ dy = c->pitch - opitch;
+ c->velocity = sqrt (dx*dx + dy*dy);
+ }
+ }
+
+# ifdef DEBUG
+ if (debug_p && 1)
+ /* Mark the point on which this camera is focusing. */
+ {
+ glPushMatrix();
+ glDisable (GL_LIGHTING);
+ glColor3f(0.3, 0.3, 0.3);
+ glBegin (GL_LINES);
+ glVertex3f (c->pos.x, c->pos.y, c->pos.z + BEAM_ZOFF);
+ glVertex3f (c->focus.x, c->focus.y, c->focus.z);
+ glEnd();
+ glColor3f(1,1,1);
+ glBegin (GL_LINES);
+ glVertex3f (c->focus.x-0.25, c->focus.y, c->focus.z);
+ glVertex3f (c->focus.x+0.25, c->focus.y, c->focus.z);
+ glVertex3f (c->focus.x, c->focus.y-0.25, c->focus.z);
+ glVertex3f (c->focus.x, c->focus.y+0.25, c->focus.z);
+ glVertex3f (c->focus.x, c->focus.y, c->focus.z-0.25);
+ glVertex3f (c->focus.x, c->focus.y, c->focus.z+0.25);
+ glEnd();
+ if (!MI_IS_WIREFRAME(mi))
+ glEnable (GL_LIGHTING);
+ glPopMatrix();
+ }
+# endif
+
+ /* If this camera is looking at another camera, get shy and look away
+ if the target sees us looking.
+ */
+ if (c->focus_id < 0)
+ {
+ camera *c2 = &bp->cameras[-1 - c->focus_id];
+ XYZ a, b;
+ GLfloat aa = c->facing / (180 / M_PI);
+ GLfloat bb = c->pitch / (180 / M_PI);
+ GLfloat angle;
+
+ a.y = cos(aa)* cos(bb);
+ a.x = sin(aa)* cos(bb);
+ a.z = sin(bb);
+
+ aa = c2->facing / (180 / M_PI);
+ bb = c2->pitch / (180 / M_PI);
+ b.y = cos(aa)* cos(bb);
+ b.x = sin(aa)* cos(bb);
+ b.z = sin(bb);
+
+ angle = vector_angle (normalize(a), normalize(b)) * (180 / M_PI);
+
+ if (angle > 100)
+ {
+ c->focus_id = 0;
+ /* Look "away" which means in the same direction. */
+ c->focus.x = c->pos.x + (c2->focus.x - c2->pos.x);
+ c->focus.y = c->pos.y + (c2->focus.y - c2->pos.y);
+ c->focus.z = c->pos.z + (c2->focus.z - c2->pos.z);
+ /* Look at either the sky or the ground.*/
+ c->focus.z = c->focus.x * (random() & 1 ? 1 : -1) * 5;
+ c->velocity = c2->velocity * 3;
+ }
+ }
+
+
+ /* Randomly pick some other things to look at.
+ */
+
+ if (c->state == IDLE &&
+ (c->focus_id <= 0
+ ? !(random() % (int) MAX (1, (50 / speed_arg)))
+ : !(random() % (int) MAX (1, (1000 / speed_arg)))))
+ {
+
+ /* Shiny! Start paying attention to something else. */
+
+ if ((bp->ncameras > 1 && !(random() % 20))) /* look at a camera */
+ {
+ int which = random() % 4;
+ long i;
+ for (i = 0; i < bp->ncameras; i++)
+ if (c == &bp->cameras[i])
+ break;
+
+ /* Look at cameras 1 or 2 away in each direction, but not farther.
+ Since we arrange them in 2 staggered lines, those are the only
+ four that are in line of sight.
+ */
+ if (i >= 2 && which == 0)
+ which = i-2;
+ else if (i >= 1 && which == 1)
+ which = i-1;
+ else if (i < bp->ncameras-2 && which == 2)
+ which = i+2;
+ else if (i == bp->ncameras-1)
+ which = i-1;
+ else /* (i < bp->ncameras-2 && which == 3) */
+ which = i+1;
+
+ c->focus_id = -1 - which;
+ }
+ else /* look at a pedestrian */
+ {
+ int n = 0;
+ pedestrian *p;
+ for (p = bp->pedestrians; p; p = p->next)
+ n++;
+ if (n > 0)
+ {
+ n = random() % n;
+ p = bp->pedestrians;
+ while (n > 0 && p)
+ p = p->next;
+ if (p)
+ c->focus_id = p->id;
+ }
+ }
+ }
+
+ /* Run the animation */
+
+ if (c->state != IDLE)
+ {
+ pedestrian *p = bp->pedestrians; /* first one */
+ if (p) c->focus_id = p->id;
+
+ switch (c->state) {
+ case WARM_UP: c->tick -= 0.01 * speed_arg; break;
+ case ZOT: c->tick -= 0.006 * speed_arg;
+ if (p) p->speed *= 0.995; /* target takes 1d6 HP damage */
+ break;
+ case COOL_DOWN: c->tick -= 0.02 * speed_arg; break;
+ default: abort(); break;
+ }
+
+ if (c->tick <= 0)
+ {
+ c->tick = 1.0;
+ switch (c->state) {
+ case WARM_UP: c->state = ZOT; break;
+ case ZOT: c->state = COOL_DOWN;
+ c->focus_id = 0;
+ if (p) p->ratio = 1.0; /* threat eliminated */
+ break;
+ case COOL_DOWN: c->state = IDLE; break;
+ default: abort(); break;
+ }
+ }
+ }
+
+ if (bp->cameras[0].state == IDLE &&
+ bp->pedestrians &&
+ bp->pedestrians[0].ratio < 0.3 &&
+ !(random() % MAX (1, (int) (50000 / speed_arg))))
+ {
+ /* CASE NIGHTMARE RED detected, engage SCORPION STARE by authority
+ of MAGINOT BLUE STARS. */
+ int i;
+ for (i = 0; i < bp->ncameras; i++)
+ {
+ bp->cameras[i].state = WARM_UP;
+ bp->cameras[i].tick = 1.0;
+ }
+ }
+}
+
+
+static int
+draw_ground (ModeInfo *mi, GLfloat color[4])
+{
+ int wire = MI_IS_WIREFRAME(mi);
+ GLfloat i, j, k, h;
+
+ /* When using fog, iOS apparently doesn't like lines or quads that are
+ really long, and extend very far outside of the scene. Maybe? If the
+ length of the line (cells * cell_size) is greater than 25 or so, lines
+ that are oriented steeply away from the viewer tend to disappear
+ (whether implemented as GL_LINES or as GL_QUADS).
+
+ So we do a bunch of smaller grids instead of one big one.
+ */
+ int cells = 20;
+ GLfloat cell_size = 0.4;
+ int points = 0;
+ int gridsw = 10;
+ int gridsh = 2;
+
+ glPushMatrix();
+
+ if (!wire)
+ {
+ GLfloat fog_color[4] = { 0, 0, 0, 1 };
+
+ glLineWidth (2);
+ glEnable (GL_LINE_SMOOTH);
+ glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable (GL_BLEND);
+
+ glFogi (GL_FOG_MODE, GL_EXP2);
+ glFogfv (GL_FOG_COLOR, fog_color);
+ glFogf (GL_FOG_DENSITY, 0.017);
+ glFogf (GL_FOG_START, -cells/2 * cell_size * gridsh);
+ glEnable (GL_FOG);
+ }
+
+ glColor4fv (color);
+ glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
+
+ glTranslatef (-cells * gridsw * cell_size / 2, 0, 0);
+ for (h = 0; h < 2; h++)
+ {
+ glPushMatrix();
+ glTranslatef (0, cells * cell_size / 2, 0);
+ for (j = 0; j < gridsh; j++)
+ {
+ glPushMatrix();
+ for (k = 0; k < gridsw; k++)
+ {
+ glBegin (GL_LINES);
+ for (i = -cells/2; i < cells/2; i++)
+ {
+ GLfloat a = i * cell_size;
+ GLfloat b = cells/2 * cell_size;
+ glVertex3f (a, -b, 0); glVertex3f (a, b, 0); points++;
+ glVertex3f (-b, a, 0); glVertex3f (b, a, 0); points++;
+ }
+ glEnd();
+ glTranslatef (cells * cell_size, 0, 0);
+ }
+ glPopMatrix();
+ glTranslatef (0, cells * cell_size, 0);
+ }
+ glPopMatrix();
+ glRotatef (90, 1, 0, 0);
+ }
+
+ if (!wire)
+ glDisable (GL_LINE_SMOOTH);
+
+ glPopMatrix();
+
+ return points;
+}
+
+
+ENTRYPOINT void
+draw_camera (ModeInfo *mi)
+{
+ camera_configuration *bp = &bps[MI_SCREEN(mi)];
+ Display *dpy = MI_DISPLAY(mi);
+ Window window = MI_WINDOW(mi);
+ GLfloat camera_size;
+ int i;
+
+ if (!bp->glx_context)
+ return;
+
+ glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glPushMatrix ();
+
+# ifdef HAVE_MOBILE
+ glRotatef (current_device_rotation(), 0, 0, 1); /* right side up */
+# endif
+
+ gltrackball_rotate (bp->user_trackball);
+
+# ifdef HAVE_MOBILE
+ {
+ GLfloat s = 0.6;
+ glScalef (s, s, s);
+ }
+# endif
+
+# ifdef DEBUG
+ if (debug_p)
+ {
+ GLfloat s = 0.2;
+ glScalef (s, s, s);
+ glRotatef (30, 0, 1, 0);
+ glRotatef (15, 1, 0, 0);
+ glTranslatef (0, 0, -80);
+ }
+# endif
+
+ mi->polygon_count = 0;
+
+ camera_size = 5;
+
+ if (MI_COUNT(mi) <= 2) /* re-frame the scene a little bit */
+ glTranslatef (0, -1, 7);
+ if (MI_COUNT(mi) >= 20)
+ glTranslatef (0, -1.5, -5);
+ if (MI_COUNT(mi) >= 40)
+ glTranslatef (0, 2, -15);
+
+ glScalef (camera_size, camera_size, camera_size);
+
+ /* +Z is toward sky; +X is toward the right along the back wall;
+ +Y is toward the viewer. */
+ glRotatef (-90, 1, 0, 0);
+ glScalef (1, -1, 1);
+
+ glPushMatrix();
+ glScalef (1/camera_size, 1/camera_size, 1/camera_size);
+ glTranslatef (0, -2.38, -8); /* Move the ground down and back */
+ glCallList (bp->dlists[GROUND]);
+ mi->polygon_count += ground->points;
+
+ glPopMatrix();
+
+ {
+ pedestrian *p, *p2;
+ for (p = bp->pedestrians, p2 = p ? p->next : 0;
+ p;
+ p = p2, p2 = p2 ? p2->next : 0)
+ {
+ mi->polygon_count += draw_pedestrian (mi, p);
+ tick_pedestrian (mi, p); /* might free p */
+ }
+
+ if (!bp->pedestrians || !(random() % MAX (1, (int) (200 / speed_arg))))
+ add_pedestrian (mi);
+ }
+
+ for (i = 0; i < bp->ncameras; i++)
+ {
+ camera *c = &bp->cameras[i];
+ mi->polygon_count += draw_camera_1 (mi, c);
+ tick_camera (mi, c);
+ }
+
+ for (i = 0; i < bp->ncameras; i++)
+ {
+ camera *c = &bp->cameras[i];
+ if (c->state == ZOT) /* Do this last, for alpha blending */
+ mi->polygon_count += draw_ray (mi, c);
+ }
+
+ glPopMatrix ();
+
+ if (mi->fps_p) do_fps (mi);
+ glFinish();
+
+ glXSwapBuffers(dpy, window);
+}
+
+XSCREENSAVER_MODULE_2 ("Vigilance", vigilance, camera)
+
+#endif /* USE_GL */