changeset 24:b2dc19044bf3

Import simple 3D preview, an old patch by Andrew Apted. Pressing 'R' key enables it, use arrows etc. to move.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 24 Sep 2011 13:12:09 +0300
parents ad9685c8cef1
children 8eaf72e2041b
files GNUmakefile cache/pixlist docsrc/index.html src/editloop.cc src/gcolour1.cc src/gcolour2.cc src/gcolour2.h src/r_images.cc src/r_images.h src/r_render.cc src/r_render.h
diffstat 11 files changed, 1788 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/GNUmakefile	Sat Sep 24 13:04:21 2011 +0300
+++ b/GNUmakefile	Sat Sep 24 13:12:09 2011 +0300
@@ -159,7 +159,7 @@
 	s_misc		s_prop		s_slice		s_split		\
 	s_swapf		s_vertices	sanity		scrnshot	\
 	selbox		selectn		selpath		selrect		\
-	serialnum	spritdir	sticker		\
+	serialnum	spritdir	sticker		r_render	r_images	\
 	t_centre	t_flags		t_prop		t_spin		\
 	textures	things		trace		v_centre	\
 	v_merge		v_polyg		vectext		verbmsg		\
@@ -237,6 +237,7 @@
 	docsrc/legal.html		\
 	docsrc/packagers_guide.html	\
 	docsrc/palette.html		\
+	docsrc/preview.html		\
 	docsrc/reporting.html		\
 	docsrc/tips.html		\
 	docsrc/trivia.html		\
--- a/cache/pixlist	Sat Sep 24 13:04:21 2011 +0300
+++ b/cache/pixlist	Sat Sep 24 13:12:09 2011 +0300
@@ -1,8 +1,8 @@
 002f2f.png
+e1.png
 E1.png
+e2.png
 E2.png
-e1.png
-e2.png
 logo.png
 logo_small.png
 mirror0.png
--- a/docsrc/index.html	Sat Sep 24 13:04:21 2011 +0300
+++ b/docsrc/index.html	Sat Sep 24 13:12:09 2011 +0300
@@ -34,6 +34,7 @@
 
 <ul>
 <li><a href="palette.html">Palette viewer</a>
+<li><a href="preview.html">3D Level Preview</a>
 <li><a href="advanced.html">Advanced user's guide</a>
 <li><a href="../TODO"><code>TODO</code></a>
 <li><a href="yadex.6">The man page for Yadex</a>
--- a/src/editloop.cc	Sat Sep 24 13:04:21 2011 +0300
+++ b/src/editloop.cc	Sat Sep 24 13:12:09 2011 +0300
@@ -69,6 +69,7 @@
 #include "x_exchng.h"
 #include "x_hover.h"
 #include "xref.h"
+#include "r_render.h"
 
 
 static int zoom_fit (edit_t&);
@@ -2229,6 +2230,13 @@
          DumpSelection (e.Selected);
          }
 
+      // [R] Render 3D view (AJA)
+      else if (is.key == 'R')
+        {
+        Render3D ();
+        RedrawMap = 1;
+        }
+
       // [T] Transfer properties to selected objects (AJA)
       else if (is.key == 'T' && e.Selected 
             && e.highlighted.num >= 0)
--- a/src/gcolour1.cc	Sat Sep 24 13:04:21 2011 +0300
+++ b/src/gcolour1.cc	Sat Sep 24 13:12:09 2011 +0300
@@ -127,6 +127,21 @@
   }
   verbmsg ("colours: colour %d remapped to %d (delta %d)\n",
     IMG_TRANSP, colour0, smallest_delta);
+   
+   rgb_c med_blue (0, 0, 128);
+   sky_colour = 0;
+   smallest_delta = INT_MAX;
+ 
+   for (size_t n = 0; n < DOOM_COLOURS; n++)
+   {
+     int delta = med_blue - rgb_values[n];
+     if (delta < smallest_delta)
+     {
+       sky_colour = n;
+       smallest_delta = delta;
+     }
+   }
+   verbmsg ("Sky Colour remapped to %d (delta %d)\n", sky_colour, smallest_delta);
 }
 
 #endif
--- a/src/gcolour2.cc	Sat Sep 24 13:04:21 2011 +0300
+++ b/src/gcolour2.cc	Sat Sep 24 13:12:09 2011 +0300
@@ -35,4 +35,5 @@
 
 pcolour_t *game_colour = 0;	// Pixel values for the DOOM_COLOURS game clrs.
 int colour0;			// Game colour to which g. colour 0 is remapped
+int sky_colour;			// Game colour for a medium sky blue
 
--- a/src/gcolour2.h	Sat Sep 24 13:04:21 2011 +0300
+++ b/src/gcolour2.h	Sat Sep 24 13:12:09 2011 +0300
@@ -10,4 +10,5 @@
 
 extern pcolour_t *game_colour;  // Pixel values for the DOOM_COLOURS game clrs.
 extern int colour0;		// Game colour to which g. colour 0 is remapped
