CCL Home Page
Up Directory CCL pdb2ps
/*
 * xyz2ps - convert an XYZ file to a binocular 3D Postscript file
 * (c) 1996  Will Ware
 *
 * These Postscript files can be viewed with a cheap stereoscope sold by
 * Edmund Scientific.
 *
 * GNU General Public License, blah blah, legalese, blah blah blah,
 * you have the right to an attorney, blah blah blah, Stallman is God,
 * blah blah, wear Birkenstocks and tie-dye, listen to the Dead, and eat
 * Ben and Jerry's, blah blah blah blah, Amen.
 */

#include "stdio.h"
#include "math.h"

typedef unsigned long ulong;

#define LINELENGTH 300

ulong num_atoms;
struct atom
{
  char name[8];
  double x, y, z;
} *atoms;
char line[LINELENGTH], compound_name[LINELENGTH];
double xmax, ymax, zmax;

/*
 * import_xyz is a no-brainer: find out how many atoms there are, allocate
 * an array for them, and fill the array.
 */

void import_pdb(char *filename)
{
  FILE *inf;
  ulong i;
  double xmin, ymin, zmin, xcenter, ycenter, zcenter;
  struct atom *p;

  /* one pass to get the number of atoms */
  inf = fopen(filename, "r");
  if (inf == NULL)
    {
      fprintf(stderr, "pdb2ps: can't open PDB file %s\n", filename);
      exit(1);
    }

  num_atoms = 0;
  compound_name[0] = '\0';
  while (fgets(line, LINELENGTH, inf) != NULL)
    {
      line[6] = '\0';
      if (strcmp(line, "ATOM  ") == 0 ||
	  strcmp(line, "HETATM") == 0)
	num_atoms++;
      if (strcmp(line, "COMPND") == 0)
	{
	  strcpy(compound_name, line + 10);
	  compound_name[62] = '\0';
	  if (compound_name[strlen(compound_name) - 1] == '\n')
	    compound_name[strlen(compound_name) - 1] = '\0';
	}
    }
  fclose(inf);

  atoms = (struct atom *) malloc(num_atoms * sizeof(struct atom));
  if (atoms == NULL)
    {
      fprintf(stderr, "Insufficient memory\n");
      exit(1);
    }

  /* another pass to get the data for the atoms */
  inf = fopen(filename, "r");
  i = 0;
  while (fgets(line, LINELENGTH, inf) != NULL)
    {
      line[6] = '\0';
      if (strcmp(line, "ATOM  ") == 0 ||
	  strcmp(line, "HETATM") == 0)
	{
	  p = &atoms[i];
	  line[16] = '\0';
	  {
	    char *s = line + 11, *t = p->name;
	    while (*s == ' ') s++;
	    while (*s != ' ' && *s != '\0') *t++ = *s++;
	    *t = '\0';
	  }
	  sscanf(line + 32, "%lf %lf %lf",
		 &(p->x), &(p->y), &(p->z));
	  if (i == 0)
	    {
	      xmin = xmax = p->x;
	      ymin = ymax = p->y;
	      zmin = zmax = p->z;
	    }
	  else
	    {
	      if (p->x < xmin) xmin = p->x;
	      if (p->x > xmax) xmax = p->x;
	      if (p->y < ymin) ymin = p->y;
	      if (p->y > ymax) ymax = p->y;
	      if (p->z < zmin) zmin = p->z;
	      if (p->z > zmax) zmax = p->z;
	    }
	  i++;
	}
    }
  fclose(inf);

  /* center the structure around the origin */
  xcenter = 0.5 * (xmin + xmax);
  ycenter = 0.5 * (ymin + ymax);
  zcenter = 0.5 * (zmin + zmax);

  for (i = 0; i < num_atoms; i++)
    {
      struct atom *p = &atoms[i];
      p->x -= xcenter;
      p->y -= ycenter;
      p->z -= zcenter;
    }

  /* update xmax, ymax, zmax */
  xmax -= xcenter;
  ymax -= ycenter;
  zmax -= zcenter;
}

/*
 * Postscript renders using a dumb painter's algorithm, so order atoms
 * by Z coordinate, furthest ones first (positive z), nearest ones last
 * (negative z).
 */

void reorder_atoms(void)
{
  ulong i, imin, j;
  double zmin;
  struct atom temp;

  for (j = num_atoms - 1; j > 0; j--)
    {
      imin = -1;
      for (i = 0; i <= j; i++)
	if (imin == -1 || atoms[i].z < zmin)
	  {
	    imin = i;
	    zmin = atoms[i].z;
	  }
      if (imin != j)
	{
	  memcpy(&temp, &atoms[j], sizeof(struct atom));
	  memcpy(&atoms[j], &atoms[imin], sizeof(struct atom));
	  memcpy(&atoms[imin], &temp, sizeof(struct atom));
	}
    }
}

const char
*carbon_names[] =
{
  "CA", "CB", "CD", "CD1", "CD2", "CE", "CE1", "CE2",
  "CG", "CG1", "CG2", "CH", "CH1", "CH2", "CZ"
},
*oxygen_names[] =
{
  "OA", "OB", "OD", "OD1", "OD2", "OE", "OE1", "OE2",
  "OG", "OG1", "OG2", "OH", "OH1", "OH2", "OXT", "OZ"
},
*nitrogen_names[] =
{
  "NA", "NB", "ND", "ND1", "ND2", "NE", "NE1", "NE2",
  "NG", "NG1", "NG2"  "NH", "NH1", "NH2", "NZ"
},
*hydrogen_names[] =
{
  ""
};

