DOOM Source

Thursday, 20th April 2006

You can download the source to the DOOM project below here.
It's a real mess in places, mainly in the way it loads level geometry. DirectX initialisation is hard-coded with no fallback code if some feature is missing or unsupported.

Usage: DOOM <iwad> <level>

Keys: use the cursor keys to move around, A and Z to move up and down.


  • Floor splitting is abysmal. It is highly inefficient and occasionally wrong. The floor splitting code breaks sectors into horizontal spans which it then splits into triangles - uneven vertex placement results in largish cracks between sectors or "hairline" dancing-pixel cracks inside sectors. Large holes are usually the case of a sector that "overflows" another one. Ideally; decode floors via ssector/seg information or just use glBSP.
  • Timing (level animation) is done via the primitive Timer class. If something starts to hog CPU time, the timers all slow down leading to inconsistent animation speeds.
  • Scrolling walls and light effects are controlled differently; sector light effects (which affect a large amount of geometry) save the vertex array written to the vertex buffer away; this array is changed then sent to a vertex buffer, overwriting the existing data. The scrolling walls (affect a small amount of geometry) read the vertex buffer, alter the texture coordinates, then write it back. This results in a confict; if a linedef has both scrolling AND lighting effects, every time the light level changes the scroll offset is reset to the original linedef's value.
  • Skies that occlude geometry that is visible normally aren't handled correctly.
  • Player start angle is occasionally backwards.
  • Some of the more bizarre walls still don't display properly.

Variable height sectors

Wednesday, 12th April 2006

I switched from 16/8 division to 24/16 division, and wall heights are now calculated rather than being dragged off a lookup table.


There's also backface culling (the cylinder around the central cube is marked as double-sided, hence no culling there) but there is still no clipping or occlusion.

@philipptr: I'm guessing your technique is to calculate the angle of the point, add an offset to it, then convert back into a new coordinate? I just adapted some 3D point rotation code I had, simplifying (mathematically) it and removing any references to z.

I will probably not use textured walls - not so much for performance issues, but for aesthetic reasons. Scaling a texture down on a wall displayed on a 96x64 monochrome display (without antialiasing) looks pretty ugly. Simple lines look a lot cleaner.

Oh, and hello aCiD2 wink.gif

Oldschool 3D

Wednesday, 5th April 2006

All this DOOM work had got me interested again in simple 3D engines, and so thoughts turned to my favourite Z80 platform, the TI-83 Plus.

There are a handful of 3D engines out there for it already; Matt3D is a vector-based wireframe engine put to good use in a roller-coaster builder/simulation game and a racing game (that even supports two-player over the link port). However, it's 8-bit and so worlds created with it tend to be very small or distorted thanks to low-resolution (I tried writing a Quake game with it years ago).

The best "wall" engines (displaying a 3D world rather than a 3D object) have been the raycasters. Gemini impresses me the most; it's a Wolfenstein-level engine with objects, sliding "half-block" doors and moving wall blocks, all neatly texture-mapped.

I'm going to try, therefore, to get a vector-based "wall" engine (there must be a proper term for it) up and running. Keeping the map to 2D simplifies the maths a lot; hopefully with a few tricks things should be fast enough! Also, I'll use 16-bit arithmetic throughout to enhance the quality and scale of the maps.


Like DOOM, I'm isolating the vertices from the wall definitions. This way, I can cut down on the number of rotations/transformations required. The maths required to rotate a point around the origin is fairly simple;

Xr = Xo × sin(a) + Yo × cos(a)
Yr = Xo × cos(a) - Yo × sin(a)

For the moment, I'll use a simple lookup-table for wall heights. Ultimately, I'd like to have variable height walls, but these will do for the moment:


The translated X is just 48+(Xr/Yr), as the screen is 96 pixels wide.

Throwing in a few extra lines for walls...


As you might be able to see, there is no clipping if any part of a wall falls outside the viewing range. Also, the walls are not occluding eachother, something you'd really want.

Lights, camera - action!

Wednesday, 22nd March 2006


I've also added a MUS lump (music format used by DOOM) player, using the MIDI message functions in winmm. The only feature left unsupported is the pitch wheel, as I can't quite work out the MIDI command for it.

If somebody could please explain the logic behind MUS, please tell me. It's essentially the same as MIDI, except each command has a different byte (so you have to convert to the correct byte first), uses channel 15 for percussion (so you have to swap 15 with 9 and 9 with 15 - as 9 is the MIDI percussion channel) and uses a controller for a patch change, whereas MIDI has a specific patch change command rather than lumping it with the controllers.

Making the world live

Monday, 20th March 2006

It might have looked liked I'd got all the walls drawing and texturing correctly, some particular combinations were still wrong.
Some walls looked completely wrong until you got up close, and with a flickering burst of z-fighting they'd resolve themselves. Or rather, fuzzy patches would resolve themselves.
This would not do!

Here's a good (or bad) example of where things were going wrong:


Something tells me I shouldn't hire the UAC's architect. The problem, it turns out, is the way connected sectors handle which coordinates to use for upper, middle and lower sections.

