From: paulf@halcyon.com (Paul Forgey)
Subject: Red/Blue 3D with POV
Organization: Northwest Nexus Inc. (206) 455-3505
Date: Tue, 26 Jan 1993 23:34:00 GMT


This is a very long message, so remember that 'n' will skip it.

I have worked out an exiting method of developing red/blue three dimentional
images with Persistance Of Vision (POVRAY 1.0).  The idea is very simple,
and involves rendering two images:  One for the left eye (red) and the other
for the right eye (blue).  

I wrote a simple program which takes two .TGA files, grey sums them, then
makes a master red/blue file out of the two images.  Be sure to keep straight
which is left/red and right/blue!  Otherwise, you will have to wear your 3D
glasses upside down.

I haven't bothered to figure out optimal ratios for camera distance, but I 
can tell you that with the following camera:

camera {
  location <0 1 -3>
  direction <0 0 1.5>
  up <0 1 0>
  right <1.33 0 0>
  look_at <0 1 2>
}

The left  image would be location <-0.05 1 -3>
The right image would be location <0.05 1 -3>

A distance of 0.1 apart seems to work well.  For lower resolutions, the
location should never be exactly in-line with the look_at parameter.

The best way to accomplish this is to have a "leftcam.pov" and a
"rightcam.pov", both of which include a "common.pov" which holds objects
common to both eyes (generally this is everything except your cameras).

The following is a really nifty demonstration of this effect, and a single
frame of an animation I am working on.

-source code for TGAMIX.EXE follows:  

(Written in Borland C++ 3.1, should work with most other platforms)


#include <stdio.h>
#include <process.h>
#include <fcntl.h>
#include <io.h>

typedef unsigned char BYTE;

typedef struct 
{
  BYTE     ID;
  BYTE     ColorMapType;
  BYTE     ImageType;
  unsigned FirstColorMapEntry;
  unsigned ColorMapLength;
  BYTE     ColorMapEntrySize;
  unsigned XOrigin;
  unsigned YOrigin;
  unsigned Width;
  unsigned Height;
  BYTE     BitsPerPixel;
  BYTE     ImageDescriptorBits;
} imagedata;

typedef struct 
{
  BYTE Blue;
  BYTE Green;
  BYTE Red;
} RGB24;

void OpenTGAFile(const char *, int *, imagedata *);
void OpenTargetFile(const char *, int *, imagedata *);
void ProcessLine(int, int, int, unsigned);
void ProcessImages(const char *, const char *, const char *);

// open a TGA file, check it's type, return the data.
// if unable to handle it, or error, exits to OS
void OpenTGAFile(const char *filename, int *handle, imagedata *data)
{
  *handle = open(filename, O_RDONLY | O_BINARY);
  if (*handle == -1)
  {
    fprintf(stderr, "\nCannot open %s for reading\n", filename);
    exit(1);
  }

  read(*handle, data, sizeof(imagedata));

  if (data->ImageType != 2 || data->BitsPerPixel != 24)
  {
    printf("\nImages must be type 2 (true color, uncompressed).\n"
           "(If this is the case, be sure file is 24-bit)\n");
    exit(1);
  }

  if (data->Width > 1024)
  {
    printf("\nImages must be no more than 1024 pixels wide.\n");
    exit(1);
  }
}

// opens the target file, writing data as a header
void OpenTargetFile(const char *filename, int *handle, imagedata *data)
{
  *handle = open(filename, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC); 
  if (*handle == -1)
  {
    fprintf(stderr, "\nCannot open %s for writing\n", filename);
    exit(1);
  }

  write(*handle, data, sizeof(imagedata));
}


