home UCM22

-----

.tHE .sIRIUS .cYBERNETICS .cORPORATION


UCM 22
--------------------------------------------------------------------------------
                     Ray spends some time building a theory
--------------------------------------------------------------------------------

Hiya fellas!

A little word in  advance: this is a  theory so please don't wonder why I didn't
write any actual  code in here. To make the idea as understandable as possible I
used some c-style pseudo-code.

Browsing the  net I found some  document that described  an advanced ray-casting
method which enables the computer to render a *DOOM*like maze without using bsp-
trees or anything even more complicated.
(note: I said "doomlike" and  I meant "doomlike" not "wolf3d-like" like  some of
those fakie  docs floating around  out there tend to do - i.e. sector based maps
and non-orthogonal walls).

I hope you're familiar  with the term "raycasting", if this is not the case here
is some very brief introduction.
Raycasting is a method to render a 3d scene based on a 2d map usually consisting
of sqare-cells. Depending on  the player's position  on that  2d map as  well as
depending on  his facing we cast out one ray for every screencolumn until we hit
a cell with  a content <> 0 which means that we've hit a wall - once we have hit
a wall we work  out the distance between the player and this wall-hit to scale a
textureslice  to the apropriate  height into the  current screencolumn  centered
vertically.
This way our scene  is build sliver by sliver. It's exactly the techniqe used by
Wolfenstein 3d  and however  it has some  drawbacks like  the fact  that all the
walls have to be orthrogonal meaning the angle between 2 walls will always be 90
degrees.

Now what the hell is special about this advanced method?!
Well, first  of all  it's quite  easy to  code compared  to let's say a bsp-tree
engine like the one used  in id-softwares original Doom. Another thing is that I
found it  perfectly  suitable for  an 8Mhz ST  as it  doesn't  perform  too many
instructions  per screenslice - comparing  it to "conventional  raycasting" it's
not *much* more cycle consumptive.
As  you  all  know, there  already  are  some  wolf3d  clones  for the ST/E like
Substation, Destruction Imminent  or at last  my own hopefully upcoming  port of
the original game - hence  an ST should  be at least  able to  render a doomlike
maze, even if this wouldn't become a complete game.

(I'm writing  this text to inspire  you, maybe you'll use this technique in your
demos or  anything - concerning me, I'm busy  with Wolfenstein 3d  atm, so  this
isn't supposed to become more than a theory, at least for this time - maybe I'll
use this algorithm to build something after wolf3d, though, who knows :) ).

So let's get  into it - the first striking  diffrence is that our 2d map will no
longer be cell-based, we'll  rather use sectors that can be defined  without any
restrictions except for the 16 or 32bit-ranges.
First of  all we'll have  to define  a list  of lines  that will  be used in our
world, those  represent our walls (this is done by using the start and end-point
of those  lines - x1,y1/x2,y2) - now every sector consists of a number of walls/
lines  used by  this sector  followed by  an according  bunch of pointers to the
line-list determining the sector's edges.
Of course, two or more sectors might be connected by something like a "portal" -
those sector-transitions  will be marked  as transparent walls, possibly using a
flag in our  line-list, if you choose a certain range for the line's coordinates
fx. [0;32767] - you  might or' that  flag into  the 15th bit  of x1 to save some
space (x1,y1,x2,y2 ought to be words in my case).
Ok, that much on the world-representation.

Next thing, just  like in  wolf3d, is  to check  intersections  between rays and
walls. What you  basically do again  is tracing out a ray for  each screencolumn
While calculating intersections with walls.
And here it  differs *alot* from wolf3d  because instead  of casting through the
map  cellstep  by  cellstep  and then  checking  for a  wall, you  will  have to
calculate  an intersection  with every  wall (line) of  the sector the player is
standing in, then, as  soon as you find a "portal" you recursively jump into the
sector that  is connected through  this transition (hence  it should  be save to
build a portal-list for every sector keeping  the number of the  next sector for
each transition, too) and so on.

