Thursday, November 30, 2023

Procedural Buildings: Office Security Rooms

3DWorld's procedural office buildings already have some special purpose rooms on the ground floor. There are storage rooms with shelves along the walls, utility rooms with furnaces, water heaters and sinks, and server rooms with computer servers. The next room I wanted to add was a security room. And what does that room contain? A desk and walls covered with security monitors, which use the same 3D model as TVs and computer monitors.

But what do we display on the monitors? The most appropriate content is security camera video feeds, which means I also need to add security cameras to the building as well. The best places to add these are the two ends of primary hallways on each floor. This placement covers the building entrances, stairs, elevators, and secondary hallways to the offices and other rooms. These cameras allow the player to watch people and zombies walk around the building in realtime.

I started by adding monitors across all four walls of the room, wherever there was space. They're currently stacked two high. Right now they have the feet/stand attached, which makes them look a bit odd, but I'll fix that later. I also put a breaker box on the wall next to do the door so that the player can turn the power off to blocks of rooms and affect the state of lights, cameras, and monitors. This made debugging the update logic easier as the player had direct control over everything.

After placing the monitors and setting up the screen drawing code, the first test used the same camera for every monitor. Which camera? The one in the player's head that the world is normally drawn from. I was expecting each monitor to show a copy of the player's view, but instead I got the result below. The problem is that I'm reusing the mirror reflection code to draw the monitor images, and this code enables the player model. In addition, the front vs. back face culling was set up incorrectly, so the view on the monitors is the inside of the player model's head. Interesting, but not what I expected.

This is what you see when the "camera" is inside the player model's head and the front vs. back face culling is wrong. There are 36 monitors along the walls of this security room.

With that fixed, I get the expected recursive view through the player's eyes. Each monitor shows the set of monitors inside it, for a picture inside a picture inside a picture... Except that the top row is backwards because I've inverted the camera direction for cameras at the other end of each hallway. Anyway, so far, so good. It appears to be working. This image is only for debugging purposes.

Recursive camera view where the camera is in the player's head, looking forward in the bottom row and backwards in the top row.
 

At this point I have the cameras all set up correctly with the location and view direction of the 3D camera object. Each camera is on the ceiling tilted downward at a 10 degree angle. This gives a relatively good view of the end of the main hallway and, in most cases, the connector hallways, stairs, and elevators. This is typically what is monitored in office buildings as it includes the main public spaces.

Ground floor hallways have the lobby reception desks, but upper floor hallways all look the same. The elevator and stairs floor numbers are only sometimes visible. I needed a way for the player to tell which monitors showed which floors. My solution was to add a system for on-screen text drawn over the main image texture and shifted slightly closer to the camera to be in front of the picture. This uses the same text drawing system I have for signs and book titles. The text string consists of the room name, floor number, and direction ("E", "W", "N", and "S"). Currently all camera rooms are named "Hall" in office buildings. Technically, the ground floor is the lobby.

Camera images are updated by rendering the building interior to a texture each update frame. This is too expensive to perform each frame for dozens of visible monitors. My solution is to update only one image per frame. The camera chosen for update in a given frame is the one with the oldest (least recently updated) image across all monitors visible to the player. This way cameras will be updated round-robin. For example, if there are 10 monitors visible, each one will be updated once every 10 frames. This is good enough to get interactive framerates on the monitors. When the player gets close to a monitor to view the image in higher resolution, there will be fewer monitors visible in their periphery, which will make the visible monitor(s) update more frequently. I also reduced camera resolution from the default screen resolution (I use 1920x1024) to 1024x768 to improve framerate. Monitor images emit light and can be seen in the dark.

This system allows the player to watch people, zombies, and animals move in the hallways in realtime. I expect this to help the player plan to avoid zombies in gameplay mode by determining which floors are safe vs. dangerous. This is important when the player plans to exit the elevator on a particular floor and may become trapped by a nearby zombie. However, the side hallways and room interiors aren't visible to the cameras, so there's always some risk.

Security monitors showing a person walking in the second floor west side hallway (top center). The east side hallway is shown below, and hallways on other floors are to the sides.

The player has some additional interactions. Monitors can be turned on and off by clicking on them with the action/interact key. Cameras and monitors can both be stolen by the player. Any monitor showing a camera that's powered off or stolen displays an animated static texture created from random white noise. Here's an example where I've removed the two cameras at the ends of the ground floor hallway.

Security monitors show static when their cameras have been stolen by the player or powered off with a circuit breaker.

These monitors look a bit wrong with the metal legs at the bottom. Stands are generally removed for monitors that are mounted to walls. Fortunately, the stand and logo are drawn with a separate material. I can reuse the material selection system that I implemented for use with rotating fan and helicopter blades to disable drawing of certain materials for hanging monitors. Now it looks like this.