void cleanup_atom_names(void)
{
  ulong i;
  int j;

  for (i = 0; i < num_atoms; i++)
    {
      for (j = 0; j < sizeof(hydrogen_names) / sizeof(char*); j++)
	if (strcmp(atoms[i].name, hydrogen_names[j]) == 0)
	  {
	    strcpy(atoms[i].name, "H");
	    goto next_atom;
	  }

      for (j = 0; j < sizeof(carbon_names) / sizeof(char*); j++)
	if (strcmp(atoms[i].name, carbon_names[j]) == 0)
	  {
	    strcpy(atoms[i].name, "C");
	    goto next_atom;
	  }

      for (j = 0; j < sizeof(oxygen_names) / sizeof(char*); j++)
	if (strcmp(atoms[i].name, oxygen_names[j]) == 0)
	  {
	    strcpy(atoms[i].name, "O");
	    goto next_atom;
	  }

      for (j = 0; j < sizeof(nitrogen_names) / sizeof(char*); j++)
	if (strcmp(atoms[i].name, nitrogen_names[j]) == 0)
	  {
	    strcpy(atoms[i].name, "N");
	    goto next_atom;
	  }

    next_atom: ;
    }
}




const char *postscript_preamble =
"%%!PS-Adobe\n"
"/Courier findfont 12 scalefont setfont\n";

/* Postscript measures everything in 1/72's of an inch */
#define PS_RESOLUTION  72.0
#define MIN_RADIUS 0.5  /* Postscript coordinates, don't disappear */
#define NOMINAL_RADIUS 0.5  /* in Angstroms */

void postscript_draw_atom(struct atom *p, double scale, FILE *outf)
{
  double pf, xl, xr, xv, y, zv, r;
  char *colorstring;

  /* Perspective */
  xv = 0.5 * xmax;  /* x coordinate of vanishing point */
  zv = 30.0 * zmax;  /* z coordinate of vanishing point */
  pf = zv / (zv + p->z);  /* perspective factor */
  xl = pf * p->x - (1.0 - pf) * xv;
  xr = pf * p->x + (1.0 - pf) * xv;
  y = pf * p->y;
  r = pf * NOMINAL_RADIUS;

  xl = 2.75 * PS_RESOLUTION + scale * xl;
  xr = 5.75 * PS_RESOLUTION + scale * xr;
  y = 8.0 * PS_RESOLUTION - scale * y;  /* Postscript flips vertically */
  r *= scale;
  if (r < MIN_RADIUS) r = MIN_RADIUS;

  /* different colors for different elements */
  if      (strcmp(p->name, "C") == 0) colorstring = "0.4 setgray ";
  else if (strcmp(p->name, "H") == 0) colorstring = "1 setgray ";
  else if (strcmp(p->name, "N") == 0) colorstring = "0 0 1 setrgbcolor ";
  else if (strcmp(p->name, "O") == 0) colorstring = "1 0 0 setrgbcolor ";
  else                                colorstring = "0 1 0 setrgbcolor ";

  /* left eye view */
  /* interior color of atom */
  fprintf(outf, "%s %lf %lf %lf 0 360 arc closepath fill\n",
	  colorstring, xl, y, r);
  /* black circle for the edge */
  fprintf(outf, "0 setgray %lf %lf %lf 0 360 arc stroke\n", xl, y, r);

  /* right eye view */
  /* interior color of atom */
  fprintf(outf, "%s %lf %lf %lf 0 360 arc closepath fill\n",
	  colorstring, xr, y, r);
  /* black circle for the edge */
  fprintf(outf, "0 setgray %lf %lf %lf 0 360 arc stroke\n", xr, y, r);
}

void export_postscript(FILE *outf)
{
  double scale1, scale2, scale;
  ulong i;

  /*
   * p->x,y,z are in angstroms, we need Postscript coordinates
   * compute scale factor so both X and Y fit comfortably on page
   */
  scale1 = 1.25 * PS_RESOLUTION / (xmax + NOMINAL_RADIUS);
  scale2 = 1.75 * PS_RESOLUTION / (ymax + NOMINAL_RADIUS);
  if (scale1 < scale2) scale = scale1;
  else scale = scale2;

  fprintf(outf, postscript_preamble);
  for (i = 0; i < num_atoms; i++)
    postscript_draw_atom(&atoms[i], scale, outf);

  fprintf(outf, "%lf %lf moveto (%s) show\n",
	  1.50 * PS_RESOLUTION, 5.75 * PS_RESOLUTION, compound_name);

  fprintf(outf, "showpage\n");
}

const char *usage_string =
"Usage: pdb2ps stuff.pdb > stuff.ps\n"
"stuff.pdb is a Brookhaven Protein Databank file\n"
"stuff.ps is a Postscript file, suitable for stereoscopic viewing when\n"
"printed on a color printer that understands Postscript\n";

void usage(void)
{
  printf(usage_string);
  exit(1);
}

void main(int argc, char *argv[])
{
  if (argc < 2) usage();
  import_pdb(argv[1]);
  reorder_atoms();
  cleanup_atom_names();
  export_postscript(stdout);
  exit(0);
}
Modified: Wed Jun 11 16:00:00 1997 GMT
Page accessed 5343 times since Sat Apr 17 21:36:02 1999 GMT