jorio/Bugdom

Increase render distance?

foote-darrell opened this issue · 34 comments

In the game, most notabily on large, open areas like Level 3: Pond, Level 5: HIve Attack, and Level 8: NIght Attack, there seems to be a very short render dinstance. The view quickly is obscured and faded with a foggy effect not very far in front of the player, becoming hard to see where I am in the map. Also in Otto Matic and Nanosaur for that matter.

This seems to be a faithful recreation of the old game's effect. But nowadays comptuters are far more powerful and ought to be able to render the full view with ease. This seemingly would be an hard encoded limit. How ought we be able to modifty the code to increase the render distance?

jorio commented
  • To make it easier to experiment for now, get rid of the cyclorama. The easiest way to do this without touching the code is to enable "low-detail mode" in the game's settings.
  • In camera.h, raise YON_DISTANCE to something like 25000. This is how far the game will render objects in the view frustum.
  • In terrain.h, raise MAX_SUPERTILE_ACTIVE_RANGE. This is the amount of terrain tiles the game will keep track of. Don't overdo it, because 4*MAX_SUPERTILE_ACTIVE_RANGE^2 must fit in a signed short. A value of 60 is reasonable. You'll run into integer overflows beyond 90.
  • In main.c, change all values in gLevelSuperTileActiveRange to MAX_SUPERTILE_ACTIVE_RANGE/2
  • In liquids.c, raise MAX_LIQUID_MESHES to something like 300

The game will most likely fall apart with these changes, but that should be a good starting point for experimentation. Have fun!

Wow, it all worked really well. It is surreal how small the map really is. Level 3: Pond didn't seem to have a cyclorama to begin with, so worked just right. Level 8: NIght Attack reported DeleteEnemy: <0 after it loaded. How do I remedy this? My computer can handle it, I got the processing power.

jorio commented

It really changes the mood of the game, doesn't it?

I'd wager the game is trying to spawn too many enemies, causing an integer overflow somewhere. Try changing the type of gNumEnemyOfKind from int8_t to int32_t in both enemy.c and game.h

The level is really huge and has lots of enemies and items. Once I changed it, I can't go back. I don't know how I was ever able to play the game with the view it so limited. Level 8 also has some gorund missing in some parts. It is just really huge though.

How can I do the same to Otto Matic and Nanosaur? Otto Matic didn't seem to have the YON_DISTANCE variable.

jorio commented

For Nanosaur, look at jorio/Nanosaur#22

Otto automatically computes the yon distance based on the supertile active range. However, Otto's DoPlayerTerrainUpdate function can only handle SUPERTILE_ACTIVE_RANGE values 4 through 9 — the game will fail to compile with anything else. To fix this, we'd need to write some code to generate gridMask for a square of arbitrary size.

But if you just want to hack something up quickly to see what Otto is like with a long draw distance, change this line to mask = 1;

Well I changed line 1838 in terrian.c to mask = 1;. The render distance looks the same, but everything is just missing instead.

jorio commented

Try setting SUPERTILE_ACTIVE_RANGE to something like 50 in terrain.h. The mask=1 hack on its own merely lets you compile the game when SUPERTILE_ACTIVE_RANGE is set to an unsupported value.

jorio commented

@foote-darrell Just realized that you can fix the missing terrain patches in Bugdom:

  • Change the the type of gTerrainScrollBuffer to uint16_t (instead of uint8_t) in terrain.c and game.h
  • Change EMPTY_SUPERTILE to 0xFFFF in terrain.h

Looks like it worked.
You're so smart, are you a software engineer in real life or something?

jorio commented

Haha, yes, I'm a programmer by trade :)

What does "PR" stand for?

@jorio: Now for my first question.

What do I need to change to turn the InfoBar UI transparent, while still leaving its individual widgets visible?

jorio commented

Ready for a long technical writeup? I'm going to assume you're not familiar with the rendering pipeline, so apologies if I'm droning on about stuff that may be obvious to you, but that might help others understand what's going on.

The swamp green color is the fog color that is used to fade out distant terrain geometry, even in high-detail mode. It also doubles as the sky color in low-detail mode. The original game actually didn't have a separate sky color for high-detail mode — gLevelClearColorWithCyc is something I added in so there wouldn't be ugly swamp green if you get Rollie to jump so high that the void above the cyclorama becomes visible.

