Wolf3D - Amiga Codex (not the C2P): STL/Damage C2P: c2p_020_fastram +optimizes Gfx: STL Quality Assurance and High Depressing: Friends :) EMAIL: andezl@kastelli.otol.fi This demo is made for an interest in coding a similar 3D routine that is used in Wolfenstein. For a long time, that was not possible due to the lack of algorithms and principles required for rendering the 3D screen from a simple 2D map... The last kick needed came from the documents written by Brian Marshall. Without him, this demo would not exist. Also extra thanks to Count Zero for supplying me with those documents. This demo is designed for every Amiga, including every configuration. The demo is, however, on its best on faster processors. Sorry. An unaccelerated 68000 just isn't fast enough. Also, the sprites slow down even the faster ones. I'm searching for optimizes, but I'm not too hopeful... I have SupraTurbo + fast memory, and it runs the smaller screen on speed approximately between 15..17 frames per second. A1200+chip is a little slower, and A1200+fast should run the bigger screen on about 15 frames/s, or perhaps much faster, as it is not tested yet. The 2x2 pixel mode doubles..triples the speeds, making the smaller screen just and just playable on an unaccelerated A500, although it looks ugly and has very low detail in order to get maximal speed. Features: · Two screen sizes, 128x96 and 192x144. · Two pixel sizes, 1x1 and 2x2. · Specially optimized rayfiring used to render the screen. · Orthagonical walls (means grid-based map). · Wolfenstein-style one-coloured floor and ceiling (lame? nah...) · Transparent wall structures and typical solid walls. · Depth cueing with realtime calculated texture dithering. · EHB graphics that are converted to five planes via dithering · Collision system of no bumpiness · Sprites. · Multiple-textured wall blocks. Yet missing: · Doors Bugs: · Do not approach wall blocks from corners, the collision system can't detect that in time and may hang the player inside a wall. · Little inaccuracy in the rays (sorry, but the speed rules)... · Texture dithering suffers from a line fashion in certain parts of transparent walls. · Sprites are not faded according the depth (gives a little speed, but this will be corrected (after I find a free register from the mish mash that handles the sprites...)) · Sprite memory access error that can even crash the whole machine, or at least mess the graphics. Very rare and appears only on very low distances, but yet exists. *** Perhaps you would be interested in how it all is done? These explanations are not very deep or detailed, they are only to awake interest... ...but anyway, the technical side of: 1. RayFiring The sight is almost illimited; every ray can be traced over forty squares of map very quickly before the texture shrinking tables run out. The rayfiring uses a method, where the detail gots chopped down as the distance grows. That means that when the ray keeps going through empty map squares the routine `gots bored' and doubles the ray delta skipping. And when hitting a wall square, the routine takes back a little and retraces the last "centimetres" more carefully to determinate the ray coordinates and distance better. BTW, the shrinking tables are used, because it is a little faster to read from memory than use addx's and add.l's. (Hi Count, I realized eventually what you wrote to me months ago... I did not use your optimation, as you suggested, in the wall loop, but in the ray loop... Faaast anyway! :) 2. Texture realtime dithering "Texture dithering? Realtime?!? Are you sure that it really was a wise decision? (pause) I mean that isn't it pretty slow and such..." was the first comment for this routine, launched by Maxwell instantly when I told I'm using such method. It sounds like an awesome timewaster, doesn't it? Think of checking all colour areas, calculating required ditherings by seeking through the palette to find two colours near enough each other to manage good-looking dithering. And yet the two colours should appear as the third colour the whole routine should represent. And think of inserting THAT into the ray firing algorithm. I did the dithering because the depth cueing required smoothness, which could not be achieved by the means of more bitplanes. Why this? Well, I had tremendous lack of will to recode most of the C2P algorithms to run 6 planes, in no guaranteed result (about half of the chunky, including all extra planes, are deleted, in result of 128x96x5 screen to be converted 96 times per second on A1200 + chip (whole area is always converted, even though parts/all of it is unchanged)). Ok, back to the less interesting subject, the dithering. This is what is done: First, fading means technically redirecting texture colours via a shadowing table. More spesifically this means changing the color given by texture to something else that is darker (or same, perhaps). The darkness required depends on distance and lighting etc... That's up to you. Ofcourse, there are as many tables as there are darkness levels. To do the fading, I created a simple loop for drawing a y-slice from texture, and instead of writing the texture value straight to chunky, I used one table more (the fade one) and writed its value to chunky. The dithering is now one little leap away. I realized that it would be best to use two parallel tables, other one for lighter and other for darker fading. Both of them are used in the same vertical line. Other one is used in pixels of odd height and the other one in pixels of even height. The two tables are swapped every second ray to manage real rastering. Notice that these tables may contain the same information, which means solid fill, no rastering. Oh yeah, it is best to variate the usage of them a little; it gives more dithering choices. 3. Transparent walls Let's imagine that a ray hits a wall block that is marked as being transparent. After the hit, that wall square will be cleared, current ray data gets saved and then the ray routine calls itself, continuing like nothing happened and drawing the rest. After finishing that ray, the routine will again call itself, remaking the map, and drawing the possible transparent wall slices over the rest, skipping selected parts of texture. This is almost nothing to mention, as it is likely to get deleted after the perspective sprites are ready and the routine is moved there. This will result in a speedup of whole Wolf world. 4. Sprites Part of the ray firing? Extra y-zooming, player-facing, movability? You can straight forget all of that in ray firing! If somebody manages sprites that way in realistic speed, he surely has passed the level of advanced semi-god. But the solution, a separate routine isn't so easy... The difficulty with sprites is that the Wolfenstein world is very special in the aspect of the projection. This is for some strange reason I don't even try to understand. But the routine is still required! With the walls, the ray firing handles it all. But now there is some trouble! How to transform a (X,Y) coordinate pair to fit in a 3D screen? The drawing routine requires a) sprite distance and b) sprite location at the screen. To draw the sprites, the distance to nearest wall block for each ray is required in order to clip the sprites correctly. Firstly, Let's suppose there is a sprite in coordinates (a,b) (player is the origo). To manage the first step in the projection, a rotation routine is required for the sprite coordinates, and they must be rotated just as much as the player has rotated itself. I mark the rotated sprite coordinates as rot((a,b),PLRangleZ) = (x,y). No there is something like this: S = sprite P = player ^ ;direction (Spr coordinate rotation makes any case similar to upwards) | ;of observing. | x | S-----+ \ | \ | r\ | y \ | \| P Oh great, this is a triangle! Now just calculate r=sqrt(x²+y²) and then use that as the distance!!! Exactly these were my first thoughts... But the result was strange; the sprites seemed to obey their own distancial laws, being at the same distance no matter of rotation angle. This was ofcourse easily understoodable as the distance r is a constant if the player rotates. But the walls, however, cared about the rotation. Mathematically this should be correct solution to get the distance, but it isn't. Back to the drawing table. When I started thinking of a vertical line in a wall, I noticed that it seemed to approach the player on the corners of the view and then move along some sort of circular curve between them, reaching the distance maximum at the middle of the vision. I also noticed that the rest of the wall was at similar stage, otherwise the walls would seem curved, not linear. Hell. What mathematic formula can that be? Well, I came to this: The distance is same as the length of the segment starting from the object ending to the normal of our observing direction in perpendicular fashion. To say it in other words, we just take the y and use that as the distance! So the x can be forgotten completely. No square roots and such stuff! Few checks proved me this was correct solution. I came to it by thinking why the walls look realistic when they are linear and not curved as they should. I thought that in the distance, only the smallest possible distance (ie. a segment in right angle) between the normal of the observing direction and a vertical line in the wall matters, otherwise the walls would not be linear. That meant also ignoring the x coordinate, I thought. And the same rule must ofcourse apply to the sprites also. A moment later I really realized it and knew that the y is all that matters! That's it of the Z. Now, the sprite location on screen is just a question of congrucial triangles. Some scaling first and then the x-coordinate comes from formula x/y. That's all folks. Then all that is required is calculated. The rest goes along the lines of sprite scaling techniques. If you want more detailed explanations, have suggestions, ideas, questions, methods, something; send your messages to: andezl@kastelli.otol.fi. Any mail would be most welcome. Right now I have splitted my time over two game processes: This Wolfenstein game, and as a low priority bored-job I'm coding Scorched Earth/Tanks / Artillery Duel / Charr -type game. I am trying to pick the best sides of all those games, mainly staying faithful to Scorhed Earth, but the excellent idea of weapon system from Charr will be picked. Why I'm doing another tank game? Charr is pretty satisfying, but there are parts and bugs which I will try to fix and avoid in my game, like the amusing amount of damage toleration with most of the shields, shitty; buggy lava system, shield system, too small damage area in the bombs, the not-splitting air-burst if the bomb gots thrown up from the repellent shield... *** Some ideas for fixing that would be (in case somebody cares to pass these to the Charr's author) good: - air burst, mid-air split must split when the main bomb is noticed to be heading downwards, not when certain part of all shot frames have been done, as it seems to me to be now... The current routine is good to have as a co-splitter, but the dy>0 must also be included. - lava system is best to be removed entirely and recoded. The idea for better lava is harder to invent, but at least it can't be worse! Perhaps some sort of lava blockade-map, which tells that when the lava has spread as much as it can on that level, only then start higher (checking empty areas in sides and below, below always on higher priority, everywhere the lava has spread, setting up nearest empty pixel, and if none found - go to upper level, but start the routine again from original hit location (or above, if already filled with lava). The lava can currently spread upwards on slowly rising terrain, or just round a dome shield. This method would kill those bugs! Also, the lava doesn't give enough damage. I don't know if I'm right, but I suppose the damage depends on how long the moving part of lava (the pixel that draws more lava) touches or is near the tank. This should be changed to system that checks the environment of every tank, and every lava pixel nearby should give little damage every time. This way, the time the lava is around the tank would define most of the damage and the result would be more realistic. Also, the lava should cause some damage to tanks that are a little above lava (read: hot air). I think the time factor is somehow implemented in the current routine. - the shields should go down while the shots are progressing (if shield damage rises to 100%, very slow disactivation), so that bombard is not able to penetrate, but fe. lava may have enought time to spread inside if the shield is very low at the beginning of the round. - Better sounds. Kick that fucking Beavis&Butthead shit! Luckily all is in iff format.