Monday, September 30, 2024

Procedural Building Conference Rooms

I'm pretty happy with the look of glass upper retail floors added in my earlier post. Maybe they're not very practical for public spaces, but they make the room look larger while also allowing for increased usable space. I need more glass in these buildings. It would be interesting to have glass walls as well. Maybe for conference rooms? We have some real conference rooms with glass walls/windows at my work office.

The first step is to choose which rooms will be conference rooms. Since most of the buildings in 3DWorld share the same vertical floorplan across many floors, it makes the most sense to have one or more dedicated conference room on each floor. The ground floors of office buildings do have some special rooms such as storage rooms, security rooms, and utility rooms, so I can have the floorplanner overwrite the conference room with a special room type on the first floor.

Since I want to add glass walls to conference rooms, it makes the most sense to place them adjacent to hallways. A glass wall between two conference rooms, or a conference room and an office, would be odd. And I don't even want to think about what a glass wall shared with a bathroom would be like! I also have a constraint that the conference room can't have an exterior wall, since the interior window won't alpha blend properly with the exterior windows. (I spent a long time trying to solve this with glass floors and never found a solution I was happy with.) Fortunately, this means I don't need to worry about walkways connecting between buildings that end at conference rooms. The candidates are all rooms next to an interior hallway that have doors that either open into a secondary hallway that meets the other hallway at a 90 degree angle, or to another parallel hallway one row over. Another requirement is that conference rooms don't have any overlapping stairs or elevators. This rules out some rooms that have notches cut out where elevators are placed along the sides of the main hallway.

There is also a size preference. I want to add large conference room tables and many chairs to these rooms, with space around the sides for people to walk. The conference room should be the largest room that meets the placement requirements. I gather a list of all legal room candidates, find the largest one, and select all rooms within 90% of the largest room's area as possible conference rooms. Then one or more of these rooms are selected. Any room that has a conflict (such as intersecting stairs) on one floor is changed to a different room type such as an office for that floor. This sometimes happens with conference rooms at the top or bottom of a building stacked partition where stairs extend above or below.

Each conference room has a single large table that runs most of the length of the room, with as many rolling office chairs as will fit along each side. One of the walls at an end of the table has a large TV hanging on the wall, and another wall has a large whiteboard. Neither of these can be placed over the window or door(s). I found a conference room phone 3D model that I placed on most of the tables in a random position and orientation. Then I added some random items on top of the tables such as laptops, pizza boxes, books, cups, and water bottles. Some rooms have trash cans, recycling bins, and/or potted plants. Maybe I should add pens, pencils, and papers as well.

Here is what an example conference room looks like. There are actually two adjacent conference rooms with glass windows on this hallway. The doors open into the next hallway over, toward the back.

Two conference rooms in an office building, viewed from the hallway through glass windows.

I added proper collision detection for these glass panels that works with the player, building people, zombies, rats, snakes, insects, thrown objects, etc. Spiders can climb on the surface. I had to fix a number of problems related to lighting and occlusion culling so that objects on the other side of the glass were lit and visible even though there was no doorway between the rooms. I was originally worried that the AI would have path finding problems if a zombie saw the player on the other side of the glass but couldn't pass through it. But no, the existing system worked fine and the zombie walked out the door and around the corner to reach me. They target the last known location of the player when the player isn't visible.

Lighting isn't perfect for these rooms. Light will pass through the glass from the adjacent room, but the sides of the glass are only lit by lights on the same side. This means that the glass on the inside of a room will appear dark when the room lights are off but the adjacent room lights are on. This is one reason why I turned all the lights on for these screenshots. I did make sure to flag conference room lights with motion detection logic so that they automatically come on when the player enters, which helps to hide this problem. The player can manually switch them off though.

Conference room in an office building, viewed from inside.

Sometimes the window is on the shorter wall of the room. The conference room below has a long whiteboard and short glass window. This room is large enough that it needs two ceiling lights, rather than the more common (and efficient) one light per room. I believe there were 7 chairs on each side of the table.

