home *** CD-ROM | disk | FTP | other *** search
/ Club Amiga de Montreal - CAM / CAM_CD_1.iso / files / 543a.lha / Nebula / source.LZH / source / clip.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-06-10  |  14.5 KB  |  786 lines

  1. /*
  2.  
  3. ------------------------------------------------------------------
  4.  
  5. Black Nebula
  6.  
  7. File :                Clip.c
  8. Programmer:        Colin Adams
  9. Date:                23/4/91
  10. Last Modified :    10/6/91
  11.  
  12. ------------------------------------------------------------------
  13.  
  14. */
  15.  
  16. #include "3d.h"
  17. #include <proto/dos.h>
  18.  
  19. #define BOUNDARY_COUNT 4
  20. #define MAX_LIMIT 500
  21.  
  22.  
  23. extern short U_rot, W_rot, View_Angle;
  24. extern short num_active[];
  25.  
  26. static point s[BOUNDARY_COUNT], first_point[BOUNDARY_COUNT];
  27. static short new_edge[BOUNDARY_COUNT];
  28. static polygon *clip_poly;
  29. static object *sort_obj; /* global for sort */
  30. static point clip_point;
  31. static int Depth[TOTAL_MAX_OBJECTS]; /* array to sort in */
  32.  
  33. static int xp[3], yp[3], zp[3];
  34.  
  35. enum
  36. {
  37.     TOP,
  38.     RIGHT,
  39.     BOTTOM,
  40.     LEFT
  41. };
  42.  
  43. /*    ------------------------------------------------------------------
  44.         Fast Square Root Code for IEEE floating point numbers
  45.         from Graphics Gems, Academic Press 1990
  46.         
  47.         Supplied by Peter Stephenson
  48.         ------------------------------------------------------------------
  49. */
  50.  
  51. static short sqrttab[0x100];
  52.  
  53. void build_table(void)
  54. {
  55.     unsigned short i;
  56.     float f;
  57.     unsigned int *fi = (int *) &f;
  58.     
  59.     for(i=0; i<= 0x7f; i++)
  60.     {
  61.         /* build a float with the bit pattern i as
  62.         mantissa and an exponent of 0, stored as 127 */
  63.         
  64.         *fi = (i << 16) | ( 127 << 23);
  65.         f = sqrt(f);
  66.         
  67.         /* take the square root then strip the first 7 bits of the
  68.         mantissa into the table */
  69.         
  70.         sqrttab[i] = (*fi & 0x7fffff) >> 16;
  71.         
  72.         /* repeat the process, this time with an exponent of 1,
  73.         stored as 128 */
  74.         
  75.         *fi = (i << 16) | (128 << 23);
  76.         f = sqrt(f);
  77.         
  78.         sqrttab[i+0x80] = (*fi & 0x7fffff) >> 16;
  79.     }
  80. }
  81.  
  82. float fsqrt(float n)
  83. {
  84.     unsigned int *num = (int *) &n;
  85.     short e;
  86.     
  87.     if(n==0) return 0;
  88.     
  89.     e = (*num >> 23) - 127;
  90.     
  91.     *num &= 0x7fffff;
  92.     
  93.     if(e & 0x01)
  94.         *num |= 0x800000;
  95.         
  96.     e >>= 1;
  97.     
  98.     *num = ((sqrttab[*num >> 16]) << 16) | ((e + 127) << 23);
  99.     return n;
  100. }
  101.  
  102. /*    ------------------------------------------------------------------
  103.         Clipping Routines
  104.         
  105.         The clipping algorithm is Sutherland and Hodgeman's.  The
  106.         description of the algorithm is in Computer Graphics by
  107.         D. Hearn & M. Baker.
  108.         ------------------------------------------------------------------
  109. */
  110.  
  111. short inside(point *p, short edge)
  112. /* returns true if the point is inside the given edge, false if not */
  113. {
  114.     switch(edge)
  115.     {
  116.         case TOP:    /* top line */
  117.             if(p->y >= Y_MIN)
  118.                 return 1;
  119.             return 0;
  120.         case RIGHT:    /* right hand side */
  121.             if(p->x <= X_MAX)
  122.                 return 1;
  123.             return 0;
  124.         case BOTTOM:    /* bottom line */
  125.             if(p->y <= Y_MAX)
  126.                 return 1;
  127.             return 0;
  128.         case LEFT:    /* left hand side */
  129.             if(p->x >= X_MIN)
  130.                 return 1;
  131.             return 0;
  132.     }    
  133. }
  134.  
  135.  
  136. short cross(point *p, point *p2, short edge)
  137. /* returns true if the 2 points cross the given edge */
  138. {
  139.     /*
  140.     
  141.     This first bit is a slight hack to make points generated a mile
  142.     apart when on the other side of the eye, not appear! May or may
  143.     not be fixed.
  144.     
  145.     2/6/91
  146.     
  147.     It is now very unlikely to be fixed, as I don't think it
  148.     can be!!  But it seems to work...
  149.     
  150.     */
  151.  
  152.     if(abs(p->x) > MAX_LIMIT || abs(p2->x) > MAX_LIMIT ||
  153.         abs(p->y) > MAX_LIMIT || abs(p2->y) > MAX_LIMIT)
  154.         return 0;
  155.  
  156.     /* end of horrible speed reducing hack */
  157.     
  158.     switch(edge)
  159.     {
  160.         case TOP: /* top edge */
  161.             if(p->y > Y_MIN)
  162.             {
  163.                 if(p2->y <= Y_MIN)
  164.                     return 1;
  165.             }
  166.             else if(p2->y > Y_MIN)
  167.                 return 1;
  168.             return 0;
  169.             
  170.         case RIGHT: /* right hand edge */
  171.             if(p->x > X_MAX)
  172.             {
  173.                 if(p2->x <= X_MAX)
  174.                     return 1;
  175.             }
  176.             else if(p2->x > X_MAX)
  177.                 return 1;
  178.             return 0;
  179.         
  180.         case BOTTOM: /* bottom edge */
  181.             if(p->y > Y_MAX)
  182.             {
  183.                 if(p2->y <= Y_MAX)
  184.                     return 1;
  185.             }
  186.             else if(p2->y > Y_MAX)
  187.                 return 1;
  188.             return 0;
  189.     
  190.         case LEFT: /* left hand edge */
  191.             if(p->x > X_MIN)
  192.             {
  193.                 if(p2->x <= X_MIN)
  194.                     return 1;
  195.             }
  196.             else if(p2->x > X_MIN)
  197.                 return 1;
  198.             return 0;
  199.     }
  200. }
  201.  
  202. void find_intersection(point *p1, point *p2, short edge, point *i)
  203. /*
  204. Find intersection point of line, uses fixed point maths.  This routine
  205. is heavily optimized, requiring only 1 integer division and 1 integer
  206. multiplication to find the intersection!!!  If you know a faster
  207. method please tell me!
  208. */
  209. {
  210.     register int mnumer, mdenom;
  211.     
  212.     /* get gradient */
  213.     
  214.     mnumer = (p2->y - p1->y);
  215.     mdenom = (p2->x - p1->x);
  216.     
  217.     switch(edge)
  218.     {
  219.         case TOP:
  220.         {
  221.             register int j = Y_MIN - p1->y;
  222.             i->y = Y_MIN;
  223.             i->x = p1->x + (mnumer ? (j * mdenom) / mnumer : 0);
  224.             return;
  225.         }        
  226.         case RIGHT:
  227.         {
  228.             register int j = (X_MAX - p1->x);
  229.             i->x = X_MAX;
  230.             i->y = p1->y + (mdenom ? (mnumer*j)/mdenom : 0);
  231.             return;
  232.         }
  233.         case BOTTOM:
  234.         {
  235.             register int j = Y_MAX - p1->y;
  236.             i->y = Y_MAX;
  237.             i->x = p1->x + (mnumer ? (j * mdenom) / mnumer : 0);
  238.             return;
  239.         }        
  240.         case LEFT:
  241.         {
  242.             register int j = (X_MIN - p1->x);
  243.             i->x = X_MIN;
  244.             i->y = p1->y + (mdenom ? (mnumer*j)/mdenom : 0);
  245.             return;
  246.         }
  247.     }
  248. }
  249.  
  250. void clip_this(point *p, short edge)
  251. /* recursively checks a point against all the boundary edges */
  252. {
  253.     point i;
  254.     
  255.     if(new_edge[edge])
  256.     {
  257.         first_point[edge].x = p->x;
  258.         first_point[edge].y = p->y;
  259.         new_edge[edge] = 0;
  260.     }
  261.     else if(cross(p, &s[edge], edge))
  262.     {
  263.         find_intersection(p, &s[edge], edge, &i);
  264.         
  265.         if(edge < 3)
  266.             clip_this(&i, edge+1);
  267.         else
  268.         {
  269.             clip_poly->clip_num++;
  270.             clip_poly->clip_x[clip_poly->clip_num] = i.x;
  271.             clip_poly->clip_y[clip_poly->clip_num] = i.y;
  272.         }    
  273.     }
  274.     
  275.     s[edge].x = p->x;
  276.     s[edge].y = p->y;
  277.     
  278.     if(inside(p, edge))
  279.     {
  280.         if(edge < 3)
  281.             clip_this(p, edge+1);
  282.         else
  283.         {
  284.             clip_poly->clip_num++;
  285.             clip_poly->clip_x[clip_poly->clip_num] = p->x;
  286.             clip_poly->clip_y[clip_poly->clip_num] = p->y;
  287.         }
  288.     }
  289. }
  290.  
  291. void clip_closer(void)
  292. /*
  293. Closing routine. For each window edge, clips the line connecting
  294. the last saved vertex and the first_point processed against the
  295. edge
  296. */
  297. {
  298.     point i;
  299.     register short edge;
  300.     
  301.     for(edge=0; edge<BOUNDARY_COUNT; edge++)
  302.     {
  303.         if(cross(&s[edge], &first_point[edge], edge))
  304.         {
  305.             find_intersection(&s[edge], &first_point[edge],edge,&i);
  306.             
  307.             if(edge<3)
  308.                 clip_this(&i, edge+1);
  309.             else
  310.             {
  311.                 clip_poly->clip_num++;
  312.                 clip_poly->clip_x[clip_poly->clip_num] = i.x;
  313.                 clip_poly->clip_y[clip_poly->clip_num] = i.y;
  314.             }
  315.         }
  316.     }
  317. }
  318.  
  319. void polygon_clip(polygon *poly)
  320. /* clips the polygon sent against the boundaries */
  321. {
  322.     register int k;
  323.  
  324.     clip_poly = poly;            /* use a global for speed */
  325.     clip_poly->clip_num = -1; /* no points in output yet */
  326.         
  327.     for(k=0; k<4; k++)
  328.         new_edge[k] = 1;
  329.  
  330.     for(k=0; k<clip_poly->numpoints; k++)
  331.     {
  332.         clip_point.x = clip_poly->x[k];
  333.         clip_point.y = clip_poly->y[k];
  334.         clip_this(&clip_point, 0);
  335.     }
  336.     clip_closer(); /* close polygon */
  337.     clip_poly->clip_num++;
  338. }
  339.     
  340. /*    ------------------------------------------------------------------
  341.         Hidden Surface Routines
  342.         
  343.         Determines the order to draw objects, and polygons within
  344.         an object
  345.         ------------------------------------------------------------------
  346. */
  347.  
  348.  
  349. int SpinPolyCentre(object *obj, polygon *poly)
  350. /*
  351. Spin a point and calculate it's depth from the eye.  The point
  352. is the centre point of a polygon, and is used for hidden surface
  353. removal.  This routine should be pretty quick, but if you know a
  354. faster algorithm, I'd love to know it!
  355. */
  356. {
  357.     register int x,y,z;
  358.     register int xrot, yrot, zrot;
  359.         
  360.     xrot = obj->rot_x;
  361.     yrot = obj->rot_y;
  362.     zrot = obj->rot_z;
  363.         
  364.     x = poly->centre.x;
  365.     y = poly->centre.y;
  366.     z = poly->centre.z;
  367.  
  368.     /* rotate a point around the z axis */
  369.         
  370.     if(zrot)
  371.     {
  372.         register int temp, temp2, temp3;
  373.         temp = x;
  374.         temp2 = costable[zrot];
  375.         temp3 = sintable[zrot];
  376.             
  377.         x = ((temp2*temp)>>16) - ((temp3*y)>>16);
  378.         y = ((temp3*temp)>>16) + ((temp2*y)>>16);
  379.     }
  380.         
  381.     /* rotate a point around the x axis */
  382.         
  383.     if(xrot)
  384.     {
  385.         register int temp, temp2, temp3;
  386.         temp = y;
  387.         temp2 = costable[xrot];
  388.         temp3 = sintable[xrot];
  389.             
  390.         y = ((temp2*temp)>>16) - ((temp3*z)>>16);
  391.         z = ((temp3*temp)>>16) + ((temp2*z)>>16);
  392.     }
  393.         
  394.     /* rotate a point around the y axis */
  395.         
  396.     if(yrot)
  397.     {
  398.         register int temp, temp2, temp3;
  399.         temp = z;
  400.         temp2 = costable[yrot];
  401.         temp3 = sintable[yrot];
  402.             
  403.         z = ((temp2*temp)>>16) - ((temp3*x)>>16);
  404.         x = ((temp3*temp)>>16) + ((temp2*x)>>16);
  405.     }
  406.         
  407.     /* calculate depths */
  408.         
  409.     x = ex - (x + obj->trans_x);
  410.     y = ey - (y + obj->trans_y);
  411.     z = ez - (z + obj->trans_z);
  412.     
  413.     return (x*x + y*y + z*z);
  414. }    
  415.  
  416. void qsort_poly(int *left, int *right, int al, int ar)
  417. /*
  418. Quicksort for polygon depths within an object.  This can be written 
  419. faster.
  420. */
  421. {
  422.     register int *p = left, *q = right, x = *(left+(right-left)/2), w;
  423.     register int tar = ar, tal = al;
  424.     
  425.     do
  426.     {
  427.         while(*p>x)
  428.         {
  429.             p++; tal++;
  430.         }
  431.         
  432.         while(*q<x)
  433.         {
  434.             q--; tar--;
  435.         }
  436.             
  437.         if(p>q) break;
  438.  
  439.         w = *p;
  440.         *p = *q;
  441.         *q = w;
  442.         
  443.         {
  444.             register polygon *temp;
  445.             temp = sort_obj->draworder[tar];
  446.             sort_obj->draworder[tar] = sort_obj->draworder[tal];
  447.             sort_obj->draworder[tal] = temp;
  448.         }
  449.         
  450.         p++; q--;
  451.         tal++; tar--;
  452.         
  453.     } while (p<=q);
  454.  
  455.     if(left<q) qsort_poly(left, q, al, tar);
  456.     if(p<right) qsort_poly(p, right, tal, ar);
  457. }
  458.  
  459. void DepthSort(object *obj)
  460. /* sorts an object's polygons into depths from the eye to determine
  461. the order in which they are to be drawn */
  462. {
  463.     register polygon *pg = obj->poly;
  464.     register short count = 0;
  465.     register char oneobj = 0;
  466.     
  467.     if(!obj->poly->next)
  468.         oneobj = 1;
  469.     
  470.     while(pg)
  471.     {
  472.         obj->draworder[count] = pg;
  473.         
  474.         /* NB. as an optimization, the actual distance from the eye is
  475.         not stored but the square of it is, so the magnitude is
  476.         relevant! */
  477.  
  478.         /*
  479.         
  480.         Check for back faces, this is the only way to make my
  481.         hidden surface code work properly... I really didn't want to
  482.         add this, but doesn't seem to slow down the code much as
  483.         it doesn't draw/erase polygons which are back faces.
  484.         
  485.         Unfortunately it still sorts faces which aren't visible,
  486.         should be fixed!
  487.         
  488.         */
  489.         
  490.         if(pg->numpoints>2  && !oneobj)
  491.         {
  492.             register int i, A, B, C, D;
  493.             
  494.             for(i=0; i<3; i++)
  495.             {
  496.                 xp[i] = manipulate_x[pg->p[i]];
  497.                 yp[i] = manipulate_y[pg->p[i]];
  498.                 zp[i] = manipulate_z[pg->p[i]];
  499.             }
  500.             
  501.             /* horrible plane equation calculations */
  502.             
  503.             A = yp[0]*(zp[1]-zp[2]) + yp[1]*(zp[2]-zp[0]) + yp[2]*(zp[0]-zp[1]);
  504.             B = zp[0]*(xp[1]-xp[2]) + zp[1]*(xp[2]-xp[0]) + zp[2]*(xp[0]-xp[1]);
  505.             C = xp[0]*(yp[1]-yp[2]) + xp[1]*(yp[2]-yp[0]) + xp[2]*(yp[0]-yp[1]);
  506.             D = - xp[0]*(yp[1]*zp[2] - yp[2]*zp[1]) - xp[1]*(yp[2]*zp[0] - yp[0]*zp[2])
  507.                 - xp[2]*(yp[0]*zp[1] - yp[1]*zp[0]);
  508.                 
  509.             if((ex*A + ey*B + ez*C + D)<0)
  510.             {
  511.                 pg->back_face = 1;
  512.                 pg->clip_num = 0;
  513.             }
  514.             else
  515.             {
  516.                 pg->back_face = 0;
  517.             }
  518.                 
  519.         }
  520.         else
  521.             pg->back_face = 0;
  522.         
  523.         Depth[count++] = pg->back_face ? 0 : SpinPolyCentre(obj, pg);
  524.         pg = (polygon *) pg->next;
  525.     }
  526.  
  527.     obj->draworder[count] = NULL; /* terminate list */
  528.  
  529.     sort_obj = obj;
  530.     if(count)
  531.         qsort_poly(Depth, &Depth[count-1], 0, count-1);
  532.         
  533. }
  534.  
  535. /* Object sorting routines */
  536.  
  537. double rad(double degrees)
  538. /* converts degrees to radians */
  539. {
  540.     double one8 = 180;
  541.     return 3.141592654/(one8/degrees);
  542. }
  543.  
  544. int GetAngle(int *h, short px, short py, short pz)
  545. {
  546.     register int o, angle, tempint;
  547.     register char quad;
  548.     int temp;
  549.     float depth, temp2;
  550.     int d;
  551.     
  552.     tempint = ex-px;
  553.     depth = tempint * tempint;
  554.     tempint = ez - pz;
  555.     depth += tempint * tempint;
  556.     tempint = ey - py;
  557.     *h = depth + tempint * tempint;
  558.  
  559.     if(px>ex)
  560.     {
  561.         o = px - ex;
  562.         if(pz>ez)
  563.             quad = 4;
  564.         else
  565.         {
  566.             if(pz==ez) return 0;
  567.             quad = 1;
  568.         }
  569.     }
  570.     else
  571.     {
  572.         if(px==ex)
  573.         {
  574.             if(pz<ez)
  575.                 return 90;
  576.             else
  577.                 return 270;
  578.         }
  579.         
  580.         o = ex - px;
  581.         
  582.         if(pz>ez)
  583.             quad = 3;
  584.         else
  585.         {
  586.             if(pz==ez) return 180;
  587.             quad = 2;
  588.         }
  589.     }
  590.     
  591.     temp = o * 4096;
  592.     temp2 = fsqrt(depth);
  593.     
  594.     d = temp/temp2;
  595.     
  596.     if(d>4096) d = 4096; /* to fix small errors which make d > 4096 */
  597.     
  598.     angle = anti_sin[d];
  599.     
  600.     switch(quad)
  601.     {
  602.         case 1: angle = 90 - angle; break;
  603.         case 2: angle = 90 + angle; break;
  604.         case 3: angle = 180 + (90-angle); break;
  605.         case 4: angle += 270; break;
  606.     }
  607.     return angle;
  608. }
  609.  
  610.  
  611. int SpinObjCentre(object *obj)
  612. {
  613.     register int x,y,z;
  614.     register short angle;
  615.     int distance;
  616.     
  617.     x = obj->trans_x + obj->centre_x;
  618.     y = obj->trans_y + obj->centre_y;
  619.     z = obj->trans_z + obj->centre_z;
  620.     
  621.     angle = GetAngle(&distance, x, y, z);
  622.  
  623.     /* tries to eliminate objects which aren't in the field of view */
  624.     
  625.     if(View_Angle>=45)
  626.     {
  627.         if(View_Angle<=315)
  628.         {
  629.             if(angle>View_Angle+45 || angle<View_Angle-45)
  630.                 obj->drawme = 0;
  631.         }
  632.         else
  633.         {
  634.             if(angle<View_Angle-45 && angle>View_Angle-315)
  635.                 obj->drawme = 0;
  636.         }
  637.     }
  638.     else
  639.     {
  640.         if(angle>View_Angle+45 && angle<View_Angle+315)
  641.             obj->drawme = 0;
  642.     }
  643.  
  644.     /* check if I hit something */
  645.     
  646.     if(distance<(obj->radius*5+500) && obj->type<MYMISSILE)
  647.     {
  648.         obj->i_am_dying = 1; /* kill enemy too! */
  649.         obj->explode = 1;
  650.         am_i_dead = 1;
  651.     }
  652.         
  653.     return distance;
  654. }
  655.  
  656. void qsort_depth(int *left, int *right, int al, int ar)
  657. /* quicksort objects into drawing order */
  658. {
  659.     register int *p = left, *q = right, x = *(left+(right-left)/2), w;
  660.     register int tar = ar, tal = al;
  661.     
  662.     do
  663.     {
  664.         while(*p>x)
  665.         {
  666.             p++; tal++;
  667.         }
  668.         
  669.         while(*q<x)
  670.         {
  671.             q--; tar--;
  672.         }
  673.             
  674.         if(p>q) break;
  675.  
  676.         w = *p;
  677.         *p = *q;
  678.         *q = w;
  679.         
  680.         {
  681.             register object *temp;
  682.             temp = active_list[tar];
  683.             active_list[tar] = active_list[tal];
  684.             active_list[tal] = temp;
  685.         }
  686.         
  687.         p++; q--;
  688.         tal++; tar--;
  689.         
  690.         
  691.     } while (p<=q);
  692.  
  693.     if(left<q) qsort_depth(left, q, al, tar);
  694.     if(p<right) qsort_depth(p, right, tal, ar);
  695. }
  696.  
  697. void CalculateObjects(void)
  698. /* sorts the objects into depths from the eye to determine
  699. the order in which they are to be drawn
  700. */
  701. {
  702.     register short i,j;
  703.     
  704.     /*
  705.     kills off objects which are floating around in a dead state but
  706.     aren't totally removed from the system yet
  707.     */
  708.     
  709.     for(i=0; i<no_objects; i++)
  710.     {
  711.         while(i<no_objects && active_list[i]->i_am_dying)
  712.         {
  713.             if(active_list[i]->explode &&    active_list[i]->i_am_dying==1)
  714.                 CreateExplosion(active_list[i]);
  715.             
  716.             if(active_list[i]->i_am_dying++>2)
  717.             {
  718.                 if(active_list[i]->type==EXPLOSION)
  719.                 {
  720.                     for(j=0; j<MAX_EXPLOSIONS; j++)
  721.                     {
  722.                         if(active_list[i] == &explosions[j])
  723.                         {
  724.                             exp_free[j] = 0;
  725.                             break;
  726.                         }
  727.                     }
  728.                 }
  729.                 else
  730.                 {
  731.                     for(j=0; j<MAX_OBJECTS; j++)
  732.                     {
  733.                         if(active_list[i] == &obj_types[active_list[i]->type][j])
  734.                         {
  735.                             obj_free[active_list[i]->type][j] = 0;
  736.                             break;
  737.                         }
  738.                     }
  739.                 }
  740.                 
  741.                 num_active[active_list[i]->type]--;
  742.                 
  743.                 for(j=i; j<no_objects-1; j++)
  744.                     active_list[j] = active_list[j+1];
  745.             
  746.                 no_objects--;
  747.             }
  748.             else
  749.             {
  750.                 active_list[i]->drawme = 0;
  751.                 break;
  752.             }
  753.         }
  754.         
  755.         if(i<no_objects)
  756.         {
  757.             if(active_list[i]->drawme)
  758.                 PrepareObject(active_list[i]);
  759.         }
  760.     }
  761.     
  762.     for(i=0; i<no_objects; i++)
  763.         Depth[i] = SpinObjCentre(active_list[i]);
  764.  
  765.     qsort_depth(Depth, &Depth[no_objects-1], 0, no_objects-1);
  766.     
  767. }
  768.  
  769. void ClearObjects(void)
  770. /* destroys the old image */
  771. {
  772.     register int i;
  773.     
  774.     for(i=0; i<no_objects; i++)
  775.             EraseObject(active_list[i]);
  776. }
  777.  
  778. void DisplayObjects(void)
  779. {
  780.     register int i;
  781.     for(i=0; i<no_objects; i++)
  782.         DrawObject(active_list[i]);
  783. }
  784.         
  785. /* end of module clip.c */
  786.