+extern int sky_colour;		// Game colour for a medium blue sky
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/r_images.cc	Sat Sep 24 13:12:09 2011 +0300
@@ -0,0 +1,400 @@
+/*
+ *	r_images.cc
+ *	AJA 2002-04-23 (based on textures.cc and flats.cc)
+ */
+
+
+/*
+This file is part of Yadex.
+
+Yadex incorporates code from DEU 5.21 that was put in the public domain in
+1994 by Raphaël Quinet and Brendon Wyber.
+
+The rest of Yadex is Copyright © 1997-2000 André Majorel.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+Place, Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+
+#include "yadex.h"
+#ifdef Y_X11
+#include <X11/Xlib.h>
+#endif
+#include "dialog.h"
+#include "game.h"      /* yg_picture_format */
+#include "gfx.h"
+#include "levels.h"
+#include "lists.h"
+#include "patchdir.h"
+#include "pic2img.h"
+#include "sticker.h"
+#include "flats.h"
+#include "textures.h"
+#include "wadfile.h"
+#include "wads.h"
+#include "wadres.h"
+#include "wstructs.h"
+
+#include "r_images.h"
+
+
+/*
+ *	flat_list_entry_match
+ *	Function used by bsearch() to locate a particular 
+ *	flat in the FTexture.
+ */
+static int flat_list_entry_match (const void *key, const void *flat_list_entry)
+{
+return y_strnicmp ((const char *) key,
+      ((const flat_list_entry_t *) flat_list_entry)->name,
+      WAD_FLAT_NAME);
+}
+
+
+/*
+ *  load a flat into a new image.  NULL if not found.
+ */
+
+Img * Flat2Img (const wad_flat_name_t& fname)
+{
+char name[WAD_FLAT_NAME + 1];
+strncpy (name, fname, WAD_FLAT_NAME);
+name[WAD_FLAT_NAME] = 0;
+
+flat_list_entry_t *flat = (flat_list_entry_t *)
+   bsearch (name, flat_list, NumFTexture, sizeof *flat_list,
+         flat_list_entry_match);
+
+if (! flat)  // Not found in list
+   return 0;
+
+int width  = DOOM_FLAT_WIDTH;  // Big deal !
+int height = DOOM_FLAT_HEIGHT;
+
+const Wad_file *wadfile = flat->wadfile;
+wadfile->seek (flat->offset);
+
+Img *img = new Img (width, height, false);
+
+wadfile->read_bytes (img->wbuf (), (long) width * height);
+
+return img;
+}
+
+
+/*
+ * load a wall texture ("TEXTURE1" or "TEXTURE2" object) into an image.
+ * Returns NULL if not found or error.
+ */
+
+Img * Tex2Img (const wad_tex_name_t& texname)
+{
+MDirPtr  dir = 0;	/* main directory pointer to the TEXTURE* entries */
+i32     *offsets;	/* array of offsets to texture names */
+int      n;		/* general counter */
+i16      width, height;	/* size of the texture */
+i16      npatches;	/* number of wall patches used to build this texture */
+i32      numtex;	/* number of texture names in TEXTURE* list */
+i32      texofs;	/* offset in the wad file to the texture data */
+char     tname[WAD_TEX_NAME + 1];	/* texture name */
+char     picname[WAD_PIC_NAME + 1];	/* wall patch name */
+bool     have_dummy_bytes;
+int      header_size;
+int      item_size;
+
+char name[WAD_TEX_NAME + 1];
+strncpy (name, texname, WAD_TEX_NAME);
+name[WAD_TEX_NAME] = 0;
+
+// Iwad-dependant details
+if (yg_texture_format == YGTF_NAMELESS)
+   {
+   have_dummy_bytes = true;
+   header_size      = 14;
+   item_size        = 10;
+   }
+else if (yg_texture_format == YGTF_NORMAL)
+   {
+   have_dummy_bytes = true;
+   header_size      = 14;
+   item_size        = 10;
+   }
+else if (yg_texture_format == YGTF_STRIFE11)
+   {
+   have_dummy_bytes = false;
+   header_size      = 10;
+   item_size        = 6;
+   }
+else
+   {
+   nf_bug ("Bad texture format %d.", (int) yg_texture_format);
+   return 0;
+   }
+
+/* offset for texture we want. */
+texofs = 0;
+// Doom alpha 0.4 : "TEXTURES", no names
+if (yg_texture_lumps == YGTL_TEXTURES && yg_texture_format == YGTF_NAMELESS)
+   {
+   dir = FindMasterDir (MasterDir, "TEXTURES");
+   if (dir != NULL)
+      {
+      dir->wadfile->seek (dir->dir.start);
+      dir->wadfile->read_i32 (&numtex);
+      if (WAD_TEX_NAME < 7) nf_bug ("WAD_TEX_NAME too small");  // Sanity
+      if (! y_strnicmp (name, "TEX", 3)
+            && isdigit (name[3])
+            && isdigit (name[4])
+            && isdigit (name[5])
+            && isdigit (name[6])
+            && name[7] == '\0')
+         {
+         long num;
+         if (sscanf (name + 3, "%4ld", &num) == 1
+               && num >= 0 && num < numtex)
+            {
+            dir->wadfile->seek (dir->dir.start + 4 + 4 * num);
+            dir->wadfile->read_i32 (&texofs);
+            texofs += dir->dir.start;
+            }
+         }
+      }
+   }
+// Doom alpha 0.5 : only "TEXTURES"
+else if (yg_texture_lumps == YGTL_TEXTURES
+      && (yg_texture_format == YGTF_NORMAL || yg_texture_format == YGTF_STRIFE11))
+   {
+   // Is it in TEXTURES ?
+   dir = FindMasterDir (MasterDir, "TEXTURES");
+   if (dir != NULL)  // (Theoretically, it should always exist)
+      {
+      dir->wadfile->seek (dir->dir.start);
+      dir->wadfile->read_i32 (&numtex);
+      /* read in the offsets for texture1 names and info. */
+      offsets = (i32 *) GetMemory ((long) numtex * 4);
+      dir->wadfile->read_i32 (offsets, numtex);
+      for (n = 0; n < numtex && !texofs; n++)
+         {
+         dir->wadfile->seek (dir->dir.start + offsets[n]);
+         dir->wadfile->read_bytes (&tname, WAD_TEX_NAME);
+         if (!y_strnicmp (tname, name, WAD_TEX_NAME))
+            texofs = dir->dir.start + offsets[n];
+         }
+      FreeMemory (offsets);
+      }
+   }
+// Other iwads : "TEXTURE1" and "TEXTURE2"
+else if (yg_texture_lumps == YGTL_NORMAL
+      && (yg_texture_format == YGTF_NORMAL || yg_texture_format == YGTF_STRIFE11))
+   {
+   // Is it in TEXTURE1 ?
+   dir = FindMasterDir (MasterDir, "TEXTURE1");
+   if (dir != NULL)  // (Theoretically, it should always exist)
+      {
+      dir->wadfile->seek (dir->dir.start);
+      dir->wadfile->read_i32 (&numtex);
+      /* read in the offsets for texture1 names and info. */
+      offsets = (i32 *) GetMemory ((long) numtex * 4);
+      dir->wadfile->read_i32 (offsets, numtex);
+      for (n = 0; n < numtex && !texofs; n++)
+         {
+         dir->wadfile->seek (dir->dir.start + offsets[n]);
+         dir->wadfile->read_bytes (&tname, WAD_TEX_NAME);
+         if (!y_strnicmp (tname, name, WAD_TEX_NAME))
+            texofs = dir->dir.start + offsets[n];
+         }
+      FreeMemory (offsets);
+      }
+   // Well, then is it in TEXTURE2 ?
+   if (texofs == 0)
+      {
+      dir = FindMasterDir (MasterDir, "TEXTURE2");
+      if (dir != NULL)  // Doom II has no TEXTURE2
+         {
+         dir->wadfile->seek (dir->dir.start);
+         dir->wadfile->read_i32 (&numtex);
+         /* read in the offsets for texture2 names */
+         offsets = (i32 *) GetMemory ((long) numtex * 4);
+         dir->wadfile->read_i32 (offsets, numtex);
+         for (n = 0; n < numtex && !texofs; n++)
+            {
+            dir->wadfile->seek (dir->dir.start + offsets[n]);
+            dir->wadfile->read_bytes (&tname, WAD_TEX_NAME);
+            if (!y_strnicmp (tname, name, WAD_TEX_NAME))
+               texofs = dir->dir.start + offsets[n];
+            }
+         FreeMemory (offsets);
+         }
+      }
+   }
+else
+   nf_bug ("Invalid texture_format/texture_lumps combination.");
+
+/* texture name not found */
+if (texofs == 0)
+   return 0;
+
+/* read the info for this texture */
+i32 header_ofs;
+if (yg_texture_format == YGTF_NAMELESS)
+   header_ofs = texofs;
+else
+   header_ofs = texofs + WAD_TEX_NAME;
+dir->wadfile->seek (header_ofs + 4);
+dir->wadfile->read_i16 (&width);
+dir->wadfile->read_i16 (&height);
+if (have_dummy_bytes)
+   {
+   i16 dummy;
+   dir->wadfile->read_i16 (&dummy);
+   dir->wadfile->read_i16 (&dummy);
+   }
+dir->wadfile->read_i16 (&npatches);
+
+/* Compose the texture */
+Img *texbuf = new Img (width, height, false);
+
+/* Paste onto the buffer all the patches that the texture is
+   made of. */
+for (n = 0; n < npatches; n++)
+   {
+   i16 xofs, yofs;	// offset in texture space for the patch
+   i16 pnameind;	// index of patch in PNAMES
+
+   dir->wadfile->seek (header_ofs + header_size + (long) n * item_size);
+   dir->wadfile->read_i16 (&xofs);
+   dir->wadfile->read_i16 (&yofs);
+   dir->wadfile->read_i16 (&pnameind);
+
+   if (have_dummy_bytes)
+      {
+      i16 stepdir;
+      i16 colormap;
+      dir->wadfile->read_i16 (&stepdir);   // Always 1, unused.
+      dir->wadfile->read_i16 (&colormap);  // Always 0, unused.
+      }
+
+   /* AYM 1998-08-08: Yes, that's weird but that's what Doom
+      does. Without these two lines, the few textures that have
+      patches with negative y-offsets (BIGDOOR7, SKY1, TEKWALL1,
+      TEKWALL5 and a few others) would not look in the texture
+      viewer quite like in Doom. This should be mentioned in
+      the UDS, by the way. */
+   if (yofs < 0)
+      yofs = 0;
+
+   Lump_loc loc;
+      {
+      wad_pic_name_t *wname = patch_dir.name_for_num (pnameind);
+      if (wname == 0)
+         {
+         warn ("texture \"%.*s\": patch %2d has bad index %d.\n",
+               WAD_TEX_NAME, tname, (int) n, (int) pnameind);
+         continue;
+         }
+      patch_dir.loc_by_name ((const char *) *wname, loc);
+      *picname = '\0';
+      strncat (picname, (const char *) *wname, sizeof picname - 1);
+      }
+
+   if (LoadPicture (*texbuf, picname, loc, xofs, yofs, 0, 0))
+      warn ("texture \"%.*s\": patch \"%.*s\" not found.\n",
+            WAD_TEX_NAME, tname, WAD_PIC_NAME, picname);
+   }
+
+return texbuf;
+}
+
+
+/* --- ImageCache methods --- */
+
+
+Img *ImageCache::GetFlat (const wad_flat_name_t& fname)
+{
+std::string f_str = WadToString(fname);
+
+flat_map_t::iterator P = flats.find (f_str);
+
+if (P != flats.end ())
+   return P->second;
+
+// flat not in the list yet.  Add it.
+
+Img *result = Flat2Img (fname);
+flats[f_str] = result;
+
+// note that a NULL return from Flat2Img is OK, it means that no
+// such flat exists.  Our renderer will revert to using a solid
+// colour.
+
+return result;
+}
+
+
+Img *ImageCache::GetTex (const wad_tex_name_t& tname)
+{
+if (tname[0] == 0 || tname[0] == '-')
+   return 0;
+
+std::string t_str = WadToString(tname);
+
+tex_map_t::iterator P = textures.find (t_str);
+
+if (P != textures.end ())
+   return P->second;
+
+// texture not in the list yet.  Add it.
+
+Img *result = Tex2Img (tname);
+textures[t_str] = result;
+
+// note that a NULL return from Tex2Img is OK, it means that no
+// such texture exists.  Our renderer will revert to using a solid
+// colour.
+
+return result;
+}
+
+
+Img *ImageCache::GetSprite (const wad_ttype_t& type)
+{
+sprite_map_t::iterator P = sprites.find (type);
+
+if (P != sprites.end ())
+   return P->second;
+
+// sprite not in the list yet.  Add it.
+
+Img *result = 0;
+
+const char *sprite_root = get_thing_sprite (type);
+if (sprite_root)
+   {
+   Lump_loc loc;
+   wad_res.sprites.loc_by_root (sprite_root, loc);
+   result = new Img ();
+
+   if (LoadPicture (*result, sprite_root, loc, 0, 0) != 0)
+      {
+      delete result;
+      result = 0;
+      }
+   }
+
+// note that a NULL image is OK.  Our renderer will just ignore the
+// missing sprite.
+
+sprites[type] = result;
+return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/r_images.h	Sat Sep 24 13:12:09 2011 +0300
@@ -0,0 +1,69 @@
+/*
+ *	r_images.h
+ *	AJA 2002-04-27
+ */
+
+
+#ifndef YH_R_IMAGES  /* DO NOT INSERT ANYTHING BEFORE THIS LINE */
+#define YH_R_IMAGES
+
+
+#include <map>
+#include <algorithm>
+#include <string>
+
+
+struct ImageCache
+{
+public:
+   typedef std::map<std::string, Img *> flat_map_t;
+   typedef std::map<std::string, Img *> tex_map_t;
+   typedef std::map<wad_ttype_t, Img *> sprite_map_t;
+
+   flat_map_t   flats;
+   tex_map_t    textures;
+   sprite_map_t sprites;
+
+   static std::string WadToString(const wad_flat_name_t& fname)
+   {
+      int len;
+
+      for (len = 0; len < WAD_NAME && fname[len]; len++)
+      { }
+        
+      return std::string(fname, len);
+   }
+
+   static void DeleteFlat(const flat_map_t::value_type& P)
+      {
+      delete P.second;
+      }
+
+   static void DeleteTex(const tex_map_t::value_type& P)
+      {
+      delete P.second;
+      }
+
+   static void DeleteSprite(const sprite_map_t::value_type& P)
+      {
+      delete P.second;
+      }
+
+   ~ImageCache ()
+      {
+      std::for_each (flats.begin (), flats.end (), DeleteFlat);
+      std::for_each (textures.begin (), textures.end (), DeleteTex);
+      std::for_each (sprites.begin (), sprites.end (), DeleteSprite);
+
+      flats.clear ();
+      textures.clear ();
+      sprites.clear ();
+      }
+
+   Img *GetFlat   (const wad_flat_name_t& fname);
+   Img *GetTex    (const wad_tex_name_t& tname);
+   Img *GetSprite (const wad_ttype_t& type);
+};
+
+
+#endif  /* DO NOT ADD ANYTHING AFTER THIS LINE */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/r_render.cc	Sat Sep 24 13:12:09 2011 +0300
@@ -0,0 +1,1275 @@
+/*
+ *	r_render.cc
+ *	3D Rendering
+ *	AJA 2002-04-21
+ */
+
+
+/*
+This file is part of Yadex.
+
+Yadex incorporates code from DEU 5.21 that was put in the public domain in
+1994 by Raphaël Quinet and Brendon Wyber.
+
+The rest of Yadex is Copyright © 1997-2000 André Majorel.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+Place, Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+
+#include "yadex.h"
+
+#include <math.h>
+#include <vector>
+#include <map>
+#include <algorithm>
+
+#ifdef Y_X11
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#endif
+#include "levels.h"
+#include "wstructs.h"
+#include "gfx.h"
+#include "img.h"
+#include "sticker.h"
+#include "gamesky.h"
+#include "things.h"
+#include "wadres.h"
+#include "objid.h"
+#include "objects.h"
+#include "pic2img.h"
+#include "rgb.h"
+#include "gcolour2.h"
+
+#include "r_render.h"
+#include "r_images.h"
+
+
+#define ML_UPPER_UNPEGGED  0x08
+#define ML_LOWER_UNPEGGED  0x10
+
+
+struct Y_View
+{
+public:
+   int p_type, px, py;
+   // player type and position.
+
+   float x, y; 
+   int z;
+   // view position.
+
+   static const int EYE_HEIGHT = 41;
+   // standard height above the floor.
+
+   float angle;
+   float Sin, Cos;
+   // view direction.
+
+   int sw, sh;
+   Img *screen;
+   // screen image.
+
+   bool texturing;
+   bool sprites;
+   bool walking;
+
+   ImageCache *im_ch;
+
+   int *thing_floors;
+
+   Y_View () { memset (this, 0, sizeof *this); }
+
+   void SetAngle (float new_ang)
+      {
+      angle = new_ang;
+
+      if (angle >= TWOPI)
+         angle -= TWOPI;
+      else if (angle < 0)
+         angle += TWOPI;
+
+      Sin = sin (angle);
+      Cos = cos (angle);
+      }
+
+   void CalcViewZ ()
+      {
+        Objid o;
+        GetCurObject (o, OBJ_SECTORS, int (x), int (y));
+      int secnum = o.num;
+      if (secnum >= 0)
+         z = Sectors[secnum].floorh + EYE_HEIGHT;
+      }
+
+   void ClearScreen ()
+      {
+      memset (screen->wbuf (), colour0, sw * sh);
+      }
+
+   void PutScreen (int x, int y)
+      {
+      DrawScreenBox3D (x, y, x + BOX_BORDER*2 + sw, y + BOX_BORDER*2 + sh);
+
+      Sticker sticker (*screen, true);
+
+      sticker.draw (drw, 't', x + BOX_BORDER, y + BOX_BORDER);
+      }
+
+   void FindThingFloors ()
+   {
+   thing_floors = new int[NumThings];
+
+   for (int i = 0; i < NumThings; i++)
+      {
+        Objid o;
+        GetCurObject (o, OBJ_SECTORS, Things[i].xpos, 
+            Things[i].ypos);
+      int secnum = o.num;
+      
+      if (secnum < 0)
+         thing_floors[i] = 0;
+      else
+         thing_floors[i] = Sectors[secnum].floorh;
+      }
+   }
+};
+
+
+static Y_View view;
+
+
+struct DrawSurf
+{
+public:
+   enum
+      {
+      K_INVIS = 0,
+      K_FLAT,
+      K_TEXTURE
+      };
+   int kind;  
+
+   int h1, h2, tex_h;
+   // heights for the surface (h1 is above h2).
+
+   Img *img;
+   img_pixel_t col;  /* used if img is zero */
+
+   enum
+      {
+      SOLID_ABOVE = 1,
+      SOLID_BELOW = 2
+      };
+   int y_clip;
+
+   /* CTor */
+
+   DrawSurf () { kind = K_INVIS; img = 0; }
+
+   void FindFlat (const wad_flat_name_t& fname, Sector *sec)
+      {
+      if (view.texturing)
+         {
+         img = view.im_ch->GetFlat (fname);
+
+         if (img != 0)
+            return;
+         }
+      col = 0x70 + ((sec - Sectors) % 48);
+      }
+
+   void FindTex (const wad_tex_name_t& tname, LineDef *ld)
+      {
+      if (view.texturing)
+         {
+         img = view.im_ch->GetTex (tname);
+
+         if (img != 0)
+            return;
+         }
+      col = 0x30 + ((ld - LineDefs) % 64);
+
+      if (col >= 0x60)
+         col += 0x70;
+      }
+};
+
+
+struct DrawWall
+{
+public:
+   typedef std::vector<struct DrawWall *> vec_t;
+
+   Thing *th;
+   // when `th' is non-zero, this is actually a sprite, and `ld' and
+   // `sd' will be zero.  Sprites use the info in the `ceil' surface.
+
+   LineDef *ld;
+   SideDef *sd;
+   Sector *sec;
+
+   int side;
+   // which side this wall faces (0 right, 1 left)
+
+   float ang1, dang, cur_ang;
+   float base_ang;
+   // clipped angles
+
+   float dist, t_dist;
+   float normal;
+   // line constants
+
+   double iz1, diz, cur_iz; 
+   double mid_iz;
+   // distance values (inverted, so they can be lerped)
+
+   float spr_tx1;
+   // translate coord, for sprite
+
+   int sx1, sx2;
+   // screen X coordinates
+ 
+   int oy1, oy2;
+   // for sprites, the remembered open space to clip to
+
+   /* surfaces */
+   
+   DrawSurf ceil;
+   DrawSurf upper;
+   DrawSurf lower;
+   DrawSurf floor;
+
+   static const double IZ_EPSILON = 0.000001;
+
+   /* PREDICATES */
+
+   struct MidDistCmp
+      {
+      inline bool operator() (const DrawWall * A, const DrawWall * B) const
+         {
+         return A->mid_iz > B->mid_iz;
+         }
+      };
+
+   struct DistCmp
+      {
+      inline bool operator() (const DrawWall * A, const DrawWall * B) const
+         {
+         if (fabs (A->cur_iz - B->cur_iz) < IZ_EPSILON)
+            return A->diz > B->diz;
+
+         return A->cur_iz > B->cur_iz;
+         }
+      };
+
+   struct SX1Cmp
+      {
+      inline bool operator() (const DrawWall * A, const DrawWall * B) const
+         {
+         return A->sx1 < B->sx1;
+         }
+
+      inline bool operator() (const DrawWall * A, int x) const
+         {
+         return A->sx1 < x;
+         }
+
+      inline bool operator() (int x, const DrawWall * A) const
+         {
+         return x < A->sx1;
+         }
+      };
+
+   struct SX2Less
+      {
+      int x;
+
+      SX2Less (int _x) : x (_x) { }
+
+      inline bool operator() (const DrawWall * A) const
+         {
+         return A->sx2 < x;
+         }
+      };
+
+   /* methods */
+
+   void ComputeWallSurface ()
+      {
+      Sector *front = sec;
+      Sector *back  = 0;
+
+      if (is_obj (side ? ld->sidedef1 : ld->sidedef2))
+         {
+         SideDef *bsd = SideDefs + (side ? ld->sidedef1 : ld->sidedef2);
+
+         if (is_obj (bsd->sector))
+            back = Sectors + bsd->sector;
+         }
+
+      bool sky_upper = back && is_sky (front->ceilt) && is_sky (back->ceilt);
+
+      if ((front->ceilh > view.z || is_sky (front->ceilt)) && ! sky_upper) 
+         {
+         ceil.kind = DrawSurf::K_FLAT;
+         ceil.h1 = +99999;
+         ceil.h2 = front->ceilh;
+         ceil.tex_h = ceil.h2;
+         ceil.y_clip = DrawSurf::SOLID_ABOVE;
+
+         if (is_sky (front->ceilt))
+            ceil.col = sky_colour;
+         else
+            ceil.FindFlat (front->ceilt, front);
+         }
+
+      if (front->floorh < view.z)
+         {
+         floor.kind = DrawSurf::K_FLAT;
+         floor.h1 = front->floorh;
+         floor.h2 = -99999;
+         floor.tex_h = floor.h1;
+         floor.y_clip = DrawSurf::SOLID_BELOW;
+
+         if (is_sky (front->floort))
+            floor.col = sky_colour;
+         else
+            floor.FindFlat (front->floort, front);
+         }
+
+      if (! back)
+         {
+         /* ONE-sided line */
+
+         lower.kind = DrawSurf::K_TEXTURE;
+         lower.h1 = front->ceilh;
+         lower.h2 = front->floorh;
+         lower.y_clip = DrawSurf::SOLID_ABOVE | DrawSurf::SOLID_BELOW;
+
+         lower.FindTex (sd->tex3, ld);
+
+         if (lower.img && (ld->flags & ML_LOWER_UNPEGGED))
+            lower.tex_h = lower.h2 + lower.img->height ();
+         else
+            lower.tex_h = lower.h1;
+         }
+      else
+         {
+         /* TWO-sided line */
+
+         if (back->ceilh < front->ceilh && ! sky_upper)
+            {
+            upper.kind = DrawSurf::K_TEXTURE;
+            upper.h1 = front->ceilh;
+            upper.h2 = back->ceilh;
+            upper.tex_h = upper.h1;
+            upper.y_clip = DrawSurf::SOLID_ABOVE;
+
+            upper.FindTex (sd->tex1, ld);
+
+            if (upper.img && ! (ld->flags & ML_UPPER_UNPEGGED))
+               upper.tex_h = upper.h2 + upper.img->height ();
+            else
+               upper.tex_h = upper.h1;
+            }
+
+         if (back->floorh > front->floorh)
+            {
+            lower.kind = DrawSurf::K_TEXTURE;
+            lower.h1 = back->floorh;
+            lower.h2 = front->floorh;
+            lower.y_clip = DrawSurf::SOLID_BELOW;
+
+            lower.FindTex (sd->tex2, ld);
+
+            if (ld->flags & ML_LOWER_UNPEGGED)
+               lower.tex_h = front->ceilh;
+            else
+               lower.tex_h = lower.h1;
+            }
+         }
+      }
+};
+
+
+struct RendInfo
+{
+public:
+   DrawWall::vec_t walls;
+   // complete set of walls/sprites to draw.
+
+   DrawWall::vec_t active;
+   // the active list.  Pointers here are always duplicates of ones in
+   // the walls list (no need to `delete' any of them).
+
+   std::vector<double> depth_x;  
+   // inverse distances over X range, 0 when empty.
+
+   int open_y1;
+   int open_y2;
+
+   static const double Y_SLOPE = 1.70;
+
+   static void DeleteWall (DrawWall *P)
+      {
+      delete P;
+      }
+
+   ~RendInfo ()
+      {
+      std::for_each (walls.begin (), walls.end (), DeleteWall);
+      
+      walls.clear ();
+      active.clear ();
+      }
+
+   void InitDepthBuf (int width)
+      {
+      depth_x.resize (width);
+
+      std::fill_n (depth_x.begin (), width, 0);
+      }
+
+   static inline float PointToAngle (float x, float y)
+      {
+      if (-0.01 < x && x < 0.01)
+         return (y > 0) ? HALFPI : (3 * HALFPI);
+
+      float angle = atan2(y, x);
+
+      if (angle < 0)
+         angle += TWOPI;
+
+      return angle;
+      }
+
+   static inline int AngleToX (float ang)
+      {
+      float t = tan (HALFPI - ang);
+
+      int x = int (view.sw * t);
+
+      x = (view.sw + x) / 2;
+
+      if (x < 0)
+         x = 0;
+      else if (x > view.sw)
+         x = view.sw;
+
+      return x;
+      }
+
+   static inline float XToAngle (int x)
+      {
+      x = x * 2 - view.sw;
+
+      float ang = HALFPI + atan (x / float (view.sw));
+
+      if (ang < 0)
+         ang = 0;
+      else if (ang > ONEPI)
+         ang = ONEPI;
+
+      return ang;
+      }
+
+   static inline int DeltaToX (double iz, float tx)
+      {
+      int x = int (view.sw * tx * iz);
+
+      x = (x + view.sw) / 2;
+
+      return x;
+      }
+
+   static inline float XToDelta (int x, double iz)
+      {
+      x = x * 2 - view.sw;
+
+      float tx = x / iz / view.sw;
+
+      return tx;
+      }
+
+   static inline int DistToY (double iz, int sec_h)
+      {
+      if (sec_h > 32770)
+         return -9999;
+
+      if (sec_h < -32770)
+         return +9999;
+
+      sec_h -= view.z;
+
+      int y = int (view.sh * sec_h * iz * Y_SLOPE);
+
+      y = (view.sh - y) / 2;
+
+      return y;
+      }
+
+   static inline float YToDist (int y, int sec_h)
+      {
+      sec_h -= view.z;
+
+      y = y * 2 - view.sh;
+
+      if (y == 0)
+         return 999999;
+
+      return view.sh * sec_h * Y_SLOPE / y;
+      }
+
+   static inline float YToSecH (int y, double iz)
+      {
+      y = y * 2 - view.sh;
+
+      return view.z - (float (y) / view.sh / iz / Y_SLOPE);
+      }
+
+   void AddLine (int linenum)
+      {
+      LineDef *ld = LineDefs + linenum;
+
+      if (! is_obj (ld->start) || ! is_obj (ld->end))
+         return;
+
+      float x1 = Vertices[ld->start].x - view.x;
+      float y1 = Vertices[ld->start].y - view.y;
+      float x2 = Vertices[ld->end].x - view.x;
+      float y2 = Vertices[ld->end].y - view.y;
+
+      float tx1 = x1 * view.Sin - y1 * view.Cos;
+      float ty1 = x1 * view.Cos + y1 * view.Sin;
+      float tx2 = x2 * view.Sin - y2 * view.Cos;
+      float ty2 = x2 * view.Cos + y2 * view.Sin;
+
+      // reject line if complete behind viewplane
+      if (ty1 <= 0 && ty2 <= 0)
+         return;
+
+      float angle1 = PointToAngle (tx1, ty1);
+      float angle2 = PointToAngle (tx2, ty2);
+      float span = angle1 - angle2;
+
+      if (span < 0)
+         span += TWOPI;
+
+      int side = 0;
+      SideDef *sd;
+
+      if (span >= ONEPI)
+         side = 1;
+
+      // ignore the line when there is no facing sidedef
+      if (! is_obj (side ? ld->sidedef2 : ld->sidedef1))
+         return;
+
+      sd = SideDefs + (side ? ld->sidedef2 : ld->sidedef1);
+
+      if (! is_obj (sd->sector))
+         return;
+
+      if (side == 1)
+         {
+         float tmp = angle1;
+         angle1 = angle2;
+         angle2 = tmp;
+         }
+
+      // clip angles to view volume
+
+      float base_ang = angle1;
+
+      float leftclip  = (3 * ONEPI / 4);
+      float rightclip = ONEPI / 4;
+
+      float tspan1 = angle1 - rightclip;
+      float tspan2 = leftclip - angle2;
+
+      if (tspan1 < 0) tspan1 += TWOPI;
+      if (tspan2 < 0) tspan2 += TWOPI;
+
+      if (tspan1 > HALFPI)
+         {
+         // Totally off the left edge?
+         if (tspan2 >= ONEPI)
+            return;
+
+         angle1 = leftclip;
+         }
+
+      if (tspan2 > HALFPI)
+         {
+         // Totally off the left edge?
+         if (tspan1 >= ONEPI)
+            return;
+
+         angle2 = rightclip;
+         }
+
+      // convert angles to on-screen X positions
+      int sx1 = AngleToX (angle1);
+      int sx2 = AngleToX (angle2) - 1;
+
+      if (sx1 > sx2)
+         return;
+
+      // compute distance from eye to wall
+      float wdx = x2 - x1;
+      float wdy = y2 - y1;
+
+      float wlen = sqrt (wdx * wdx + wdy * wdy);
+      float dist = fabs ((y1 * wdx / wlen) - (x1 * wdy / wlen));
+
+      if (dist < 0.01)
+         return;
+
+      // compute normal of wall (translated coords)
+      float normal;
+
+      if (side == 1)
+         normal = PointToAngle (ty2 - ty1, tx1 - tx2);
+      else
+         normal = PointToAngle (ty1 - ty2, tx2 - tx1);
+
+      // compute inverse distances
+      double iz1 = cos (normal - angle1) / dist / cos (HALFPI - angle1);
+      double iz2 = cos (normal - angle2) / dist / cos (HALFPI - angle2);
+
+      double diz = (iz2 - iz1) / y_max (1, sx2 - sx1);
+
+      // create drawwall structure
+
+      DrawWall *dw = new DrawWall;
+
+      dw->th = 0;
+      dw->ld = ld;
+      dw->sd = sd;
+      dw->sec = Sectors + sd->sector;
+
+      dw->side = side;
+
+      dw->base_ang = base_ang;
+      dw->ang1 = angle1;
+      dw->dang = (angle2 - angle1) / y_max (1, sx2 - sx1);
+
+      dw->dist = dist;
+      dw->normal = normal;
+      dw->t_dist = tan (base_ang - normal) * dist;
+
+      dw->iz1 = iz1;
+      dw->diz = diz;
+      dw->mid_iz = iz1 + (sx2 - sx1 + 1) * diz / 2;
+
+      dw->sx1 = sx1;  dw->sx2 = sx2;
+
+      walls.push_back (dw);
+      }
+
+   void AddThing (int thingnum)
+      {
+      Thing *th = Things + thingnum;
+
+      float x = th->xpos - view.x;
+      float y = th->ypos - view.y;
+
+      float tx = x * view.Sin - y * view.Cos;
+      float ty = x * view.Cos + y * view.Sin;
+
+      // reject sprite if complete behind viewplane
+      if (ty < 4)
+         return;
+
+      Img *sprite = view.im_ch->GetSprite (th->type);
+      if (! sprite)
+         return;
+
+      float tx1 = tx - sprite->width () / 2.0;
+      float tx2 = tx + sprite->width () / 2.0;
+
+      double iz = 1 / ty;
+
+      int sx1 = DeltaToX (iz, tx1);
+      int sx2 = DeltaToX (iz, tx2) - 1;
+
+      if (sx1 < 0)
+         sx1 = 0;
+
+      if (sx2 >= view.sw)
+         sx2 = view.sw - 1;
+
+      if (sx1 > sx2)
+         return;
+
+      int h2 = view.thing_floors[thingnum];
+      int h1 = h2 + sprite->height ();
+
+      // create drawwall structure
+
+      DrawWall *dw = new DrawWall;
+
+      dw->th = th;
+      dw->ld = 0;
+      dw->sd = 0;
+      dw->sec = 0;
+
+      dw->spr_tx1 = tx1;
+
+      dw->ang1 = dw->dang = 0;
+
+      dw->iz1 = dw->mid_iz = iz;
+      dw->diz = 0;
+
+      dw->sx1 = sx1;  dw->sx2 = sx2;
+
+      dw->ceil.img = sprite;
+      dw->ceil.h1  = h1;
+      dw->ceil.h2  = h2;
+
+      walls.push_back (dw);
+      }
+
+   void ComputeSurfaces ()
+      {
+      DrawWall::vec_t::iterator S;
+
+      for (S = walls.begin (); S != walls.end (); S++)
+         if ((*S)->ld)
+            (*S)->ComputeWallSurface ();
+      }
+
+   void ClipSolids ()
+      {
+      // perform a rough depth sort of the walls and sprites.
+
+      std::sort (walls.begin (), walls.end (), DrawWall::MidDistCmp ());
+
+      // go forwards, from closest to furthest away
+
+      DrawWall::vec_t::iterator S;
+
+      for (S = walls.begin (); S != walls.end (); S++)
+         {
+         DrawWall *dw = (*S);
+
+         if (! dw)
+            continue;
+
+         int one_sided = dw->ld && ! is_obj (dw->ld->sidedef2);
+         int vis_count = dw->sx2 - dw->sx1 + 1;
+
+         for (int x = dw->sx1; x <= dw->sx2; x++)
+            {
+            double iz = dw->iz1 + (dw->diz * (x - dw->sx1));
+
+            if (iz < depth_x[x])
+               vis_count--;
+            else if (one_sided)
+               depth_x[x] = iz;
+            }
+
+         if (vis_count == 0)
+            {
+            delete dw;
+            (*S) = 0;
+            }
+         }
+
+      // remove null pointers
+
+      S = std::remove (walls.begin (), walls.end (), (DrawWall *) 0);
+
+      walls.erase (S, walls.end ());
+      }
+
+   void RenderFlatColumn (DrawWall *dw, DrawSurf& surf,
+         int x, int y1, int y2)
+      {
+      img_pixel_t *buf = view.screen->wbuf ();
+      img_pixel_t *wbuf = surf.img->wbuf ();
+
+      int tw = surf.img->width ();
+      int th = surf.img->height ();
+
+      float ang = XToAngle (x);
+      float modv = cos (ang - HALFPI);
+
+      float t_cos = cos (ONEPI + -view.angle + ang) / modv;
+      float t_sin = sin (ONEPI + -view.angle + ang) / modv;
+
+      buf += x + y1 * view.sw;
+
+      for (; y1 <= y2; y1++, buf += view.sw)
+         {
+         float dist = YToDist (y1, surf.tex_h);
+
+         int tx = int ( view.x + t_sin * dist) & (tw - 1);
+         int ty = int (-view.y - t_cos * dist) & (th - 1);
+
+         *buf = wbuf[ty * tw + tx];
+         }
+      }
+
+   void RenderTexColumn (DrawWall *dw, DrawSurf& surf,
+         int x, int y1, int y2)
+      {
+      img_pixel_t *buf = view.screen->wbuf ();
+      img_pixel_t *wbuf = surf.img->wbuf ();
+
+      int tw = surf.img->width ();
+      int th = surf.img->height ();
+
+      /* compute texture X coord */
+
+      int tx = int (dw->t_dist - tan (dw->cur_ang - dw->normal) * dw->dist);
+
+      tx = (dw->sd->xoff + tx) & (tw - 1);
+
+      /* compute texture Y coords */
+
+      float base_h = surf.tex_h + dw->sd->yoff;
+
+      float h1 = base_h - YToSecH (y1, dw->cur_iz);
+      float dh = base_h - YToSecH (y2, dw->cur_iz);
+
+      dh = (dh - h1) / y_max (1, y2 - y1);
+       
+      buf  += x + y1 * view.sw;
+      wbuf += tx;
+
+      for (; y1 <= y2; y1++, h1 += dh, buf += view.sw)
+         {
+         int ty = int (h1) % th;
+
+         // handle negative values (use % twice)
+         ty = (ty + th) % th;
+
+         *buf = wbuf[ty * tw];
+         }
+      }
+
+   void RenderSolidColumn (DrawWall *w, DrawSurf& surf,
+         int x, int y1, int y2)
+      {
+      img_pixel_t *buf = view.screen->wbuf ();
+
+      buf += x + y1 * view.sw;
+       
+      for (; y1 <= y2; y1++, buf += view.sw)
+         {
+         *buf = surf.col;
+         }
+      }
+
+   inline void RenderWallSurface (DrawWall *dw, DrawSurf& surf, 
+         int x)
+      {
+      if (surf.kind == DrawSurf::K_INVIS)
+         return;
+
+      int y1 = DistToY (dw->cur_iz, surf.h1);
+      int y2 = DistToY (dw->cur_iz, surf.h2) - 1;
+
+      if (y1 < open_y1)
+         y1 = open_y1;
+
+      if (y2 > open_y2)
+         y2 = open_y2;
+
+      if (y1 > y2)
+         return;
+
+      /* clip the open region */
+
+      if (surf.y_clip & DrawSurf::SOLID_ABOVE)
+         if (y2 > open_y1)
+            open_y1 = y2;
+
+      if (surf.y_clip & DrawSurf::SOLID_BELOW)
+         if (y1 < open_y2)
+            open_y2 = y1;
+
+      /* fill pixels */
+
+      if (! surf.img)
+         {
+         RenderSolidColumn (dw, surf, x, y1, y2);
+         }
+      else switch (surf.kind)
+         {
+         case DrawSurf::K_FLAT:
+            RenderFlatColumn (dw, surf, x, y1, y2);
+            break;
+
+         case DrawSurf::K_TEXTURE:
+            RenderTexColumn  (dw, surf, x, y1, y2);
+            break;
+         }
+      }
+
+   inline void RenderSprite (DrawWall *dw, int x)
+      {
+      int y1 = DistToY (dw->cur_iz, dw->ceil.h1);
+      int y2 = DistToY (dw->cur_iz, dw->ceil.h2) - 1;
+
+      if (y1 < dw->oy1)
+         y1 = dw->oy1;
+
+      if (y2 > dw->oy2)
+         y2 = dw->oy2;
+
+      if (y1 > y2)
+         return;
+
+      /* fill pixels */
+
+      img_pixel_t *buf = view.screen->wbuf ();
+      img_pixel_t *wbuf = dw->ceil.img->wbuf ();
+
+      int tw = dw->ceil.img->width ();
+      int th = dw->ceil.img->height ();
+
+      int tx = int (XToDelta (x, dw->cur_iz) - dw->spr_tx1);
+
+      if (tx < 0 || tx >= tw)
+         return;
+
+      float h1 = dw->ceil.h1 - YToSecH (y1, dw->cur_iz);
+      float dh = dw->ceil.h1 - YToSecH (y2, dw->cur_iz);
+
+      dh = (dh - h1) / y_max (1, y2 - y1);
+       
+      buf  += x + y1 * view.sw;
+      wbuf += tx;
+
+      for (; y1 <= y2; y1++, h1 += dh, buf += view.sw)
+         {
+         int ty = int (h1);
+
+         if (ty < 0 || ty >= th)
+            continue;
+
+         img_pixel_t pix = wbuf[ty * tw];
+
+         if (pix != IMG_TRANSP)
+            *buf = pix;
+         }
+      }
+
+   void UpdateActiveList (int x)
+      {
+      DrawWall::vec_t::iterator S, E, P;
+
+      bool changes = false;
+
+      // remove walls that have finished.
+
+      S = active.begin ();
+      E = active.end ();
+
+      S = std::remove_if (S, E, DrawWall::SX2Less (x));
+
+      if (S != E)
+         {
+         active.erase (S, E);
+         changes = true;
+         }
+
+      // add new walls that start in this column.
+
+      S = walls.begin ();
+      E = walls.end ();
+
+      S = std::lower_bound (S, E, x, DrawWall::SX1Cmp ());
+      E = std::upper_bound (S, E, x, DrawWall::SX1Cmp ());
+
+      if (S != E)
+         changes = true;
+
+      for (; S != E; S++)
+         {
+         active.push_back (*S);
+         }
+
+      // calculate new depth values
+
+      S = active.begin ();
+      E = active.end ();
+
+      for (P=S; (P != E); P++)
+         {
+         DrawWall *dw = (*P);
+
+         dw->cur_iz = dw->iz1 + dw->diz * (x - dw->sx1);
+
+         if (P != S && (*(P-1))->cur_iz < dw->cur_iz)
+            changes = true;
+
+         dw->cur_ang = dw->ang1 + dw->dang * (x - dw->sx1);
+         }
+
+      // if there are changes, re-sort the active list...
+
+      if (changes)
+         {
+         std::sort (active.begin (), active.end (), DrawWall::DistCmp ());
+         }
+      }
+
+   void RenderWalls ()
+      {
+      // sort walls by their starting column, to allow binary search.
+
+      std::sort (walls.begin (), walls.end (), DrawWall::SX1Cmp ());
+
+      active.clear ();
+
+      for (int x=0; x < view.sw; x++)
+         {
+         // clear vertical depth buffer
+
+         open_y1 = 0;
+         open_y2 = view.sh - 1;
+
+         UpdateActiveList (x);
+
+         // render, front to back
+
+         DrawWall::vec_t::iterator S, E, P;
+
+         S = active.begin ();
+         E = active.end ();
+
+         for (P=S; P != E; P++)
+            {
+            DrawWall *dw = (*P);
+
+            // for things, just remember the open space
+            if (dw->th)
+               {
+               dw->oy1 = open_y1;
+               dw->oy2 = open_y2;
+               continue;
+               }
+
+            RenderWallSurface (dw, dw->ceil,  x);
+            RenderWallSurface (dw, dw->floor, x);
+            RenderWallSurface (dw, dw->upper, x);
+            RenderWallSurface (dw, dw->lower, x);
+
+            if (open_y1 >= open_y2)
+               break;
+            }
+
+         // now render things, back to front
+
+         if (P == E)
+            P--;
+
+         for (; P != (S-1); P--)
+            {
+            DrawWall *dw = (*P);
+
+            if (dw->th)
+               RenderSprite (dw, x);
+            }
+         }
+      }
+
+   void DoRender3D ()
+      {
+      view.ClearScreen ();
+
+      InitDepthBuf (view.sw);
+
+      for (int i=0; i < NumLineDefs; i++)
+         AddLine (i);
+
+      if (view.sprites)
+         for (int j=0; j < NumThings; j++)
+            AddThing (j);
+
+      ClipSolids ();
+      ComputeSurfaces ();
+      RenderWalls ();
+      }
+};
+
+
+static Thing *FindPlayer (int typenum)
+{
+for (int i=0; i < NumThings; i++)
+   if (Things[i].type == typenum)
+      return Things + i;
+
+return 0;
+}
+
+
+/*
+ *  Render a 3D view from the player's position. 
+ */
+
+void Render3D ()
+{
+if (! view.p_type)
+   {
+   view.p_type = THING_PLAYER1;
+   view.px = 99999;
+   }
+
+Thing *player = FindPlayer (view.p_type);
+
+if (! player)
+   {
+   if (view.p_type != THING_DEATHMATCH)
+      view.p_type = THING_DEATHMATCH;
+
+   player = FindPlayer (view.p_type);
+
+   if (! player)
+      return;
+   }
+
+if (view.px != player->xpos || view.py != player->ypos)
+   {
+   // if player moved, re-create view parameters
+
+   view.x = view.px = player->xpos;
+   view.y = view.py = player->ypos;
+
+   view.CalcViewZ ();
+   view.SetAngle (player->angle * ONEPI / 180.0);
+   }
+
+/* create image */
+
+view.sw = 640;
+view.sh = 400;
+
+view.screen = new Img ((unsigned short int) view.sw, (unsigned short int) view.sh, false);
+view.im_ch = new ImageCache;
+
+view.FindThingFloors ();
+
+bool Redraw = true;
+
+/* input loop */
+
+for (;;)
+   {
+   /* render image */
+
+   if (Redraw)
+      {
+      if (view.walking)
+         view.CalcViewZ ();
+
+      RendInfo rend;
+
+      rend.DoRender3D ();
+
+      view.PutScreen (40, 40);
+
+      Redraw = false;
+      }
+
+   /* handle keypress */
+
+   int key = get_key ();
+
+   if (key == YK_ESC || key == 'q')
+      break;
+
+   if ((key & ~YK_SHIFT) == YK_LEFT)
+      {
+      view.SetAngle (view.angle + ONEPI / ((key & YK_SHIFT) ? 4 : 8));
+      Redraw = true;
+      }
+   else if ((key & ~YK_SHIFT) == YK_RIGHT)
+      {
+      view.SetAngle (view.angle -ONEPI / ((key & YK_SHIFT) ? 4 : 8));
+      Redraw = true;
+      }
+   else if ((key & ~YK_SHIFT) == YK_UP)
+      {
+      view.x += view.Cos * ((key & YK_SHIFT) ? 192 : 32);
+      view.y += view.Sin * ((key & YK_SHIFT) ? 192 : 32);
+      Redraw = true;
+      }
+   else if ((key & ~YK_SHIFT) == YK_DOWN)
+      {
+      view.x -= view.Cos * ((key & YK_SHIFT) ? 192 : 32);
+      view.y -= view.Sin * ((key & YK_SHIFT) ? 192 : 32);
+      Redraw = true;
+      }
+   else if (key == 'n' || key == 'N')
+      {
+      view.x -= view.Sin * ((key == 'N') ? 192 : 32);
+      view.y += view.Cos * ((key == 'N') ? 192 : 32);
+      Redraw = true;
+      }
+   else if (key == 'm' || key == 'M')
+      {
+      view.x += view.Sin * ((key == 'M') ? 192 : 32);
+      view.y -= view.Cos * ((key == 'M') ? 192 : 32);
+      Redraw = true;
+      }
+   else if (key == 'd' || key == 'D')
+      {
+      view.z += (key == 'D') ? 128 : 32;
+      Redraw = true;
+      }
+   else if (key == 'c' || key == 'C')
+      {
+      view.z -= (key == 'C') ? 128 : 32;
+      Redraw = true;
+      }
+   else if (key == 't')
+      {
+      view.texturing = ! view.texturing;
+      Redraw = true;
+      }
+   else if (key == 's')
+      {
+      view.sprites = ! view.sprites;
+      Redraw = true;
+      }
+   else if (key == 'w')
+      {
+      view.walking = ! view.walking;
+      Redraw = true;
+      }
+   else if (key)
+      {
+      // key no good, get another one
+      Beep ();
+      }
+   }
+
+/* all done */
+
+delete view.screen;
+view.screen = 0;
+
+delete view.im_ch;
+view.im_ch = 0;
+
+delete[] view.thing_floors;
+view.thing_floors = 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/r_render.h	Sat Sep 24 13:12:09 2011 +0300
@@ -0,0 +1,14 @@
+/*
+ *	r_render.h
+ *	AJA 2002-04-27
+ */
+
+
+#ifndef YH_R_RENDER  /* DO NOT INSERT ANYTHING BEFORE THIS LINE */
+#define YH_R_RENDER
+
+
+void Render3D ();
+
+
+#endif  /* DO NOT ADD ANYTHING AFTER THIS LINE */