/* 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 release_camera 0
#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);
{
GLfloat s = (MI_WIDTH(mi) < MI_HEIGHT(mi)
? (MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi))
: 1);
glScalef (s, s, s);
}
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);
}
free (string);
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 ();
glRotatef (current_device_rotation(), 0, 0, 1); /* right side up */
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);
}
ENTRYPOINT void
free_camera (ModeInfo *mi)
{
camera_configuration *bp = &bps[MI_SCREEN(mi)];
pedestrian *p;
int i;
if (!bp->glx_context) return;
glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);
if (bp->user_trackball) gltrackball_free (bp->user_trackball);
if (bp->cameras) free (bp->cameras);
p = bp->pedestrians;
while (p) {
pedestrian *p2 = p->next;
free (p);
p = p2;
}
if (bp->dlists) {
for (i = 0; i < countof(all_objs); i++)
if (glIsList(bp->dlists[i])) glDeleteLists(bp->dlists[i], 1);
free (bp->dlists);
}
}
XSCREENSAVER_MODULE_2 ("Vigilance", vigilance, camera)
#endif /* USE_GL */