In my other two maze postings I was concentrating on generating the maze and getting it to work in various web browsers. First I developed the maze in Perl and then I ported the Perl to client-side JavaScript. The JavaScript version enabled you to view the maze inside your browser. While functional the appearance of the final maze was somewhat boring. I decided that something needed to be done about that.
So without further ado I give you Desert Labyrinth, the product of about 25 hours of tinkering:
This new version of the maze offers an isometric 3D view of the labyrinth as it would appear centered around the viewer. Go walk around the maze for a while and when you get bored (mazes really are quite boring unless you're actually trapped inside one and then they're pretty freaking scary!) come back here and I'll explain how I made it.
Back already? Okay!
Building the Maze
The maze itself is generated using a slightly modified version of the previous JavaScript code (ain't code reuse grand?!). The 3D view is a complex aggregation of HTML DIVs, CSS z-order layering, and a whole bunch of GIF background images with transparency settings applied to them. Each wall, shadow, and character view was painstakingly rendered in LightWave 7.5d into its own .GIF file, cropped, given transparency attributes, placed in an HTML DIV tag, and then positioned and layered with CSS. There are actually about a hundred individual images in all.
I used three different pieces of software:
- LightWave 7.5 for 3D rendering
- PaintShop Pro 8 for image editing and cropping
- Macromedia Dreamweaver 8 for HTML editing
Rendering with LightWave
Here's a screen shot I took of LightWave while I was laying out the labyrinth in 3D space:
Of course, I wasn't able to render the scene all at once like you see in the screen shot. The process of rendering in LightWave actually went something like this:
For every wall segment visible to the camera:
- Hide everything but the ground plane.
- Render the wall segment's shadow falling on the ground plane.
- Hide the ground.
- Make the wall segment visible and render it.
- If another wall segment's shadow could fall across the current wall segment then turn on that segment's shadow and render the current segment again with the other wall's shadow falling across it.
Depending on where the wall segment was located in the view port you need to render at least 2 views of it, one with shadows on it, one without.
Yes, it was tedious. And no, I wouldn't want to do it again unless I was being paid to do so. Once I realized how much work was involved it was only a grim sort of "I want this thing done!" persistence that enabled me to finish.
Pay No Attention to the Man Behind the Curtain
As you move through the maze and the wall configuration around the viewer changes the JavaScript code modifies the visibility and background image properties of the DIVs appropriately. This creates the illusion of a 3D environment. If you outline block-level elements in FireFox you can see that it's really just smoke and mirrors:
Other than the original LightWave rendering process no actual 3D is going on here.
Layering the Wall Segments and Shadows
Layering the DIVs is easy. You simply do something like this:
<style type="text/css">
#x02y02_twall {
position:absolute;
left:5px;
top:0px;
width:94px;
height:50px;
background-image: url(images/x02y02_twall.gif);
visibility: hidden;
z-index:3;
}
</style>
blah blah blah
<div id="x02y02_twall"></div>
The most import part is getting the CSS absolute positioning coordinates correct so the GIF is shown at the right place in the view port. If you misplace it the wall will be shown in the wrong area. If you're lucky the misplaced wall segment will obviously be out of place because it's perspective will be "off". If you're NOT lucky then it'll be moved by only a pixel or two and ten DIVS down the road you'll be wondering why the walls don't quite match up.
Only slightly less important is getting the layering correct. The DIVs must be "stacked" atop each other in a three-dimensional "back to front" order. This require some 3D thinking but LightWave has a great measuring tool that can help with that. For example, to get the walls to display correctly you must first draw the flat ground at layer zero so it will be drawn below all the other layers:
Then, on top of the ground, beginning at z-order one, you start stacking the shadow DIVs. The "back to front" layering ensures that any overlapping shadows fall across each other properly:
(Sorry about the poor JPEG compression on the above pop-up images. I was trying to save space on TypePad and set the compression too high. The images below are slightly better.)
You then place the character's shadow on top of the wall shadows:
Once all the shadows are visible you enable the wall at the top of the cell, taking into account the wall to its immediate left and choosing either the shadowed or non-shadowed version of it:
Then, you turn on the character, selecting one of the four different versions based on the direction he is facing:
And finally, you enable the rest of the walls and the illusion of a complete 3D maze cell is complete:
Of course, this is somewhat oversimplified because it only takes into account the walls immediately around the character. A labyrinth with only four walls is not really a maze at all: it's a prison cell!
To reiterate: the important thing is to work from the back of the scene towards the camera layering stuff on top of each other. If you do it right the "close" stuff will cover the "far" stuff, giving a convincing illusion of depth. Transparency settings in the "close" GIF files allows lower images to show through in the right places.
It's Not Perfect
There were several "gotchas" that I ran into while developing this:
- Because the wall segments (and wall shadows) are separate entities and must be displayed (or not displayed) individually based on the maze configuration, it is vitally important that they NOT overlap in 3D space. When I was positioning the walls in LightWave I just started out by building one room centered around the character and then just sort of expanded into the other rooms, not being especially careful on how the walls fit together.
I didn't think doing it this way would be a problem because after all, we're just
re-creating the LightWave scene in a browser using DIVs. As long as it
looks good in LightWave it'll look good in HTML. Right?
Wrong!
Later (much later), when it came time to start drawing random wall sections I ran into problems with the walls not fitting together seamlessly. You can see the results of the overlapping walls in the strange seams, "floating" walls, and poor joins at some of the corners. Ideally, the wall segments should NEVER touch in 3D-space, and smooth, 90-degree corners accomplished through corner "patch" GIFs that would hide the places where the walls meet. This, of course, would've added additional renderings to the project and increased the complexity of the display code.
- Transparency in the GIF format is imperfect and will lead to "halo"-like effects if the transparency color is too different from the overall image average. I was using a bright Red color at first and noticed a slight 1-pixel red halo. Fixing it required changing the transparency color to a closer-to-average color. For the images in this project that was a soft, golden brown color. There are still a few visual glitches but you don't really notice them unless you know they're there.
It's possible that using a more advanced image format like PNG would've solved some of this. But after doing some test renderings I realized that using PNG was out of the question because the resulting image file sizes were just too large. Using PNG would've increased bandwidth requirements by at least an order of magnitude.
- My camera view was too large and encompassed too wide a field of view. By moving the camera slightly closer to the character I could've eliminated the need to render about 10 walls from the project. Also, by zooming in a bit, there would be no need to keep track of such a large number of wall segments and the JavaScript display code could've been simpler.
An interesting phenomenon related to this is that if a wall segment (even if it's just a tiny "sliver" barely visible at the extreme top of the view port) is missing, your eye will fixate on it immediately. You may not realize exactly what's wrong but you'll still know that something is "off" with what you're looking at. There were several tiny "slivers" of off-the-map walls that I ended up rendering to stop this.
- The Safari browser on the Macintosh seems to have trouble with the maze generation code. Since I don't have access to a Mac I can't troubleshoot the problem an further. Unless some kind soul driving a Macintosh can test it out and tell me where in the JavaScript code it's blowing up there isn't much I can do about it.
Of the "gotchas" the most serious by far is the slightly overlapping walls. Making that "right" would require re-rendering everything and basically amounts to starting over from scratch. Unfortunately I didn't realize the overlap was going to be a problem until I was already 10 hours deep and I sure wasn't about to start over. Rather than scrap the project I decided to just keep plugging ahead and see what happened. The result, while not perfect, is "good enough" for a hobby project.
It's Not Really a Game - It's a Technology Demonstration
I'll close with an apology of sorts. When I show Desert Labyrinth to others
the first thing they ask is invariably some
disappointed variant of:
"Where are the monsters?"
You're probably wondering the same. The only person so far that hasn't reacted that way was a web-developer
friend of mine who immediately began asking questions about how I did
it and how it works. Almost everyone else needed to have the
significance of Desert Labyrinth explained to them.
So for those that haven't figured it out yet and were about to post a comment asking where are the monsters? I will clarify what the point behind Desert Labyrinth is. Despite the 3D veneer it is NOT a game and was never meant to be. It's main purpose grew out of my desire to learn CSS and client-side JavaScript, not to write the next great web-game. So if you were expecting Doom 3 then you're probably disappointed. Then again, if you got this far in this posting hoping for some hack-n-slash, then you've got other problems.
With that in mind, I may eventually add some game-like aspects to Desert Labyrinth. But for now my motivation to continue working on maze-based projects is flagging so don't expect anything soon.