Thursday, May 23, 2024

Assigning Functions to Procedural Buildings

So far I only have two types of procedural buildings in 3D World: office buildings and houses. Technically, some houses are multi-family units, but they're still considered houses in the code. It's time to add more variety by assigning special building types to office buildings. This was something I had been considering for awhile. It must have been the walkways that finally encouraged me to look into specialized buildings.


Hospitals

My first attempt was to make some of the larger cube-based city buildings into hospitals. The newly added walkways seem like a good fit for hospitals, and city buildings are large enough for this function. I don't have any hospital specific room objects yet, so the only thing I can add right now are exterior signs.

I found a hospital sign image with a white "H" on a blue background and added the word "Hospital" under it using an existing sign drawing function. These signs are about the height of a person and are placed to one random side of each hospital front or side door. I check for intersections with other buildings and city objects. If the placement is invalid, the other side of the door is considered as an alternate candidate. The distance from the door was determined experimentally: too far, and the sign might intersect an adjacent building; too near, and it may block the path from the sidewalk to the door.

"Vooj Hospital" with a hospital sign by the front door. Yeah, that's a strange generated name.

I considered adding signs to the roof of the building as well, but they're not very easy to see from the ground level. Maybe if they were larger? I'm not sure. For now the signs are only on the ground.

I also added garbage dumpsters to the back sides of some buildings. These aren't specifically added to hospitals, though hospitals do often have dumpsters. I'm mostly showing them here because they don't fit any other blog post category and would not be shown otherwise.

Two garbage dumpsters placed in the alley between the back sides of two office buildings.

Dumpsters have a clearance area in front of them that must not intersect other objects. This placement check is similar to the checks done for hospital signs. The extra clearance is required to allow the player and pedestrians to walk through alleys like the one shown above with dumpsters on both sides. If the clearance check is not met, a new random position is chosen. They're not interactive at the moment, though I may make it possible for the player to push them around at a later time.


Hotels and Apartment Buildings

Next, I decided to reassign some office buildings to hotels and apartment buildings. Several of the "office building" textures I've been using have exterior details like balconies, window AC units, window curtains, etc. These definitely look more like residential buildings than commercial offices. I tagged each building texture with a probability for being a residential building: 0% for office textures, 100% for residential textures, and 25% for brick and stone textures.

I started with only city buildings using skyscraper textures, but these don't have interior windows. They only have exterior windows that are drawn as lit rectangles at night time. (More on that below.) Brick and stone textures are used in secondary buildings placed outside cities, and these do have windows present on both the interior and exterior. Apartments and hotel rooms definitely look more realistic when they have windows. As my old roommate once stated: windowless bedrooms are really just live-in closets. Maybe it would help if I slapped a sky texture on the ceiling? Maybe not.

Anyway, I started writing the code to assign cube-shaped buildings above a minimum size as hotels and apartment buildings. These use the same central hallway template as office buildings, but there are no secondary or ring hallways. Instead, the larger rooms to the sides of the hallways that would normally be turned into offices with cubicles are now converted to residential units. Since there's only a single central hallway, all of the units are adjacent to at least one exterior wall and can have windows. Er, if I was actually adding windows to these buildings. At least there's more freedom to place room objects if the placer doesn't need to worry about blocking windows with showers, TVs, bookcases, etc.

 

Lounges

I pretty quickly ran into a problem in cities though: Walkways connecting to a hotel room or apartment bedroom look very wrong. I feel really bad for the people staying in those units. I'm sure it's no fun when people are walking through your bedroom at all hours of the day and night. It's not as bad as having the walkway open into a bathroom stall (see previous post), but it's still pretty invasive to privacy. I left the rooms connected to walkways as offices for commercial buildings. I suppose they're "shared" offices. I need to create a new room type for this. How about a lounge?

Lounges started out taking up the entire space of the unit because I hadn't added interior walls yet. This left plenty of space for adding furniture and other objects. I added a table in the center, couches, chairs/bar stools, bookcases, potted plants, rugs, a TV on the wall, and maybe a refrigerator. I even added a fish tank to some of the residential building lounges. Here are some examples of furnished lounges.

Early attempt at lounge room.

Another lounge, on the floor below the previous image.

Okay, I need to make some improvements. That acoustic ceiling tile texture doesn't work for residential buildings. I changed it to use the same white patterns and stucco ceiling textures as houses. The table also looks wrong without any chairs, so I'll have to add some. Next, it was time to add the walls to split apartment and hotel units into multiple rooms. This causes some problems with lounges.