Larger conference room with a long table that has a phone and a laptop on it. There are 14 chairs.

I haven't made these windows reflective yet. That's a complex process that reduces framerate significantly as some parts of the building interior must be drawn twice. Most indoor windows are designed to be anti-reflective anyway, so the reflections are only visible at sharp angles. I don't feel that the benefit of adding reflections justifies their cost.

While writing this post I though that it would be interesting if the player could break, or at least crack, these windows. Maybe throwing a large enough object at the window will create a crack, similar to throwing a ball at a mirror. The problem is that the largest object the player can actually throw is a ball. I don't think a basketball or soccer ball would normally crack a window. Maybe I should add some new object that can be thrown, where the only purpose is to crack windows. Should the player be able to throw a fire extinguisher? That would do it. Currently fire extinguishers can only be dropped. Or maybe the player can throw a laptop left on the conference room table, or the phone? Should I allow zombies to break through windows to get to the player?

Thursday, September 19, 2024

City Walkway Elevators

I added elevated walkways and skyways to 3DWorld's procedural cities earlier this year. The skyway with its moving belts on the floor provides the player with a transportation system that's several times faster than walking, assuming they're not setting the movement speed higher like I often do when debugging. The problem is that the skyway is very high up in the air, and it takes a long time to get to it by taking the stairs or elevator from inside a building. This means that it's not a very effective transportation system, considering the skyway is also limited to a single city.

I need to add a faster way for the player to gain elevation without passing through a building. One idea I had was to add elevators that start at ground level in an open area of the city near the sidewalk and extend upward to connect to the side of a walkway. I had this idea around the same time as my escalators, which I showed in the previous post. I decided to make these elevators have transparent glass walls similar to the upper glass floors I added to retail areas. However, I haven't made these glass elevators reflective, so the process has been easier. People don't use these elevators (yet) either. All they have to do is avoid walking into the elevator shaft.

Elevators are placed at the same position as the support pillar under the walkway, off to one side. This is typically around the midpoint of the walkway span. The pillar region has already been chosen to be in an open area free of other objects, which makes it likely that the elevator shaft can fit there as well. The elevator is omitted if there's a collision with another object such as a building. This includes objects that block the entrance to the elevator on the lower level.

Here is an example of an elevator connected to a relatively low walkway between two buildings that's two floors tall. There are doors at both the top and bottom that open and close to prevent the player from falling into the elevator shaft or being crushed by the elevator at the bottom. The upper floor doors open to the sides like normal elevator doors. I couldn't find a good way to add side opening doors at the bottom because of the way the elevator shaft is narrow and vertical, so I added a door that slides upward instead. It's really more of a gate - a grid of black metal bars that doesn't block the player's view through the glass walls of the elevator.

City walkway elevator viewed from the outside. Elevators have glass walls, a metal frame and platform, and a gate with bars guarding the bottom entrance.

These elevators have no player controls. They're fully automatic and will anticipate the player's movement so that they're open and waiting on whatever floor the player is currently on. They will change floors as the player changes elevation inside buildings or by flying in debug/noclip mode. They're also pretty safe as the glass walls and doors prevent the player from falling into or out of the elevator shaft. I did add gameplay fall damage as a "reward" just in case the player can find a way around this system.

I cut a hole in the exterior wall on one side of the walkway to add the elevator entrance. This had to be done for both sides of the walkway wall since the exterior wall is "owned" by the city while the interior is owned by one of the connected buildings. The entrance on each floor has typical dark window/door trim added around it to cover up the edges of the walls. I set up buildings so that the walkway interior is drawn when the player is in the elevator shaft. It's typically blocked by closed doors when the player is outside of the elevator. Elevators use outdoor city lighting with sun and moon light + shadows since the majority of their surfaces are outside the walkway.

City walkway elevator with doors open, seen from inside the walkway

These elevators use the same door opening/closing and bell sounds as interior building elevators. In fact a lot of the ideas and code were copied from indoor elevators. Once I've worked out the logic for these types of objects it's much easier to reuse in other places.

Here is a short video where I travel up and down an elevator leading to a high walkway that connects to the skyway.