Security monitors with the stand at the bottom removed. The logo is also gone because it's part of the same material.

Note that these rooms are darker than they should be because the indirect lighting computation was broken at some point, likely when I was working on swimming pools for the previous post. I eventually did realize this and fixed it (after hours of effort). But I didn't want to go back and recreate these screenshots, especially the ones resulting from bugs that had been fixed by now.

Cameras are created from black and gray untextured metal materials. They had low contrast against the mostly grayscale colors used for office building walls and ceilings. This was especially true at the ends of long hallways with the broken indirect lighting, where the ceiling lights didn't reach this far. I added a red light to the front of each camera that flashes on and off once per second. The light is emissive when on and can always be seen, even in complete darkness. This way the player can notice the flashing lights and know they're being watched. I was able to avoid sending new vertex data to the GPU each frame by toggling the material emissive field on and off for all cameras in the building every half second.

A security camera hanging from the ceiling by the front entrance door with a blinking red light added. A camera is placed at each end of the primary hallway on each floor. This building has 38 cameras.

Here's a video I recorded where I enter the security room, view people walking on the monitors, and steal some cameras. This was created before I removed the monitor stands and fixed the indirect lighting.


There was one other addition I made. After experimenting with this a bit, I wanted an easy way to test the system that handled offline/unpowered cameras and monitors. It's inconvenient when the player has to walk long distances around the building to remove the cameras. So I added a breaker box on the wall by the door to allow the player easy access to controls that disabled cameras. However, this turned out to not be as useful as I had imagined because all of the primary hallways across floors happened to be connected to the same circuit breaker. This meant I could turn all of the cameras off, but I couldn't control them individually.

Another problem was that the original circuit breaker box generator only added labels for the elevator and parking garage breakers. If I happened to toggle the breaker associated with the security room, this would turn every monitor off. I would then need to manually turn each one back on after flipping the breaker back. This made testing more frustrating. I had to label the security room breaker. But if I was going to write the code to figure this out and generate a custom label, I may as well label every breaker with the room(s) it controls. But what about breakers that affect multiple rooms? My solution was to assign a priority to each room type, and label the breaker with the highest priority room type it controls. For example, the room type "office" is too generic to be shown on every breaker. Obviously, the "security" room should be assigned the highest priority based on the original problem I'm trying to solve.

Breaker panel with all breakers labeled with the rooms/functions they control. Sorry about the shadow on the right side labels!

That's all for this update. I may go back and add cameras to other rooms, or possibly houses. I'll have to think about this later.

Monday, November 6, 2023

Pool Rooms and Pool Rooms

When I say "pool room," do I mean a room with a pool table, or a room with a swimming pool? The answer is, both! I've added rooms with both pool tables and swimming pools to my scenes. I started with pool tables, and then later added swimming pools because I kept thinking about them every time I looked at the code. I'll describe how I added each type of room to 3DWorld's procedural buildings below.

Pool Tables

Some of the houses I've generated have quite a few unassigned rooms in the basements and extended basements. These are windowless rooms that don't work as bedrooms and some of the other room types. They're currently assigned functions such as storage rooms, laundry rooms, and card rooms. I've decided that a pool table would be a good fit for some of these rooms, at most one per house. I added code that picked the largest room and assigned it as a pool [table] room if it was large enough to fit the table with enough space around it for people to play pool.

I briefly considered trying to create an entire pool table with code, but I wasn't convinced it would look very good. Cubes can only go so far. Fortunately, there were plenty of free pool table 3D models available online. I chose one with the nice green felt surface and no other objects since I wanted to add the balls, etc. myself. The goal was to have a single table 3D model with a unique placement of pool balls and pool cues. I found a texture atlas image with all of the numbered ball patterns that I could apply to randomly placed spheres with random rotations/orientations. I added some couches to the room and a rug on the floor to get something like this screenshot. So far, so good.

Pool table with individual pool balls that the player can take. I have indirect lighting enabled because it looks much better this way. See the green reflecting on the ceiling?

Next, it was time to generate some pool cues. These are simple shapes made from truncated cones that I can already draw. I spent some time trying to find a pool cue texture online, but eventually gave up and drew my own in the Gimp image editor, using an image as a reference. Now that I had pool cues, I had to figure out where to place them. I decided to put a wooden cue holder on the wall with four slots, then place the cues randomly either in the holder, on top of the table, or on the floor leaning against the table. It took some extra effort to move the cues around as the player pushed or pulled the table. (Pool tables are too heavy for the player to pick up.)

Pool table with pool balls and pool cues in the holder, on the table, and leaning against the table. The balls are in different positions compared to the previous screenshot.