The ugly swamp green skies is actually how I remember playing the game as a kid! I had the demo on a first-gen iMac from '98, and it had a measly ATI Rage IIc GPU. The original Mac game had a checkbox for Rage II cards that turned off a bunch of fluff specifically for these low-specced machines. Here's what the low-detail mode does exactly:

  • Lower terrain texture resolution
  • 3 LODs for terrain textures (far-away supertiles are drawn in even lower resolution)
  • Nearest-neighbor texture filtering
  • No cyclorama
  • No lens flares
  • I think the OS 9 version's Rage II mode turned off fog as well, and made the viewport a tad smaller.

Most of this mode is preserved in the port for completeness's sake. I do agree that for all the nostalgic value this low-res mode holds for the few of us that played the game on a Rage II, it really doesn't show the game in its best light, so we ought to make high-detail mode work.

Now, to understand why the cyc is problematic for extending the draw distance, let's talk about what it actually is. The cyc is a giant cylinder with a radius of about 4 or 5 supertiles, and it moves (almost) in sync with the camera's position.

Here's the biggie: the cyc's geometry is written to the depth buffer, or z-buffer as the source code calls it.

The z-buffer is a hidden image that the GPU uses to keep track of the "depth" of every pixel that has been drawn in the frame so far. Whenever you submit a mesh for rendering, the corresponding depth values are drawn to the z-buffer. The closer an object is to the camera, the lower its depth values will be in the z-buffer. When you submit a new mesh for rendering, your GPU looks at what's already in the z-buffer where the mesh is located, to determine whether it should bother drawing the mesh or not. If a new mesh's depth is lower than whatever's in the z-buffer at that position (i.e. closer to the camera), then the mesh is drawn over whatever was drawn there before, otherwise it's occluded (i.e. farther from the camera than geometry that had already been drawn). Wikipedia has an in-depth (heh) article about this technique if you're interested in learning more.

Back to the cyc — Since the cyc is written to the z-buffer, this means that the giant cylinder will occlude any geometry that lies beyond it. Because the cyc has a limited radius, bumping the draw distance beyond the radius of the cyc wouldn't cause any visible change! This is why I initially said it was easier to just turn it off.

Writing the cyc to the z-buffer actually has some use in the game. Hiding far-flung supertiles behind the cyc sorta prevents supertiles from suddenly popping in and out of view as the terrain engine streams them in and out of memory. Levels that don't have a cyc mitigate this artifact by bringing in the fog even closer to the cam. (You'll notice that this isn't perfect and there are a couple places in the game where you can actually see the supertiles being streamed in, but at 640x480 it wasn't very noticeable back then.)

So, I see two solutions to this problem:

  • We could adjust the dimensions of the cyc to make its radius bigger, but that might be fiddly to get right if we want to preserve its look and movement relative to the camera.
  • Or, we can keep the cyc as it is, except we won't write it into the z-buffer so that it doesn't obstruct terrain geometry. This might just be good enough, because terrain streaming artifacts might not be visible to the naked eye with a very long draw distance.

So let's look into disabling z-buffer writes for the cyc. This way, the GPU will draw the cyc to the color buffer, but it won't write the cyc's depth to the z-buffer. In CreateCyclorama (items.c), just add STATUS_BIT_NOZWRITE to gNewObjectDefinition.flags.

This isn't enough, though. You'll notice that the cyc is still drawn on top of some of the terrain. Remember, to determine if a pixel from the cyc should be written to the color buffer, the GPU looks at the depth of whatever's already in the frame at that position so far. As it stands, the game submits the cyc for rendering after the entire terrain has been drawn. So, when it's time to draw the cyc, the GPU will scan the z-buffer for what's already been drawn so far (the terrain) — if the depth of a piece of terrain is farther than the cyc, the GPU will write the cyc to the color buffer on top of that piece of terrain, because the cyc is closer!

To remedy this, we can force the renderer to draw the cyc mesh earlier than any terrain meshes to achieve the desired effect. In my renderer, I have gathered the draw order in renderer.h. This ought to do the trick:

enum
{
    kDrawOrder_Cyclorama = -128,   // give it a lower value than the terrain to draw it first
    kDrawOrder_Terrain = -127,
    kDrawOrder_Fences,
    ...

And there you have it: we're drawing the cyc to the color buffer before the terrain. And, since we're not updating the z-buffer when drawing the cyc, the terrain never gets occluded by the cyc.

I'll answer your question about the infobar another time :)

I had the demo on a first-gen iMac from '98, and it had a measly ATI Rage IIc GPU.

Believe it or not, Bugdom was actually my very first video game! I played it on my first computer, a hand-me-down blue-white Power Mac G3, running Mac OS 8 (Mac OS 9 was a later upgrade). Some of my earliest computer-related memories are of deliberately seeking out glitches in Bugdom.

And there you have it

Thanks for explaining both the steps and the reason for them! I patched and re-enabled the cyclorama code in my fork.

The sky textures are now visible again…without overlapping the terrain. Yahoo!

@jorio: I have another question…

After extending the rendering distance, I keep getting an assert in Level 9. Apparently, Level 9 has enough particles across the map to overload the particle group system. (The assert triggered on the game trying to add over 300 particles.)

So what do I need to change, to safely raise the particle cap to something bigger than a byte?

EDIT: Speaking of particles, how would I go about porting Nanosaur's shield effect to Bugdom?

jorio commented

That being said, what do I need to change to, as I said, turn the InfoBar UI transparent, while still leaving its individual widgets visible?

To add transparency to the artwork itself:

  • Change the infobar texture's internal format to GL_RGBA instead of GL_RGB
  • Change gInfobarBottomMesh's texturing mode to kQ3TexturingModeAlphaBlend instead of Opaque. Do that for the top mesh as well if you like.

To make the entire UI translucent, change the alpha component of kDefaultRenderMods_UI.diffuseColor.

You may also want to adjust the viewport's clip pane (in InitArea()) so the 3D view covers the entire screen.

So what do I need to change, to safely raise the particle cap to something bigger than a byte?

This assert may be obsolete. At a quick glance, I can't see any blockers that would prevent bumping the particle cap beyond 255 per particle group. (But I haven't tried!)

Also, it looks like whenever the game needs to keep track of a particle group number in an ObjNode, it stores it in a short or a long, so it should be fine to raise the number of particle groups as well.

Speaking of particles, how would I go about porting Nanosaur's shield effect to Bugdom?