Sunday, September 8, 2024

Escalators for Procedural Buildings

I added skyways to 3DWorld's procedural cities a few posts ago. These included horizontal moving walkways that the player could use to get around more quickly. This got me thinking that I should add escalators, since I already had stairs and elevators. At first I considered using escalators to replace the short sets of stairs that lead from walkways to skyways above them, but these are uncommon and don't have much space to work with.

Escalators

The first question was, where do I put escalators? I want them to be placed inside procedural buildings somewhere. I already have stairs and elevators connecting the floors of my buildings together. Escalators need more space, and are ideal for rooms more than one floor tall. How about placing them in tall retail areas? There's plenty of open space, both horizontally and vertically. I decided to add a pair of escalators going in opposite directions next to each other along the center line of the long dimension of the room. I start by placing them in the center of the room and then shift them alternately to either side until I find an open place between existing stairs, elevators, and exterior doors.

Originally, the escalator placement failed some of the time because the stairs and elevators were spaced out in the center of the primary hallway in the office building floors above the retail room. But now I've moved some of the elevators to the sides of the hallway, which frees up more space for escalators in the smaller buildings. You can see one of these side elevators in the screenshot below.

Here's an early version of a working escalator prototype. The bottom floor/steps part is animated in my attempt to recreate the movement pattern of escalator stairs. The player can ride it up and down, though there's no floor connected at the top. All you can do is fall off the high end. Walking up and down is much faster than standing and letting the escalator move you. Objects such as balls and animals collide with escalators as well, though they can't actually move with the escalator.


Glass Floors

The next question to decide was what to put at the top of escalators. I wanted to add another level to the retail section, something different than standard tile floor. I like working with reflections, so I added a transparent glass floor. Maybe this isn't very realistic for multi-level public interior spaces where one person can look up at someone above them. But I had already added this before someone pointed out this problem. Each floor is currently a single rectangle. In the future, I may add a second shape to form an "L". As I expected, the combination of transparency and reflectivity made this extra challenging.

First, the transparency: In OpenGL (and other graphics APIs), transparent objects must be drawn after the opaque objects for them to properly alpha blend with geometry drawn behind them. The problem is that the glass floor is next to windows that lead outside the building. This means that exterior objects such as the terrain, grass, trees, roads, etc. must be drawn before the glass floor. This is a problem because buildings are drawn first, and interiors use a different lighting system from exteriors. I had to draw the glass floors in a final pass after everything else in the scene was drawn. I didn't want the overhead of setting up the interior lighting pipeline a second time in one frame, so I changed the lighting system to use a custom ambient value for each face of the glass. This technically works because it allows the glass to be lit from both the interior lights and the outside sun, from either the top or the bottom side. It avoids the unrealistic dark surface on the bottom "unlit" side of the glass. The only downside is that the floor doesn't receive shadows from other objects. I think this looks acceptable, especially when reflections are added.

Reflections were the next challenge. I only needed to make the top surface reflective, so that the reflection was only enabled when the player was standing on top of the glass floor. 3DWorld doesn't write a buffer that contains normals that can be used for screen space reflections, so I had to render the scene a second time from a reflected camera. This works the same way as bathroom mirrors and swimming pool water surfaces, except it's an entire floor of a large room filled with tons of objects. Compare this to swimming pool rooms and flooded basements that have sparse object placement, and mirrors that have a small reflected region. On top of this, the exterior is visible through windows and must be drawn as well. It was quite difficult to get fast reflections that didn't halve the framerate. (Keep in mind that I'm targeting older computers for 3DWorld, so my goal is 120+ FPS on my gaming PC.) I had to rewrite parts of the object occlusion culling system and implement horizontal clipping planes for drawing.

Finally, it was time to add objects to the upper glass floor. I started by adding railings along the open end to keep the player and AI people from falling off. Then I added additional rows of "shelf rack" objects on the upper floor above the rows on the lower floor where there was space. Any shelf racks that partially fit were cut to shorter lengths as long as the clipped rack was above a certain minimum length. I made sure to add extra padding around the end of the escalator and any openings of stairs and elevators that reached the second floor. This works, though the spaces can be tight in some cases.