That looks pretty good. Each pool ball and pool cue is a separate object that the player can take and put in their inventory. I'm considering allowing the player to actually hit the balls around with the cues. I have most of the ball physics in place already, so it may not be that difficult. The main problem is that the pool table 3D model has complex geometry for the pockets that makes collision detection and physics difficult. It's not clear how to put the balls into the pockets. I'm leaving this as future work for now and moving on to swimming pools.

Indoor Swimming Pools

My original plan was to add only outdoor pools to residential yards. This mostly worked, but the system I have in place for exterior areas doesn't allow for as much detail as interiors. It also doesn't handle objects that extend under the terrain mesh such as in-ground pools. Plus, I really liked how the underground backrooms water from a few blog posts ago turned out. I did add water to bathtubs, but that's not quite the same because the player can't even enter them.

It's not easy to add water to above ground rooms due to the way they need to be cut into the terrain on the ground floor, and it doesn't make sense to have swimming pools on upper floors. That leaves basements and extended basements. House basements generally don't have rooms that are large enough to fit a pool. That leaves extended basements. This is convenient because I can reuse much of the extended basements water code from flooded backrooms to handle the water in indoor swimming pools. I've enabled underground pools for both houses and smaller office buildings that have no parking garages or backrooms. This allows me to separate the water logic between flooded backrooms and swimming pools because no buildings will have both.

The first step is similar to selecting pool table rooms, where the largest room is found and considered for a swimming pool. The main entrance door is determined, and the length of the room opposite the door is extended as far as possible up to a maximum distance or until an obstacle is reached. Obstacles include other underground rooms of this building, rooms of nearby buildings, and low points on the terrain. The room is also expanded by a smaller amount in width to allow for a larger (wider) pool and more space to walk around it. The room is rejected if the final size after all room expansion is too small.

The pool is then cut into the floor in the center of the room and takes up most of the space. A minimum of one doorway width is added along the sides, and two doorway widths along the ends. The end closest to the main entrance door has stairs added so that the player and building AI people can enter and leave the pool. The maximum water depth and number of stairs is determined by the length of the pool, and max depth is limited to one floor's height (about 8 feet). Pools above a minimum length have sloped bottoms with a shallow end at the stairs and a deep end at the far wall. Two or three gold colored metal railings are placed along the stairs.

I chose several shiny tile textures with normal maps for the walls, ceilings, and floors of the room, and the interior lining of the pool. The high specular component of the material makes them appear reflective and wet, and also helps to increase the overall lighting of these rooms. The ceiling and floor textures are the same at the moment. The wall texture looks exactly like the walls in the indoor pool room where I took swimming lessons many years ago. Water is drawn using the same shader to render reflections, refraction, and absorption that I used with flooded backrooms. I only had to tweak the constants to make it look good for swimming pools.

Here is what we have at this point:

Indoor underground swimming pool with shiny tiles on the walls, floor, and ceiling. Kind of plain.

That looks pretty good, but it's too empty. It's time to add beach balls and pool floats. I found a good striped color texture for use with large plastic beach balls. These are placed either in the water or off to the sides. These balls can be picked up, thrown, and kicked by the player. They float on the water surface and will rise to the surface if released by the player when underwater. They can be carried by the player to other rooms in the building.

Pool floats are modeled as simple torus-shaped inner tubes. I experimented with making them partially transparent, but I couldn't get the alpha blending/sorting to work between the water surface and multiple pool floats that may be visible through each other. I also considered using two sided transmissive lighting similar to what I used with tree leaves, but the current rendering and shader system didn't easily support this. So instead I opted for a simple small amount of emissive lighting in the color of the float's plastic to increase their brightness and reduce the shadows on their undersides. Pool floats can be picked up by the player but can't yet be pushed around or stood on. However, they have proper torus collision detection, so it's possible to throw a ball through their centers.

Here's a screenshot showing an example pool at this point in development. Notice that I've also increased the number of ceiling lights in pool rooms.

Brightly lit pool with pool floats and beach ball.

Pool with a float and two beach balls. This pool is in an office building, which is why the lights are rectangles.

That's looking better. Next I wanted to add some simple benches and a diving board to the larger pools. Maybe you wouldn't expect a diving board to be installed with a low ceiling underground pool though? And while I'm working on this, I may as well add a second pool inner surface material that's white with a rough finish. This is the style of the lining in the outdoor pool in my real back yard.

Diving boards caused some trouble with player collision detection because the system I had in place didn't support an object that could be walked on that was suspended above the floor. I eventually got this working, but the player can't actually jump off; they can only fall off the sides or end.

Pool with balls, floats, benches, and a diving board.

I was looking for diving board 3D models and found a nice model of a side pool ladder, so I decided to add those to the pools as well. I later went and added these ladders to the outdoor in-ground pools found in residential neighborhoods.

At this point I felt that the room looks pretty good. There were some minor fixes, such as reducing the period of the wave ripples and adjusting lighting inside the pool. It's time to move onto AI, player, and gameplay for swimming pools.

