Main Page   Namespace List   Class Hierarchy   Alphabetical List   Compound List   File List   Namespace Members   Compound Members   File Members   Related Pages  

Voltool.C

Go to the documentation of this file.
00001 /***************************************************************************
00002  *cr
00003  *cr            (C) Copyright 1995-2019 The Board of Trustees of the
00004  *cr                        University of Illinois
00005  *cr                         All Rights Reserved
00006  *cr
00007  ***************************************************************************/
00008 
00009 /***************************************************************************
00010  * RCS INFORMATION:
00011  *
00012  *      $RCSfile: Voltool.C,v $
00013  *      $Author: johns $        $Locker:  $             $State: Exp $
00014  *      $Revision: 1.11 $      $Date: 2020/10/15 17:52:06 $
00015  *
00016  ***************************************************************************
00017  * DESCRIPTION:
00018  *  General volumetric data processing routines, particularly supporting MDFF 
00019  *
00020  ***************************************************************************/
00021 
00022 #include <stdio.h>
00023 #include <stdlib.h>
00024 #include <string.h>
00025 #include <float.h> // FLT_MAX etc
00026 #include "Inform.h"
00027 #include "utilities.h"
00028 //#include "SymbolTable.h"
00029 
00030 #include "AtomSel.h"
00031 #include "VMDApp.h"
00032 #include "MoleculeList.h"
00033 #include "VolumetricData.h"
00034 #include "VolMapCreate.h" // volmap_write_dx_file()
00035 
00036 #include "CUDAMDFF.h"
00037 #include "MDFF.h"
00038 #include <math.h>
00039 #include <tcl.h>
00040 #include "TclCommands.h"
00041 #include "Measure.h"
00042 #include "MolFilePlugin.h"
00043 
00044 #include <iostream>
00045 #include <string>
00046 #include <sstream>
00047 #include "Voltool.h"
00048 
00051 void init_from_intersection(VolumetricData *mapA, const VolumetricData *mapB, VolumetricData *newvol) {
00052   int d;
00053   
00054   // Find intersection of A and B
00055   // The following has been verified for orthog. cells
00056   // (Does not work for non-orthog cells)
00057   
00058   for (d=0; d<3; d++) {
00059     newvol->origin[d] = MAX(mapA->origin[d], mapB->origin[d]);
00060     newvol->xaxis[d] = MAX(MIN(mapA->origin[d]+mapA->xaxis[d], mapB->origin[d]+mapB->xaxis[d]), newvol->origin[d]);
00061     newvol->yaxis[d] = MAX(MIN(mapA->origin[d]+mapA->yaxis[d], mapB->origin[d]+mapB->yaxis[d]), newvol->origin[d]);
00062     newvol->zaxis[d] = MAX(MIN(mapA->origin[d]+mapA->zaxis[d], mapB->origin[d]+mapB->zaxis[d]), newvol->origin[d]);
00063   }
00064     
00065   vec_sub(newvol->xaxis, newvol->xaxis, newvol->origin);
00066   vec_sub(newvol->yaxis, newvol->yaxis, newvol->origin);
00067   vec_sub(newvol->zaxis, newvol->zaxis, newvol->origin);
00068   
00069   newvol->xsize = (int) MAX(dot_prod(newvol->xaxis,mapA->xaxis)*mapA->xsize/dot_prod(mapA->xaxis,mapA->xaxis), \
00070                     dot_prod(newvol->xaxis,mapB->xaxis)*mapB->xsize/dot_prod(mapB->xaxis,mapB->xaxis));
00071   newvol->ysize = (int) MAX(dot_prod(newvol->yaxis,mapA->yaxis)*mapA->ysize/dot_prod(mapA->yaxis,mapA->yaxis), \
00072                     dot_prod(newvol->yaxis,mapB->yaxis)*mapB->ysize/dot_prod(mapB->yaxis,mapB->yaxis));
00073   newvol->zsize = (int) MAX(dot_prod(newvol->zaxis,mapA->zaxis)*mapA->zsize/dot_prod(mapA->zaxis,mapA->zaxis), \
00074                     dot_prod(newvol->zaxis,mapB->zaxis)*mapB->zsize/dot_prod(mapB->zaxis,mapB->zaxis));
00075   // Create map...
00076   if (newvol->data) delete[] newvol->data;
00077   newvol->data = new float[newvol->gridsize()];
00078 }
00079 
00082 void init_from_intersection(VolumetricData *mapA, VolumetricData *mapB, VolumetricData *newvol) {
00083   int d;
00084   
00085   // Find intersection of A and B
00086   // The following has been verified for orthog. cells
00087   // (Does not work for non-orthog cells)
00088   
00089   for (d=0; d<3; d++) {
00090     newvol->origin[d] = MAX(mapA->origin[d], mapB->origin[d]);
00091     newvol->xaxis[d] = MAX(MIN(mapA->origin[d]+mapA->xaxis[d], mapB->origin[d]+mapB->xaxis[d]), newvol->origin[d]);
00092     newvol->yaxis[d] = MAX(MIN(mapA->origin[d]+mapA->yaxis[d], mapB->origin[d]+mapB->yaxis[d]), newvol->origin[d]);
00093     newvol->zaxis[d] = MAX(MIN(mapA->origin[d]+mapA->zaxis[d], mapB->origin[d]+mapB->zaxis[d]), newvol->origin[d]);
00094   }
00095     
00096   vec_sub(newvol->xaxis, newvol->xaxis, newvol->origin);
00097   vec_sub(newvol->yaxis, newvol->yaxis, newvol->origin);
00098   vec_sub(newvol->zaxis, newvol->zaxis, newvol->origin);
00099   
00100   newvol->xsize = (int) MAX(dot_prod(newvol->xaxis,mapA->xaxis)*mapA->xsize/dot_prod(mapA->xaxis,mapA->xaxis), \
00101                     dot_prod(newvol->xaxis,mapB->xaxis)*mapB->xsize/dot_prod(mapB->xaxis,mapB->xaxis));
00102   newvol->ysize = (int) MAX(dot_prod(newvol->yaxis,mapA->yaxis)*mapA->ysize/dot_prod(mapA->yaxis,mapA->yaxis), \
00103                     dot_prod(newvol->yaxis,mapB->yaxis)*mapB->ysize/dot_prod(mapB->yaxis,mapB->yaxis));
00104   newvol->zsize = (int) MAX(dot_prod(newvol->zaxis,mapA->zaxis)*mapA->zsize/dot_prod(mapA->zaxis,mapA->zaxis), \
00105                     dot_prod(newvol->zaxis,mapB->zaxis)*mapB->zsize/dot_prod(mapB->zaxis,mapB->zaxis));
00106   // Create map...
00107   if (newvol->data) delete[] newvol->data;
00108   newvol->data = new float[newvol->gridsize()];
00109 }
00110 
00111 
00114 void init_from_union(VolumetricData *mapA, const VolumetricData *mapB, VolumetricData *newvol) {
00115   // Find union of A and B
00116   // The following has been verified for orthog. cells
00117   // (Does not work for non-orthog cells)
00118   
00119   vec_zero(newvol->xaxis);
00120   vec_zero(newvol->yaxis);
00121   vec_zero(newvol->zaxis);
00122   
00123   int d;
00124   for (d=0; d<3; d++) {
00125     newvol->origin[d] = MIN(mapA->origin[d], mapB->origin[d]);
00126   }
00127   d=0;
00128   newvol->xaxis[d] = MAX(MAX(mapA->origin[d]+mapA->xaxis[d], mapB->origin[d]+mapB->xaxis[d]), newvol->origin[d]);
00129   d=1;
00130   newvol->yaxis[d] = MAX(MAX(mapA->origin[d]+mapA->yaxis[d], mapB->origin[d]+mapB->yaxis[d]), newvol->origin[d]);
00131   d=2;
00132   newvol->zaxis[d] = MAX(MAX(mapA->origin[d]+mapA->zaxis[d], mapB->origin[d]+mapB->zaxis[d]), newvol->origin[d]);
00133   
00134   newvol->xaxis[0] -= newvol->origin[0];
00135   newvol->yaxis[1] -= newvol->origin[1];
00136   newvol->zaxis[2] -= newvol->origin[2];
00137   
00138   newvol->xsize = (int) MAX(dot_prod(newvol->xaxis,mapA->xaxis)*mapA->xsize/dot_prod(mapA->xaxis,mapA->xaxis), \
00139                     dot_prod(newvol->xaxis,mapB->xaxis)*mapB->xsize/dot_prod(mapB->xaxis,mapB->xaxis));
00140   newvol->ysize = (int) MAX(dot_prod(newvol->yaxis,mapA->yaxis)*mapA->ysize/dot_prod(mapA->yaxis,mapA->yaxis), \
00141                     dot_prod(newvol->yaxis,mapB->yaxis)*mapB->ysize/dot_prod(mapB->yaxis,mapB->yaxis));
00142   newvol->zsize = (int) MAX(dot_prod(newvol->zaxis,mapA->zaxis)*mapA->zsize/dot_prod(mapA->zaxis,mapA->zaxis), \
00143                     dot_prod(newvol->zaxis,mapB->zaxis)*mapB->zsize/dot_prod(mapB->zaxis,mapB->zaxis));
00144   // Create map...
00145   if (newvol->data) delete[] newvol->data;
00146   newvol->data = new float[newvol->gridsize()];
00147 }
00148 
00149 void init_from_identity(VolumetricData *mapA, VolumetricData *newvol) {
00150 
00151   vec_copy(newvol->origin, mapA->origin);
00152   vec_copy(newvol->xaxis, mapA->xaxis);
00153   vec_copy(newvol->yaxis, mapA->yaxis);
00154   vec_copy(newvol->zaxis, mapA->zaxis); 
00155   
00156   newvol->xsize = mapA->xsize;
00157   newvol->ysize = mapA->ysize;
00158   newvol->zsize = mapA->zsize;
00159   // Create map...
00160   if (newvol->data) delete[] newvol->data;
00161   newvol->data = new float[newvol->gridsize()];
00162 }
00163 
00164 
00167 void init_from_union(VolumetricData *mapA, VolumetricData *mapB, VolumetricData *newvol) {
00168   // Find union of A and B
00169   // The following has been verified for orthog. cells
00170   // (Does not work for non-orthog cells)
00171   
00172   vec_zero(newvol->xaxis);
00173   vec_zero(newvol->yaxis);
00174   vec_zero(newvol->zaxis);
00175   
00176   int d;
00177   for (d=0; d<3; d++) {
00178     newvol->origin[d] = MIN(mapA->origin[d], mapB->origin[d]);
00179   }
00180   d=0;
00181   newvol->xaxis[d] = MAX(MAX(mapA->origin[d]+mapA->xaxis[d], mapB->origin[d]+mapB->xaxis[d]), newvol->origin[d]);
00182   d=1;
00183   newvol->yaxis[d] = MAX(MAX(mapA->origin[d]+mapA->yaxis[d], mapB->origin[d]+mapB->yaxis[d]), newvol->origin[d]);
00184   d=2;
00185   newvol->zaxis[d] = MAX(MAX(mapA->origin[d]+mapA->zaxis[d], mapB->origin[d]+mapB->zaxis[d]), newvol->origin[d]);
00186   
00187   newvol->xaxis[0] -= newvol->origin[0];
00188   newvol->yaxis[1] -= newvol->origin[1];
00189   newvol->zaxis[2] -= newvol->origin[2];
00190   
00191   newvol->xsize = (int) MAX(dot_prod(newvol->xaxis,mapA->xaxis)*mapA->xsize/dot_prod(mapA->xaxis,mapA->xaxis), \
00192                     dot_prod(newvol->xaxis,mapB->xaxis)*mapB->xsize/dot_prod(mapB->xaxis,mapB->xaxis));
00193   newvol->ysize = (int) MAX(dot_prod(newvol->yaxis,mapA->yaxis)*mapA->ysize/dot_prod(mapA->yaxis,mapA->yaxis), \
00194                     dot_prod(newvol->yaxis,mapB->yaxis)*mapB->ysize/dot_prod(mapB->yaxis,mapB->yaxis));
00195   newvol->zsize = (int) MAX(dot_prod(newvol->zaxis,mapA->zaxis)*mapA->zsize/dot_prod(mapA->zaxis,mapA->zaxis), \
00196                     dot_prod(newvol->zaxis,mapB->zaxis)*mapB->zsize/dot_prod(mapB->zaxis,mapB->zaxis));
00197   // Create map...
00198   if (newvol->data) delete[] newvol->data;
00199   newvol->data = new float[newvol->gridsize()];
00200 }
00201 
00202 VolumetricData * init_new_volume(){
00203   double origin[3] = {0., 0., 0.};
00204   double xaxis[3] = {0., 0., 0.};
00205   double yaxis[3] = {0., 0., 0.};
00206   double zaxis[3] = {0., 0., 0.};
00207   int numvoxels [3] = {0, 0, 0};
00208   float *data = NULL;
00209   VolumetricData *newvol  = new VolumetricData("density map", origin, xaxis, yaxis, zaxis,
00210                                  numvoxels[0], numvoxels[1], numvoxels[2],
00211                                  data);
00212   return newvol; 
00213 }
00214 
00215 
00216 int init_new_volume_molecule(VMDApp *app, VolumetricData *newvol, const char *name){
00217   int newvolmolid = app->molecule_new(name,0,1);
00218   
00219   app->molecule_add_volumetric(newvolmolid, "density newvol", newvol->origin, 
00220                               newvol->xaxis, newvol->yaxis, newvol->zaxis, newvol->xsize, newvol->ysize, 
00221                               newvol->zsize, newvol->data);
00222   app->molecule_set_style("Isosurface");
00223   app->molecule_addrep(newvolmolid);
00224   
00225   return newvolmolid;
00226 }
00227 
00228 
00229 void vol_com(VolumetricData *vol, float *com){
00230   float ix,iy,iz;
00231   
00232   vec_zero(com);
00233   double mass = 0.0;
00234 
00235   for (int i = 0; i < vol->gridsize(); i++) {
00236     float m = vol->data[i];
00237     vol->voxel_coord(i, ix, iy, iz);
00238     float coord[3] = {ix,iy,iz};    
00239     mass = mass+m;
00240     vec_scale(coord, m, coord);
00241     vec_add(com, com, coord); 
00242      
00243   }
00244   
00245   float scale = 1.0f/float(mass);
00246   vec_scale(com, scale, com);
00247 }
00248 
00249 void add(VolumetricData *mapA, VolumetricData  *mapB, VolumetricData *newvol, bool interp, bool USE_UNION) {
00250 
00251   // adding maps by spatial coords is slower than doing it directly, but
00252   // allows for precisely subtracting unaligned maps, and/or maps of
00253   // different resolutions
00254 
00255   if ( USE_UNION) {
00256     // UNION VERSION
00257     init_from_union(mapA, mapB, newvol);
00258   } else {
00259     // INTERSECTION VERSION
00260     init_from_intersection(mapA, mapB, newvol);
00261   }
00262   for (long i=0; i<newvol->gridsize(); i++){
00263     float x, y, z;
00264     newvol->voxel_coord(i, x, y, z);
00265 
00266     if (interp) newvol->data[i] = \
00267       mapA->voxel_value_interpolate_from_coord_safe(x,y,z) + \
00268       mapB->voxel_value_interpolate_from_coord_safe(x,y,z);
00269     else newvol->data[i] = \
00270       mapA->voxel_value_from_coord_safe(x,y,z) + \
00271       mapB->voxel_value_from_coord_safe(x,y,z);
00272   } 
00273 
00274 }
00275 
00276 void subtract(VolumetricData *mapA, VolumetricData  *mapB, VolumetricData *newvol, bool interp, bool USE_UNION) {
00277 
00278   // adding maps by spatial coords is slower than doing it directly, but
00279   // allows for precisely subtracting unaligned maps, and/or maps of
00280   // different resolutions
00281 
00282   if ( USE_UNION) {
00283     // UNION VERSION
00284     init_from_union(mapA, mapB, newvol);
00285   } else {
00286     // INTERSECTION VERSION
00287     init_from_intersection(mapA, mapB, newvol);
00288   }
00289   for (long i=0; i<newvol->gridsize(); i++){
00290     float x, y, z;
00291     newvol->voxel_coord(i, x, y, z);
00292 
00293     if (interp) newvol->data[i] = \
00294       mapA->voxel_value_interpolate_from_coord_safe(x,y,z) - \
00295       mapB->voxel_value_interpolate_from_coord_safe(x,y,z);
00296     else newvol->data[i] = \
00297       mapA->voxel_value_from_coord_safe(x,y,z) - \
00298       mapB->voxel_value_from_coord_safe(x,y,z);
00299   } 
00300 
00301 }
00302 
00303 void multiply(VolumetricData *mapA, VolumetricData  *mapB, VolumetricData *newvol, bool interp, bool USE_UNION) {
00304 
00305   // adding maps by spatial coords is slower than doing it directly, but
00306   // allows for precisely subtracting unaligned maps, and/or maps of
00307   // different resolutions
00308 
00309   if ( USE_UNION) {
00310     // UNION VERSION
00311     init_from_union(mapA, mapB, newvol);
00312     for (long i=0; i<newvol->gridsize(); i++){
00313       float x, y, z;
00314       float voxelA, voxelB;
00315       newvol->voxel_coord(i, x, y, z);
00316       if (interp) {
00317         voxelA = mapA->voxel_value_interpolate_from_coord(x,y,z);
00318         voxelB = mapB->voxel_value_interpolate_from_coord(x,y,z);
00319       } else {
00320         voxelA = mapA->voxel_value_from_coord(x,y,z);
00321         voxelB = mapB->voxel_value_from_coord(x,y,z);
00322       }
00323 
00324      if (!myisnan(voxelA) && !myisnan(voxelB))
00325        newvol->data[i] = voxelA * voxelB;
00326      else if (!myisnan(voxelA) && myisnan(voxelB))
00327        newvol->data[i] = voxelA;
00328      else if (myisnan(voxelA) && !myisnan(voxelB))
00329        newvol->data[i] = voxelB;
00330      else
00331        newvol->data[i] = 0.;
00332     }
00333   } else {
00334     // INTERSECTION VERSION
00335     init_from_intersection(mapA, mapB, newvol);
00336     
00337     for (long i=0; i<newvol->gridsize(); i++){
00338       float x, y, z;
00339       newvol->voxel_coord(i, x, y, z);
00340 
00341       if (interp) newvol->data[i] = \
00342         mapA->voxel_value_interpolate_from_coord(x,y,z) * \
00343         mapB->voxel_value_interpolate_from_coord(x,y,z);
00344       else newvol->data[i] = \
00345         mapA->voxel_value_from_coord(x,y,z) * \
00346         mapB->voxel_value_from_coord(x,y,z);
00347     } 
00348 
00349   }
00350 }
00351 
00352 void average(VolumetricData *mapA, VolumetricData  *mapB, VolumetricData *newvol, bool interp, bool USE_UNION) {
00353 
00354   // adding maps by spatial coords is slower than doing it directly, but
00355   // allows for precisely subtracting unaligned maps, and/or maps of
00356   // different resolutions
00357 
00358   if ( USE_UNION) {
00359     // UNION VERSION
00360     init_from_union(mapA, mapB, newvol);
00361   } else {
00362     // INTERSECTION VERSION
00363     init_from_intersection(mapA, mapB, newvol);
00364   }
00365   for (long i=0; i<newvol->gridsize(); i++){
00366     float x, y, z;
00367     newvol->voxel_coord(i, x, y, z);
00368 
00369     if (interp) newvol->data[i] = \
00370       (mapA->voxel_value_interpolate_from_coord_safe(x,y,z) + \
00371        mapB->voxel_value_interpolate_from_coord_safe(x,y,z))*0.5f;
00372     else newvol->data[i] = \
00373       (mapA->voxel_value_from_coord_safe(x,y,z) + \
00374        mapB->voxel_value_from_coord_safe(x,y,z))*0.5f;
00375   } 
00376 
00377 }
00378 
00379 void vol_moveto(VolumetricData *vol, float *com, float *pos){
00380   float origin[3] = {0.0, 0.0, 0.0};
00381   origin[0] = (float)vol->origin[0];
00382   origin[1] = (float)vol->origin[1];
00383   origin[2] = (float)vol->origin[2];
00384 
00385   float transvector[3];
00386   vec_sub(transvector, pos, com);
00387   vec_add(origin, origin, transvector);   
00388   vol->origin[0] = origin[0];
00389   vol->origin[1] = origin[1];
00390   vol->origin[2] = origin[2];
00391 }
00392 /*
00393 void vectrans(float *npoint, float *mat, double *vec){
00394   npoint[0]=vec[0]*mat[0]+vec[1]*mat[4]+vec[2]*mat[8];
00395   npoint[1]=vec[0]*mat[1]+vec[1]*mat[5]+vec[2]*mat[9];
00396   npoint[2]=vec[0]*mat[2]+vec[1]*mat[6]+vec[2]*mat[10];
00397 }
00398 */
00399 void vol_move(VolumetricData *vol,  float *mat){
00400   float origin[3] = {0.0, 0.0, 0.0};
00401   origin[0] = (float)vol->origin[0];
00402   origin[1] = (float)vol->origin[1];
00403   origin[2] = (float)vol->origin[2];
00404  
00405   float transvector[3] = {mat[12], mat[13], mat[14]};
00406  
00407   float npoint[3];
00408   
00409   //deal with origin transformation
00410   //vectrans  
00411   npoint[0]=origin[0]*mat[0]+origin[1]*mat[4]+origin[2]*mat[8];
00412   npoint[1]=origin[0]*mat[1]+origin[1]*mat[5]+origin[2]*mat[9];
00413   npoint[2]=origin[0]*mat[2]+origin[1]*mat[6]+origin[2]*mat[10];
00414 
00415   vec_add(origin, npoint, transvector);
00416   vol->origin[0] = origin[0]; 
00417   vol->origin[1] = origin[1]; 
00418   vol->origin[2] = origin[2]; 
00419      
00420   //deal with delta transformation
00421   double deltax[3] = {vol->xaxis[0],vol->xaxis[1],vol->xaxis[2]};
00422   double deltay[3] = {vol->yaxis[0],vol->yaxis[1],vol->yaxis[2]};
00423   double deltaz[3] = {vol->zaxis[0],vol->zaxis[1],vol->zaxis[2]};
00424  
00425   float npointx[3];
00426   float npointy[3];
00427   float npointz[3];
00428   vectrans(npointx, mat, deltax);
00429   vectrans(npointy, mat, deltay);
00430   vectrans(npointz, mat, deltaz);
00431   
00432   for (int i = 0; i<3; i++){
00433     vol->xaxis[i] = npointx[i];
00434     vol->yaxis[i] = npointy[i];
00435     vol->zaxis[i] = npointz[i];
00436   }
00437 
00438 }
00439 
00443 void histogram( VolumetricData *vol, int nbins, long *bins, float *midpts) {
00444   //get minmax values of map
00445   float min, max;
00446   vol->datarange(min, max);
00447   // Calculate the width of each bin
00448   double binwidth = (max-min)/nbins;
00449   //precompute inverse
00450   double binwidthinv = 1/binwidth;
00451   // Allocate array that will contain the number of voxels in each bin
00452   //int *bins = (int*) malloc(nbins*sizeof(int));
00453   memset(bins, 0, nbins*sizeof(long));
00454 
00455   // Calculate histogram
00456   for (long i=0; i<vol->gridsize(); i++) 
00457     bins[long((vol->data[i]-min)*binwidthinv)]++;
00458   
00459   for (int j = 0; j < nbins; j++)
00460     midpts[j] = float(min + (0.5f*binwidth) + (j*binwidth));
00461 }
00462 
00464 int write_file(VMDApp *app, Molecule *volmol, int volid, const char* filename) {
00465   
00466   /* Get the extension */
00467   // XXX - casting to avoid a compilation error on SOLARIS. 
00468   // char* ext = strrchr(filename, '.');
00469   char* ext = (char *)strrchr(filename, '.');
00470 //  if (!ext) {
00471 //    fprintf(stderr, "Couldn't find an extension in the filename '%s'\n", filename);
00472 //    return NULL;
00473 //  }
00474 
00475   ext = &ext[1];
00476   
00477   vmdplugin_t *p = app->get_plugin("mol file reader", ext);
00478   
00479   if (!p) {
00480     p = app->get_plugin("mol file converter", ext);
00481   }
00482   
00483   MolFilePlugin *plugin = NULL;
00484   
00485   if (p) {
00486     plugin = new MolFilePlugin(p);
00487   }
00488   
00489   if (plugin != NULL){
00490     int natom = 0;
00491     int filehandler = plugin->init_write(filename, natom);
00492     if (filehandler == MOLFILE_ERROR) {
00493       return 0;
00494     }
00495     
00496     plugin->write_volumetric(volmol, volid);
00497   }
00498   
00499   return 1;
00500 
00501 }

Generated on Fri Oct 11 02:44:58 2024 for VMD (current) by doxygen1.2.14 written by Dimitri van Heesch, © 1997-2002