Here is what the escalators and glass floor looked like after these changes.

A pair of moving escalators in the retail area of a building leading up to a glass partial floor.

The escalator has upper and lower straight ends and a 45 degree polygon in the middle. I didn't have the patience to try and write the code to create proper curved sides like I see in most real world escalators. These shapes are much easier to write collision checks for anyway. Each escalator has a vertical pillar at the high end to help support it and the glass floor attached to it. I thought this would give them a more interesting look than filling in the area under the escalator with material.

Side view of the same pair of escalators. Maybe they need more depth to the sloped part? From this angle they look like a pair of high heeled shoes!

Here you can see some building AI people managed to make it up the escalator to this empty glass floor. It's easier to test reflections of dynamic objects without the shelf racks in the way. (Well, that's true, but in reality I've added these screenshots out of order.) I actually started by removing the transparency effect and setting the Fresnel term to 1.0 to turn these floors into a perfect mirror. That made it more obvious when something was missing or otherwise wrong with the reflection.

A glass floor at the top of the escalators with a railing and not much else to do. The reflections of the people and the interior of this room are visible in the glass.

It looks questionable when the escalator is in the center of a large building and half the building footprint is a single unsupported glass floor. That must be some pretty strong glass! So for the larger areas, I added vertical columns that pass from the floor below, through the glass, and into the ceiling. Technically these already existed on the floor below. I then added horizontal steel beams under the glass connecting the pillars with the exterior walls and each other. I suppose it looks more structurally stable this way. Here is what this looks like before objects were added to the upper floor.

A larger reflective glass floor area with an elevator through it, plus vertical support columns and horizontal metal support beams.

Note that this floor has an elevator going through it. Two elevators in fact, though the one on the left doesn't have enough clearance from the escalator to have a door on that side. Only the right side elevator can access the glass floor. The glass is clipped out in the elevator shaft so that you don't pass through it when riding in the elevator. It should be possible for enclosed stairs to pass through the glass floor, though I've never seen this happen in a building I've visited. I believe the reason is that stairs are placed in the center of the hallway, the escalator is placed next to them, and the glass floor is always the smaller half of the room area (which excludes the stairs).

And here is the same area with shelf racks added. You can see the reflections of the racks and their contained objects on the floor.

The same reflective glass floor as above, but with racks of shelves added on top to give people a reason to come up here.

AI People

Did I say the glass floor transparency and reflections were challenging? They were easy compared to updating the AI logic. It actually wasn't that bad to make the normal building people use escalators. They work like stairs, except they must select the correct "up" or "down" escalator based on the direction they're headed. People walk up and down escalators rather than standing and riding them because it was easier than trying to blend in an idle animation. I might attempt the standing/idle version of escalators sometime later.

Now that I have people getting to these floors, I have to give them a destination so that they don't simply stand there at the top of the escalator. Some random spot between the racks should work. I just need to make sure they stay over the glass area and don't walk out over the rest of the retail area where they float in the air. The problem with the existing logic is that it assumes each floor covers the entire horizontal extents of the room. I had to add logic to clip the walkable area to the bounds of the glass floor to many places. But then I realized the real bug was that people didn't consider the railing a collider, and making them avoid the railing fixed most of the remaining problems.