Since both your ray and all the walls are lines you might equate and resolve the
following way:

     ray(x)  = rayslope*x  + b1    ; the ray's equation
     wall(x) = wallslope*x + b2    ; the current line's equation

The ray's  slope depends  on the  current angle of the ray and it's equal to tan
(rayangle). the wall's slope can be computed  as (y2-y1)/(x2-x1) - pre-calculate
and put it into the line-list for optmization.

To make them intersect, do this:

     wall(x)       = ray(x)
     rayslope*x+b1 = wallslope*x+b2

We want x, but  b1 and b2  are unknown - to  get rid  of this  problem  make the
coordinate-system's  origin  move  around  with  the player, meaning the player-
position will be the origin and b1 will be 0:

    b2 = (playerx-x1)*wallslope+(playery-y1)   (x1 and y1 are taken
                  from the current line)

  further:

    rayslope*x = wallslope*x+(playerx-x1)*wallslope+(playery-y1)
    x*(rayslope-wallslope) = (playerx-x1)*wallslope+(playery-y1)

    x = (playerx-x1)*wallslope+(playery-y1)/(rayslope-wallslope)

  that's all, now you may easily use the wall's equation to calclate y:

    y = y1+(x-x1)*wallslope

  and there's your intersection with the current wall.

Well I hear you say, "Which intersection will be the right one ?". That's easy -
as you might  have noticed, you'll at  least find as many intersections as there
are lines in the current sector.
To find out  the proper intersection you first of all sort out all intersections
that will be behind the player - you get those intersections since your ray is a
straight line, following ascii-pic might visualise it:

       intersection in front of the player
              ____ ___________________
             /     \                  |
            /       \ ray             |
           /         \                |
          /           \               |
         /             * player       |
        /  sector       \             |
       /                 \            |
      /___________________\___________|

          intersection behind 'em


So before you  determine the distance  based on an intersection you have to find
out if  this intersection  is in  front  of the  player, i.e. you  test if  it's
visible and if you have to calc the distance, at all.
This can be done  by comparing the quadrant of the ray and the quadrant in which
the  intersection  appears - for  the ray  just use  the rayangle, here  is some
pseudo-code:

  byte GetRayQuadrant(Angle)
  {
     if (Angle >= 0deg && Angle < 90deg) return 1;    // 1st quadrant
     if (Angle >= 90deg && Angle < 180deg) return 2;  // 2nd    "
     if (Angle >= 180deg && Angle < 270deg) return 3; // 3rd    "
     if (Angle >= 270deg && Angle < 360deg) return 4; // 4th    "
  }


  the intersection's quadrant might be determined using deltaX and deltaY
  (deltaX = PlayerX-IntersectionX, deltaX = PlayerY-IntersectionY):

  byte GetIntersectionQuadrant(deltaX,deltaY)
  {
       if (deltaX > 0)
       { if (deltaY > 0) return(4); else return(1); }
       else
       { if (deltaY > 0) return(3); else return(2); }
  }


The next  problem  is that  in front  of you  there'll  often  be more  than one
intersection because you don't take into acount where a wall starts and where it
ends, meaning  that your intersection-test doesn't mind if a ray just passes the
current wall, actually.
This can be solved easily since the intersection that has the shortest  distance
to the player will be the valid one.