Create a spherical mesh and attach it to a new ObjNode. Bind the ObjNode's move call to a function that tracks Rollie's position (see Nanosaur's MoveShield()). Nanosaur makes the sphere spin super fast, which makes it look like it's vibrating because since it's low poly.

If you don't want to create the vertices for a spherical mesh by hand, you can repurpose an existing mesh from elsewhere in the game. I think GLOBAL1_MObjType_ShockWave should do the trick.

To add transparency to the artwork itself…

Awesome! Thank you very much!

Would you happen to also know how to keep the top bar from stretching across the entire width of the screen? (It gets ugly in widescreen resolutions.)

@jorio:

Eesh. I was just reviewing the distance code, when I realized that statically increasing YON_DISTANCE and gLevelSuperTileActiveRange (like you initially advised) actually breaks an if check which scales both the yon distance and the cyclorama to the value of gLevelSuperTileActiveRange.

In fact, the camera itself uses gCurrentYon (the value set by this if check), instead of YON_DISTANCE!

jorio commented

Eesh. I was just reviewing the distance code, when I realized that statically increasing YON_DISTANCE and gLevelSuperTileActiveRange (like you initially advised) actually breaks an if check

If we look at it another way, there are only 2 different draw distances:

  • lawn and dragonfly level types use a draw distance of 5 supertiles in the base game
  • all the rest use 4 supertiles

So in essence, all that if statement does is adjusting the cyc's scale for specific level types. We can do just that instead of depending on specific draw distances. I think the snippet below is better than the original if statement:

	switch (gLevelType)
	{
		case LEVEL_TYPE_LAWN:
		case LEVEL_TYPE_FOREST:
			gCurrentYon = YON_DISTANCE + 1700;
			gCycScale = 81;
			break;

		default:
			gCurrentYon = YON_DISTANCE;
			gCycScale = 50;
			break;
	}

The +1700 might even be negligible with a very large yon distance anyway.

how to keep the top bar from stretching

The game's UI viewport is 640x480, regardless of the dimensions of the screen. It's then stretched across the entire width/height of the screen.

So, you can tweak the top/bottom meshes' dimensions relative to the screen's aspect ratio.

The top bar is 640x62 assuming a 640x480 UI viewport (4:3 aspect ratio). To adjust its size to a 16:9 aspect ratio, you could keep the width as 640 and change the height to $${{originalHeight * targetAR} \over {originalAR}} = {{62 * 16/9} \over {4/3}} = 82.7$$

Ideally, you should adjust these dimensions whenever the aspect ratio changes, e.g. if the user resizes the window. The current aspect ratio can be retrieved from gGameViewInfoPtr->aspectRatio.

If we look at it another way, there are only 2 different draw distances

I did notice the original two-way split. But I found that when playing in heavily-occluded indoor levels (namely the Hive and Anthill) most of the extended view distance ended up being wasted.

So for my fork, I added a third draw distance to the game for low-visibility outdoor levels (namely the Pond and Night), and dropped the base YON_DISTANCE down to 5000.0f for indoor levels. (I also changed to your proposed switch style for future maintainability.)

Then I had to tweak the auto-fade distance and cyclorama scale for these templates, to roughly match their original ratio to draw distance.

It took a bit of tweaking, but I would say the in-game results are more than satisfactory.

@jorio:

1.3.2 (June 27, 2022)

Oof. Rebasing my fork was a major headache (it involved nuking my whole repository and copying my previous changes line by line), but I am glad I did!

At this point I have dealt with most of the low-hanging fruit for my fork, and am starting to work on thornier issues I am not sure how to deal with.

@jorio: Some of the items in my to-do list are things I already have an action plan on, but have hit a snag preventing me from actually implementing said plan.

For instance, individually spawning and attaching up to 10 Buddy Bugs is easy enough to do—but I really have no idea how to refactor MoveMyBuddy() or MoveBuddyTowardEnemy() to fire them off one by one. I hit Tab, and the whole stack of Buddy Bugs makes a beeline for the nearest enemy!

Similarly, I don't have a clue where or how to hook an event to an animation state—which I need to know in order to adjust collision on dead enemies. The one time I tried hooking a damage event to the player's falling and landing states, it did absolutely nothing.

Finally, there are items that require tools or expertise I definitely lack. Anything that requires me to read 3DMF file internals, write original code, or develop on any platform other than Windows, is never going to happen—so please let me know if any of my to-do items falls under one of those categories, and I'll strike it from the list. 👋

Glad you mentioned the reading of 3DMF files, I've been wondering if the source could be used to develop a tool to convert all the assets into more common formats like FBX and such. Could open up the possibility of custom levels and the likes as well.

@Arenovas:

develop a tool to convert all the assets into more common formats like FBX and such.

Oh, that should definitely be possible—if far beyond my own personal skillset.

It would involve building some sort of conversion interface on top of Quesa, since 3DMF is a Quickdraw 3D format. But presumably that would not be too complicated for someone familiar with the relevant formats and libraries, since Quesa's developers have already put in most of the necessary work. 🤷

jorio commented

Quesa is a great project! I used it to bootstrap the Nano and Bug ports before I rewrote their 3D code.

I can also suggest looking at the 3DMF parser that's built into my versions of Nanosaur and Bugdom. It doesn't support as many 3DMF features as Quesa does, but it's just enough for the models in the Pangea games.

Animations were made in a custom tool (BioOreo Pro) and are not stored in the 3DMF itself.

For what it's worth, there's a built-in model viewer in Bugdom (see CHEATS.md).


@LordNyriox, all of the items in your list look doable without the need for baroque tooling.

Rebasing my fork was a major headache

I'd guess that's because of your commit that cleans up trailing whitespace? It touches the entire codebase so I wouldn't be surprised if it made merging a pain.

I still have not found whatever function removes dead enemies after they leave the viewport

Look at MoveAnt_Death, MoveBoxerFly_Death, etc.

10 buddy bugs

Please refactor the buddies into an array first. Then, when you detect the tab keypress in MoveMyBuddy, you can set some sort of flag, or cooldown timer, to prevent launching other buddies during the same frame.

fall damage

You're currently updating the fall damage value continuously. As the character gets closer to the ground during his fall, gMyDistToFloor decreases. When it inevitably goes below 1800, you pin fall damage to 0. You should probably compute fall damage at the apex of the fall instead.

The Bugdom Extreme project is a nice touch and it is wonderful to see that, in the spirit of OpenSource, others have begun experimenting with the wonderful work of @jorio (who's ports actually mean a great deal to me). However, I wonder if the existing "original" game port could be made to only play and look a little better, instead of extreme-ing the game...

I just played Level 1 and 2 of the Bugdom port in v1.3.2 on my big-ass 4k TV in a wonderful framerate with x4 AA and it was glorious. Nostalgia, pure nostalgia so not much needs changing in my opinion and every broken thing should still be broken.
Note: I marveled at the inclusion of the ATI Rage mode, which made the game look almost exactly as weird as it did back then (I remember black boxes around the camera lense effect caused by the sun), although I quickly went back to the High Detail mode.
The thing I was wondering about is if we could keep the gameplay (like enemy amount, aggression and behavior) intact while extending the draw distance at least somewhat. As I go back to 90's games from Pangea (just finished Otto-Matic), I find it very interesting how same-y their individual levels are compared to some of the competition when it comes to the visuals. Of course, that is fine because I want the levels to be like they were over 20 years ago... ...but I would like to see more of them.

The draw distances of yesteryear were of course a technical necessity (what is the size of that z-buffer? 8-bit tops?), but they were also much more acceptable (and still are) when one plays in the same low resolutions as the yesteryear PCs had. When enjoying these games in a more modern setting however, it is actually the only part of the visuals that are bothersome (especially since the inclusion of seamless terrain rendering). Anything except the draw distance benefits greatly from a modern PC's horsepower, more pixels, higher framerats, AA and one could probably even force anisotropic filtering through the driver. So, everything gets cooler except the draw distance...

@jorio , while I have not checked the source code in its entirety, it seems to me (from what you wrote above) that one could probably achieve a happy "medium" way of doing things without breaking too much, especially since you already pointed out that we could even switch the values depending on the level type.
Do you think that, aside from the High Detail-Mode, the inclusion of an Extended High Detail-Mode would be acceptable to the spirit of this project that e.g. doubled or trippled the draw-distance (and tile count) without modifying other aspects of the gameplay?

@EliasHeydrich:

doubled or trippled the draw-distance (and tile count) without modifying other aspects of the gameplay?

Truthfully, I was thinking of making a branch for that anyway. Bugdom Extreme won't be ready for a while, due to my long list of gameplay tweaks which need to be assessed or implemented.

But I could easily isolate my non-gameplay commits in just a couple of days, and release the visual and auditory tweaks as 'Bugdom Enhanced'.

Would you be interested in that?

I will take anything that safes be building-from-source by myself. So, sure, I would be interested.

My general question however remains directed at @jorio , if a extended-drawdistance-mode is something that is on the agenda for any of the Pangea Ports. This issue has strayed a bit from the original intention of the very first post, so consider my post an attempt to re-direct it. Because if the answer is a no, the issue could actually be closed.

I'd definitely love to see the standard game receive an extended draw distance option, even a doubling would probably make it a bit more enjoyable. Definitely wasn't too much of a problem back when I played on an old Mac as a kid.

Also, the model viewer is definitely handy, definitely can help with extracting the model textures. I did find a program that did let me convert the models to FBX but the UVs are a bit glitched and the animations can't be given to them. And I still have no way to convert levels, curious if that 3DMF parser also works on the levels or if it's something else?

jorio commented

The reason why I haven't provided the option to extend the draw distance in the baseline version of Bugdom yet is that it would require a lot of care to ensure it doesn't break the balance of the game.

Distant enemies despawn when the supertile they stand on is too far to be visible. Also, there is a cap on the number of each type of enemy that may spawn in the game world. So, with a longer draw distance, enemies might not despawn as often, which may in turn prevent new enemies from spawning.

We'd need to recalibrate those caps to a longer draw distance to preserve the balance of the original. Or, we could decorrelate enemy spawning zones from the actual draw distance.

There's also an open-ended question of how far we can go without straying from the artistic intent of the original. For instance, the mood in level 5 is drastically altered by removing the heavy fog.

So in a nutshell, my answer isn't "no", but getting it just right is going to require a lot of careful work.

Did anything come of this? I checked https://github.com/LordNyriox/Bugdom but that seems to have stalled...

As @jorio wrote, I suspect its pretty tricky to make a modification as deep as this while keeping the game playing as it is supposed to...