The final step was to get the zombie AI to behave itself while chasing the player around the escalators and glass floors. Two of the more difficult primary objectives were to (a) chase the player to any reachable area, and (b) not get stuck around the escalators. This took me many, many iterations over the course of more than a week. There were just so many special cases and rare failures to deal with:

  • What if the player goes up the down escalator? Do we follow them going the wrong direction, or take the correct escalator and try to catch them up top? And if the player stops on the escalator, do we go up to the glass floor on the up escalator, then go down on the down escalator after them? Or wait at the bottom for them instead?
  • What if another zombie is going the wrong way on the escalator we're on? What if a zombie is stopped at the top or bottom of our escalator? Do we stay (walk in place) on the escalator waiting for them to move? Push them to the side? Clip through them?
  • What if the player no-clips/flies (aka cheats) to a spot above the retail area that's not over the glass floor? Do we go to the lower floor and stand under them? Clip through the railing and walk on air to get to them? Go to the closest reachable point to them on the upper floor? Ignore them and go about our business of selecting random destinations?
  • What if there are stairs through or adjacent to the glass floor that don't connect to it, or we don't have a path to them, we're on the stairs, and the player is on the glass floor? Zombies really wanted to clip through the stairs walls in that case. (Technically, if the stairs pass through the floor, then they can be on the floor while also on the stairs.)
  • What if someone pushed a zombie onto an escalator that it wasn't planning to use? Or off of an escalator? For stairs I just snap the person to the nearest point outside the stairs, but for escalators that point may not be over the glass floor.

In the end I just had to keep adding special cases to handle situations like this, test, and iterate until everything was working. Every time I fixed one problem I encountered another. In the process I had to add missing error checks, convert fatal errors to debug printouts, add more debug visualizations, and things like that. I suppose those changes are generally useful. It mostly works now, though it's not perfect. There are two minor issues that I haven't solved and have flagged as accepted behavior.

Sometimes a zombie will get pushed by another zombie into that spot exactly between the two escalators. In this case the zombie effectively gets trapped because it intersects both escalators at the same time and can't leave that spot without clipping through at least one escalator. So it just stands there and looks at the player. I think this is acceptable though because it can still attack the player when using either escalator. They're effectively camping the escalators. It's actually worse for the player because it makes the escalators unusable and may trap the player on the upper floor if there is no other exit. On the plus side, I don't believe it's possible for more than one zombie to be stuck in this location at the same time.

The second problem is when a zombie exits the stairwell onto the ground floor of the retail room, sees the player on the glass floor, and immediately re-enters the stairwell. It's nonfatal because they'll eventually reach the floor above or below, then make their way back to the retail room to chase the player. This behavior seems to be random and somewhat rare. I'm not sure what causes it. It's difficult to debug because I don't know until after I see them enter the stairs, so I can't query the system for their state before they made that decision. I attempted to fix this multiple times. Each time I thought the fix worked because I didn't catch a zombie doing this, but I saw it happen again later when I was trying to debug something else.

Here's a final video showing the player and AIs using escalators and walking on the reflective glass floor. Maybe later I'll record a video of zombies in the retail room.

At this point I'm calling escalators and glass floors "done enough". I may get back to them later. Next up: glass elevators to city elevated walkways. I already have elevators, walkways, and the drawing code for glass surfaces, so why not?

Thursday, September 5, 2024

Updated Water for Swimming Pools and Flooded Basements

I've been working on water rendering for indoor and outdoor swimming pools and flooded basements on and off for the past month or so. This has been going on in parallel to my work on interior dirt and damage, escalators, and glass floors. I'm actually writing both blog posts at the same time, but water was technically started first, so it will be published first. Working on water is pretty fun and a nice break from the more difficult parts of development such as AI navigation and building object placement logic. The theme here is to make water look more realistic, but also add a varying degree of dirt and mess to match with the recent changes to basements. This post is mostly a slideshow of screenshots of water surfaces in various states of cleanliness.

But the water shader isn't the only thing that's new. Now some swimming pools and flooded rooms have fish! Someone actually suggested it a while back. Adding fish to these areas didn't feel very realistic at the time, but I changed my mind because I felt it would be interesting. I'm always looking to add moving elements to these mostly static scenes. Plus the fish near the surface can generate water ripples to improve the realism of the water surface.

Fish are drawn and animated using the same code as the fish I added to fish tanks some months ago. I had to write a new system for their movement logic though since it's more complex. Fish in fish tanks have a rectangular area to swim in and only need to avoid colliding with each other. However, fish in swimming pools have to avoid the pool stairs, sloped floor, and ladder as well. Fish in basements have quite a few objects to avoid, such as walls and doors. And in both cases, fish will run away from the player when you get too close to them.