Implemented in pseudo-code your intersection test could look the following way:

  word TestIntersection(Sector,Rayslope,Angle) // returns the distance to
  {                                            // next valid intersection

  int     deltaX,deltaY,
     xs,ys,        // player to wall deltas
     distance,
     newdistance,
     x1,y1,x2,y2;
     ix,iy;        // intersection
  byte    rayquadrant;

  float   wallslope;

  word    wall;
  offset  wallnumber;

   distance = 32767; // some great value

   rayquadrant = GetRayQuadrant(Angle);

   for wall = 0 to SectorList[Sector].number_of_walls-1
   {
      // get the right wall
     wallnumber =
        WallOffsetList[SectorList[Sector].firstpointer+wall];

      // recursively cast into the next sector if the wall is a portal
      if (linelist[wallnumber].portal == true) { distance =
         TestIntersection(portallist[Sector,wallnumber],Rayslope); }
      else
      {
       x1 = linelist[wallnumber].x1;  // get apropriate line
       y1 = linelist[wallnumber].y1;
       x2 = linelist[wallnumber].x2;
       y2 = linelist[wallnumber].y2;

       wallslope = linelist[wallnumber].slope;

       xs = playerx-x1;
       ys = playery-y1;

       // compute intersection
       ix = xs*wallslope+ys/(rayslope-wallslope);
       iy = y1+(ix-x1)*wallslope;

       deltaX = playerx-ix;     // intersection deltas
       deltaY = playery-iy;

       // calcualte distance if intersection is visible
       if (GetIntersectionQuadrant(deltaX,deltaY) == RayQuadrant)
       {
          // use 1/cos(Angle) or 1/sin(Angle to get the
          // distance depending on deltaX or deltaY
          newdistance = getdistance(deltaX,deltaY,Angle);
       }

       // the smaller distance will be the valid one
       if (newdistance < distance) { distance = newdistance; }
      }
   }
   return(distance); // return last valid distance
  }


Basically this  is all to say  about the  whole topic, not too hard, eh? However
there're  still many things  to do like  playermovement and  collision-detection
with walls (don't  forget to set  the PlayerSector  up to the next sector if the
player walked  through a  portal), as well  as in  wolf you  have to correct the
fisheye-bug...and anyway this  engine would be  far from Doom since there aren't
any variable floor-/ceiling-heights, but those could be easily added if you save
additional height information for every sector to scale your  texture-slivers to
the right  screenrow instead  of just centering  them to the  horizon - this way
your worlds might even contain steps !!
Don't forget to  handle special cases  with wall- or rayslopes of 0 or infinity,
either.

Just be creative  to make something  out of it, if you should do so one day, let
me know - otherwise I'll let you  people know as soon as I made something out of
it :).


In the  end, one  general hint and  a pseudo-code  example of the  world render-
procedure:

If you decide  to map textures onto your slices, there really is only one option
that makes  sense for on the ST - use a nibble-chunkybuffer with a codetable-c2p
+ precompiled  texturescalers for all the possible sliver-heights...that way you
might gain an acceptable vbl useage, even on an 8Mhz ST (at least that's the way
I'm using in the new beta of my wolf3d-engine :) ).

So here comes the render-module:

  void render(playerSector,playerX,playerY,Angle)
  {
  int   distance;
  byte  texuresliver;
  word  screencolumn;
  float rayslope;   // use fixedpoint for an 68k implementation :)

       for screencolumn = 0 to screencolumns-1
       {
      Rayslope      = tan(Angle);
      distance      = TestIntersection(playerSector,Rayslope,Angle);
      texturesliver = lastintersection; // this might be easi to imple-
                    // ment, though

      correct_distance(screencolumn); // cancel fisheye-bug using a
                  // correction cosine


      mapsliver (screencolumn,distance,texturesliver) // maybe you could
                        // even assign
                        // your walls with
                        // diffrent textu-
                        // res to add a
                        // texturenumber
                        // here ;)

      inc(Angle);          // next ray
      keepangle_in_range;  // ensure that Angle stays in the 0..360deg
            // range
       }
  }


If you have questions or comments please drop an email to:
reimund.dratwa@freenet.de
- I haven't tested this method yet so it might be completely wrong, in that case
I'm sorry - at least  the idea works out  fine and one maybe even more pc guy(s)
has/have already implemented it:

Regards have to go to Andreas Chrisitan Seidel who seems to have worked out this
algorithm, actually - check his implementation and read his tute at:

http://www.acs-home.de/rex3d
http://www.gamedev.net/reference/articles/article872.asp


ray//.tSCc.                                                             03-16-02
--------------------------------------------------------------------------------

UCM 22