First, they became smaller. I had to replace the large table in the center with a smaller, taller table with smaller bar stool chairs. I added long thin tables in front of couches where they fit. I also changed the sizes, distribution, and object placement order to have a better chance of placing a variety of different object types.

Smaller lounge with tall table and chairs, couch with table, and indirect lighting enabled.

Another lounge room with different furniture arrangement. The TV is attached to the wall rather than sitting on a small table as in living rooms.

The second problem was that lounges only replaced one of several private rooms. Remember how 3DWorld's buildings work: Each cube-shaped "part" is divided horizontally into rooms, but all floors share the same vertical floorplan. This means that rooms and walls extend through all floors/levels. This is a much more compact data representation, but it does complicate the placement of lounges. Walkways only span 1-3 floors rather than the entire height of a part, which can be as many as 20 floors. It's not possible to remove the walls and merge the rooms in a subset of the floors contained in a part.

However, room assignments can be customized per floor. I had to implement this so that I could, for example, place bedrooms above living rooms in houses. So we can still have a lounge with bedrooms on the floors above and below it. The only real problem is that we may replace one room of an apartment or hotel unit with a lounge while leaving the other rooms as their original private room assignments. Maybe the bedroom doesn't have the walkway door inside it, but it could be connected to the lounge so that the resident must walk through a public room to get to their front door. That's clearly no good.

I tried various solutions that didn't work. In the end I realized that I had to track the entire unit as a whole, and tag each room with the unit number it belonged to. A first pass iterated over all rooms of a unit and determined whether any of them connected to a walkway or stairs. [Yes, in the process of working on this I realized that some rooms had stairs connecting to parts (stacks) above or below as well. Stairs also make rooms public.] Any unit with a walkway or stairs was tagged as "non-private" and all rooms associated with the unit were reassigned for that floor. Bedrooms and kitchens became lounges. Living rooms stayed as living rooms since they basically had a subset of the items as lounges anyway, though I reassigned the room names to "entryway". Bathrooms stayed as bathrooms, except they had showers and bathtubs removed. Public restrooms only need to have sinks and toilets.


Shower + Tub Combos

Speaking of showers and bathtubs ... There's one more missing piece I had to implement before adding hotel rooms. I only had bathtub and shower room objects. Hotel rooms almost always have those bathtub + shower combinations with the curtains that can be used as either, since these take up less space than a separate tub and shower. I was able to reuse parts of the drawing code to place a bathtub with shower tile along the walls. Then I added a plastered wall at the end, curtain rod, curtains, controls, and the shower head. There are two types of shower controls that are randomly selected between: a single large turning handle, and two smaller knobs for hot and cold. Here's an example shower + tub combo.

Shower plus bathtub combo, typical of hotels. The curtains can be opened and closed by the player.

The player can't step inside and hide from zombies like they can with showers. The water can't be turned on either. I felt like I had to make these shower + tubs interactive though, so I made the curtains open and close when the player clicked on them. The tub part can be filled with water and objects will float or sink in them, just like in normal (non-shower) bathtubs. I get that for free because these are really two objects placed on top of each other!

The only downside is that these objects take up more space than separate showers and tubs and are more difficult to place. They're long like tubs, but unlike tubs, they can't be placed against windows. Fortunately, these hotel bathrooms rarely have windows anyway. As I said above, none of the city buildings have windows. The brick and stone hotels do have bathroom windows, but only for the units on the ends of the building. Sometimes these end units will have shower-less tubs for this reason. Oh, and yes, bathroom windows use a non-transparent glass block material that the player can't see through.

I like the look of these new showers, so I added them to houses as well with a random probability. They come with two tile colors, pink and brown.


Room Interiors

It's finally time to describe the apartment and hotel room partitioning and assignment logic, with many screenshots. I had this code partially implemented for over a week while I worked on the problems related to windows, walkways, and showers explained above.

I decided that there will be two initial room layouts. Hotels will have a living room/entryway connected to the front door, a bedroom connected between this room and the exterior wall (possibly with windows), and a bathroom coming off the entry room to the side.