// reads a line from red and blue, sums the colors, then writes the
// composite to output
void ProcessLine(int red, int blue, int output, unsigned width)
{
  static RGB24 redBuffer[1024];
  static RGB24 blueBuffer[1024];
  static RGB24 outBuffer[1024];
  unsigned i;

  read(red, redBuffer, width * sizeof(RGB24));
  read(blue, blueBuffer, width * sizeof(RGB24));

  for (i=0; i < width; ++i)
  {
    outBuffer[i].Green = 0;
    
    outBuffer[i].Red =  (BYTE)(0.30 * (double)(redBuffer[i].Red) +
                               0.59 * (double)(redBuffer[i].Green) +
                               0.11 * (double)(redBuffer[i].Blue));

    outBuffer[i].Blue = (BYTE)(0.30 * (double)(blueBuffer[i].Red) +
                               0.59 * (double)(blueBuffer[i].Green) +
                               0.11 * (double)(blueBuffer[i].Blue));

  }

  write(output, outBuffer, width * sizeof(RGB24));
}

// does the whole damn thing
void ProcessImages(const char *redFile, 
                   const char *blueFile,
                   const char *outFile)
{
  static imagedata redData;
  static imagedata blueData;
  unsigned width, height, i;
  int redHandle, blueHandle, outHandle;

  OpenTGAFile(redFile, &redHandle, &redData);
  OpenTGAFile(blueFile, &blueHandle, &blueData);

  if (redData.Width != blueData.Width)
  {
    printf("\nRed and Blue files must be of the same width.\n");
    exit(1);
  }

  OpenTargetFile(outFile, &outHandle, &redData);

  width = redData.Width;
  height = redData.Height;

  for (i = 0; i < height; ++i)
  {
    ProcessLine(redHandle, blueHandle, outHandle, width);
  }

  close(redHandle);
  close(blueHandle);
  close(outHandle);
}


void main(int argc, char **argv)
{
  if (argc < 4)
  {
    printf("\nUsage:\n");
    printf("%s redfile bluefile outfile\n", argv[0]);
    exit(0);
  }

  ProcessImages(argv[1], argv[2], argv[3]);
}

The rest of the files may seem complicated, but this is a frame of an 
animation with common and frame specific objects

-following is leftcam.pov

camera {
  location   <-0.05 1 -3>
  direction  <0 0 1.5>
  up         <0 1 0>
  right      <1.33 0 0>
  look_at    <0 1 2>
}

-following is rightcam.pov


camera {
  location   <0.05 1 -3>
  direction  <0 0 1.5>
  up         <0 1 0>
  right      <1.33 0 0>
  look_at    <0 1 2>
}


-following is common.pov


#include "colors.inc"
#include "shapes.inc"
#include "textures.inc"

object {
  light_source { 
    <2 4 -3> 
    color 
      White 
  }
}

object {
  light_source {
    <-2 4 -3>
    color
      White
  }
}

object {
  plane { 
    <0 1 0> 0
  }
  texture {
    checker
      color White
      color Black
  }
}

-following is frame1l.pov (frame 1, left image)


#include "left.pov"

object {
  sphere {
    <0 1 1> .5
    texture {
      color Grey
      phong 1
    }
  }
}

object {
  sphere {
    <1.5 1.5 2> .5
    texture {
      color Grey
      phong 1
    }
  }
}

object {
  sphere {
    <0 2 3> .5
    texture {
      color Grey
      phong 1
    }
  }
}

object {
  sphere {
    <-1.5 1.5 2> .5
    texture {
      color Grey
      phong 1
    }
  }
}

-to create frame1r.pov, simply #include "right.pov" instead of "left.pov"
in the preceeding.

-the following is left.pov


#include "common.pov"
#include "leftcam.pov"

-to create right.pov, simply #include "rightcam.pov" instead of "left.pov"


-the following is BUILD.BAT, an MS-DOS batch file to assemble the entire
mess


povray -iframe1l.pov -oleft1.tga
povray -iframe1r.pov -oright1.tga
tgamix left1.tga right1.tga data1.tga
pl286 2gif data1

-and finally, the PicLab script to convert a tga to a gif:

tload %1.tga
makepal
map
gsave %1.gif


Please notice there is a minimum of command line options.  Read the
documentation for POVRAY and modify your environment variable or POVRAY.DEF
file accordingly.

Have fun.

Paul Forgey
paulf@halcyon.com
Newsgroups: comp.graphics.visualization,alt.books.reviews,rec.arts.books,comp.infosys.gis