Indoor Pools

Indoor pools come in a variety of shapes, sizes, water quality, and states of disrepair. These are controlled by randomly chosen constants that vary parameters such as color, light attenuation/scattering, and other shader effects. If anyone is interested, the updated GLSL water shader source code can be found in my GitHub repo here

We have clean pools with blue tinted water like the one below. The majority of pools are clean like this.

Underground swimming pool with fish swimming in the water. This is the original clean look. Oops, I forgot to enable indirect lighting.
 

At the other extreme, we have pools with cloudy, green, algae filled water and dirt all over the walls, ceilings, and floors. They can still have fish in them though, and the poor water quality doesn't harm the player. It only makes things more difficult to see when walking underwater.

An indoor swimming pool full of algae with dirty walls, ceiling, and floor. Would you swim in this? Would you *drink* this?

Some extra long extended basement rooms have fog in them, or maybe some sort of smoke or haze. This isn't really related to swimming pools, but it does give them a creepy feel. You never know what might be lurking in the shadows at the far end of this pool.

Long and narrow basement swimming pool with green water and fish in a dark, dirty, and foggy room.


Flooded Basements

The lower floors of extended basements ("backrooms") sometimes have water in them as well. The water level varies randomly, from a shallow, almost invisible later, to water so deep the lowest floor is completely submerged. Fish live in these flooded levels, though these mazes of rooms can be so large that the fish are hard to find. As with swimming pools, flooded rooms have a range of water quality represented by a random mixture of mud/dirt (brown) and algae (green).

I remember seeing one basement with a shallow layer of cloudy green water on the floor that made it look like a swamp. But I wasn't able to find it again as these colors are somewhat rare, so here's a screenshot of murky green-black water. (I have indirect lighting intentionally disabled here so that it looks darker.)

Dark, flooded basement with medium depth green water full of mud and algae.

Then we have rooms with a very shallow layer of water on the floor. The layer is so thin that it's hard to tell what color the water is, but I'm pretty sure this one has clean water. Ripples look pretty good in shallow water. The walls and floor are dirty though. It looks odd with water stains under the water, so I guess these dark stains are dirt, mold, or something else.

Dirty flooded basement with a thin layer of clear water on the floor and some blue colored lights.

Here's an interesting looking area with a completely flooded lower level and water on the floor of the level above. I like the way the colored lights interact with the water. There's no visible dirt in this basement, though I believe the water is slightly muddy.

A relatively clean basement with an entire flooded level and submerged stairs, with orange lighting.


Outdoor Pools

Next, we move outdoors into the sunlight to view some exterior swimming pools. There are two styles of pool: round above ground, and rectangular in-ground.

I've made two changes to these pools. First, they now have caustic light patterns drawn on underwater surfaces visible to the sun. This is done with a texture decal applied to the surfaces that varies in brightness and color to match the sun color and angle. Shadows are also applied to this layer.

Here's a screenshot of a round above ground pool in someone's back yard. This pool has crystal clear water and the sunlight reaches to the bottom.

Round above ground pool with clean, clear water and caustic light patterns on the inside sides and bottom.

And here's a nice clean rectangular in-ground pool with a sloped bottom. The pool is next to a back yard covered deck/porch. This one is also very clean and blue, just like the pool at my (real life) house currently is. (Yes, it makes for a good reference.)

Square in-ground swimming pool with sloped bottom and caustic patterns visible under the water.

But we can have dirty green pools outdoors as well as indoors. In fact they're probably more common outdoors where the sunlight makes the algae grow. The water is cloudy and opaque, and no light reaches the bottom to produce caustics. In fact, you can't even see the bottom of the pool ladder. The owner of this house really needs to do a better job cleaning and maintaining their pool!

Rectangular in-ground pool with cloudy green water. No caustics are visible in this one. It needs more chlorine.

These back yard pools are often fenced off and unreachable to the player. Sometimes they're accessible through the house if it has both a front and back door like the house in the second screenshot. They're visible through chain link fences, but it's easier to take screenshots using my debug "flight mode".