Thursday, August 15, 2024

Procedural Building Door Handles

I've been experimenting with different types of handles for the doors in 3DWorld's procedural buildings. This will be a very short post on that topic. I have several different door textures for use with buildings. Two of them are used for exterior office building doors, one is for interior office doors, and one is shared for both house exterior and interior doors. I'm mostly concerned about handles for the interior doors because these are more likely to be close and visible to the player. They're also the ones the player can directly interact with.

These doors were originally simple box shapes with a door texture drawn on each side and white quads added to cover the exposed edges of open doors. The door handle (and lock, in the case of office doors) was included as part of the texture. This was very efficient and looked acceptable, though some amount of lighting was baked into the door texture and didn't always match the lighting inside the building. The biggest problem was that these handles were only 2D decals and didn't have any depth.

I decided to experiment with different forms of door handle geometry. The requirement is that door handles must rotate with the door when opening and closing, and also must be relatively efficient to draw. (Some of the larger buildings have as many as 1000 doors!) My first attempt was to add some cube/box shapes for the plate, shaft, and handle. The plate and shaft extended through the door to both sides while handles are added separately to each side, resulting in four boxes per door. This was quite efficient and also matched the rectangular geometry uses with most of the other interior objects and details. However, it didn't look all that good. In particular, there were no curved surfaces to catch the specular reflection from ceiling lights.

I wasn't too happy with this result. It was time to find a proper 3D model that I could use with doors. The most efficient model for this purpose is one with a somewhat low poly geometry and a single material, to minimize the number of draw calls. Fortunately, there were a number of free 3D models available online that worked.

The most difficult step was calculating the proper transform (rotate + translate) needed to make the handle move with the door when opened or closed by the player or AI people. The next most difficult part was to make door handle drawing efficient. I was able to enable both view frustum culling and wall/floor/ceiling occlusion culling using the same code as the other 3D models placed in buildings. In addition, I only consider drawing handles of doors within one floor of the player, and skip handles on the opposite side of closed doors from the player. It may still be possible to see a missing door handle in rare cases such as when looking down multiple flights of stairs. Fortunately, the door texture still has a handle visible underneath, so it's not too obvious when the handle geometry is missing. Exterior door handles are not drawn, and neither are handles for doors in buildings other than the one the player is in.

I added a variety of door handle colors. Houses use brass and various shades of white to gray. Office buildings use black and darker shades of gray. Technically, the grays should really be silver/metal, but there's currently no reflection drawn on the surface of the material.

Here is what these door handles look like. I changed the code to randomly add {no (texture), boxes, model} handles to each door, and found a room in a house and office building that had all three types of door handles visible.

House door handles, from left to right: cube geometry, 3D model, texture. These are brass colored.


Office door handles, from left to right: texture, cube geometry, 3D model. These are black painted metal.

That's good enough for now. The model handle colors don't always match the door plate and lock colors, but it's still better than not having them. I made door handle type a config file option and left the default as 3D model, since it looks the best in my opinion. The added draw time appears to be insignificant, maybe ~1% longer. I may go back and work on this again later.

Saturday, August 3, 2024

Procedural Buildings: Interior Dirt, Damage, and Messiness

I'm going back to work on building interior details. Up until now building interiors have been mostly clean and in perfect condition, except for the occasional flickering or sparking ceiling light. The only mess I've added since I started on buildings is clothes lying on the floor. I did also add trash in the form of paper balls in some trash cans, but that doesn't make the room look very messy. I'm going to show the various "imperfections" I've added to building basements, in approximately the order that I added them. This will be a mostly show-and-tell post with screenshots and very little technical detail, since most of these were relatively simple building improvements.

My first non-clean addition was empty swimming pools with deflated pool floats such as the one shown in the screenshot below. Deflated pool floats have been scaled in height by one tenth and use a darker opaque material. There is no water and there are no fish in these pools. Since the edge of an empty pool represents a significant drop onto a hard surface, this is one of the rare locations where the player can take fall damage in gameplay mode.

Empty basement swimming pool with sad, deflated pool floats. Empty pools have player fall damage.

It's time to add some dirt and damage effects. I'm starting with building basements and parking garages, since these tend to be messier in general. These areas are used for storage and are less likely be seen by visitors. Dark and dirty basements with their creepy sounds provide a good contrast to the clean and bright rooms found above ground.

The first damage effect I added was water damage, which reused the existing rain puddle shader effect to add random patches of dark/wet areas to surfaces using procedural noise. Material colors are gradually shifted toward moldy black and the specular lighting component is increased to produce a reflective, wet effect. Each building has a random threshold selected to control the frequency of water damage spots so that the strength of the effect varies across buildings. This applies to every fixed building surface in the basement. It doesn't apply to objects such as people, cars, and animals. The effect uses 3D noise so that it properly wraps around surfaces and works even on objects with complex curved shapes such as the sprinkler pipes.

Parking garage with water damage on the walls, ceilings, and floors.

The second effect I added was cracks to walls and floors. It's not as simple as drawing a crack texture as a decal over the existing geometry. This would be complex and slow because it requires drawing all crack-enabled geometry twice, the second time with a custom shader that applies an alpha mask so that it only applies to certain pixels. A texture-based solution would need to tile and could have obvious repetition in the crack pattern. There are also potential problems with blending and Z-fighting of the fragments.

My solution was to use another shader effect that applies the same 3D noise as water damage, except using a threshold and large exponent to create dark lines along the surface contours. Any noise value within a narrow band is shifted toward a matte black color. The approach is similar to how marble patterns and mineral veins are created in some shaders. These aren't quite the same as real cracks because they can form loops and don't branch very frequently. However, the technique is quite simple and efficient.

