RESEARCH
 
PEOPLE
 
INFORMATION
 
EVENTS
 
SPONSORS
 

Stickydots

The following source code creates a scintillating random dot cinematogram (SRDC) using OpenGL.  It bases the shape of this SRDC on the depth map in the current OpenGL depth buffer (Z-buffer).  The main routine in this code is the Draw() function.  The other functions help adjust the various parameters associated with SRDC displays, such as the number of dots, frequency of update, and percent refreshed during each update.

For the anaglyphic presentation mode, the blue color is a mix of 100% blue and 50% green. We find this works best with the anaglyphics glasses we have purchased at www.reel3d.com. The red color is 100% red plus 10% green. For reasons unknown, the anti-aliasing algorithm on our nVidia Quadro4 graphics card does not perform correctly if pure red pixels are rendered (anti-aliasing becomes spatially asymmetric).  In our research studies, we have actually used dual channel graphics hardware which provides independent displays for each eye.  We've found the anaglyphs useful for demonstrating the stimuli on normal computer monitors.

More to come--please feel free to contact me in the meantime.
Andy Beall-
beall@psych.ucsb.edu

 
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <math.h>
#include <gl\gl.h>              // OpenGL
#include <gl\glu.h>             // GLU library 
#define LEFT_EYE        0
#define RIGHT_EYE       1
#define MAX_DOTS        9000
float gPointSize        = 3;
float gColorRed         = 1.0;
float gColorGreen       = 1.0;
float gColorBlue        = 1.0;
int         W                       = 640;
int         H                       = 480; 
int         gNumDots          = 1000;
int         gSkipFrames       = 30;
int         gNumRefresh       = 500;
int         lastDotIndex      = 0;
int         gHTop             = 0;
char  	    gRandomize        = 1;
FILE  *fout;
char  gSaveData         = 0;
char  gAnaglyph         = 0;
char  ok2clear          = 1;
static int w[MAX_DOTS], h[MAX_DOTS];
static GLdouble objX[MAX_DOTS], objY[MAX_DOTS], objZ[MAX_DOTS];
/*********************************************************************
Find out the viewport size
*********************************************************************/
void
Initialize(void)
{
      int data[4];
      glGetIntegerv(GL_VIEWPORT, data);
      W = data[2];
      H = data[3];
}
/*********************************************************************
This function is the heart of the stickydot technique.  It accesses
the OpenGL Z (depth) buffer and then in confunction with the PROJECTION
and the MODELVIEW matrices, it is straightforward to extract the 
X, Y, Z coordinates of any surface point currently visible in the view
frustum.  This function samples the 3D scene by randomly drawing from 
a uniform distribution of points in the projection plane.  Once found,
this routine re-draws these surface points using the MODELVIEW matrix
provided by the calling OpenGL draw routine (which has drawn the actual
scene) for stereo disparity.
*********************************************************************/
void
Draw(void)
{
      static char       whichEye;  
      int                     i, j, k, d, v, index;
      static unsigned ctr;
      float             x, y;
 
      // Used to read the OGL depth buffer
      GLint             viewport[4]; 
      GLdouble          modelview[16],projection[16]; 
      GLdouble          winX,winY,winZ;
      GLfloat                 z;
      
      /*------------------------------------------------------------------
      This block of code is executed once perform graphics update for both
      the left and right eye.  As implemented here, it assumes that the 
      scene has already been rendered to the GL graphics buffer.  
      */
      if (whichEye == LEFT_EYE && (ctr % gSkipFrames == 0)) {
            
            // Prepare to unproject the scene
            glGetDoublev (GL_MODELVIEW_MATRIX, modelview);
            glGetDoublev (GL_PROJECTION_MATRIX, projection);
            glGetIntegerv(GL_VIEWPORT, viewport); 
            
            // If needed, create a random array of points in screen coordinates
            for (i=0; i<gNumRefresh; i++) {
                  index = (i + lastDotIndex) % gNumDots;
                  
                  if (gRandomize) {
                        if (gAnaglyph) {
                              // Points will be confined to a W by H viewport
                              w[index] = (int)(((float)rand() / 32767.0 * W));
                              h[index] = (int)((float)rand() / 32767.0 * (H-gHTop) + gHTop);
                        }
                        else {
                              // Points are shifted W to the right
                              w[index] = (int)(((float)rand() / 32767.0 * W)+W);
                              h[index] = (int)((float)rand() / 32767.0 * (H-gHTop) + gHTop);
                        }
                  }
                        
                  // Unproject the scene for array of points
                  glReadPixels (w[index], H-h[index],1,1, GL_DEPTH_COMPONENT, GL_FLOAT, &z);
                  gluUnProject (w[index], H-h[index], z,  modelview,  projection, viewport, &objX[index], &objY[index], &objZ[index]); 
            }
 
            // Keep track of which points were updated for case for partial refresh
            lastDotIndex = index;         
 
            // Is this really necessary??
            // Undo IPD shift
            glPopMatrix();
 
            if (ok2clear)
                  glClear(GL_COLOR_BUFFER_BIT);
      }
 
      
      if (gSaveData) {
            fout = fopen("data3d.txt", "w");
      }
      
      if (gAnaglyph) {
            
            if (whichEye == LEFT_EYE) {
                  // Left eye is blue
                  glColor3f(0,.5,1);
            }
            else {
                  // Right eye is red
                  glColor3f(1,.1,0);
 
                  // Since this is first pass, it's OK 
                  if (ok2clear)
                        glClear(GL_COLOR_BUFFER_BIT);
            }
      }
 
      // Store current state of the depth test
      glPushAttrib(GL_DEPTH_BUFFER_BIT | GL_POINT_BIT | GL_CURRENT_BIT);
 
      glDisable(GL_DEPTH_TEST);
      glPointSize(gPointSize);
 
      if (!gAnaglyph)
            glColor3f(gColorRed, gColorGreen, gColorBlue);
 
      glBegin(GL_POINTS);
      for(i=0;i<gNumDots;i++){
            glVertex3f(objX[i], objY[i], objZ[i]);
            
            if (gSaveData) 
                  fprintf(fout, "%10.4f\t%10.4f\t%10.4f\n", objX[i], objY[i], objZ[i]);
      }
      glEnd();
 
      glPopAttrib();
 
      
      if (gSaveData) {
            gSaveData = 0;
            fclose(fout);
      }
      
      // Everytime time the right eye is drawn, increment the graphics frame counter
      if(whichEye == RIGHT_EYE)
            ctr++;
      
      whichEye ^= 1;
 
}
 
 
/*********************************************************************
Enable point anti-aliasing
*********************************************************************/
void
SetPointAntialias(void)
{
    glEnable(GL_POINT_SMOOTH);
    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
}
 
 
/*********************************************************************
Enable anaglyph rendering as opposed to frame parallel
*********************************************************************/
void
SetAnaglyphMode(void)
{
      gAnaglyph = 1;
}
 
 
/*********************************************************************
Change the number of points to render
*********************************************************************/
void
SetNumDots(int num)
{
      gNumDots = num;
      
      if (gNumDots < 1) {
            gNumDots = 1;
      }
      
      if (gNumDots > MAX_DOTS) {
            printf("**ERROR: Max dots allowed is: %i\n", MAX_DOTS);
            gNumDots = MAX_DOTS;
      }
}
 
 
/*********************************************************************
Change the number of frames to skip between point updates
*********************************************************************/
void
SetSkipFrames(int skip)
{
      gSkipFrames = skip;
      
      if (skip < 1)
            gSkipFrames = 1;
      printf("skip frames set to %i\n", gSkipFrames);
      
}
 