I was using the current sector's ceiling and floor height as the midsection, and the areas between the two sectors' ceilings or floors as the upper and lower coordinates.

The problem is if the current sector is taller than the adjacent sector. This makes the "middle" section very tall, and the upper and lower sections end up folding back down on themselves - like this:


I tried all combinations of ordering, none of which worked - either rendering that area correctly and removing old healthy walls; or displaying correctly but with texture coordinates awry. The solution in the end was very simple - sort the four height levels into TopCeiling, MidCeiling, MidFloor, and LowFloor.


Two things in that image are wrong. Most noticably - no alpha blending on the walls. You should be able to see through that grille. The second is that in the real DOOM, you can see under the grille itself.

The latter is quite easy to fix; looking in my WAD spec guide, I see:There are some transparent textures which can be used as middle textures on 2-sided sidedefs (between sectors). These textures need to be composed of a single patch (see [8-4]), and note that on a very tall wall, they will NOT be tiled. Only one will be placed, at the spot determined by the "lower unpegged" flag being on/off and the sidedef's y offset. And if a transparent texture is used as an upper or lower texture, then the good old "Tutti Frutti" effect will have its way.

In short; if the ceiling_height - floor_height > texture_height then floor_height = ceiling_height - texture_height (the Z axis, floor height, points "upwards", so ceilings have a greater Z coordinate than floors).

As for transparency, my aim to keeping this simple prevailed. Rather than sort the walls from back to front manually, I isolate the vertex buffers relating to textures with transparent areas and draw them after all the other walls. This would still lead to problems where looking through one transparent wall at another would leave holes in the far wall if it had been drawn afterwards, so I draw all the walls twice - the first time with z-writes disabled, the second with them reenabled.

The entry room from E1M3 with alpha blending and fixed wall coordinates looks like this, now:


I bought the collector's edition of DOOM for this project (it's quite cheap) and picked it up over the weekend. Mostly because it had Final DOOM on it (I only had Ultimate DOOM and DOOM II), but also because it claimed to run on Windows XP. It didn't even list DOS as a supported platform.

I was surprised to find that it's just DOOM95, which quite simply doesn't work on Windows XP. Neither does it work on 2000. The mouse doesn't work, and the colours are all wrong. Using XP's compatibility mode and 256 colour mode seems to cure it for a few minutes before it goes all weird again. (I don't know if it's entirely graphics-card related, as DOOM95 ran fine under Windows 95 on my old PC - then went funky colours after upgrading to 2000).

There is a relevance to this - loading the Plutonium WAD file slowed my PC right down for about 3 minutes - though the CPU usage was extremely low. The reason? A slight memory hogging issue...


Turns out it was trying to decode some extra files that had appeared in the WAD file as images, with dimensions of 10,000 by 10,000 or greater. No wonder it was consuming memory at such a rate!

Loading the Plutonia WAD also showed this error:


The pillar isn't really meant to be a pillar - in reality, only the top skull is meant to display. This sounds a bit like a middle section not tiling glitch - and looking at the lines that make up the skull, each is marked as double sided. Double sided lines are generally used for transparent wall sections - so rather than check the transparency of a wall section, I check to see if it's double sided or not.


Much better!

Two things that made the DOOM environment come to life - excluding, of course, actually coming to life and moving around - were the changes in sector lighting and animated textures on floors and walls.

Adjusting the light level of sectors - blinking or pulsating lights, for example - would require quite a lot of code. On the other hand, all the animated textures need is a bit of extra code on the WAD parser to work out which textures come between the animated texture markers and a bit of extra code on the renderer to swap texture indices around every ~300ms.


Well, that's the easy bit done. Interesting to note, however, that Doomsday (jDoom) - or at least my copy of it - does not handle animated textures correctly. A particular set of textures used in Final DOOM - the spinning tape drives on computers - appear static in jDoom, or only use the first few frames.

Currently, the level's geometry is split up by texture - so I cycle through each different texture, setting it as current, then sending all the geometry that uses that texture (as a vertex buffer) to be drawn. Better control over sectors would mean that I should split by texture, then by sector.

Unfortunately, my attempt resulted in a, uh, slight performance hit. By slight, I mean from ~370FPS on my Radeon 9550 to ~4FPS. Whoops!

It turns out that I was accidentally repeating each sector - even with that fixed, I was still at about 50FPS.

It would probably be simpler - not to mention faster - to order the vertices in sector order, but to record the offset and length inside the vertex buffer for each sector so that should I need to change it I could very easily lock and update it. To put it more clearly, I should maintain a list, for each sector, recording which vertex buffers form part of it and the offset and length (in bytes) to the vertices specific to that sector. That way I can easily (not to mention quickly) tweak the vertices to my heart's content - be it adjusting their Color property to flicker lights, or their heights to open and close doors.


Page 39 of 49 135 36 37 38 39 40 41 42 4349

Older postsNewer postsLatest posts RSSSearchBrowse by dateIndexTags