X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Fglx%2Fvigilance.c;fp=hacks%2Fglx%2Fvigilance.c;h=e276bd75bd3dc9ffbff065fe3d4c9d2eeb978eda;hp=0000000000000000000000000000000000000000;hb=4361b69d3178d7fc98d0388f9a223af6c2651aba;hpb=d6b0217f2417bd19187f0ebc389d6c5c2233b11c diff --git a/hacks/glx/vigilance.c b/hacks/glx/vigilance.c new file mode 100644 index 00000000..e276bd75 --- /dev/null +++ b/hacks/glx/vigilance.c @@ -0,0 +1,1140 @@ +/* vigilance, Copyright (c) 2017 Jamie Zawinski + * + * 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 refresh_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 "xpm-ximage.h" +#include "normals.h" + +#include + +#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; + glViewport (0, 0, 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, 0); + + 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-1 && which == 2) + which = i+2; + 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 */