home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 4 / Apprentice-Release4.iso / Source Code / C++ / Snippets / New Venus / src / view_3d.cc < prev    next >
Encoding:
Text File  |  1995-11-05  |  26.8 KB  |  621 lines  |  [TEXT/CWIE]

  1. /*
  2.  ************************************************************************
  3.  *
  4.  *                   2D rendering of a surface in 3D space (a map)
  5.  *
  6.  * Suppose we have a surface in space z=f(x,y) and we want to render it to view
  7.  * on a screen (in 2D). The function z=f(x,y) may be specified as a 2D matrix, or
  8.  * as an image/map. In the latter case we also need to know how to transform a pixel
  9.  * value into z, an elevation of the corresponding point (x,y) on the map.
  10.  *
  11.  * Coordinate systems and notation
  12.  * World coordinate system: axes Ox, Oy, Oz. We choose Oz to go straight up (the higher
  13.  * z, the higher the elevation), and lay Ox, Oy along the map's column/row respectively,
  14.  * in the direction of increasing column/row indices. We assume that z=0 corresponds to 
  15.  * the base of a map (base of clouds).
  16.  *
  17.  * We set the view point (eye point, camera point) at point E (xe,ye,ze), with the direction
  18.  * of gaze specified by the vector g (gx,gy,gz). That is, an observer looks from point E
  19.  * in the general direction g, and sees the image of the world projected onto a focal
  20.  * plane ƒ. Thus the local coordinate system (associated with the view plane) thus is:
  21.  * vector g is a "depth" vector, vector v of the plane shows the "up" direction,
  22.  * and vector u of the plane gives a horizontal direction; u is chosen to make
  23.  * a left-handed coordinate system, that is, u = v x g). The easiest way to specify
  24.  * the up direction, v, is to project (Oz) onto the plane:
  25.  *        v = (0,0,1) - g((0,0,1),g)/|g|^2
  26.  * where (a,b) is a scalar (dot) product of two vectors, a and b.
  27.  *
  28.  * We need an equation of the focal plane to project the world onto it. The focal plane
  29.  * is orthogonal to the gaze vector g and is located at a distance f0 off the view
  30.  * point E. This gives us 
  31.  *                        (g,(r-E)) - f0*|g| = 0
  32.  * Note that point F, a projection of E onto the plane, can be found from
  33.  *                        (g,(F-E)) - f0*|g| = 0
  34.  * well, since EF || g, F = f0*g/|g| + E
  35.  *
  36.  * To project a point A (x,y,z) of the world onto the focal plane, we draw a line
  37.  * from E to A, whose intersection with the focal plane (point B) is the desired
  38.  * projection.
  39.  * Note that EB || EA, that is, EB = p*EA, and B = p*(A-E) + E, where p is some scalar.
  40.  * To find it, we note that B belongs to ƒ, that is, it has to satisfy the focal plane
  41.  * equation
  42.  *            (g,(B-E)) = f0*|g|,
  43.  * which quickly gives us p:
  44.  *            p = f0*|g|/(g,(A-E))
  45.  * To find "local" coordinates of B on a focal plane, we need to project
  46.  * vector FB onto vectors u and v. But first let's determine a "view depth"
  47.  * of A, which is needed for hidden surface/line removal.
  48.  * One can choose the depth of A to be either a distance from A to the focal plane,
  49.  * or the length of vector BA. The latter seems to be more natural,
  50.  *    |BA| = (1-p)|EA|
  51.  * but we'll run into problems later (see below). So we assume that the depth
  52.  * of A, d(A), is its distance from the focal plane
  53.  *    d(A) = (g,(A-E))/|g| - f0
  54.  * For practical purposes, it's better to define the "depth" simply as
  55.  *    d(A) = (g,(A-E))/|g|
  56.  * which could range from f0 (for points on the focal plane) through some
  57.  * upper limit s0 (sight depth).
  58.  * Coming back to vector FB, it can be estimated as follows
  59.  *    FB = B - F = p*(A-E) + E - f0*g/|g| - E = p*(A-E) - f0*g/|g|
  60.  *
  61.  * Note, that g is orthogonal to both u and v vectors of the local coordinate system,
  62.  * so
  63.  *    u = f0/d(A) * (u,(A-E))
  64.  *    v = f0/d(A) * (v,(A-E))
  65.  * To complete the transformation to screen coordinates u and v, we multiply by a
  66.  * scaling factor and add an appropriate offset
  67.  *  u = beta*f0/d(A) * (u,(A-E)) + Ou
  68.  *  v = beta*f0/d(A) * (v,(A-E)) + Ov
  69.  * (note, scaling factors for u and v could be different, it depends on the screen
  70.  * aspect ratio; but we assume it is 1 for now).
  71.  *
  72.  * The drawing method we're going to use, voxel method, involves scanning of the
  73.  * world from fatherst to closest points, and from left to right. That is, given
  74.  * a fixed depth d and azimuth u, we should be able to find a corresponding point
  75.  * (x,y) on the map. It's easy to see it involves solving a set of equations
  76.  *    (x-xe)*gx + (y-ye)*gy + (z-ze)*gz = d*|g|
  77.  *  (x-xe)*ux + (y-ye)*uy + (z-ze)*uz = const
  78.  *  z = z(x,y)
  79.  * The first equation defines a plane (parallel to ƒ ) that intersects the 2d surface
  80.  * (elevation map) along some line(s), which is to be projected to the view plane.
  81.  * Unfortunately, unless the plane is vertical, the lines are many (consider what a
  82.  * horizontal plane would do slicing through the "mountain peaks"). For the voxel algorithm
  83.  * below, we really need only a single nice intersection (without looping, etc).
  84.  * We have to make a simplification: assume that the gaze g is strictly horizontal,
  85.  * and the up direction v is straight "up", that is, v || (Oz). That means that tilting
  86.  * is out, it has to be done by transforming the map itself.
  87.  * Still, we can _simulate_ tilting by making distant peaks appear taller than they
  88.  * would otherwise be in the perspective projection, see below.
  89.  *
  90.  * If the gaze is strictly horizontal, gz=0. The focal plane ƒ is then vertical, and
  91.  * the up direction (vector v) can be chosen to be (0,0,1). The u vector of the trio
  92.  * is then u = (v x g)/|v x g| = (-gy gx 0)/|g|
  93.  * So, the projection formulas from A(x,y,z) into B(u,v,d) become
  94.  *     u = beta*f0/d(A) * (-gy*(x-xe) + gx*(y-ye))/|g| + Ou
  95.  *     v = beta*f0/d(A) * (z-ze) + Ov
  96.  *     d(A) = (gx*(x-xe) + gy*(y-ye))/|g|
  97.  * Without loss of generality, we can assume f0=1 (that is, it can be absorbed in
  98.  * the scaling factor beta, an arbitrary constant at the moment).
  99.  *
  100.  * We scan the world along the planes d=const, and then for each plane of constant
  101.  * depth we scan our 'view port' from left to right, that is, from u=0 to u=D-1,
  102.  * D being the width of our view scope. For each scan point (u,d) we should be able
  103.  * to figure out the corresponding point (x,y) on the map, find out its elevation
  104.  * z=z(x,y), and compute the projection v.
  105.  * First thing first, given d and u, we need to find x and y from
  106.  *    -gy*(x-xe) + gx*(y-ye) = (u-Ou)*d*|g|/beta
  107.  *   gx*(x-xe) + gy*(y-ye) = d*|g|
  108.  * The solution is obviously
  109.  *    x = xe + (d*gx - gy*(u-Ou)*d/beta)/|g|
  110.  *  y = ye + (d*gy + gx*(u-Ou)*d/beta)/|g|
  111.  * The most efficient way of computing x and y is to evaluate the previous formulas
  112.  * at u=0:
  113.  *  xr = xe + (d*gx + gy*Ou*d/beta)/|g|
  114.  *  yr = ye + (d*gy - gx*Ou*d/beta)/|g|
  115.  * and then evaluate increments xinc, yinc when u changes by one
  116.  *  xinc = - gy*d/beta/|g|
  117.  *  yinc =   gx*d/beta/|g|
  118.  * Keeping in mind that d=const at a u scan.
  119.  * It's easy to see that it's convenient to express xinc, yinc in units of |g|, and
  120.  * select |g| to be something nice, like |g|=1<<14. Thus |g| makes a very suitable units
  121.  * in which xr, xinc, etc quantities would be measured. The neatest thing is that
  122.  * all these quantities would be integral in |g| units (or can be rounded to be integral
  123.  * without losing too much precision). Note that |g| is better be that big. At each step
  124.  * in d in the code below, xinc, yinc are decremented by -gy/beta, gx/beta. We need to make
  125.  * at least 100 of these steps for good representation of depth. It follows then that |g|/beta
  126.  * should be of order 10-100 (so max error of 1 in xinc decrement won't translate into
  127.  * a huge accumulated error 100/|g|*number_of_u_steps). Otherwise the animation would be
  128.  * jerky.
  129.  *
  130.  * So, the algorithm to process scanlines d=const is as follows:
  131.  * 1. fix d=sight_depth and compute
  132.  *      xr = xe*|g| + d*gx + gy*Ou*d/beta                // In units of |g|
  133.  *      yr = ye*|g| + d*gy - gx*Ou*d/beta
  134.  *      xinc = - gy*d/beta
  135.  *      yinc =   gx*d/beta
  136.  *      zmax = ze*|g| + (vmax-Ov)*d*|g|/beta            // In units of |g| for extra precision
  137.  *      zmin = ze*|g| + (vmin-Ov)*d*|g|/beta
  138.  * 3. set xu=xr, yu=yr
  139.  *    for u=0 to D-1
  140.  * 4.     compute x=xu/|g|, y=yu/|g|
  141.  * 5.     find z=f(x,y) from the map
  142.  *          if z*|g| > zmax set v=vmax
  143.  *          else if z*|g| < zmin set v=vmin
  144.  *          else v = Ov + beta*(z-ze)/d
  145.  *          Note a scanline point (u,v) and connect to the previous scanline
  146.  *          xu += xinc; yu += yinc
  147.  *    end u-loop
  148.  *      d -= 1                                    // move a step closer to the focal plane
  149.  *      xr -= gx + gy*Ou/beta                        // and recompute the "ref" quantities
  150.  *      yr -= gy - gx*Ou/beta
  151.  *      xinc -= - gy/beta
  152.  *      yinc -=   gx/beta
  153.  *      zmax -= (vmax-Ov)*|g|/beta-16                // That'll keep [zmin,zmax] a bit wider (by 32/|g|) 
  154.  *      zmin -= (vmin-Ov)*|g|/beta+16                // than necessary to offset possible "round-off" errors
  155.  *  repeat while d > |g|
  156.  *
  157.  * Again, all the quantities above, xr, yr, xinc, etc. are integral. That means
  158.  * that all the divisions are integer divisions, too (and can be precomputed
  159.  * in all the cases but one, evaluation of v).
  160.  *
  161.  * Since we scan from larger d's to smaller d's (that is, from farther ahead
  162.  * to near, that is, towards the observer), then if a (u,v) point generated by
  163.  * the algorithm should obscure (u,v) point generated at an earlier pass
  164.  * (with larger d), it would just overwrite the old point. However, if we just
  165.  * draw (u,v) dots we've computed we get a dotty picture. Note, that actually we see
  166.  * a segment of a line (x,y,z)-(x1,y-dy,z1). If we connect two points generated
  167.  * in two consecutive scans, (u,v1) and (u,vold), it wouldn't be always
  168.  * right, because the original line could been obscured. Suppose that
  169.  * ze (elevation of the observation point) is high enough (comparable with
  170.  * the hight of the tallest peaks). Then if v(u,d+delta_d) > v(u,d) for some
  171.  * u and delta_d>0, then we have a descending slope, and the line is visible.
  172.  * Otherwise the line is on ascending slope, and it couldn't be visible
  173.  * because it will be obscured by the descending slope (which should be
  174.  * closer to us). Note the line from (u,c1) to (u,vold) is a vertical one,
  175.  * and therefore, is very easy to draw (and clip, if necessary).
  176.  *
  177.  * When we create (u,v) map, we assign to the point (u,v) the intensity
  178.  * (color) of the f(x,y) point of the original map. We can use Gouraud
  179.  * interpolation in drawing the line.
  180.  *
  181.  * Colors and elevations: we assume that the pixel value of the map image,
  182.  * map(j,i) is an "elevation code" for the corresponding point x,y. That means,
  183.  * that the true elevation of the point can be computed via some simple formula
  184.  * z = map(j,i) < z_cut_off ? 0 : (map(i,j)*elevation_factor)>>9;
  185.  * This formula can scale both up and down the pixel values.
  186.  * Moreover, each "elevation code" is associated with a corresponding color via a
  187.  * private colormap, CLUT resource ImageView::elevation_color_CLUT_id. We assume
  188.  * that this colormap is arranged in some order to allow "interpolation". That is,
  189.  * the color for the entry k showd be in a sense "in-between" the colors
  190.  * for entries k-1 and k+1.
  191.  * Note if we decrease the elevation_factor as we project closer and closer point,
  192.  * we can simulate a "camera tilt". it's not going to a be a genuine tilt (say,
  193.  * we can't look into a distant valley this way); still, it seems to create a
  194.  * good impression.
  195.  *
  196.  * The color-interpolation algorithm warrants some explanation. Suppose we are to
  197.  * draw a vertical line from v1 to v2 (v2>v1). Point v1 has color index c1,
  198.  * point v2 has color index c2. Suppose c2 > c1 and c_span = c2-c1 < v_span = v2-v1
  199.  * We will use an algorithm that is in spirit of Bresenham's line-drawing algorithm
  200.  * (and does not use any multiplications/divisions)
  201.  * The algorithm is simple: we start at point v1 going up to v2, assigning each
  202.  * pixel, as we go along, color curr_color, or ++curr_color. At each point we have
  203.  * to choose whether to increment the curr_color or not. Notice that curr_color
  204.  * has to be incremented every round(v_span/c_span) steps. If we make an accumulator
  205.  * and increment it by c_span at every v step, curr_color has to be incremented when the
  206.  * accumulator reaches v_span. Thus the algorithm is simple:
  207.  *  acc = 1            // equivalent to "rounding up" at round(v_span/c_span)
  208.  *  curr_color = c1
  209.  *  for v=v1 to v2
  210.  *    pixel = curr_color
  211.  *    acc += c_span
  212.  *    if( acc >= v_span )
  213.  *      acc -= v_span, ++curr_color
  214.  *  end
  215.  *
  216.  * Note that in accessing pixels of an IMAGE, map(j,i), the first argument is the
  217.  * row index (corresponding to y), and the second argument is the col index
  218.  * (corresponding to x).
  219.  *
  220.  * Inspiration:
  221.  * Tim Clarke, tjc1005@hermes.cam.ac.uk
  222.  * But this version of Venus is *very* far away from MARS.
  223.  *
  224.  * Generalization: draw boxes (quadraluzation), than we can increase the
  225.  * scanning step in u (or in y) in the algorithm above.
  226.  * step on u in two instead of one, and interpolate v in between
  227.  * 
  228.  ************************************************************************
  229.  */
  230.  
  231. #include "image.h"
  232. #include "ImageViews.h"
  233.  
  234. #define SIMULATE_TILT 0
  235. static const int Sight_depth = 130;                        // This actually tells the range
  236. static const int Focus_distance = 30;                    // d, depth, can vary
  237.  
  238.                                 // Kind of a formal constructor, merely an initialization
  239. ThreeDView::ThreeDView(const IMAGE& image_map, const ViewerPosition& _viewer_pos,
  240.                        const ProjectionParameters& _projection_parms)
  241.   : OffScreenBuffer(image_map,ImageView::elevation_color_CLUT_id),
  242.     map(image_map),
  243.     viewer_pos(_viewer_pos),
  244.     projection_parms(_projection_parms),
  245.     scanline1(image_map.q_ncols()),
  246.     scanline2(image_map.q_ncols())
  247. {
  248.   assert( ViewerPosition::g_unit == (1<<14) );
  249. }
  250.  
  251.                                 // This is a real constructor, but called late
  252. void ThreeDView::bind(const ModelessDialog& the_dialog, const int item_no)
  253. {
  254.   UserItem::bind(the_dialog,item_no);
  255.   project();
  256. }
  257.  
  258. /*
  259.  *----------------------------------------------------------------------
  260.  *                Dealing with scanlines of the view window buffer
  261.  */
  262.  
  263.                                 // Handling a scanline: allocate one scanline
  264. ThreeDView::OneViewScanline::OneViewScanline(const int _width)
  265.   : width(_width), scan_points(new ScanPoint[_width])
  266. {
  267.   assert( scan_points != nil );
  268. }
  269.  
  270.                                 // Destroy the scanline
  271. ThreeDView::OneViewScanline::~OneViewScanline(void)
  272. {
  273.   assert( scan_points != nil );
  274.   delete [] scan_points;
  275. }
  276.  
  277.                                 // print a scanline point
  278. void ThreeDView::OneViewScanline::print(const card i) const
  279. {
  280.   assert( i < width );
  281.   message("scanline point %d is (%d,%d)\n",i,scan_points[i].v,scan_points[i].color);
  282. }
  283.  
  284.  
  285.                                 // The following is just an ugly kludge in lieu of member
  286.                                 // function templates
  287. class ScanLineAccess
  288. {
  289. protected:
  290.   ThreeDView::OneViewScanline& the_scanline;
  291. public:
  292.   ScanLineAccess(ThreeDView::OneViewScanline& a_scanline) : the_scanline(a_scanline) {}
  293.   int q_width(void) const                { return the_scanline.q_width(); }
  294.                                     // This is the real reason for this class
  295.   ThreeDView::OneViewScanline::ScanPoint * q_scan_begin(void) const 
  296.                                           { return the_scanline.scan_points; }
  297. };
  298.  
  299.                                 // Note that the entire iteration would be done "in-line"
  300.                                 // Class T (an iteratee) is supposed to have a member
  301.                                 //    operator () (ScanPoint& curr_point)
  302.                                 // to do something meaningful with the current point
  303.                                 // (say, fill it in)
  304. template <class T> struct ScanLineIterator : ScanLineAccess
  305. {
  306.   ScanLineIterator(ThreeDView::OneViewScanline& a_scanline) : ScanLineAccess(a_scanline) {}
  307.   ThreeDView::OneViewScanline& get_scanline(void) const { return the_scanline; }
  308.   void for_each(T& iteratee)            // Get the iteratee to do something for each point
  309.    {
  310.      ThreeDView::OneViewScanline::ScanPoint * p = q_scan_begin();
  311.      const ThreeDView::OneViewScanline::ScanPoint * p_end = p + q_width();
  312.      while( p < p_end )
  313.        iteratee(*p++);
  314.    }
  315. };
  316.  
  317.                                 // This is an iterator fow two scanlines
  318.                                 // class T has to have a member function 'operator()'
  319.                                 // that takes two scanline points (from the previous and
  320.                                 // the current scanline) and 'handles' them
  321. template <class T> class TwoScanLinesIterator
  322. {
  323.   const ScanLineAccess from_scanline;
  324.   const ScanLineAccess to_scanline;
  325. public:
  326.   TwoScanLinesIterator(ThreeDView::OneViewScanline& _from_scanline,
  327.                          ThreeDView::OneViewScanline& _to_scanline)
  328.     : from_scanline(_from_scanline), to_scanline(_to_scanline) {}
  329.   void for_each(T& iteratee)
  330.    {
  331.      const ThreeDView::OneViewScanline::ScanPoint * from_p = from_scanline.q_scan_begin();
  332.      const ThreeDView::OneViewScanline::ScanPoint * from_p_end = from_p + from_scanline.q_width();
  333.      const ThreeDView::OneViewScanline::ScanPoint * to_p = to_scanline.q_scan_begin();
  334.      while( from_p < from_p_end )
  335.        iteratee(*from_p++,*to_p++);
  336.    }
  337. };
  338.  
  339.  
  340. /*
  341.  *----------------------------------------------------------------------
  342.  *                        Projection algorithm itself
  343.  */
  344.  
  345.                                     // This is the class that fills out a scanline
  346.                                     // of the view plane for a current depth d
  347. class ThreeDProjector
  348. {
  349.   int d;                                        // The current projection depth
  350.   const IMAGE& map;
  351.   const int map_width_u, map_height_u;            // Unnormalized (in "units" |g|, that is,
  352.                                                 // shifted by 8) xmax and ymax
  353.     
  354.   const int xe, ye, ze;                            // World coordinates of a view point
  355.   const int gx, gy;                                // Directions of a gaze vector (gz=0)
  356.   const int beta;                                // Local coordinates scaling factor
  357.   const int Ou, Ov;                                // Origin of the local coordinate ref Pt
  358.   const int vmin, vmax;                            // Allowable span of the local v coordinate
  359.     
  360.   int xr, yr;                                    // In units of |g|
  361.   int xinc, yinc;                                // Increment in xu, yu, when u changes by 1
  362.   int zmax, zmin;                                // When z lies within [zmin, zmax], v would
  363.                                                   // _almost_ surely fall within [vmin,vmax]
  364.  
  365.                                                 // The following are changes (derivatives)
  366.                                                 // in the corresponding quantities when
  367.                                                 // d changes by 1, precomputed for easy
  368.                                                 // reference
  369.   const int xr_d, yr_d, xinc_d, yinc_d, zmax_d, zmin_d;
  370.   
  371.   int z_cutoff, elevation_factor;            // Coefficients to determine elevation from
  372.                                                   // the map's "elevation code"
  373.  
  374.   int xu, yu;                                    // Current x, y unnormalized
  375.  
  376.  
  377.   int clip_v(const int v) const            { return v < vmin ? vmin : v > vmax ? vmax : v; }
  378.   
  379.                                             // This constructor has too many arguments
  380.                                             // But it's inline, so hopefully it won't matter
  381. public:
  382.     ThreeDProjector(const IMAGE& _map, const int _xe, const int _ye, const int _ze,
  383.               const int _gx, const int _gy, const int _beta,
  384.               const int _Ou, const int _Ov, const int _z_cutoff, const int _elevation_factor)
  385.       : map(_map), d(Sight_depth),
  386.         map_width_u(_map.q_ncols()<<14), map_height_u(_map.q_nrows()<<14),
  387.         xe(_xe), ye(_ye), ze(_ze),
  388.         gx(_gx), gy(_gy), beta(_beta), Ou(_Ou), Ov(_Ov),
  389.         vmin(0), vmax(_map.q_nrows()-1),
  390.         xr_d(_gx + (_gy*_Ou)/_beta), yr_d(_gy - (_gx*_Ou)/_beta),
  391.         xinc_d(-_gy/_beta), yinc_d(_gx/_beta),
  392.         zmax_d(((vmax-_Ov)<<9)/_beta-32), zmin_d(((vmin-_Ov)<<9)/_beta+32), // to account for roundoff errors
  393.         z_cutoff(_z_cutoff), elevation_factor(_elevation_factor)
  394.         
  395.       { xu = xr = (xe<<14) + d*xr_d;                            // In units of |g| 
  396.         yu = yr = (ye<<14) + d*yr_d;
  397.         xinc = - (gy*d)/beta;
  398.         yinc =   (gx*d)/beta;
  399.         zmax = (ze<<9) + ((vmax-Ov)*(d<<9))/beta;            // In units of |g| for greater precision
  400.         zmin = (ze<<9) + ((vmin-Ov)*(d<<9))/beta;
  401.       }
  402.  
  403.     inline Boolean can_go_closer(void) const         { return d >= Focus_distance; }
  404.     void move_closer(void)                    // Prepare to handle a new scanline one step closer
  405.       { d -= 1; xu = (xr -= xr_d); yu = (yr -= yr_d);
  406.         xinc -= xinc_d; yinc -= yinc_d;
  407.         zmax -= zmax_d; zmin -= zmin_d;
  408.         #if SIMULATE_TILT
  409.         elevation_factor -= 1;
  410.         #endif
  411.       }
  412.                                               // Iteratee: filler of the current scan point
  413.     inline void operator () (ThreeDView::OneViewScanline::ScanPoint& point)
  414.     {
  415.       if( xu < 0 || xu >= map_width_u || yu < 0 || yu >= map_height_u )
  416.       {
  417.         point.v = vmin, point.color = 0;        // The corresponding world point was outside the map
  418.         xu += xinc, yu += yinc;
  419.         return;
  420.       }
  421.       int z = point.color = (unsigned)map(yu>>14,xu>>14); // first coordinate is row number
  422.       if( z < z_cutoff )
  423.       {
  424.         point.v = vmin, point.color = 0;        // The corresponding world point was outside the map
  425.         xu += xinc, yu += yinc;
  426.         return;
  427.       }
  428.       z = z * elevation_factor;                // implied shift by 9
  429.                                               // Note, even if z was within limits, v still can
  430.                                               // fall outside [vmin,vmax] because of rounding
  431.                                               // errors in computing zmax, zmin
  432.       point.v = z >= zmax ? vmax : z <= zmin ? vmin : clip_v( Ov + (beta*((z>>9)-ze))/d );
  433.       //_error("point.v %d xu %d, yu %d", point.v,xu,yu);
  434.       xu += xinc, yu += yinc;
  435.     }
  436.   };
  437.  
  438.  
  439.                                         // Clean the last scan line
  440. struct LastScanLineCleaner
  441. {
  442.   inline void operator () (ThreeDView::OneViewScanline::ScanPoint& point)
  443.   {
  444.     point.v = 0, point.color = 0;
  445.   }
  446. };
  447.  
  448.  
  449.                                 // Connect two scanlines, sl0 to sl1, drawing
  450.                                 // vertical lines between the corresponding points
  451.                                 // (if the lines are visible)
  452.                                 // We really need some trust here, that is, we want to 
  453.                                 // believe the projector made v lie within necessary limits
  454.                                 // See the explanation for color interpolation algorithm
  455.                                 // above
  456. class ScanLines_Connector
  457. {
  458.   const PixMapHandle pixmap;
  459.   const int maxrow;                            // max row number of the pixmap
  460.   const int bytes_per_row;                    // bytes per row in the pixmap
  461.   unsigned char * orig_pixels_ptr;            // Ptr to the beginning of the PixMap
  462.   unsigned char * pixels_ptr;                // current pixmap pointer
  463.   int next_point(void) { return pixels_ptr++, 0; }    // the return result is dummy, it's the increment that matters
  464. public:
  465.   ScanLines_Connector(const OffScreenBuffer& canvas)
  466.     : pixmap(canvas.get_pixmap()), maxrow(canvas.height()-1),
  467.       bytes_per_row(canvas.bytes_per_row())
  468.   {
  469.     assert( LockPixels(pixmap) );
  470.     pixels_ptr = orig_pixels_ptr = (unsigned char *)GetPixBaseAddr(pixmap);
  471.     const int background_pixel = 0;            // Clear the canvas before we start drawing
  472.     const int scan_width = canvas.width();
  473.     register unsigned char * rp = orig_pixels_ptr;
  474.     for(register int i=0; i <= maxrow; i++, rp += bytes_per_row)
  475.        memset(rp,background_pixel,scan_width);
  476.   }
  477.   ~ScanLines_Connector(void)    {  UnlockPixels(pixmap); }
  478.   
  479.   void reset(void)            { pixels_ptr = orig_pixels_ptr; }    // Reset for the new run
  480.   
  481.                                         // This is a real scanpoint connector
  482.                                         // connects from_point to to_point
  483.                                         // (provided the line is visible, that is,
  484.                                         // on the descending slope)
  485.                                         // Note that we assumed that larger v mean
  486.                                         // points close to the top. On a Mac though
  487.                                         // larger row number corresponds to the
  488.                                         // bottom points. That is, we need a flip
  489.   int operator() (const ThreeDView::OneViewScanline::ScanPoint& from_point,
  490.                     const ThreeDView::OneViewScanline::ScanPoint& to_point)
  491.   {
  492.     if( to_point.v > from_point.v )
  493.       return next_point();            // The line is on the ascending slope and *will* be obscured later
  494.                                     // Keep in mind that to_v <= from_v now
  495.     if( from_point.v <= 0 )
  496.       return next_point();                    // means both v and vold are out-of-picture
  497.  
  498.     if( from_point.color == 0 && to_point.color == 0 )    // The line is in "background" color
  499.       return next_point();                                // won't waste our time
  500.  
  501.     assert( to_point.v >= 0 && from_point.v <= maxrow );
  502.                                     // Now, 0 <= to_v <= from_v
  503.     int v_span = from_point.v - to_point.v;
  504.     if( v_span == 0 )                        // one-pixel-long line
  505.     {
  506.       pixels_ptr[(maxrow-to_point.v) * bytes_per_row] = to_point.color;
  507.       return next_point(); 
  508.     }
  509.     if( v_span == 1 )                // The vertical line is only two-pixel tall
  510.     {                                // No color-interpolation necessary
  511.       unsigned char * pixp = pixels_ptr + (maxrow-to_point.v) * bytes_per_row;
  512.       *pixp = to_point.color;
  513.       *(pixp-bytes_per_row) = from_point.color;
  514.       return next_point(); 
  515.     }
  516.     int color_span = from_point.color - to_point.color;
  517.     if( color_span == 0 )                // The line is of the same color, no color-interpolation
  518.     {
  519.       int color = from_point.color;
  520.       unsigned char * pixp_beg = pixels_ptr + (maxrow-to_point.v) * bytes_per_row;
  521.       for(register int v=v_span; v >= 0; v--, pixp_beg -= bytes_per_row)
  522.           *pixp_beg = color;
  523.       return next_point();
  524.     }
  525.     
  526.     int acc_dec = v_span;                // by how much to decrement acc 
  527.     int curr_color_inc = 1;                // and increment curr_color for the next point
  528.     if( color_span > v_span )
  529.     {
  530.       acc_dec = 0; curr_color_inc = 0;
  531.       for(register int acc=1+color_span; acc >= v_span; acc -= v_span)
  532.          acc_dec += v_span, curr_color_inc++;
  533.     }
  534.     if( color_span < 0 ) 
  535.     {
  536.       color_span = -color_span;
  537.       if( color_span > v_span )
  538.       {
  539.         acc_dec = 0; curr_color_inc = 0;
  540.         for(register int acc=1+color_span; acc >= v_span; acc -= v_span)
  541.            acc_dec += v_span, curr_color_inc--;
  542.       }
  543.       else
  544.         curr_color_inc = -1;
  545.     }
  546.     int curr_color = to_point.color;
  547.     int acc = 1;
  548.     unsigned char * pixp_beg = pixels_ptr + (maxrow-to_point.v) * bytes_per_row;
  549.     for(register int v=v_span; v >= 0; v--, pixp_beg -= bytes_per_row)
  550.     {
  551.       if( curr_color > 0 )
  552.         *pixp_beg = curr_color;
  553.       acc += color_span;
  554.       while( acc >= v_span )                // Note this loop runs at most twice
  555.         acc -= acc_dec,
  556.         curr_color += curr_color_inc;
  557.     }
  558.     #if 0
  559.     if( abs(*(pixp_beg+bytes_per_row) - from_point.color) >= abs(curr_color_inc) )
  560.     _error("from (%d,%d) to (%d,%d), color_span %d, acc_dec %d, inc %d last %d\n",
  561.             from_point.v,from_point.color,to_point.v,to_point.color,
  562.             color_span, acc_dec, curr_color_inc,
  563.             *(pixp_beg+bytes_per_row));
  564.     #endif
  565.     return next_point();
  566.   }
  567. };
  568.  
  569. /*
  570.  *----------------------------------------------------------------------
  571.  *                Root module to draw a projection of the world
  572.  *                        in an offscreen graf world
  573.  */
  574.  
  575. void ThreeDView::project(void)
  576. {
  577.   ThreeDProjector projector(map, viewer_pos.xe, viewer_pos.ye, projection_parms.ze,  
  578.                               viewer_pos.gx,viewer_pos.gy, projection_parms.beta, 
  579.                               projection_parms.Ou, projection_parms.Ov, 
  580.                               projection_parms.z_cutoff, projection_parms.elevation_factor );
  581.  
  582.   ScanLineIterator<ThreeDProjector> fill_scanline_1(scanline1);
  583.   ScanLineIterator<ThreeDProjector> fill_scanline_2(scanline2);
  584.   ScanLineIterator<ThreeDProjector> * prev_scanline = &fill_scanline_1,
  585.                                         * curr_scanline = &fill_scanline_2;
  586.   
  587.   ScanLines_Connector scanlines_connectee(*this);
  588.                                                 // note curr_scanline ^ pointer_diff
  589.                                                 // gives prev_scanline, and
  590.                                                 // vice versa
  591.   const int pointer_diff = (long int)curr_scanline ^ (long int)prev_scanline;
  592.   
  593.   (*prev_scanline).for_each(projector);
  594.  
  595.   while( projector.can_go_closer() )
  596.   {
  597.     (*curr_scanline).for_each(projector);
  598.     TwoScanLinesIterator<ScanLines_Connector>
  599.         scanlines_connector(prev_scanline->get_scanline(),curr_scanline->get_scanline());
  600.     scanlines_connector.for_each(scanlines_connectee);
  601.  
  602.                                             // exchange pointers
  603.     (long int&)prev_scanline ^= pointer_diff;
  604.     (long int&)curr_scanline ^= pointer_diff;
  605.  
  606.     projector.move_closer();
  607.     scanlines_connectee.reset();
  608.   }
  609.  
  610. #if 1
  611.  
  612.   {                                            // Handle the last scanline
  613.    ScanLineIterator<LastScanLineCleaner> last_scanline_filler(curr_scanline->get_scanline());
  614.    last_scanline_filler.for_each(LastScanLineCleaner());
  615.     TwoScanLinesIterator<ScanLines_Connector>
  616.         scanlines_connector(prev_scanline->get_scanline(),last_scanline_filler.get_scanline());
  617.     scanlines_connector.for_each(scanlines_connectee);
  618.   }
  619. #endif
  620. }
  621.