Apartments will have five rooms in total. A small entry room will connect to the front door and the other surrounding rooms. The bathroom and kitchen will be to the sides of the entry room, with the kitchen on whichever side has more space from the door to the edge of the unit. In the case where the door is centered on the room, I shift the entryway a bit to one side relative to the door to make the kitchen larger than the bathroom. The bedroom will be between the bathroom and the exterior wall, and the living room will be between the kitchen and the exterior wall. The living room will connect to the bedroom and probably the kitchen. The bathroom will have a door to either the entryway or the bedroom, determined by a combination of randomness and the room size/geometry. The entryway may connect to the living room and/or bedroom if there's space for a door, which depends on where the walls happen to be placed. In practice it seems to most often connect only to the bedroom with the current set of parameters. This means that the living room can only be reached through the kitchen, which is odd but not a huge problem. I may go back and experiment with this again later.

Does this sound too complicated? If you can't picture this room layout, I added an overhead map near the bottom of the post that may help.

One issue I ran into is that some of the smaller apartment buildings don't have enough space for these five rooms. Specifically, neither the kitchen nor living room is large enough to place a table, which leaves the unit without a table or chairs. I solved this in two ways. Sometimes apartment buildings have unequal room sizes with a somewhat larger end unit with an extra window. When at least one unit in the row is large enough for five rooms, I kept this as an apartment building where some rooms have no kitchen. But when none of the units can fit a kitchen, I change the type from an apartment building to a hotel. These two cases are about equally likely, which means that we end up with more hotels than apartment buildings. The apartment buildings tend to be larger though, which somewhat balances things out.

Apartments currently have a single bed in the bedroom. Hotel rooms have two matching beds that use the same style, size, color, and texture. They can be in different orientations or have different blankets. The other room objects such as dressers, nightstands, desks, and closets are placed the same as in houses.

Here's a series of screenshots of the same hotel room, in a non-city building that actually has windows. I have indirect lighting turned off because it makes the interiors too bright. Maybe I need to adjust the constants to work better with hotel rooms, or reduce the size of ceiling lights instead. These screenshots remind me of house listings typically found online that have photographs of each room.

Hotel room with matching beds and bathroom visible in the back. Flooring is carpeted like office buildings rather than using hardwood floors as in houses. This is a corner unit because it has windows on two sides.

Hotel room entry area with couch, bathroom to the side, and bedroom with windows in the back.

Below is another screenshot series showing an apartment. I couldn't find an apartment building large enough for five rooms in the player's starting area, so instead I used a windowless city building. Just imagine there will be windows along some of these walls. I have indirect lighting enabled here because it helps to make up for the missing window light.

Apartment living room with couch, table, and TV. Bedroom is to the left, kitchen is at the back. Windows have not yet been added. They should be on the wall behind the player, and on the wall on the right (with the picture) for end units.

Apartment kitchen with table, chairs, counters, sink, and appliances. Living room is to the left, entryway is to the right.

Apartment bedroom with bed, dresser, and nightstand. Entryway is to the left, living room is to the right. Windows have not yet been added. They should be on the wall behind the player and the wall on the left for end units.

Apartment entry area with bathroom to the left and kitchen to the right. The bedroom door is behind the camera. The front door is open, showing the main hallway beyond.

Here's an image of the hallway outside this apartment unit. All private apartment units and hotel rooms have unique room numbers, even on one side and odd on the other. Room numbers also vary with floor as you would expect. Hallways are the same as in office buildings and have stairs, elevators, cameras, and possibly fire extinguishers and water fountains. However, there are no security rooms with camera monitors in these buildings.

Apartment hallway showing numbered rooms on each side. Hotels are very similar.

I almost forgot, I have this neat debug drawing mode for building interiors. Here is part of one floor of an apartment building. Walls are drawn in white and room objects are shown in various colors. You can see that the floorplan/walls are regular and consistent, but the room object placement has more variety.

Note that the spacing around objects is larger than what's normally found in real buildings to prevent the AI (people and zombies) from getting stuck on objects. Their bounding volumes are large enough to include all animation frames; they can't squish their arms against their bodies to fit in tight spaces. AI diameter is slightly smaller than the width of doorways, which show as gaps in the thin white walls.

Overhead floorplan of part of an apartment building showing rooms and placed objects. Larger rooms are 15-20 feet on a side. The building is about 90 feet wide and 150 feet long (off the bottom of the image).

