home *** CD-ROM | disk | FTP | other *** search
/ The Datafile PD-CD 3 / PDCD_3.iso / utilities / utilsf / graphtext / Eddie2 < prev   
Encoding:
Internet Message Format  |  1995-06-12  |  9.5 KB

  1. To: A.Pereira@cs.ucl.ac.uk
  2. From: Eddie Edwards <eddie@powerslv.demon.co.uk>
  3. Subject: Re: Fast texture coordinate calculation
  4. Reply-To: eddie@powerslv.demon.co.uk
  5. Date: Tue, 02 May 1995 20:23:49 +0100
  6. Message-ID: <19950502.202349.32@powerslv.demon.co.uk>
  7. Organization: I Inhaled
  8. X-Mailer: Archimedes TTFN Version 0.36
  9.  
  10.  > Cool! Maybe we'll be able to figure out how Michael Abrash managed to
  11.  > get all of Quake's divides for free!
  12.  
  13.  *All* of them - now that's impressive.  What I've worked out is somewhat
  14.  more modest than that.  It can only be used to speed up a series of
  15.  'similar' divides (I'll define 'similar' in a minute!).  So it's not much
  16.  use for, say, doing perspective transformations on random groups of
  17.  points.  In fact, I'd be amazed if it was *possible* to eliminate all the
  18.  divides from a 3D game - but then, that's Michael Abrash for you ;-)
  19.  
  20.  I'll try not to get *too* mathematical, but it is a mathematical trick so
  21.  it's inevitable to some extent.
  22.  
  23.  PROBLEM
  24.  =======
  25.  
  26.  To calculate u in the fast texture coordinate calculation for DOOM-style
  27.  walls (see previous post) requires a divide per strip.  We don't want that.
  28.  
  29.  ANALYSIS
  30.  ========
  31.  
  32.  The divide occurs in the following statement:
  33.  
  34.  u = (128 * y2)/height
  35.  
  36.  y2 and height are both functions of x (the integer loop variable,
  37.  representing the current strip's x coordinate) and hence u is too.
  38.  Assuming for simplicity that x starts at 0 (and goes upwards in increments
  39.  of 1),
  40.  
  41.  y2 = y2inc * x
  42.  height = lheight + (heightinc * x)
  43.  
  44.  Since y2inc, lheight and heightinc are arbitrary fixed-point numbers, what
  45.  we need is a general way of calculating the value of
  46.  
  47.  A.n / (C.n + D)
  48.  
  49.  For A,C,D any fixed-point numbers and n an integer going from zero upwards.
  50.  
  51.  In fact, why not make the work pay off later and say we want to calculate
  52.  
  53.  (A.n + B) / (C.n + D)
  54.  
  55.  Then it might come in handy in another program ;-)
  56.  
  57.  THOUGHTS
  58.  ========
  59.  
  60.  Well, the Bresenham line algorithm is famous for eliminating the divide and
  61.  using only integer arithmetic.  It basically calculates values of the
  62.  function
  63.  
  64.  A.x / B
  65.  
  66.  without calculating A/B first.  I am going to introduce a new way of
  67.  looking at the Bresenham algorithm, in terms of integer equalities.
  68.  
  69.  Firstly, this assumes *fixed point* numbers.  This is important, because
  70.  there must exist a value X such that A.X is an integer for any value of
  71.  A.  For, say, 16.16 fixed-point numbers, this value is 65536.  Basically,
  72.  you're just 'forgetting' the point, but this isn't as easy to do with
  73.  floating point, and FP is too slow anyway (certainly without a copro ;-)
  74.  
  75.  Since y is an integer (it's a pixel coordinate) and x is too, then when we
  76.  write
  77.  
  78.  y = A.x / B
  79.  
  80.  we are lying!  y is an integer, but A.x / B is a rational number and very
  81.  probably *not* an integer.  They are almost certainly *not* equal!  To make
  82.  this into an *equality* we must add an error term:
  83.  
  84.  y = A.x / B + e
  85.  
  86.  e is the actual error in the calculation.  I always round down (rounding to
  87.  nearest or rounding up can be implemented using offsets to the original
  88.  values, as in Bresenham, if you prefer), so to get the best value of y we
  89.  must have e in the range 0 (inclusive) to 1 (exclusive).
  90.  
  91.  I'm going to stop there and move back to the problem of
  92.  (A.n + B) / (C.n + D).
  93.  The methods applied later can be brought back and applied to the Bresenham
  94.  equality above, and Bresenham's algorithm can be derived (as an exercise
  95.  for the reader ;-)
  96.  
  97.  DERIVING THE SOLUTION
  98.  =====================
  99.  
  100.  For the problem in hand, the equation is:
  101.  
  102.  y = (A.x + B) / (C.x + D) + e         0 <= e < 1
  103.  
  104.  Let's multiply through by (C.x + D) to get rid of that divide
  105.  
  106.  y.(C.x + D) = A.x + B + e.(C.x + D)   0 <= e < 1
  107.  
  108.  Trick #1: e is just a random error, and we don't care what value it has.
  109.            We just need to limit it's value so that we get the right value
  110.            of y.  Therefore, just rename e.(C.x + D) to f and change the
  111.            limits of f to be between 0 and (C.x + D).  Then rename f to e
  112.            again ;-)
  113.  
  114.  y.(C.x + D) = A.x + B + e             0 <= e <= (C.x + D)
  115.  
  116.  Now, multiply through by 65536 and every fixed-point number becomes an
  117.  integer.
  118.  Simply forget the points on all the numbers.  Easy!  The above is now an
  119.  integer equality.
  120.  
  121.  The algorithm must take a value of y and x, increment x, and return a
  122.  value of y for the new x.  This might hurt ...
  123.  
  124.  Keep the equality 'God'.  That equation above must always be satisfied.  If
  125.  you change the equation by adding something to both sides, or switching
  126.  values around, you must still have a valid equation.
  127.  
  128.  We want to replace all the 'x's in the equation above with '(x+1)'s.  We
  129.  do so in two stages, at all times *keeping the equality*.  First we change
  130.  the right x to x+1, and to keep the equality we subtract A from e:
  131.  
  132.  y.(C.x + D) = A.(x+1) + B + e'     e' = (e - A)                  (1)
  133.  
  134.  But now e' might be out of range.  We check to see if it is (only one check
  135.  is needed if you know the sign of A in advance) and if it is we adjust y by
  136.  units of 1, and adjust e by units of (C.x + D) (again, keeping the
  137.  equality) until it is correct.  This is exactly analogous to the Bresenham
  138.  step.
  139.  
  140.  Now adjust x on the left:
  141.  
  142.  y.(C.(x+1) + D) = A.(x+1) + B + e"     e" = (e' + C.y)           (2)
  143.  
  144.  Then adjust e" and y (using (C.(x+1) + D) as the adjustment) until e" is in
  145.  the *new* range 0 to (C.(x+1) + D).
  146.  
  147.  It's almost like mutating the numbers until they form the next equation in
  148.  the set.  Look at what we have:
  149.  
  150.  y'.(C.x' + D) = A.x' + B + e'
  151.  
  152.  x' is x+1, e' is some new error - don't care what it is, but it's between
  153.  0 and (C.x' + d).  Which means that y' is the number we are after!
  154.  Hoorah! ;-)
  155.  
  156.  To start the ball rolling, we just need the value of y and e for x = 0.
  157.  This is just y = B/D and e = Dy - B (note that e will not be zero because
  158.  of the finite precision of the divide).  If B happens to be zero, y starts
  159.  at zero and not one divide is needed.  This is the case in the
  160.  implementation below.
  161.  
  162.  IMPLEMENTATION
  163.  ==============
  164.  
  165.  You can see a lot of interlacing of work here.  The calculation of C.y
  166.  looks expensive, but in fact you need merely keep a running total which
  167.  you adjust when you adjust y.
  168.  
  169.  Here's the full algorithm, plus a few typos (undoubtedly!) in pseudo-C.
  170.  
  171.  map(lx,lh,lt,ly,rx,rh,rt,ry,scrn)
  172.  /*
  173.     l* and r* are for the left and right sides of the wall to be drawn
  174.     lx,rx - x coordinates
  175.     lh,rh - heights
  176.     lt,rt - texture x coordinates
  177.     ly,ry - top pixel of strip
  178.  
  179.     All these are ints, and the left ones are inclusive values while the
  180.     right ones are exclusive.  I.e. we start at the left values and
  181.     approach, but never quite reach, the right values.
  182.  
  183.     scrn is a pointer to the frame buffer, which is 320 pixels wide
  184.  */
  185.  
  186.  w = rx - lx                         // width of wall in pixels
  187.  h = lh << 16                        // height in fixed-point ( > 0 )
  188.  hinc = ((rh - lh) << 16) / w        // height increment per x pixel
  189.  y = ly << 16                        // top y in fixed-point ( > 0 )
  190.  yinc = ((ry - ly) << 16) / w        // y increment per x pixel
  191.  t = lt                              // current value of texture index (int)
  192.  hinct = 0                           // current value of C.x for calcs
  193.  et = 0                              // current value of error term
  194.  esub = (((rt - lt) << 16) / w) * rh // the value subtracted first from e
  195.                                      // note that A is greater than zero
  196.  
  197.  for (i = lx ; i < rx ; i++)
  198.  {
  199.    sp = scrn + (y >> 16)*320 + i     // not a multiply, 2 shifts
  200.  
  201.    plot_strip(sp, h >> 16, t)        // (target, height, texture coordinate)
  202.  
  203.    et -= esub                        // now in equation (1)
  204.  
  205.    while (et < 0)                    // only need to check for less than
  206.    {                                 // as A is positive
  207.      et += h                         // adjust et
  208.      t += 1                          // adjust t
  209.      hinct += hinc                   // adjust C.t
  210.    }
  211.  
  212.    h += hinc                         // adjust (C.x + D) - the height
  213.    et += hinct                       // adjust et by C.t (equation (2))
  214.  
  215.    while (et < 0)                    // this loop not needed if hinc < 0
  216.    {
  217.      et -= h                         // adjust et down
  218.      t -= 1                          // adjust t down
  219.      hinct -= hinc                   // adjust C.t down
  220.    }
  221.  
  222.    while (et > 0)                    // this loop not needed if hinc > 0
  223.    {
  224.      et += h
  225.      t += h
  226.      hinct += hinc
  227.    }
  228.  
  229.    y += yinc                         // update y coordinate of wall
  230.  }
  231.  
  232.  EXTENSIONS
  233.  ==========
  234.  
  235.  In plot_strip you'll probably want to divide the texture height by the
  236.  strip height, to get an increment value.  This expression is of the form:
  237.  
  238.  A / (C.x + D)
  239.  
  240.  and can also be calculated using this method.  Note that the divides at the
  241.  start (3 of them) are all by the same number, so only one divide is really
  242.  needed.  Add the divide to get the initial value A/D (or texture height
  243.  over initial strip height) for the plot_strip increment, and thats two
  244.  divides per polygon!  Not bad ;-)
  245.  
  246.  Note the following fascinating fact:  If hinc > 0, then *every* while loop
  247.  increments t by 1.  Therefore, the total number of while loops executed is
  248.  exactly equal to (rt - lt).  This limits the overall cost of the algorithm.
  249.  If hinc < 0, we can just write another function which plots walls
  250.  backwards and brings hinc > 0 again ;-)
  251.  
  252.  TO THOSE WHO SNEER AT DOOM-STYLE WALLS
  253.  ======================================
  254.  
  255.  In the future, when Owls have Virtual Reality, these algorithms will become
  256.  important once more.  <grin>
  257.  
  258. --
  259. Eddie xxx (eddie@powerslv.demon.co.uk, ee@datcon.co.uk, leg@lize.it)