I would say the most difficult step was making zombies interact correctly with swimming pools. This actually took nearly half the total development time of pools! The default behavior without handling them was that people and zombies walked over the water like it didn't exist. The problem is that the water is actually under what would normally be the floor of the room. The AI system needs to special case handle the floors in these rooms by checking if points are over/under the water during path finding and physics updates.

It was relatively straightforward to have people treat the entire pool (including space above it) as a large collider to avoid walking into/through. This mostly worked, except for cases where one person pushed another over the pool when two people tried to pass each other on the narrow walkway between the room wall and the side of the pool. I had to add a check when the person was over the pool and have them fall into it with a smooth falling motion, rather than immediately dropping to the bottom in a single game frame. This was accompanied by a splash and ripple effect added to the water surface. Once they reach the bottom, a new target location is chosen as the closest point that's just beyond the pool stairs. The AI will turn and exit the pool in this direction. They climb the ramp and stairs to exit, and then revert to normal building room navigation when they're no longer inside the pool. I also added hard boundaries for the three non-exit sides of the pool to keep people from pushing each other through the pool walls.

Zombies proved to be more difficult than regular AI people since they follow the player. I had to change their behavior to keep them from falling in when the player was close to the edge of the pool or inside the pool. There was one bug that took me hours to fix where zombies intentionally walked into pools. I had to add debug printouts and debug visualization code to figure it out. The problem was that I had vertical wall collision objects along the sides of the pool to keep the player from walking through them when inside the pool. These were being picked up by the building AI navigation system even though they're not used for AI collisions. When the player was near the edge of the pool such that their bounding cylinder overlapped one of these walls, the zombie's target point was moved from the player location back toward the zombie location until there was no collision. This moved the target point inside the pool when the zombie was directly opposite the pool from the player. The fact that the path crossed through a blocking object (the pool) was ignored because the game logic assumes that if the player can stand there, so can a zombie. But this logic shouldn't apply when the target position has moved from the player due to collisions. The fix came in multiple parts: Ignore vertical pool walls, reject target locations inside of another object, and always enforce pool collision checks.

Zombies will still sometimes fall into pools, but it's not intentional. Usually it's because they're in a group pushing each other to get to the player. The zombie closer to the wall will sometimes push an adjacent zombie into the pool, particularly when cutting a corner. This is okay because there will always be at least one zombie outside the pool to chase the player.

Here's a screenshot of zombies having a pool party before I fixed the bugs. They try to follow the player, but keep falling into the pool. You can see the heads of a few zombies poking out of the water near the exit stairs on the far end of the pool.

Zombies in and around the pool. They try to chase me but keep falling in!

I decided to make the player "hidden" from zombies when fully under the water. When the player is standing in the pool with their head out of the water, the zombies can see them. But since zombies don't willingly enter the pool, they will simply walk around it and hang out by the stairs waiting for the player to exit. Zombies in the pool with the player will mostly ignore the player, but may attack when the player is located along their path and their head is out of the water.

I reduced the player's speed when walking (or swimming?) in water. The reduction scales with the depth of water around the player and reaches 40% when fully underwater. This isn't enough of a reduction to simulate real water movement, but a higher reduction makes it take too long to get out of the water when the player falls in. I already added a similar speed reduction for other cases such as when the player is climbing stairs.
 
The final step was to allow for player drowning when staying underwater for too long. This is mostly for game balance as it prevents them from hiding under the water until the zombies leave the pool area. I added an oxygen bar in cyan that only appears when oxygen is below 100%. The player has 30 seconds of air when underwater. When out of the water, the oxygen bar will refill completely at 10x speed, in 3 seconds. I increased the strength of the wavy water effect postprocessing shader as oxygen is reduced and made the screen begin fading to black when oxygen drops below 10% as a sign to the player that they're about to drown. Drowning will play a special gurgling sound and reset the player to the starting location with an empty inventory and no money.
 
I'm considering adding the player's dead body in the pool, if I can find a good animation or pose for that. It could even be a pickup item! I currently only spawn a pool of blood on the floor when the zombies kill the player. I guess they eat the player, or something like that? I'm not sure what happens to the items in the player's inventory on death. Maybe some items should drop? Only the item the player is holding at the time of death?

Here's a video showing how zombies and the player interact with swimming pools. I disabled the bug fix so that zombies fall into the pool because this is more entertaining to watch. Of course it also makes gameplay too easy.


Now that I have swimming pools with nice blue water, the water in flooded basements seems too clean. I changed the backrooms water material to be 50% mud to give it a dirty brownish color, and the increased variety looks much better. Here's a new screenshot for reference.

Flooded backrooms with muddy water.

Everything is working well now. I may add some sort of player swimming ability later if I can find a good way to control it with the keyboard. I might also try to improve outdoor pools and actually allow the player to enter them.