/*********************************************************************
Change proportion of points to refresh during graphics updates
*********************************************************************/
void
SetRefreshPercent(float percent)
{
      gNumRefresh = percent * gNumDots;
      
      if (gNumRefresh < 1)
            gNumRefresh = 1;
      
      if (gNumRefresh > gNumDots)
            gNumRefresh = gNumDots;
      
}
 
/*********************************************************************
Set top of horizontal point boundard (useful for concentrating points
on a ground surface for apps such as driving simulators.
*********************************************************************/
void
SetHTop(int top)
{
      gHTop = top;
}
 
 
/*********************************************************************
Define the width of the graphics viewport
*********************************************************************/
void
SetViewportWidth(int width)
{
      W = width;
}
 
/*********************************************************************
Define the height of the graphics viewport
*********************************************************************/
void
SetViewportHeight(int height)
{
      H = height;
}
 
 
/*********************************************************************
Save a single "snapshot" of the 3D point coordinates
*********************************************************************/
void
SetSaveData(void)
{
      gSaveData = 1;
}
 
 
/*********************************************************************
TRUE to resample point locations in the projection plane; FALSE
to stop resampling in projection plane.
*********************************************************************/
void
SetRandomize(int state)
{
      gRandomize = state;
}
 
/*********************************************************************
Define size of points (can be fractional units with anti-aliasing)
*********************************************************************/
void
SetPointSize(int size)
{
      gPointSize = size;
}
 
 
 
/*********************************************************************
Set the RGB color of points for non-anaglyph mode
*********************************************************************/
void
SetColorRGB(float red, float green, float blue)
{
      gColorRed   = red;
      gColorGreen = green;
      gColorBlue  = blue;
}
 
 
 
/*********************************************************************
Toggle whether to actively clear color buffer (to erase the scene
drawn by the calling drawing function).
*********************************************************************/
void
ToggleOK2Clear(void)
{
      ok2clear ^= 1;
}
 
 

 

 

 

RECVEB 2003