home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / C ++ / Applications / Venus 3.5 / view_3d.cc < prev    next >
Encoding:
Text File  |  1997-04-23  |  29.9 KB  |  726 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 + (vmax-Ov)*d/beta
  137.  *      zmin = ze + (vmin-Ov)*d/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 > zmax set v=vmax
  143.  *          else if z < 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)/beta-1                // That'll keep [zmin,zmax] a bit wider (by 2) 
  154.  *      zmin -= (vmin-Ov)/beta+1                // 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 the pixel values both up and down.
  186.  * Moreover, each "elevation code" is associated with a corresponding color via a
  187.  * private colormap, see class. We assume that this colormap is arranged in some order
  188.  * to allow "interpolation". That is, the color for the entry k should be in a sense
  189.  * "in-between" the colors for entries k-1 and k+1.
  190.  * Note if we decrease the ze as we project closer and closer point,
  191.  * we can simulate a "camera tilt". it's not going to a be a genuine tilt (say,
  192.  * we can't look into a distant valley this way); still, it seems to create a
  193.  * good impression.
  194.  *
  195.  * The color-interpolation algorithm warrants some explanation. Suppose we are to
  196.  * draw a vertical line from v1 to v2 (v2>v1). Point v1 has color index c1,
  197.  * point v2 has color index c2. Suppose c2 > c1 and c_span = c2-c1 < v_span = v2-v1
  198.  * We will use an algorithm that is in spirit of Bresenham's line-drawing algorithm
  199.  * (and does not use any multiplications/divisions)
  200.  * The algorithm is simple: we start at point v1 going up to v2, assigning each
  201.  * pixel, as we go along, color curr_color, or ++curr_color. At each point we have
  202.  * to choose whether to increment the curr_color or not. Notice that curr_color
  203.  * has to be incremented every round(v_span/c_span) steps. If we make an accumulator
  204.  * and increment it by c_span at every v step, curr_color has to be incremented when the
  205.  * accumulator reaches v_span. Thus the algorithm is simple:
  206.  *  acc = 1            // equivalent to "rounding up" at round(v_span/c_span)
  207.  *  curr_color = c1
  208.  *  for v=v1 to v2
  209.  *    pixel = curr_color
  210.  *    acc += c_span
  211.  *    if( acc >= v_span )
  212.  *      acc -= v_span, ++curr_color
  213.  *  end
  214.  *
  215.  * Note that in accessing pixels of an IMAGE, map(j,i), the first argument is the
  216.  * row index (corresponding to y), and the second argument is the col index
  217.  * (corresponding to x).
  218.  *
  219.  * Inspiration:
  220.  * Tim Clarke, tjc1005@hermes.cam.ac.uk
  221.  * But this version of Venus is *very* far away from MARS.
  222.  *
  223.  * Generalization: draw boxes (quadraluzation), than we can increase the
  224.  * scanning step in u (or in y) in the algorithm above.
  225.  * step on u in two instead of one, and interpolate v in between
  226.  * 
  227.  ************************************************************************
  228.  */
  229.  
  230. #include "ImageViews.h"
  231.  
  232. #define SIMULATE_TILT 0
  233.  
  234.                                 // Kind of a formal constructor, merely an initialization
  235. ThreeDViewBase::ThreeDViewBase(const ElevationMap& image_map, const ViewerPosition& _viewer_pos,
  236.                        const ProjectionParameters& _projection_parms)
  237.   : OffScreenBuffer(_projection_parms.q_view_rect(),image_map.q_clut()),
  238.     map(image_map),
  239.     viewer_pos(_viewer_pos),
  240.     projection_parms(_projection_parms),
  241.     scanline1(_projection_parms.q_width()),
  242.     scanline2(_projection_parms.q_width())
  243. {
  244.   assert( ViewerPosition::g_unit == (1<<14) );
  245. }
  246.  
  247. template <class LineProjector>
  248. ThreeDView<LineProjector>::ThreeDView(const ElevationMap& image_map, const ViewerPosition& _viewer_pos,
  249.                        const ProjectionParameters& _projection_parms)
  250.   : ThreeDViewBase(image_map,_viewer_pos,_projection_parms)
  251. {
  252. }
  253.  
  254. ProjectionParameters::ProjectionParameters
  255.     (const ScreenRect& bounds, const int _ze, const int _beta)
  256.     : ze(_ze), beta(_beta),
  257.       u_sup(bounds.q_width()),
  258.       v_sup(bounds.q_height()),
  259.       Ou(u_sup/2), Ov(v_sup/2)                // Pick up somewhere in the middle
  260. {}
  261.  
  262.                             // Dump the contents of a ViewerPosition
  263. void ViewerPosition::dump(void) const
  264. {
  265.   message("\nObserver is at (%d,%d) gazing in the direction (%d,%d)\n",
  266.             xe,ye,gx,gy);
  267. }
  268.  
  269.                             // Dump the contents of ProjectionParameters
  270. void ProjectionParameters::dump(void) const
  271. {
  272.   message("\n-->Projection parameters:");
  273.   message("\n  observers' elevation %d",ze);
  274.   message("\n  zoom factor (local frame scaling factor) %d",beta);
  275.   message("\n  local frame %dx%d",u_sup,v_sup);
  276.   message("\n  local frame origin (%d,%d)\n",Ou,Ov);
  277. }
  278.  
  279. /*
  280.  *----------------------------------------------------------------------
  281.  *                Dealing with scanlines of the view window buffer
  282.  */
  283.  
  284.                                 // Handling a scanline: allocate one scanline
  285. ThreeDViewBase::OneViewScanline::OneViewScanline(const int _width)
  286.   : width(_width), scan_points(new ScanPoint[_width])
  287. {
  288.   assert( scan_points != nil );
  289. }
  290.  
  291.                                 // Destroy the scanline
  292. ThreeDViewBase::OneViewScanline::~OneViewScanline(void)
  293. {
  294.   assert( scan_points != nil );
  295.   delete [] scan_points;
  296. }
  297.  
  298.                                 // print a scanline point
  299. void ThreeDViewBase::OneViewScanline::print(const card i) const
  300. {
  301.   assert( i < width );
  302.   message("scanline point %d is (%d,%d)\n",i,scan_points[i].v,scan_points[i].color);
  303. }
  304.  
  305.  
  306.                                 // The following is just an ugly kludge in lieu of member
  307.                                 // function templates
  308. class ScanLineAccess
  309. {
  310. protected:
  311.   ThreeDViewBase::OneViewScanline& the_scanline;
  312. public:
  313.   ScanLineAccess(ThreeDViewBase::OneViewScanline& a_scanline) : the_scanline(a_scanline) {}
  314.   int q_width(void) const                { return the_scanline.q_width(); }
  315.                                     // This is the real reason for this class
  316.   ThreeDViewBase::OneViewScanline::ScanPoint * q_scan_begin(void) const 
  317.                                           { return the_scanline.scan_points; }
  318. };
  319.  
  320.                                 // Note that the entire iteration would be done "in-line"
  321.                                 // Class T (an iteratee) is supposed to have a member
  322.                                 //    operator () (ScanPoint& curr_point)
  323.                                 // to do something meaningful with the current point
  324.                                 // (say, fill it in)
  325. template <class T> struct ScanLineIterator : ScanLineAccess
  326. {
  327.   ScanLineIterator(ThreeDViewBase::OneViewScanline& a_scanline) : ScanLineAccess(a_scanline) {}
  328.   ThreeDViewBase::OneViewScanline& get_scanline(void) const { return the_scanline; }
  329.   void for_each(T& iteratee)            // Get the iteratee to do something for each point
  330.    {
  331.      ThreeDViewBase::OneViewScanline::ScanPoint * p = q_scan_begin();
  332.      const ThreeDViewBase::OneViewScanline::ScanPoint * p_end = p + q_width();
  333.      while( p < p_end )
  334.        iteratee(*p++);
  335.    }
  336. };
  337.  
  338.                                 // This is an iterator fow two scanlines
  339.                                 // class T has to have a member function 'operator()'
  340.                                 // that takes two scanline points (from the previous and
  341.                                 // the current scanline) and 'handles' them
  342. template <class T> class TwoScanLinesIterator
  343. {
  344.   const ScanLineAccess from_scanline;
  345.   const ScanLineAccess to_scanline;
  346. public:
  347.   TwoScanLinesIterator(ThreeDViewBase::OneViewScanline& _from_scanline,
  348.                          ThreeDViewBase::OneViewScanline& _to_scanline)
  349.     : from_scanline(_from_scanline), to_scanline(_to_scanline) {}
  350.   void for_each(T& iteratee)
  351.    {
  352.      const ThreeDViewBase::OneViewScanline::ScanPoint * from_p = from_scanline.q_scan_begin();
  353.      const ThreeDViewBase::OneViewScanline::ScanPoint * from_p_end = from_p + from_scanline.q_width();
  354.      const ThreeDViewBase::OneViewScanline::ScanPoint * to_p = to_scanline.q_scan_begin();
  355.      while( from_p < from_p_end )
  356.        iteratee(*from_p++,*to_p++);
  357.    }
  358. };
  359.  
  360.  
  361. /*
  362.  *----------------------------------------------------------------------
  363.  *                        Projection algorithm itself
  364.  */
  365.  
  366.                                     // This is the class that fills out a scanline
  367.                                     // of the view plane for a current depth d
  368. class ThreeDProjector
  369. {
  370.   enum {                                // A range d, the depth, can vary in
  371.          Sight_depth = 140,                        // The farthest the observer can see
  372.          Focus_distance = 40,                    // The closest he can see
  373.                                          // classification of depths into far, medium, near
  374.          far_tier  = Focus_distance+90,            // d > far_tier: far away objects
  375.          near_tier = Focus_distance+70 };
  376.  
  377. protected:
  378.  
  379.   typedef ThreeDViewBase::OneViewScanline::ScanPoint Point;        // Just an abbreviation
  380.   int d;                                        // The current projection depth
  381.   const IMAGE& map;
  382.   const int map_width_u, map_height_u;            // Unnormalized (in "units" |g|, that is,
  383.                                                 // shifted by 14) xmax and ymax
  384.     
  385.   const int xe, ye, ze;                            // World coordinates of a view point
  386.   const int gx, gy;                                // Directions of a gaze vector (gz=0)
  387.   const int beta;                                // Local coordinates scaling factor
  388.   const int Ou, Ov;                                // Origin of the local coordinate ref Pt
  389.   const int vmin, vmax;                            // Allowable span of the local v coordinate
  390.   const int z_cutoff, elevation_factor;            // Coefficients to determine elevation from
  391.                                                   // the map's "elevation code"
  392.  
  393.     
  394.   int xr, yr;                                    // In units of |g|
  395.   int xinc, yinc;                                // Increment in xu, yu, when u changes by 1
  396.   int zmax, zmin;                                // When z lies within [zmin, zmax], v would
  397.                                                   // _almost_ surely fall within [vmin,vmax]
  398.   const int beta_times_elev;                    // (beta*elevation_factor)
  399.   int beta_over_d;                                // (beta/d)*elevation_factor
  400.   int ze_u;                                        // ze/elevation_factor
  401.   
  402.                                                 // The following are changes (derivatives)
  403.                                                 // in the corresponding quantities when
  404.                                                 // d changes by 1, precomputed for easy
  405.                                                 // reference
  406.   const int xr_d, yr_d, xinc_d, yinc_d, zmax_d, zmin_d;
  407.   
  408.   int xu, yu;                                    // Current x, y unnormalized
  409.  
  410.  
  411.   int clip_v(const int v) const            { return v < vmin ? vmin : v > vmax ? vmax : v; }
  412.  
  413.                                               // Project the current point on the map into 'point'
  414.                                               // Return FALSE if the projection appears to be
  415.                                               // off-limits
  416.   inline bool project_to_point(Point& point)
  417.   {
  418.     if( xu < 0 || xu >= map_width_u || yu < 0 || yu >= map_height_u )
  419.       return false;                        // The corresponding world point was outside the map
  420.     const int z = point.color = (unsigned)map(yu>>14,xu>>14); // first coordinate is row number
  421.     if( z < z_cutoff )
  422.       return false;                        // The elevation was below the horizon
  423.     
  424.                                               // Note, even if z was within limits, v still can
  425.                                               // fall outside [vmin,vmax] because of rounding
  426.                                               // errors in computing zmax, zmin
  427.       //    point.v = vmin; 
  428.     point.v = z >= zmax ? vmax : z <= zmin ? vmin : clip_v( Ov + ((beta_over_d * (z-ze_u))>>9) );
  429. //    if( Ov > 100 & point.v > 0 ) _error("point.v %d xu %d, yu %d, zmin %d, zmax %d,ze_u %d, z %d d %d", point.v,xu,yu,zmin,zmax,ze_u,z,d);
  430.     return true;
  431.   }
  432.   
  433. public:
  434.     ThreeDProjector(const ElevationMap& _map, const ViewerPosition& viewer_pos,
  435.                     const ProjectionParameters& projp)
  436.       : map(_map), d(Sight_depth),
  437.         map_width_u(_map.q_ncols()<<14), map_height_u(_map.q_nrows()<<14),
  438.         xe(viewer_pos.xe), ye(viewer_pos.ye), ze(projp.ze),
  439.         gx(viewer_pos.gx), gy(viewer_pos.gy), beta(projp.beta), Ou(projp.Ou), Ov(projp.Ov),
  440.         vmin(0), vmax(projp.q_height()-1),
  441.         z_cutoff(_map.q_z_cutoff()),
  442.         elevation_factor(_map.q_elev_scale()),
  443.         beta_times_elev(beta*elevation_factor),
  444.         ze_u((ze<<9)/elevation_factor),
  445.         xr_d(gx + (gy*Ou)/beta),
  446.         yr_d(gy - (gx*Ou)/beta),
  447.         xinc_d(-gy/beta), yinc_d(gx/beta),
  448.         zmax_d(((vmax-Ov)<<9)/beta_times_elev-1),
  449.         zmin_d(((vmin-Ov)<<9)/beta_times_elev+1)            // to account for roundoff errors
  450.         
  451.       { xu = xr = (xe<<14) + d*xr_d;                            // In units of |g| 
  452.         yu = yr = (ye<<14) + d*yr_d;
  453.         xinc = - (gy*d)/beta;
  454.         yinc = (gx*d)/beta;
  455.         beta_over_d = beta_times_elev/d;
  456.         zmax = ze_u + ((vmax-Ov)<<9)/beta_over_d + 1;
  457.         zmin = ze_u + ((vmin-Ov)<<9)/beta_over_d - 1;
  458.       }
  459.  
  460.     inline bool can_go_closer(void) const         { return d >= Focus_distance; }
  461.     inline bool q_looking_far(void) const        { return d > far_tier; }
  462.     inline bool q_looking_near(void) const        { return d <= near_tier; }
  463.     
  464.     void move_closer(void)                    // Prepare to handle a new scanline one step closer
  465.       { d -= 1; xu = (xr -= xr_d); yu = (yr -= yr_d);
  466.         xinc -= xinc_d; yinc -= yinc_d;
  467.         zmax -= zmax_d; zmin -= zmin_d;
  468.         #if SIMULATE_TILT
  469.         ze_u += 1;
  470.         #endif
  471.         beta_over_d = beta_times_elev/d;
  472.       }
  473.       
  474.     inline void operator () (Point& point)
  475.     {
  476.       if( !project_to_point(point) )
  477.         point.v = vmin, point.color = 0;
  478.       xu += xinc, yu += yinc;
  479.     }
  480.   };
  481.  
  482. class ThreeDProjectorCached : public ThreeDProjector
  483. {
  484.   typedef ThreeDViewBase::OneViewScanline::ScanPoint Point;        // Just an abbreviation
  485.   
  486.   Point cached_point;
  487.   int use_cached;                    // when it is 0 or negative, recompute the point
  488.   int cache_inc;
  489.  
  490. public:
  491.   ThreeDProjectorCached(const ElevationMap& map, const ViewerPosition& viewer_pos,
  492.                         const ProjectionParameters& projp)
  493.       : ThreeDProjector(map,viewer_pos,projp),
  494.         use_cached(0), cache_inc(1) {}
  495.  
  496.   void move_closer(void)                    // Prepare to handle a new scanline one step closer
  497.   {
  498.     ThreeDProjector::move_closer();
  499.     use_cached = 0;
  500.     if( q_looking_near() )
  501.       cache_inc = 0;                        // disables skipping of the point on close range
  502.   }
  503.   
  504.   inline void operator () (Point& point)
  505.   {
  506.     if( --use_cached < 0 )                    // if use_cached > 0, return the previous (cached)
  507.     {                                        // point rather than really computing it...
  508.       if( !project_to_point(cached_point) )
  509.         cached_point.v = vmin, cached_point.color = 0;
  510.       use_cached = cache_inc;
  511.     }
  512.     point = cached_point;
  513.     xu += xinc, yu += yinc;
  514.   }
  515. };
  516.  
  517.                                         // Clean the last scan line
  518. struct LastScanLineCleaner
  519. {
  520.   inline void operator () (ThreeDViewBase::OneViewScanline::ScanPoint& point)
  521.   {
  522.     point.v = 0, point.color = 0;
  523.   }
  524. };
  525.  
  526.  
  527.                                 // Connect two scanlines, sl0 to sl1, drawing
  528.                                 // vertical lines between the corresponding points
  529.                                 // (if the lines are visible)
  530.                                 // We really need some trust here, that is, we want to 
  531.                                 // believe the projector made v lie within necessary limits
  532.                                 // See the explanation for color interpolation algorithm
  533.                                 // above
  534. class ScanLines_Connector
  535. {
  536.   const PixMapHandle pixmap;
  537.   const int maxrow;                            // max row number of the pixmap
  538.   const int bytes_per_row;                    // bytes per row in the pixmap
  539.   unsigned char * orig_pixels_ptr;            // Ptr to the beginning of the PixMap
  540.   unsigned char * pixels_ptr;                // current pixmap pointer
  541.  
  542.                                         // This is a real scanpoint connector
  543.                                         // connects from_point to to_point
  544.                                         // (provided the line is visible, that is,
  545.                                         // on the descending slope)
  546.                                         // Note that we assumed that larger v mean
  547.                                         // points close to the top. On a Mac though
  548.                                         // larger row number corresponds to the
  549.                                         // bottom points. That is, we need a flip
  550.                                         // !! Further optimization: build a scanlines
  551.                                         // row index (to get rid of multiplications by
  552.                                         // bytes_per_row below)
  553.   inline void draw_vert_line(const ThreeDViewBase::OneViewScanline::ScanPoint& from_point,
  554.                                  const ThreeDViewBase::OneViewScanline::ScanPoint& to_point)
  555.   {
  556.     if( to_point.v > from_point.v )
  557.       return;                        // The line is on the ascending slope and *will* be obscured later
  558.                                     // Keep in mind that to_v <= from_v now
  559.     if( from_point.v <= 0 )
  560.       return;                        // means both v and vold are out-of-picture
  561.  
  562.     if( from_point.color == 0 && to_point.color == 0 )    // The line is in "background" color
  563.       return;                                            // won't waste our time
  564.  
  565.     assert( to_point.v >= 0 && from_point.v <= maxrow );
  566.                                     // Now, 0 <= to_v <= from_v
  567.     int v_span = from_point.v - to_point.v;
  568.     if( v_span == 0 )                        // one-pixel-long line
  569.     {
  570.       pixels_ptr[(maxrow-to_point.v) * bytes_per_row] = to_point.color;
  571.       return; 
  572.     }
  573.     if( v_span == 1 )                // The vertical line is only two-pixel tall
  574.     {                                // No color-interpolation necessary
  575.       unsigned char * pixp = pixels_ptr + (maxrow-to_point.v) * bytes_per_row;
  576.       *pixp = to_point.color;
  577.       *(pixp-bytes_per_row) = from_point.color;
  578.       return; 
  579.     }
  580.     int color_span = from_point.color - to_point.color;
  581.                     // The line is of the same color, no color-interpolation
  582.                     // or the line goes to the background: won't interpolate either
  583.     if( color_span == 0 || to_point.color == 0)
  584.     {
  585.       const int color = from_point.color;
  586.       unsigned char * pixp_beg = pixels_ptr + (maxrow-to_point.v) * bytes_per_row;
  587.       for(register int v=v_span; v >= 0; v--, pixp_beg -= bytes_per_row)
  588.           *pixp_beg = color;
  589.       return;
  590.     }
  591.     
  592.     int acc_dec = v_span;                // by how much to decrement acc 
  593.     int curr_color_inc = 1;                // and increment curr_color for the next point
  594.     if( color_span > v_span )
  595.     {
  596.       acc_dec = 0; curr_color_inc = 0;
  597.       for(register int acc=1+color_span; acc >= v_span; acc -= v_span)
  598.          acc_dec += v_span, curr_color_inc++;
  599.     }
  600.     if( color_span < 0 ) 
  601.     {
  602.       color_span = -color_span;
  603.       if( color_span > v_span )
  604.       {
  605.         acc_dec = 0; curr_color_inc = 0;
  606.         for(register int acc=1+color_span; acc >= v_span; acc -= v_span)
  607.            acc_dec += v_span, curr_color_inc--;
  608.       }
  609.       else
  610.         curr_color_inc = -1;
  611.     }
  612.     int curr_color = to_point.color;
  613.     int acc = 1;
  614.     unsigned char * pixp_beg = pixels_ptr + (maxrow-to_point.v) * bytes_per_row;
  615.     for(register int v=v_span; v >= 0; v--, pixp_beg -= bytes_per_row)
  616.     {
  617.       *pixp_beg = curr_color;
  618.       acc += color_span;
  619.       while( acc >= v_span )                // Note this loop runs at most twice
  620.         acc -= acc_dec,
  621.         curr_color += curr_color_inc;
  622.     }
  623.     #if 0
  624.     if( abs(*(pixp_beg+bytes_per_row) - from_point.color) >= abs(curr_color_inc) )
  625.     _error("from (%d,%d) to (%d,%d), color_span %d, acc_dec %d, inc %d last %d\n",
  626.             from_point.v,from_point.color,to_point.v,to_point.color,
  627.             color_span, acc_dec, curr_color_inc,
  628.             *(pixp_beg+bytes_per_row));
  629.     #endif
  630.   }
  631.  
  632. public:
  633.   ScanLines_Connector(const OffScreenBuffer& canvas)
  634.     : pixmap(canvas.get_pixmap()), maxrow(canvas.height()-1),
  635.       bytes_per_row(canvas.bytes_per_row())
  636.   {
  637.     assert( LockPixels(pixmap) );
  638.     pixels_ptr = orig_pixels_ptr = (unsigned char *)GetPixBaseAddr(pixmap);
  639.   }
  640.   ~ScanLines_Connector(void)    {  UnlockPixels(pixmap); }
  641.   
  642.   void reset(void)            { pixels_ptr = orig_pixels_ptr; }    // Reset for the new run
  643.   
  644.                                         // A connector-iteratee
  645.                                         // It is given to scan points (for every column in turn)
  646.                                         // and its' job to connect the point via a vertical
  647.                                         // line
  648.   void operator() (const ThreeDViewBase::OneViewScanline::ScanPoint& from_point,
  649.                      const ThreeDViewBase::OneViewScanline::ScanPoint& to_point)
  650.   {
  651.     draw_vert_line(from_point,to_point);    // The real job is done by somebody else
  652.     pixels_ptr++;                            // we merely advance the (cached) curr scan pointer
  653.   }
  654.     
  655. };
  656.  
  657. /*
  658.  *----------------------------------------------------------------------
  659.  *                Root module to draw a projection of the world
  660.  *                        in an offscreen graf world
  661.  */
  662.  
  663.                                     // Prepare the buffer (eg, clean it) 
  664. void ThreeDViewBase::prepaint(void)
  665. {
  666.   clear();
  667. }
  668.  
  669. template <class LineProjector>
  670. void ThreeDView<LineProjector>::project(void)
  671. {
  672.   prepaint();
  673.   
  674.   LineProjector projector(map, viewer_pos, projection_parms);
  675.  
  676.   ScanLineIterator<LineProjector> fill_scanline_1(scanline1);
  677.   ScanLineIterator<LineProjector> fill_scanline_2(scanline2);
  678.   ScanLineIterator<LineProjector> * prev_scanline = &fill_scanline_1,
  679.                                         * curr_scanline = &fill_scanline_2;
  680.   
  681.   ScanLines_Connector scanlines_connectee(*this);
  682.                                                 // note curr_scanline ^ pointer_diff
  683.                                                 // gives prev_scanline, and
  684.                                                 // vice versa
  685.   const int pointer_diff = (long int)curr_scanline ^ (long int)prev_scanline;
  686.   
  687.   (*prev_scanline).for_each(projector);
  688.  
  689.   while( projector.can_go_closer() )
  690.   {
  691.     (*curr_scanline).for_each(projector);
  692.     TwoScanLinesIterator<ScanLines_Connector>
  693.         scanlines_connector(prev_scanline->get_scanline(),curr_scanline->get_scanline());
  694.     scanlines_connector.for_each(scanlines_connectee);
  695.  
  696.                                             // exchange pointers
  697.     (long int&)prev_scanline ^= pointer_diff;
  698.     (long int&)curr_scanline ^= pointer_diff;
  699.  
  700.     projector.move_closer();
  701.     if( !projector.q_looking_near() )        // Unless we're projecting near-view objects,
  702.       projector.move_closer();                // we decrement the depth at a faster rate...
  703.     if( projector.q_looking_far() )
  704.       projector.move_closer();
  705.     scanlines_connectee.reset();
  706.   }
  707.  
  708.  
  709.  
  710.   {                                            // Handle the last scanline
  711.    ScanLineIterator<LastScanLineCleaner> last_scanline_filler(curr_scanline->get_scanline());
  712.    last_scanline_filler.for_each(LastScanLineCleaner());
  713.     TwoScanLinesIterator<ScanLines_Connector>
  714.         scanlines_connector(prev_scanline->get_scanline(),last_scanline_filler.get_scanline());
  715.     scanlines_connector.for_each(scanlines_connectee);
  716.   }
  717. }
  718.  
  719.                         // Tell me about this view
  720. void ThreeDViewBase::dump(const char title []) const
  721. {
  722.   map.dump();
  723.   viewer_pos.dump();
  724.   projection_parms.dump();
  725. }
  726.