Color code - based on average textured color of room objects:

  • Gray: floor (inside), concrete courtyard/sidewalk (outside)
  • Lighter gray (inside): bathroom tile
  • White cubes: walls, closets, showers/tubs, toilets, sinks, refrigerators, and stoves
  • White circles: ceiling lights or ceiling fans
  • Brown: wood items such as kitchen counters, tables, chairs, dressers, nightstands, and desks
  • Large brownish/pinkish rectangles: rugs
  • Orange-brown: couches
  • Blueish: beds
  • Black: couches and TVs
  • Lighter gray (in hallway): stairs
  • Blue circles: trashcans or recycle bins
  • Red circle: fire extinguisher
  • Green (outside): garbage dumpster
  • Dark gray (bottom left and top center, outside): walkway, on the floor above or below

I definitely feel like there's more work to do. The obvious next step is to add interior windows. I need a system for city buildings that fakes windows with two different geometries for interiors vs. exteriors. Exterior windows must match the exterior wall textures for night time window lights to look correct. Interior windows must match the vertical floor spacing and horizontal spacing of individual units and rooms. The large variation in exterior office building window size, spacing, and aspect ratio makes it difficult to use the same window geometry for both purposes.

 

Open Wall / Non-Rectangular Rooms

I started taking the screenshots and writing this post, but then I realized that something looked wrong in the images. Most of the hotels rooms I've stayed in were an open living area. The bathroom was a separate room, but the living room and bedroom were really one larger connected area with no door between them. It's not really possible to create this arrangement with rectangular rooms unless the bathroom is long and thin and runs the length of the unit (from entryway to window), or the guests have to walk through the bathroom to get to the front door.

What I really need is to remove that wall and door separating the bedroom area from the living room/entry area. This will produce a nice L-shaped open area that occupies most of the space of the unit. I had previously tried (and failed) to remove walls during my first attempt involving merging the living and dining rooms of houses. I got stuck primarily for two reasons:

First, I only wanted to remove the wall on the ground floor. As stated earlier in this post, the floorplan, walls, and doors are shared between all floors of a part. I can't simply remove the wall for the first floor but keep the walls on upper floors. I would need to remove the wall for the floors above as well, which may result in merging two bedrooms. However, hotel rooms share the same room assignment across all floors, so this isn't a problem. We actually want to remove the wall for all floors in the part.

Second, wall and door placement happens during the floorplanning step, while room assignment happens during the object placement step. These two steps are separated for performance reasons. Floorplanning is required to determine the placement of exterior objects such as doors. The exterior is generated for all buildings at load time since it's visible from a very large distance, especially when lit up at night. Rooms and walls are shared across floors and are very compact in memory, so it's okay to store these for all 18,000 buildings. Floorplanning can also be run with multiple threads to speed things up. We can't remove walls to merge rooms during this step because rooms such as the living room and dining room have not yet been assigned. I can't assign rooms such as the living room here either because the exterior doors are added to houses after floorplan generation and room partitioning.

On the other hand, object placement must be done at runtime as the player gets closer to buildings. There are far too many objects to store in memory for this many buildings. There are tens of millions of total objects placed across all buildings. Objects are placed separately on each floor and not shared. Much of the runtime is updating GPU buffers, which is not easy to parallelize across threads. It's not possible to remove walls at this point either because this may affect exterior geometry that has already been sent to the GPU for drawing.

Fortunately, this is also not a problem for hotels because rooms are assigned during the floorplanning step. Room types are forced by the placement constraints:

  • Living rooms/entryways must connect to the exterior door, so there is only one candidate.
  • Bedrooms must have windows and be opposite the hallway, so there is only one candidate.
  • Bathrooms are the smallest rooms, so there is only one candidate.

This means that we don't need to remove any walls or doors, we can simply not place them between the bedroom and living room/entryway areas. This produces L-shaped rooms such as the one shown below.

Hotel room with an open/removed wall between the bedroom and entry/living room areas.

Of course, there was quite a bit more work to be done. I had to add extra room blockers so that objects aren't placed in a way that blocks people from walking between the two sub-rooms. I had to flag these missing walls as places where light switches, outlets, air vents, and pictures can't be placed. I had to modify the occlusion culling to let it know that these rooms were visible without having an open door between them. The light propagation code had to treat these rooms as connected. The AI navigation graph had to insert a node in the center of where the wall should have been. Is that all? I'm probably forgetting some changes. It does appear to be working now after all of this effort.

Will I go back and try to solve the wall removal for the living room and dining room cases? It's unclear. I was thinking of removing the walls for single floor houses to avoid the problem with upper floors. This way I can assume the room connected to the front door is a living room and the adjacent room is another public room such as a dining room. I could attempt to add this in the future, though it might be unsuccessful.