I only apply cracks to basement interior walls and floors. Ceilings of some houses are wood, and cracks don't look good on wood, so I disabled the crack effect on ceilings entirely. Walls are always plaster/stucco or concrete. The floor is usually concrete or tile, but can occasionally be carpet. Neither the drawing code nor the shader actually track which sections are carpet, so it's not easy to avoid carpet cracks. Objects on walls and floors such as pictures and rugs don't have cracks enabled. 40% of buildings have basement cracks present.

Basement with cracked walls and floors, and a bit of water damage near the ceiling.

The third effect I added was stains on the floor. I had an existing system to draw blood and bug guts as decals using a custom colored alpha masked texture atlas. This can be modified to add random stains or puddles of liquid with various colors such as red (for blood), green, and black. Since stains are small and locally applied, it's okay to draw them over the existing geometry. These stains work equally well with all floor coverings including concrete, tile, carpet, and rugs. Here's a screenshot with an excessive number of floor stains.

Extended basement rooms with stains on the floor in a variety of shapes and colors. The number of stains was set larger than normal for testing purposes.

What about adding stains to walls? That should work as well, though it requires more effort to check for blocking objects and set the orientation of stains to align to arbitrary surfaces. I also added more random size and orientation variations for stains to make them less repetitive. Here is what this looks like in a different building's basement pool room.

Room with both wall and floor stains. (The top surface of the pool table reflects a green color from the ceiling light.)

Now it's time to make a mess of some of the room objects. One idea is to scatter items such as books across the floor in random locations and orientations. Another idea is to rotate objects to non-orthogonal angles so that they're no longer parallel to walls and other objects. Maybe some of the larger items such as bookcases can be pushed over and/or broken in some way. I would apply the broken glass texture to TVs, computer monitors, and mirrors. The problem is that these generally aren't placed in basements.

I just need to be careful not to block the path of the player and building AI people with these "clutter" objects. The placement system will add most of the larger items along walls in a way that they can't block the center area of a room. But tall and narrow items such as bookcases could extend further into a room when pushed over. Fortunately, the existing collision detection logic should allow the player to walk over and stand on low objects, which means they don't truly block the player.

Books from the basement library are scattered around randomly on the cracked floor.

We have books on the floor. What about adding furniture that's fallen over? I can see that working with objects such as bookcases, chairs, tables, desks, trashcans, etc. I just need to rotate these objects around a pivot point such as the back legs of a chair, make sure there's space for the fallen over object to not collide with anything, and draw it in the new orientation. Of course it's not enough to simply update the drawing code, I need to also change the detailed collision detection logic for chair legs vs. balls, animals, etc. Here's what this looks like for chairs.

Basement room with fallen over chairs and books scattered on the floor. Here you can see what cracks look like on carpet.

That cracks effect looks pretty bad on carpets. I mentioned this potential problem above, but it actually looks worse than I imagined. I'll have to fix this with some nasty special cases that test if the filename contains the string "carpet" and set the crack effect scale to zero for those materials. Here, this looks better. The cracks are only applied to the walls, not the carpeted floors.

Office building basement with cracks effect applied to the walls but not the carpet.

Fallen over bookcases can be added as well. In this case they can only fall away from the wall and toward the center of the room. I do worry that zombies could be blocked by fallen bookcases and not be able to chase the player into a room. However, in this case they can probably walk around them.

An extended basement library where some of the bookcases have fallen over, spilling books onto the floor. There's also water damage in this room.

I can create broken objects as well. The player can currently break mirrors, TVs, and computer monitors by throwing objects such as balls at them. Broken objects use a crack texture as a decal drawn over their surfaces. I can apply this same crack texture to some of the basement glass table tops. The only difference is that I need a special transparent version of this texture that only has opaque pixels along the crack lines. These cracked tables, along with other broken items, have less value to the player when taken.

Basement room with cracked glass table and another fallen over chair.

How about more trash on the floor? One idea is to scatter empty bottles of random types and in random orientations across the floor. I had to add custom rotation logic for drawing bottles that aren't aligned to a coordinate axis. Here you see a mixture of water, coke, beer, wine, medicine, and poison bottles thrown everywhere in this house. Some are upright, but most are on their sides. I did make sure that spiders and rats can properly collide with floor clutter objects.

Empty bottles are randomly strewn across the floor in these basement rooms with random rotations.

It would be nice if there was an easy way to make some of these bottles broken. I can't think of a good way to do it, but I can at least add some piles of broken glass on the floor. This reuses the same broken glass pile drawing as cars where the player has broken a window to steal the items inside. This screenshot shows one of my favorite rooms with a bottle on top of a stain and broken glass on a rug nearby.

A pile of broken glass on the floor, next to my favorite wine bottle with a stain under it that looks like the wine was spilled.

Adding floor clutter definitely improves the feel of these underground rooms. I need to add trash as well, and for this I can place paper balls drawn with the same random distorted spheres that I use for trash in trashcans. These are distributed randomly on the floor with a variety of sizes and colors.

Office building basement rooms with randomly sized and colored balls of paper trash scattered across the floor. This is an excessive number of paper balls.

That's definitely too much trash for a single room. And the earlier screenshot probably has too many bottles on the floor as well. These tests were good for checking object distributions and overlaps with other room items, but I need to reduce their counts by an order of magnitude. I've set the max number of bottles to 10 and the max number of paper balls to 5. The screenshot below looks much more reasonable, and is better for performance. There's still a decent mess, but not an excessive amount.

A more reasonable number of objects placed on a basement room floor: several books, several bottles, and several pieces of trash.

That covers everything I've added so far. I'm moving on to other tasks now, though I may get back to this topic later. Leave a comment if you would like to suggest other items or effects that I can add.