home *** CD-ROM | disk | FTP | other *** search
/ RBBS in a Box Volume 1 #3.1 / RBBSIABOX31.cdr / trac / trace1.c < prev    next >
Text File  |  1990-09-29  |  18KB  |  657 lines

  1. /*************************************************************************
  2. **                                    **
  3. **    TRACE1.C    A wee little ray-tracing program for IBM-XT,    **
  4. **            Cubicomp CS-5, and Microsoft-C.            **
  5. **                                    **
  6. **    K.A.Bjorke    10 July 1984                    **
  7. **    (C) 1984    5th Generation Digital Research            **
  8. **                                    **
  9. *************************************************************************/
  10.  
  11. /*
  12. .fo             /* TRACE1.C Page # */
  13. (dot commands in some comments for listing with wordstar)
  14. */
  15.  
  16. #include <stdio.h>
  17. #include <conio.h>    /* All I/O current thru monitors */
  18.  
  19. #define VERSION "1.00"
  20.  
  21. #define LIGHTSOURCE 1    /* enumerated object types */
  22. #define GLASS 2
  23. #define PLANE 3
  24.  
  25. #define MAXLITES 4    /* maximum Number of each enumerated type above */
  26. #define MAXBALLS 6
  27. #define MAXPOLYS 8
  28.  
  29. #define NULL 0
  30. #define FALSE 0
  31. #define TRUE 1
  32. #define ZERO 0.0
  33.  
  34. #define AIR 0.985    /* per-unit transmission in open air (close to 1) */
  35. #define INFINITY 65565    /* maximum scene-space value */
  36. #define MAXLEVEL 10    /* maximum levels of recursive tracing */
  37. #define BRILLIANCE transmit    /* to aid in "cheating" */
  38. #define BACKGROUND ZERO    /* intensity if there's nothing there */
  39.  
  40. #define XREG 0x0300    /* Cubicomp Control-Register Ports */
  41. #define YREG 0x0302
  42. #define DATA 0x0304
  43. #define RED 0x0306
  44. #define GREEN 0x0308
  45. #define BLUE 0x030A
  46. #define CONTROL 0x030C
  47. #define COMMAND 0x030E
  48.  
  49. #define MAXSCAN 512    /* miscellaneous Machine Equates */
  50. #define CENTER (MAXSCAN/2)
  51. #define MAXGREY 256    /* Number of gray scales for monochrome picture */
  52.  
  53. /*
  54. .pa 
  55. */
  56.  
  57. /*********************************
  58. **     External Functions     **
  59. *********************************/
  60.  
  61. extern sqrt();    /* pointer to float as argument!! */
  62.  
  63. /*****************************************
  64. **     Global structures & types     **
  65. *****************************************/
  66.  
  67. /* typedef int (*PFI)(); */       /* used in plane structure definition */
  68. typedef float VECTOR[3];       /* used a lot */
  69.  
  70. struct ball {
  71.     int center[3];
  72.     int radius, r2;           /* r2 = radius * radius */
  73.     float k, spec;           /* refract, reflect */
  74.     float transmit;           /* cheat and use transmit as intensity */
  75. } sphere[MAXBALLS], light[MAXLITES];
  76.  
  77. struct beam {
  78.     VECTOR origin;
  79.     VECTOR direction;
  80.     float ac, bc, cc, dc;       /* cc & dc are PARTIAL quadratic terms */
  81. };
  82.  
  83. struct plane {
  84.     float aco, bco, cco, dco;  /* as in ax+by+cz+d=0 */
  85.     float diff;           /* assume only diffuse reflections */
  86.     VECTOR normal;           /* normal-to-plane */
  87.      /*    PFI style; */           /* function which would define bounds */
  88. } poly[MAXPOLYS];
  89.  
  90. struct strike {
  91.     float t;
  92.     int type;
  93.     int index;
  94. };
  95.  
  96. /************************************
  97. **     Other Global Variables        **
  98. ************************************/
  99.  
  100. VECTOR viewpoint;            /* camera position */
  101. int lightsnow, ballsnow, polysnow;    /* how many for this pic? */ 
  102. float intamb;                /* ambient light */
  103. float greys[MAXSCAN+1];            /* used for anti-aliasing */
  104. /* FILE *fpt; */            /* general-purpose file pointer */
  105. /* int _fmode = 0x8000;    */        /* binary file mode */
  106. char title[32];                /* title for picture */
  107. int px, py;                /* current screen coordinates */
  108. /*
  109. .pa 
  110. */
  111.  
  112. /*********************************
  113. **                **
  114. **    Code Begins Here    **
  115. **                **
  116. *********************************/
  117.  
  118. main(argc,argv)
  119.     int argc;
  120.     char *argv[];
  121. {
  122.     advertise();        /* (C) 1984 */
  123.     if (argc != 1)
  124.         tutor();    /* arguments? what? */
  125.     /* else... */
  126.     read_script();        /* set up scene */
  127.     grey_map();        /* create lookup table */
  128.     init_screen();        /* clear screen and set for continuous write */
  129.     draw_pic();        /* trace like crazy */
  130. }
  131.  
  132. /***********************
  133. ** Vector dot product **
  134. ***********************/
  135. float dotprod(a,b)
  136.     float *a, *b;
  137. {
  138.     int i;
  139.     float q;
  140.     q = ZERO;
  141.     for (i = 0; i < 3; ++i)
  142.         q += (*a++ * (*b++));
  143.     return (q);
  144. }
  145.  
  146. /*
  147. .pa 
  148. */
  149.  
  150. /*********************************
  151. ** Recursive Ray-Trace Function **
  152. *********************************/
  153. float trace_ray(zap, medium, level)
  154.     struct beam *zap; 
  155.     float medium;
  156.     int level;
  157. {
  158.     int i, j, m, ct;
  159.     float med2, result, falloff;
  160.     VECTOR util;        /* utility vector */
  161.     struct strike intersect[MAXLITES+MAXBALLS+MAXPOLYS+1];
  162.     struct beam newray;    /* created by GLASS objects */
  163.  
  164.     result = ZERO;
  165.     if ((--level == 0) || (medium == ZERO))
  166.         return(result);        /* to prevent endless reflections */
  167.                     /* & skip opaque objects */
  168.     ct = 0;
  169.     prepare_beam(zap);
  170.     blank_intersects(&intersect, (MAXLITES+MAXBALLS+MAXPOLYS));
  171.         /* now, search for intersections with all objects... */
  172.     for (i = 0; i < lightsnow; ++i) {
  173.         if (cross_ball(&light[i], zap, &intersect[ct].t) != FALSE) {
  174.             intersect[ct].index = i;
  175.             intersect[ct++].type = LIGHTSOURCE;
  176.         }
  177.     }
  178.     for (i = 0; i < ballsnow; ++i) {
  179.         if (cross_ball(&sphere[i], zap, &intersect[ct].t) != FALSE) {
  180.             intersect[ct].index = i;
  181.             intersect[ct++].type = GLASS;
  182.         }
  183.     }
  184.     for (i = 0; i < polysnow; ++i) {
  185.         if (cross_poly(&poly[i], zap, &intersect[ct].t) != FALSE) {
  186.             intersect[ct].index = i;
  187.             intersect[ct++].type = PLANE;
  188.         }
  189.     }
  190.     if (ct == 0)
  191.         return((result = BACKGROUND));        /* nuthin */
  192.     /* else... */
  193.     i = sort_by_t(&intersect);        /* now pick nearest */
  194.     j = intersect[i].index;            /* for convenience */
  195.     falloff = medium / intersect[i].t;    /* this can get used a lot */
  196. /* [Continued]
  197. .pa 
  198. */
  199.  
  200.     if (intersect[i].type == LIGHTSOURCE) {   /* simplest case */
  201.         result = light[j].BRILLIANCE * falloff;
  202.     } else if (intersect[i].type == GLASS) {
  203.             /* going in or out of sphere? */
  204.         med2 = (sphere[j].transmit == medium) ?
  205.             AIR : sphere[j].transmit;
  206.             /* calculate REFRACTION */
  207.         for (m = 0; m < 3; ++m) 
  208.             util[m] = sphere[j].center[m] -
  209.                     (newray.origin[m] = (zap->origin[m] + 
  210.                     intersect[i].t * (zap->direction[m])));
  211.         normalize(util);    /* used as normal vector */
  212.             /* note normal goes INTO the sphere */
  213.             /* this is because zap does, too */
  214.         for (m = 0; m < 3; ++m)
  215.             newray.direction[m] = zap->direction[m] + 
  216.                           sphere[j].k * util[m];
  217.                 /* deflect newray in direction of normal */
  218.         result = trace_ray(&newray, med2, level) * falloff;
  219.  
  220.             /* now add REFLECTION */
  221.         med2 = 2 * dotprod(util, &zap->direction[0]); 
  222.                 /* N.L = cos(angle_of_incidence) */
  223.         for (m = 0; m < 3; ++m)
  224.             newray.direction[m] = zap->direction[m] - 
  225.                           util[m] * med2;
  226.                 /* for regular vectors, incid + reflect = */
  227.                 /* normal * 2 cos(angle_of_incidence) */
  228.                 /* note reversal of signs! */
  229.         result += trace_ray(&newray, medium, level) * 
  230.                sphere[j].spec * falloff;
  231.     } else {        /* must be a plane, then */
  232.         result = intamb * poly[j].diff * falloff;
  233.         for (m = 0; m < 3; ++m)
  234.             util[m] = zap->origin[m] + 
  235.                     intersect[i].t * zap->direction[m];
  236.         for (m = 0; m < lightsnow; ++m) {
  237.             visible(m, i, util, medium, falloff, &result);
  238.         }
  239.     }
  240.     return(result);
  241. }
  242.  
  243. /*
  244. .pa
  245. */
  246.  
  247. /************************
  248. ** Actual drawing loop **
  249. ************************/
  250. draw_pic()
  251. {
  252.     int i;
  253.     struct beam laser;        /* to stress point sampling */
  254.     float intensity, prev, avg;
  255.  
  256.     cprintf("\n\n\n\nNow Drawing %s.\n",title);
  257.     for (i = 0; i < 3; ++i)
  258.         laser.origin[i] = viewpoint[i];        /* consistent */
  259.     for (py = 0; py <= MAXSCAN; ++py) {        /* scan rows */
  260.         for (px = 0; px <= MAXSCAN; ++px) {    /* scan columns */
  261.             prev = intensity;
  262.             laser.direction[0] = px - CENTER;
  263.             laser.direction[1] = py - CENTER;
  264.             laser.direction[2] = -viewpoint[2];
  265.                 /* now do all the real work: */
  266.             intensity = trace_ray(&laser, AIR, MAXLEVEL);
  267.             if ((px != 0) && (py != 0)) {
  268.                 avg = (intensity + prev + greys[px] +
  269.                        greys[px - 1]) / 4;
  270.                 if (avg > (MAXGREY-1))
  271.                     avg = (MAXGREY-1);
  272.                 write_pixel((px-1), (py-1), (int)avg);
  273.                     /* avg is anti-aliased */
  274.             }
  275.             greys[((px == 0) ? MAXSCAN : (px-1))] = prev;
  276.         }
  277.     }
  278. }
  279.  
  280. /*
  281. .pa 
  282. */
  283.  
  284. /***********************************************************************
  285. ** find out if a lightsource is visible from some point and add it in **
  286. ***********************************************************************/
  287. int visible(i,f,point, medium, falloff, value)
  288.     int i,f;            /* light & poly indices */
  289.     float point[];            /* point of incidence */
  290.     float medium;            /* local transmission value */
  291.     float falloff;            /* falloff to origin of first ray */
  292.     float *value;            /* cumulative intensity */
  293. {
  294.     int m, j, ct;
  295.     float dist;
  296.     struct strike testint[MAXBALLS+MAXPOLYS+1];
  297.     struct beam testray;
  298.         /* we only have to check intersects of light-stoppers */
  299.  
  300.     ct = 0;
  301.     blank_intersects(testint,MAXLITES);
  302.         /* point testray at light[i] */
  303.     for (m = 0; m < 3; ++m) {
  304.         testray.origin[m] = point[m];
  305.         testray.direction[m] = light[i].center[m] - point[m];
  306.     }
  307.     prepare_beam(&testray);
  308.     cross_ball(&light[i], &testray, &dist);    /* get distance to light */
  309.     for (j = 0; j < ballsnow; ++j) {
  310.         if (cross_ball(&sphere[j], &testray, &testint[ct].t) != NULL) {
  311.             testint[ct].index = j;
  312.             testint[ct++].type = GLASS;
  313.         }
  314.     }
  315.     for (j = 0; j < polysnow; ++j) {
  316.         if (cross_poly(&poly[j], &testray, &testint[ct].t) != NULL) {
  317.             testint[ct].index = j;
  318.             testint[ct++].type = PLANE;
  319.         }
  320.     }
  321.         /* if (ct == 0) then trivial accept -- all clear */
  322.     if (ct != 0) {
  323.         j = sort_by_t(testint);
  324.         if (testint[j].t < dist)
  325.             return(FALSE);    /* something between us and light */
  326.     }
  327.     /* light is visible, so... */
  328.     /* Lambert: ir = ia*ka+ip*kd(L.N) */
  329.     *value += (light[i].BRILLIANCE * poly[f].diff * 
  330.           dotprod(&poly[f].normal[0], &testray.direction[0]) *
  331.           medium / dist) * falloff;
  332.                /* note no assumption of AIR made */
  333.     return(TRUE);
  334. }
  335.  
  336. /*
  337. .pa
  338. */
  339.  
  340. /**********************************************************
  341. ** Normalize direction vector & Pre-calculate Quadratics **
  342. **********************************************************/
  343. prepare_beam(illum)
  344.     struct beam *illum;
  345. {
  346.     int i;
  347.     illum->ac = illum->bc = illum->cc = 0;
  348.     normalize(&illum->direction[0]);
  349.     for (i = 0; i < 3; ++i) {
  350.         illum->ac += illum->direction[i] * illum->direction[i];
  351.         illum->bc += illum->direction[i] * illum->origin[i];
  352.         illum->cc += illum->origin[i] * illum->origin[i];
  353.     }
  354.     illum->bc *= 2;    /* quadratics will be the same for every ball struc */
  355.     illum->ac *= 2;    /* only change in equation will be center[] & r2... */
  356.     illum->dc = illum->bc * illum->bc;
  357. }
  358.  
  359. /*
  360. .pa
  361. */
  362.  
  363. /*********************************************************************
  364. ** calculate ray-ball intersections - return TRUE if any valid ones **
  365. *********************************************************************/
  366. int cross_ball(globe, ray, t1)
  367.     struct ball *globe;
  368.     struct beam *ray;
  369.     float *t1;
  370. {
  371.     float c, d, t2;
  372.     c = ray->cc - globe->r2;
  373.     d = ray->dc - 2 * ray->ac * c;    /* quadratic determinant */
  374.     if (d < 0)
  375.         return (FALSE);        /* no real solutions */
  376.     /* else... */
  377.     if (d == 0) {            /* one real solution */
  378.         *t1 = -(ray->bc / ray->ac);
  379.         /* return ( (*t1 <= 0) ? FALSE : TRUE); */
  380.         if (*t1 <= 0)
  381.             return(FALSE);    /* but in the wrong direction */
  382.         /* else... */
  383.         return(TRUE);        /* if it's okay */
  384.     }
  385.     /* else two real solutions */
  386.     sqrt(&d);            /* now in the form: */
  387.                     /* - b +- sqrt(d)/(2*a) */
  388.     *t1 = -(ray->bc + d) / ray->ac;
  389.     t2  = -(ray->bc - d) / ray->ac;
  390.     if ((t2 <= 0) && (*t1 <= 0))
  391.         return(FALSE);        /* both behind current position */
  392.     if (t2 <= 0)
  393.         return(TRUE);           /* t1 must be okay */
  394.     *t1 = (*t1 <= 0) ? t2 :           /* t2 must be okay */
  395.                    min(*t1, t2);   /* else just take the closer one */
  396.     return(TRUE);
  397. }
  398.  
  399. /*
  400. .pa
  401. */
  402.  
  403. /***********************************************************************
  404. ** calculate ray-plane intersections - return TRUE if valid one found **
  405. ***********************************************************************/
  406. int cross_poly(flat, ray, t)
  407.     struct plane *flat;
  408.     struct beam *ray;
  409.     float *t;
  410. {
  411.     float dt;
  412.     dt = flat->aco * ray->direction[0] +
  413.          flat->bco * ray->direction[1] +
  414.          flat->cco * ray->direction[2];
  415.     if (dt == 0)
  416.         return (FALSE);        /* ray and plane are parallel */
  417.     /* else */
  418.     /* simple linear math */
  419.     *t = -( flat->aco * ray->origin[0] +
  420.         flat->bco * ray->origin[1] +
  421.         flat->cco * ray->origin[2] +
  422.         flat->dco) / dt;
  423.     return(TRUE);
  424. }
  425.  
  426. /*
  427. .pa 
  428. */
  429.  
  430. /**************************
  431. ** Normalize a 3d vector **
  432. **************************/
  433. normalize(vect)
  434.     float vect[];
  435. {
  436.     int i;
  437.     float magnitude;
  438.     magnitude = 0;
  439.     for (i = 0; i <3; ++i)
  440.         magnitude += vect[i] * vect[i];
  441.     sqrt(&magnitude);    /* length of [a b c] = sqrt(a^2 + b^2 + c^2) */
  442.     for (i = 0; i < 3; ++i)
  443.         vect[i] /= magnitude;
  444. }
  445.  
  446. /*
  447. .pa
  448. */
  449.  
  450. /********************************************
  451. ** find the nearest intersection in a list **
  452. ********************************************/
  453. int sort_by_t(intersect)
  454.     struct strike intersect[];
  455. {
  456.     int i, j;
  457.     float t2;
  458.     t2 = INFINITY;
  459.     for (i = 0; intersect[i].type != NULL; ++i)
  460.         j = (intersect[i].t < t2) ? i : j;
  461.     return(j);
  462. }
  463.  
  464. /*********************************************************
  465. ** blank out types in an intersection list (initialize) **
  466. *********************************************************/
  467. blank_intersects(intersect, top)
  468.     struct strike intersect[];
  469.     int top;
  470. {
  471.     int i;
  472.     for (i = 0; i <= top; ++i)
  473.         intersect[i].type = NULL;
  474. }
  475.  
  476. /*******************************************
  477. ** set up the Cubicomp screen for drawing **
  478. *******************************************/
  479. init_screen()
  480. {
  481.     outw(CONTROL, 0x0000);        /* frame 0, no masks, normal */
  482.     outw(DATA, 0);
  483.     outp(CONTROL, 0x0010);        /* clear screen to black */
  484.     while ((inp(CONTROL+1) && 0x0080) == NULL);     /* wait for ready */
  485.     outw(XREG, 0);
  486.     outw(YREG, 0);        /* point to upper-left corner for counting */
  487. }
  488.  
  489. /*******************************************************
  490. ** write a value to the current pixel & count forward **
  491. *******************************************************/
  492. write_pixel(x, y, value)
  493.     int x, y, value;    /* this routine actually throws away x & y */
  494.                 /* since the Cubicomp auto-increments */
  495. {
  496.     outw(DATA, value);
  497.     outp(CONTROL, 0x0021);    /* write and increment */
  498. }
  499.  
  500. /*
  501. .pa
  502. */
  503.  
  504. /*****************************************
  505. ** Make a grey-scale map, 0-(MAXGREY-1) **
  506. *****************************************/
  507. grey_map()
  508. {
  509.     int i;
  510.     for (i = 0; i < MAXGREY; ++i) {
  511.         outp(RED, i);            /* same value to all regs */
  512.         outp(GREEN, i);
  513.         outp(BLUE, i);
  514.         outw(DATA, i);            /* and as table address */
  515.         outp(CONTROL, 4);        /* Map-write */
  516.         while((inp(CONTROL+1) && 0x0080) == NULL);    /* wait... */
  517.     }
  518. }
  519.  
  520. /********************************************************
  521. ** send a 16-bit value thru the IBM's 8-bit data lines **
  522. ********************************************************/
  523. outw(port,wrd)
  524.     int port, wrd;
  525. {
  526.     outp(port,(wrd & 0x00FF));
  527.     outp(++port,(wrd >> 8));
  528. }
  529.  
  530. /************************
  531. ** toot one's own horn **
  532. ************************/
  533. advertise()
  534. {
  535.     int i;
  536.     for (i = 0; i < 24; ++i)
  537.         putch('\n',stderr);    /* crude clear-monitor */
  538.     cputs("Trace1\tVersion ");
  539.     cputs(VERSION);
  540.     cputs("\tK. A. Bjorke\n");
  541.     cputs("(C) 1984 5th Generation Digital\n\n");
  542. }
  543.  
  544. /*
  545. .pa
  546. */
  547.     
  548. /**********************************
  549. ** respond to a bad command-line **
  550. **********************************/
  551. tutor()
  552. {
  553.     cputs("Correct Command Line Format:\n");
  554.     cputs("\tTRACE1 [No Args]\n");
  555.     exit(1);
  556. }
  557.  
  558. /*****************************************************
  559. ** Read script file (from keyboard in this version) **
  560. *****************************************************/
  561. read_script()
  562. {
  563.     int i, j;
  564.     char xyz[3];
  565.     xyz[0] = 'X'; xyz[1] = 'Y'; xyz[2] = 'Z';
  566.     lightsnow = ballsnow = polysnow = 0;
  567.     cputs("Enter Title of Image: ");
  568.     cgets(title);
  569.     cputs("\nEnter \'Camera\' Viewpoint as XYZ triplet:\n");
  570.     for (j = 0; j < 3; ++j) {
  571.         putch(xyz[j]);
  572.         cputs(": ");
  573.         cscanf("%d", &viewpoint[j]);
  574.     }
  575.     cputs("\nEnter the Intensity of Ambient Light: ");
  576.     cscanf("%f",intamb);
  577.     cputs("\nNumber of Light Sources: ");
  578.     cscanf("%d", &lightsnow);
  579.     for (i = 0; i < lightsnow; ++i) {
  580.         cprintf("\n\nLIGHT SOURCE %1d:",(i + 1));
  581.         cputs("\nEnter Center Point as XYZ triplet:\n");
  582.         for (j = 0; j < 3; ++j) {
  583.             putch(xyz[j]);
  584.             cputs(": ");
  585.             cscanf("%d", &light[i].center[j]);
  586.         }
  587.         cputs("\nRadius: ");
  588.         cscanf("%d", &light[i].radius);
  589.         cputs("\nIntensity: ");
  590.         cscanf("%f", &light[i].BRILLIANCE);
  591.         light[i].r2 = light[i].radius * light[i].radius;
  592.     }
  593. /*        [Continued]
  594. .pa
  595. */
  596.     cputs("\nNumber of \'Glass\' Spheres: ");
  597.     cscanf("%d", &ballsnow);
  598.     for (i = 0; i < lightsnow; ++i) {
  599.         cprintf("\n\nSPHERE %1d:",(i + 1));
  600.         cputs("\nEnter Center Point as XYZ triplet:\n");
  601.         for (j = 0; j < 3; ++j) {
  602.             putch(xyz[j]);
  603.             cputs(": ");
  604.             cscanf("%d", &sphere[i].center[j]);
  605.         }
  606.         cputs("\nRadius: ");
  607.         cscanf("%d", &sphere[i].radius);
  608.         cputs("\nIndex of Reflection: ");
  609.         cscanf("%f", &sphere[i].spec);
  610.         cputs("\nIndex of Refraction: ");
  611.         cscanf("%f", &sphere[i].k);
  612.         cputs("\nTransmission per unit of material: ");
  613.         cscanf("%f", &sphere[i].transmit);
  614.         sphere[i].r2 = sphere[i].radius * light[i].radius;
  615.     }
  616.     cputs("\n\nEnter Number of Arbitrary Planes: ");
  617.     cscanf("%d",&polysnow);
  618.     for (i = 0; i < polysnow; ++i) {
  619.         cprintf("\n\nPLANE %1d\n",(i + 1));
  620.         cputs("\tEnter coefficients of the form:\n");
  621.         cputs("\t  Ax + By + Cz + D = 0"); 
  622.         cputs("\nA: ");
  623.         cscanf("%f", &poly[i].aco);
  624.         cputs("\nB: ");
  625.         cscanf("%f", &poly[i].bco);
  626.         cputs("\nC: ");
  627.         cscanf("%f", &poly[i].cco);
  628.         cputs("\nD: ");
  629.         cscanf("%f", &poly[i].dco);
  630.         cputs("\n Plane Grey-Scale Value: ");
  631.         cscanf("%f", &poly[i].diff);
  632.         poly[i].normal[0] = poly[i].aco;
  633.         poly[i].normal[1] = poly[i].bco;
  634.         poly[i].normal[2] = poly[i].cco;
  635.         normalize(&poly[i].normal[0]);    /* precalculate normal */
  636.     }
  637. }
  638.  
  639. /*
  640. .pa
  641. */
  642.  
  643. /***************************
  644. ** If file not found, etc **
  645. ***************************/
  646. nofile(s)
  647.     char s[];
  648. {
  649.     cputs("Error! - Can't open ");
  650.     cputs(s);
  651.     cputs("!\n");
  652.     exit(2);
  653. }
  654.  
  655.  
  656. /* eof */
  657.