Saturday, December 20, 2025

Restaurants and Kitchens

I'm continuing my work on 3DWorld's interiors for special building types. This post will cover some additions I've made to procedural buildings: kitchen pantries, walk-in freezers, commercial kitchens, and mall food court restaurants. Each of these features required implementing new object types and placement algorithms. The last three are also related by containment, since restaurants contain commercial kitchens which contain walk-in freezers. I'll discuss these four topics below in the (inside-out) order in which I added them.

Kitchen Pantries

Up to this point, only retail stores contained food items. I put a lot of effort into making some of these objects, so I wanted to add them to houses as well. Many houses have a pantry in the kitchen, which is a small room for storing food related items. It's actually quite similar to a single door bedroom closet, so I decided to reuse that code. Pantries are placed in one of the corners of the room that doesn't block a window. The only real differences are that these "closets" are in kitchens rather than bedrooms, contain shelves instead of hanger rods, and have food items in place of clothing. The walls, door, and ceiling light are identical to closets.

I decided to add three rows of shelves that wrap around three sides of the interior. The side with the door is of course excluded. A random type of object is selected for each side of every shelf, and in some cases are added to the floor under the bottom shelf. This is the same set of food items placed in retail stores: boxes of crackers and cereal, bottles of water, Coke, beer, or wine, and cans of Coke or beer. I thought of adding pizza boxes, but they generally don't fit on these narrow shelves. Here's an example early version of the kitchen pantry.

Kitchen pantry filled with food boxes and drinks.

I later added non-food items that are still food related such as stacks of plates and rolls of paper towels. I'll continue to add more items when I think of them, or when I add new food related items to other room types in the future. Keep in mind that the player can take and use all of these objects.

Kitchen pantry in another house, with the addition of paper towel rolls.
I added one new type of object for pantries: the spice jar. This is a small semi-transparent glass container with a random amount of colored "spice". These are usually placed in a row on a shelf with various types (colors) of spices randomly selected. Sometimes the placement is uneven with gaps. It's a simple addition, but everything helps to improve realism and variety.

Kitchen pantry with a row of jars of spices. And a rat on the floor!

Walk-In Freezers

Next, I added have walk-in freezers. And guess what? I reused the closet code yet again! In fact, walk-in freezers were copied from pantries since the have the same shelf system. The major difference is in the materials used. In place of plaster walls there's shiny steel for both the interior and exterior. The hardwood floor and wooden door are replaced with metal flooring and a metal door. Even the shelves are metal, in the form of a horizontal grid of thin bars.

Freezers are placed in a corner of the kitchen. These are always one standard floor spacing tall and don't extend to the tall ceiling of mall stores, which means that the top surface may need to be drawn. I added extra clearance around the door to allow it to open (with a different sound). The door is still recessed and thin because it uses the same geometry and rotation logic as interior building doors.

Walk-in freezer in a commercial kitchen, exterior view. Shiny! In fact  most of the kitchen objects are shiny.

Now I'll show the freezer interior. I placed a freezer fan in the top back of the interior using the same code as house AC units. Shelves are metal racks with some vertical supports at the inside corners and objects placed on or below them. I didn't really have any good frozen object types other than stacks of pizza boxes, so I added closed cardboard boxes that the player can imagine has some type of frozen food in them. They can't be opened because I don't have anything good to draw inside the opened box. I may place additional items in freezers later when I think of them. Maybe frozen fish?

Interior view of a walk-in freezer with shelves full of boxes, a few stacks of frozen pizzas, and an AC unit in the top back.

Commercial Kitchens

Pantries and freezers were relatively easy to add. Now that I had freezers, it was time to start on commercial kitchens. These rooms are placed in buildings that can have cafeterias or other eating areas such as schools, prisons, hospitals, and malls. One easy first step was adding a tile floor over the concrete, carpeting, or whatever base floor material the building was using. The remaining steps proved far more difficult, mostly due to the large number of new object types I needed. At first I attempted to create these various kitchen appliances with procedural geometry. I didn't get very far though before I realized it would take countless hours to write the code for all of these. So, with a heavy heart, I deleted the initial code and went looking for 3D models. At least the freezer is procedurally generated.

I found 12 different commercial kitchen object models on Sketchfab which included refrigerators, ovens, microwaves, deep fryers, stoves, grills, and sinks. Most of these were low poly models that should allow me to add quite a few objects without running into framerate problems. I tried to find models that had plain white textures so that I could more easily change the materials. Combined with the existing refrigerator and walk-in freezer objects, that meant I had a total of 14 item types to place. The goal was to add a system that selected and placed random models like I had done with prison gyms.

I already have some nice functions to place 3D models along walls that checks for blocking windows, doors, stairs, other objects, player/AI paths, etc. It takes a height value and automatically calculates the object width and depth based on the relative model sizes in each dimension. My first attempt to use this approach created a random set of objects scattered around the perimeter of the room with gaps of empty space everywhere. That's a reasonable start on the programming side, but it's an absolute mess visually and looks like a very inefficient kitchen layout.

This needs a number of fixes. First, I need to solve the problem of everything placed along the perimeter that leaves empty space in the center of the room. If the kitchen is large enough, a procedural metal table is added in the center. I can then place a variety of smaller items on the table such as the small deep fryer model, a microwave, a toaster, and cooking items that include pans, plates, cups, and trays. I even found a simple 3D model of a milk carton to add. The center of the kitchen is no longer empty space.

The second part of this fix was placing some random rolling carts in areas of empty space. I reused the "trolley" model I had placed in hospital operating rooms, since it seemed like something that could be found in the kitchen. Then I placed stacked plates on these, and configured them so that the player could push and pull them around. (This is important because these carts can sometimes block the path to the door.) One problem down, several to go.

The next issue is the random gaps between objects. Normally a commercial kitchen is packed with adjacent appliances to fit as much as possible into the limited space. Rather than randomly placing individual objects, I changed it to place a single seed object and then iteratively add new random models to each side until there's no available space left. This produces a dense group of adjacent objects along one wall. It's not always the entire wall, as objects such as doors break up the groups.

It's still a mess of object types though. To make matters worse, some of them have very different aspect ratios that don't fit well together. The solution to this is to group the objects by function, which conveniently also somewhat groups them by size and shape. The three categories I came up with are:

  • Objects needing a vent/hood: grills, fryers, stoves
  • Other cooking related objects: ovens and microwaves
  • Non-cooking objects: fridges, freezers, sinks, and dishwashers

Each of these categories has 4-5 different items/models. The entire contiguous group of objects is assigned the same category. Categories are randomly selected, with a higher weight given to the one with the fewest placed objects so far. The placement system also attempts to place at least one of each unique object when possible. This approach mostly balances out the model distributions and maximizes variety. This did a reasonable job of object placement and created a kitchen that looked much more organized.

The only remaining problem was that the models used different colors, materials, and styles. They didn't all fit well together. So I did the only reasonable thing I could come up with: make everything uniformly shiny. I Set almost every material to reflective metal, which removed almost all of the color and texture. The appliances also now match the metal freezer and center table as well. Even the trolleys are shiny metal. Everything is better when it's shiny, right? I need to take advantage of the cube map reflections I added a few months ago. It's actually less obvious that reflections are incorrect when the room is full of many small objects with surfaces at different angles.

There's still one thing missing: ventilation. Kitchens need good airflow to remove all of the smoke, fumes, and steam from cooking. I began by adding rectangular ducts with vents along the ceilings using the same code I had added for malls. That's a good start for general ventilation, but I needed something more industrial for the fryers and grills. After changing my mind a few times, I eventually decided on rectangular metal hoods with internal fans hanging on the ceiling above the first category of items (grills, fryers, and stoves) and spanning the bounds of the entire row of models. That's another bonus of combining rows by type.

Since hoods have to scale to the size and shape of the objects, I create them procedurally rather than using 3D models. These objects have sloped edges so that the front is higher than the back. I added a repeating fan texture to the interior top surface (the same as an AC unit) and a metal grid texture (street grate) to the back.

With everything combined, here is what we get: 

Commercial kitchen inside the windowless room of a school. Shiny!

Very large commercial kitchen inside a prison, with a central table and appliances lining the walls. An open freezer is in the back.

Stoves and some grills have pans on them. To finish things off, I placed some stains on the floor using common food colors like white, black, dark red, and dark green. I liked the look of those stains, so I added similar stains to some of the trays and plates as well.

Mall restaurant kitchen showing some new features such as fish on plates, objects on trays, and stains on trays.

I'm sure I could add more objects such as pots, pans, hanging utensils, etc. to fill in the remaining empty spaces. I don't want to load or draw too many 3D models just for kitchens though. It may be okay if I can find models that can be added to other room types in the future. We'll see.

Mall restaurant kitchens have counters that run their length and separate them from the public areas. I placed more food related items on these counters.

Mall kitchen with a bowl of apples on a counter.

Mall Food Stores/Restaurants

My final food related addition was mall restaurants. These aren't the fancy sit down restaurants with small tables though. No, they're the type of fast food places typically found at food courts. I felt like this simplified layout was an easier place to start. I actually worked on restaurants and commercial kitchens at the same time as this required several iterations to get right. There are also many similarities between the two, in particular the metal surfaces and the various placed food items. And of course commercial kitchens account for about half of the interior space of the restaurant.

There are two different styles of restaurants. The first type has a simple counter and no public interior space. This is your typical fast food counter full of cash registers where customers order their food and then sit down at the central mall food court tables to eat. These are divided into a smaller space where the cashiers and packagers work and a larger back kitchen area. Here are two examples of this type of restaurant, viewed from both inside and outside.

Mall restaurant shown from inside looking out into the mall concourse. The reverse side of the image above. The cash register model is new. Note that some of the store sign backgrounds are colored now.

Mall restaurant walk-up counter with cash registers. Orders to go only. Note that the name sign is directly above the order counter. I really should be using multiple fonts for these signs.

The second style has a public area with entrance doors and large store front windows. This is the same geometry as non-food stores found in malls. In fact I had to add custom code to remove the doors and windows for the counter-only restaurants described above. Here the counter with the register is further inside and separates the public area from the kitchen. I placed a food and drink vending machine in the public section if they fit.

There are two sub-types of this style of restaurant, chosen depending on the size of the public area. If the space is deep enough, rows of tables are added and this becomes a sit down restaurant. Otherwise, the public space is left mostly empty except for the vending machines. Customers buy their food and then go back to the central food court area to eat it. I may decide to add more small items here later. Below are screenshots of both the empty space and tables cases. 

Mall restaurant with an indoor area for ordering and vending machines, but no seating.

Mall restaurant with indoor seating. Vending machines are off-screen to the sides.

I'll finish this post by showing some of the new detail items that I've added to restaurants. First, I added a bowl type to complement the plate type. The only real difference is the use of a spherical section rather than a conical section when drawing. Bowls can be empty or contain apples or soup. Soup is drawn as a simple circle at a random height, using the same set of food colors as floor stains. I reused the liquid drawing code from cups of coffee and buckets.

Bowls of apples can be seen in the screenshot below and also an earlier commercial kitchen screenshot. I originally wanted to use a sphere packing algorithm to place the apples as densely as possible. Unfortunately, packing spheres into a spherical section is quite a complex problem, and I wasn't able to find a simple mathematical way to do that. It's really not worth spending days working on the solution to such a minor feature. So instead, I placed one apple on the bottom and three more in a second ring/level. The player can take apples and then take the bowl itself when empty.

If bowls can be nonempty, then some plates should be nonempty as well. The only existing object I could find that worked well for a plate was a fish. I can conveniently reuse the same fish model I have for house aquariums, I just have to flip it on its side. I can even take advantage of the stacking/containment logic and place these new nonempty bowls and plates on trays. Now you can find a fish on a bowl on a tray on a table, and the game logic requires the player to pick up these objects in the correct stacking order to avoid having anything become floating in the air. The stack of objects will also move with the table if pushed by the player.

As an added bonus, I can reuse some of these food related objects in house kitchens and cafeterias. 

Closer view of bowl of apples and fish on plate on tray.

Abandoned bowl of soup on a tray on a cafeteria table.

Wow, that was a ton of work! I'm surprised I was able to get all of this done before Christmas. I feel like I'm not 100% done yet. I'll probably spend a few weeks debugging, tweaking, and optimizing kitchens. Maybe I'll go back and add some smaller objects to fill the empty space.


Tuesday, November 11, 2025

Adding Damage and Wear to Procedural Building Basements

I'm back to working on building interiors. I wanted to add more wear and damage to buildings, in particular the maze-like extended basement hallways. They're currently somewhat plain with no objects on the floor, and only rows of doors to either side, with an occasional picture on the wall or rug on the floor. It's too easy to get lost because there are no real landmarks, since every hallway looks similar. The cracks and water damage I added a while back applies uniformly to each room.

I've added more localized damage: missing ceiling tiles, broken lights, trash on the floor, mushrooms, graffiti, and holes in walls. I'll go into more detail on each of these topics below, listed in the order in which I added them.

Missing Ceiling Tiles

My first change was to cut out some areas of basement ceilings to add missing acoustic tiles. Any hallway that's selected to have missing tiles gets an additional rectangular space above the ceiling. This is only legal for underground rooms that have sufficient space between the bottom of the ceiling and the terrain or buildings above. Multiple tiles can be removed per hallway, and they end up as randomly placed and rotated rectangular objects on the floor that the player can pick up and add to their inventory.

The space above the ceiling is filled with pipes, ducts, wires, and spider webs. These are only placed in locations where they're visible through the openings. The pipes and ducts aren't actually connected to anything yet. I added hanging electrical wires to the corners of some tiles. Sometimes missing tiles are adjacent to ceiling lights, so I had to add metal support brackets to the lights to keep them from floating in space if their surrounding tiles are removed. The walls inside the ceiling space are textured with concrete and have per-vertex lighting where the brightness varies based on the distance to the nearest opening. This makes the space lit above the opening while extending into darkness. I added a brown frame around the edges of each opening to give it some thickness and fill the gaps next to the walls.

Surprisingly, the most difficult step was aligning the texture to tile exactly to the width and length of the  hallway in a way where I could calculate the exact coordinates of individual tiles. This required varying the length and width of a tile's texture coordinates to match the room dimensions, and then recording enough info about this to reconstruct the grid in the part of the code that places objects. The ceiling itself is drawn by the "building interior" system, while the tiles and holes themselves are drawn by the "room object" system, using different drawing code.

Extended basement hallway with a missing ceiling tile, showing pipes and a round duct in the ceiling and a hanging wire by the doorway.

The player, people, zombies, and rats can all walk over any ceiling tiles on the floor. Spiders will avoid them as their legs will clip through, and they will also avoid the openings in the ceiling. I haven't written the code for spiders to use these spaces yet. Technically, the area is outside the building.

Hallway with ceiling tiles that fell onto the floor, exposing pipes and a rectangular duct. This person will walk over the tiles.

Tiles placed on the floor are added within a small distance from their previous spot on the ceiling and in a random orientation. They're constrained to avoid the path of doors and stairs. Hallways don't have any other objects on the floors that must be avoided. This is helpful because I don't actually have a list of colliders for each room, since they're added in an earlier step than the ceiling tiles.

Another hallway with missing ceiling tiles and stairs at the far end.

Broken Ceiling Lights

There are two new types of broken ceiling lights found in extended basements. The first is a light that's missing the outer plastic cover and has the inner fluorescent tubes visible. These are emissive when the light is on. I added reflectors to the sides that function as mirrors using the cube map reflections added in an earlier post. This doesn't work all that well though, since the reflections are centered on the camera. This often means the lights reflect the texture of the floor under the player. That's why the reflectors look like concrete in the image below.

This ceiling light is missing its plastic cover, which is on the floor. The fluorescent tubes and reflectors are visible. The top of the wall is dark inside the hole because it doesn't pick up any indirect light from the room below the ceiling.

The second addition is a light that's fallen down and is hanging vertically by one end. These lights are rotated by 90 degrees and have a horizontal spotlight pattern that produces bright walls on one side and dark hallways on the other.

I originally wanted to make these colliders for the player, building people, and zombies. The player can easily walk around them. It's annoying to have to do so, but I usually find myself walking around them even if they don't have collision enabled. The problem is that the person/zombie AI path finding may not be able to navigate around these hanging lights in narrow hallways. I had to add sufficient clearance to the path finding to prevent 3D models from clipping through objects in any animation frame. The primary goal of gameplay is that each room can be navigated by the AI as they chase the player. It's no fun if a zombie can't follow you down a hallway. So for now I'm not making these lights collidable, and people may clip through them when walking. 

This ceiling light has fallen down and is hanging by one end. The light emitted is now horizontal rather than vertical, leaving a dark hallway behind it.

Mushrooms

Ah, mushrooms. These can be found in extended basements with wetness > 50%. I originally added these as decorations placed in groups of 2-3 along walls and in corners of rooms. Later I decided the player should be able to pick and eat them, and they would serve a gameplay purpose. The effects must be generally positive to encourage the player to walk around picking them up. But I also wanted to add a negative effect so that they're not too overpowered, considering how many mushrooms can be found in some areas.

I had a lot of fun writing postprocessing effect shaders for when the player was drunk a few years ago. I already had a wavy image shader, a blur shader, and a double vision shader. The problem with double vision is that it only happens when the player is extremely drunk, which is rare considering the sparse placement of alcoholic drinks and the fact that you can't drink the bottles found on store shelves. So I reused the double vision shader for one species of mushrooms. This is implemented as an effect that lasts for 10 seconds and then slowly fades away. It's stackable, so eating multiple mushrooms in a short period of time will extend the effect's duration.

But one effect is no fun. I need more variety. I added two more mushroom types with different stats, and two new shader effects. Now I have:

  • White Mushroom: +25% health, 10s of double vision
  • Red Mushroom: +25% drunk/high (damage reduction), 10s of psycho color effects
  • Red with White Spots Mushroom: 30s of invincibility, -25% health, 30s of floating/moving colored spots

All of these effects are very disorienting and distracting. What's great is that they stack/accumulate as well, so you can have two or all three active at once. And to encourage this, I added the 25% health loss for spotted mushrooms so that the player is more likely to eat a white mushroom at the same time to gain back the health. All three shaders were fun to create, a nice break from generating building geometry. The double vision and color changing shaders look very good to me. The floating dots shader was more of an experiment with GPU based particle simulation, and consists of 40 brightly colored emissive circles that float across the screen in random directions and speed, with randomly varying size/intensity and color.

Extended basement room with small white mushrooms against the wall and in the corner. The picture on the left is one of my shader generated procedural art images.

I would like to add that the framed picture on the left is one of the shader-based images I added a few months ago. I found several interesting shader effects, including various types of fractals and noise, and render these to images that are then used as pictures placed on room walls. Each picture uses a random time or position offset so that each one is unique.

Mushrooms are placed non-uniformly across rooms. Most rooms have none, but some rooms have quite a few. In fact you can sometimes find a single room that has all three mushroom types. Here is such a room.

A bathroom with all three mushroom types: red with white spots in the front left, red in the back center, and white in the back right corner. The picture on the right is another shader effect.

Here are some screenshots of the three shader effects. The first effect is double vision.

Double Vision: Makes is very difficult to pick up and interact with objects.

Then we have the newer color changing shader effect. Input colors are mapped to bright reds, blues, and greens. If you think this image is bad, imagine what it's like when the colors are constantly moving!

Trippy Colors: This is done by applying a nonlinear equation to the RGB color values to get a very different set of RGB values out, plus it varies with time. Very disorienting.

And finally we have the random colored dots shader. This is supposed to simulate some sort of hallucinations or spots in the player's vision. These are constantly moving and changing, which makes them very distracting to gameplay. At least the player is invincible during this time. Eating too many of these mushrooms in a short period of time will lead to death.

Floating Dots: 2D screen space particle simulation with bright circles that move and change size/color. Maybe not as bad as the other effects, but still distracting.

And here's a video showing all of these in action. The screenshots aren't enough to show just how difficult it is to play the game with these effects active. I even got lost in the basement in the video.


Graffiti

I've been wanting to add graffiti to 3DWorld's buildings for a while now. My daughter recently had a school art project where she had to draw graffiti, and I think that's what finally pushed me to add it to my buildings. I initially started by looking for free textures I could use, but I couldn't find any really good ones. Most of them weren't alpha masked and were difficult to separate the paint from the background wall image. Many of them had art or letters clipped, or other non-graffiti objects. It's not enough to select a few of these images. I probably need at least ten to avoid being too repetitive.

So instead I did what I always do, and turned to procedural generation. But how? I couldn't find any good approaches described online. There was an Unreal Engine graffiti plugin, but it looked too complex to include in 3DWorld and required a variety of input art assets such as a custom character font. I have no experience with graffiti and can't even read the text in many of the reference images. That doesn't help.

I eventually gave up trying to do something complex and went with a simple set of random overlapping lines with varying thickness in a rectangular area. This is very similar to how I implemented spray paint, and it even uses the same rendering system. The only big difference is that it's formed from lines rather than dots, which means I can get more color with fewer vertices. But it still looks very close to something the player can create with the spray paint can.

Extended basement hallway with graffiti on the walls, both sides.

These graffiti "blobs" are placed at random locations along hallway walls. They're applied over vents, outlets, and light switches. I limited them to hallways because there aren't any other objects blocking the walls. For example, it definitely looks wrong to have graffiti on the wall behind a machine in a machine room. I still sometimes see pictures hanging over graffiti, but I guess we can assume someone put them up to cover it.

I originally had a check that each new graffiti region had no overlaps with any previously placed graffiti. Then I realized that overlapping multiple colors actually looked better. Each stroke is applied over the previous stroke and alpha blended on top of it for a natural looking spray paint effect. I changed the code so that it sometimes places new graffiti at the same location as the previous graffiti using a different color. The screenshot below shows what this looks like.

A second hallway with more graffiti, this time with overlapping colors. Also, more missing ceiling tiles.

I know, it's not the greatest result. These aren't real letters or symbols. It somewhat reminds me of those paint lines added to roads and sidewalks to mark where the pipes are before digging. However, it does make each hallway feel more unique. I'm sure it also helps the player find their way through these hallway mazes. "Turn right at the orange and white paint blob."

Holes/Gaps in Walls

This was the last effect I added. Holes in walls are a great complement to holes in ceilings. I originally wanted to cut larger numbers of irregular circular holes in extended basement walls, but I couldn't find a visually pleasing and efficient way to do this. It's not so easy to subtract 3D geometry, and the building interior system doesn't currently support alpha blending, alpha testing, or masking of walls/ceilings/floors. The best solution I could find was to remove entire rectangular sections of walls (and trim) and then add parts of them back in to create a final large and irregular "hole". I cut at most one hole into random walls that are at least a minimum length and not adjacent to neighboring rooms.

Wall gaps are visual effects only. They function just like regular walls for blocking the player, building AI, animals, balls, etc. Only cockroaches can pass through them. 

I drew a plywood surface on the back side of the wall and added vertical wooden studs about every 16 inches horizontally, plus an extra 2x4 along the top and bottom to cover the ceiling and floor gaps. Then I added a number of pipes and Romex wire cables in multiple colors that cross through the studs from left to right at various heights. This combination should model a realistic wall interior.

I wanted to have rough, broken edges on the plaster, so I extended the edges toward the center using points randomly displaced both horizontally and vertically. These are textured to match the walls to either side with no visible seam. The edges of the front face (the sheet rock) are also drawn as thin quads. Each edge has unique procedural geometry for a more natural look. The only limitation is that cracks drawn on the wall with a special shader aren't added to these extended triangles because it uses a different control flow and different shader settings. 

Extended basement hallway with many cracks and missing sections of wall on both sides. Conduits/pipes and wires are visible through the holes.

I think the wet basement shader effect looks pretty good on these wall cutouts. The increased specular component interacts well with the normal maps on the wooden studs to make them appear slimy. Should there be plaster/stucco triangle fragments on the floor as well?

Broken section of wall with a wet shader effect applied to it.

Some walls have that pink fiberglass insulation inside them. I'm reusing my "cotton candy" texture from house attics. Here's the post, but I don't seem to have an image with that insulation in it. This is actually a cloud texture where I've changed the sky color from blue to pink and kept the clouds as white. It's not the greatest and has no normal map, but it seems to do the job.

Missing wall with pink fiberglass insulation visible through the hole.

The wood in the previous screenshots was dark and rough. I experimented with a few other lighter wood textures with more fine details until I found one that I liked. This is shown below. It's still not the typical light lumber texture with darker knots in the wood. The problem with that texture is the repeating pattern of the knots that looks artificial when tiled over all wood elements.

Missing plaster section with a lighter wood texture. Also, more mushrooms.

Is this lighter texture better than the darker one? It makes the wood look more fresh, but that's not exactly the look I'm going for. As a compromise, I set 50% of buildings to use each of the two textures.

What else can I add? Maybe some of these walls should allow the player to walk through them and connect to other rooms or caves. That would certainly be interesting. I'll have to consider caves for my next post topic.

Wednesday, October 29, 2025

Procedural City Gas Stations

I took a break from adding custom room and building types to work on cities again. I've had gas stations on my to-do list for a while now, so I decided to finally add these. I'm only adding the outdoor gas pumps and roof rather than the full building. This makes them have a smaller footprint that's easier to fit in the space between roads and buildings.

The first step was to place gas stations around the city. I'm only adding them to commercial cities with office buildings and not to the residential cities with houses. The best location is probably on the corner of the block adjacent to two intersecting roads. That way I can have cars enter from one road and leave on the other without having to worry about driveways, or cars crossing each other's paths. I moved the interior area with the pumps far enough from the edge of the roads so that pedestrians can walk by without colliding or blocking the cars.

It doesn't look correct to have two gas stations across the street from each other, so I set a minimum separation distance of four city blocks. This tends to produce around four gas stations per city in their own quadrants, which seems reasonable. That works out to about one gas station per 50 city blocks / 120 buildings / 200 cars. That's probably still too many, but I'm going with that number anyway.

Each gas station has a rectangular roof and four supporting pillars, with a gas pump next to each pillar. This seems to be the most common layout I've seen in reference images. I found two different gas pump 3D models that worked in my cities. I placed a sign with gas prices on the side by one road, and added five lights to the underside of the roof that turn on during night time. I went with a white roof with red trim, since that seemed like a pretty common combination in real gas stations. I'm not going to add any sort of logo at this time. I also went with blue pillars to get good contrast with the red on the roof and add some more color to cities. To finish things off, I added four circular manhole covers over the concrete floor to cover the fuel storage tank openings.

Here are some screenshots showing gas stations in the city environment with cars and pedestrians enabled. 

Day time gas station in a city with one car parked next to a gas pump.

Gas station using a second gas pump model with two cars and a truck stopped at it.

About half the total time was spent writing, debugging, and testing the logic for cars to use gas stations. I added a fuel_amt variable to each car that's initialized to a random value and have fuel decrease over time based on distance traveled. When fuel drops below 20%, cars will attempt to reserve a spot in the closest gas station within a limited travel radius. I had to use reserved slots to avoid the complexity of queuing lines and multiple cars attempting to turn into the gas station from different directions. If no slots are available, the car will choose a random travel destination and retry reserving a gas station a few minutes later. Nothing happens when a car runs out of fuel, which occurs quite often. After refilling at a pump, the car is good for another 10-20 minutes or so of driving around the city.

Cars in residential cities have no local gas stations to visit. I start these cars with more fuel on average and have it decrease more slowly. When fuel_amt reaches 25%, cars will find the nearest connected city that has a gas station and navigate to the global connector road leading to that city. Once there, they'll have to compete with other cars to reserve a gas station lane. Hopefully I set the fuel use rate for residential cars low enough that they don't all converge on the commercial cities while leaving the residential areas empty. This system appears to work relatively well in my limited time testing.

Each gas station has four "lanes" connected to the "input" road, two on the outsides of the pumps and two on the inside. I could probably fit two cars in each lane since there are two pumps, but that makes navigation more difficult, so I limited it to one car per lane at any given time. Cars will enter the lane, stop at one of the two pumps for 30-60s, then turn and exit. Their engines and headlights will turn off while stopped. There's a single output lane that exits the gas station onto the "output" road. This output lane must also be reserved and can only be used by one of the cars in the input lanes at a time. I reused much of the driveway and parking lot state machine code for gas stations. Each of the five lanes (4 input + 1 output) is represented as a driveway in the road network and automatically picks up the enter, exit, stop, and turn logic.

Pedestrians caused a lot of problems, as usual. Those street corners where the gas stations are placed are right next to the places where people stop and wait to cross the road. The combination of multiple people walking together and multiple cars trying to enter or exit the gas station at the same time tends to lead to deadlock where no one can move. The problem is that both the pedestrians stop for cars and the cars stop for pedestrians. The system is intentionally too cautious to make up for the fact that it misses some potential collision cases. Both cars and pedestrians will stop when their projected path *may* collide with something else within the next ~2 seconds.

The easiest fix was to simply disable the lane closest to the road as this is where the majority of people are walking or standing. This reduces the number of available lanes that cars can use from four to three. It avoids problems most of the time, but the system can still deadlock when a person gets stuck between two cars that are entering at the same time, for example in the two interior lanes. I didn't want to spend an entire week debugging this, so I gave up and made the cars ignore people once they've entered the gas station's bounding box. They still check for people before pulling in or exiting the driveways, and they still avoid other cars. But once cars start to turn onto a gas station driveway, they'll continue to turn and pull in/out even if a person crosses into their path. People will still attempt to avoid cars to some extent. They won't actively walk into a car, but they won't move out of the way either. This pedestrian logic is inaccurate because, unlike on straight city roads, they can't easily predict the way cars will turn and drive around gas stations.

With the hard part out of the way, you get to see some night time screenshots. Those lights on the underside of the roof come on automatically when it gets dark outside. These cast shadows and are very similar to streetlights, except their color is more blue than yellow. 

Night time version of the gas station from above, showing the lights on the underside of the roof.

Here I've enabled light bloom, which runs in a postprocessing shader. This makes light sources such as streetlights, window lights, and headlights appear brighter. The sign is hard to read, but it does reflect the current average gas prices here in my home state of California.

Another gas station at nigh time, with light bloom enabled. The sign with gas prices is visible but not illuminated.

Here's a short video showing cars entering and leaving a gas station while I play around with the sun position to change the time of day. You can even see a car colliding with a person. Don't worry, everything is fine. People can't get hurt in this simulation.


Bonus Content

Since this post is shorter than usual, I decided to add some bonus images showing off features I've added but not yet written about. None of these were important enough to create a separate blog post on the topic. I've included a gas station in the background of each one to fit today's theme.

First we have wind turbines. I added these many months ago. I believe you can catch some of these in my earlier videos if you look closely, but I never officially showed them off in this blog. They're placed on hilltops away from cities and tall buildings. They look close, but they're actually huge and far away. I think they're at least twice as tall as a building. There are about 20 wind turbines in my scene.

View from above a city gas station with three wind turbines visible on the hills behind the buildings.

I just recently (while writing this post) added parking structures to cities. They're probably taller than they should be, and they may have too many cars to get a good framerate. I do like the look of these buildings though. I even managed to connect them to adjacent office buildings with overhead walkways. This screenshot has three parking garages on adjacent city blocks. I'll probably add a distance limit similar to how I handled gas stations to even out the framerate.

View from above a gas stations with three tall city parking structures visible. The one in the back has a walkway connecting it to the adjacent office building.

Finally, here's a screenshot of one of the strange light sculptures I added to cities. These are formed from a cluster of sphere and torus shapes connected to a black metal cylinder frame. They turn emissive when the sun sets, similar to streetlights. They're more like area lights, so they don't cast shadows.

One of the many emissive light sculptures I have scattered around 3DWorld's procedural city, viewed at night time, with a gas station behind it.

That's it for gas stations and other city additions. I've started working on extended basements again in another attempt to make them look old and abandoned with missing ceiling tiles and hanging wires. I may write about that in the next post.

Saturday, October 4, 2025

City Reflections

My previous post on building interior cube map reflections ended with a comment that I planned to make this work for city exteriors, and that's exactly what I did. Exterior reflections work almost the same as interior reflections. This wasn't too difficult to add because they already shared the same shader code. However, there was still some work to be done.

The first step was to go through all of my building exterior textures and determine which ones should be reflective. I had to decide what building materials were shiny metal or glass vs. other surfaces such as concrete. Three of the skyscraper textures were good candidates for reflections, but they had a mixture of colors for the parts that were windows vs. something else. I didn't want to make the entire surface the same reflectivity because that didn't look very realistic for mixed surface material buildings. I spent a while experimenting with different math that used the luminance (average of red, green and blue colors) of a texel to weight the reflection constants until I found a result that looked reasonable.

Another related problem was that I had a different reflection function for metals and glass. Metals support colored reflections and variable roughness/shininess, while glass uses Fresnel reflection and refraction based on the index of refraction with white (uncolored) reflections. It's not possible to use both at the same time for mixed metal and glass surfaces. I went with an equation closer to metal because I felt the variable roughness parameter gave me more control over the final look.

Here is an example of a flat-sided reflective building exterior viewed from the roof of an adjacent building. The lighter pixels are more reflective than the darker ones.

Reflective glass and steel exterior of a city office building, viewed from the roof of a nearby building.

The reflected image includes other buildings in the same city, sidewalks, roads, streetlights, telephone poles, and a few other objects. It doesn't include cars, pedestrians, trees, and buildings outside this city because these objects have high geometric complexity and are slow to draw. In addition, moving cars and people would require regenerating the cube map faces every frame, even when the player was standing still.

Cars themselves aren't reflective either. I added those 3D models years ago before 3DWorld supported metal materials and custom index of refraction values. These started as OBJ files, so I could always go back and modify the material parameters as they're stored as text. That would be a lot of effort though, especially considering that many cars have a large number of materials with names that are either generic (such as "material1") or not in English. It would be hours of trial-and-error trying to guess the correct settings for dozens of materials in each of the 13 car and truck models. I may do this eventually though. 

I haven't yet solved the reflection sliding problem from the previous post. Cube maps are still being rendered at the camera (player's eye position) and only updated when the player moves. I have some amount of parallax correction includes, which helps somewhat.

Curved cylindrical buildings are supported as well. I particularly like the reflections they create. The sliding effect isn't as obvious on these curved surfaces.

Some curved buildings also have reflective surfaces. (That red stack of spheres on the right is one of my new outdoor abstract art decorations.)

It's not only buildings that are reflective. I've enabled reflections for all of the shiny surfaces. This includes metals such as walkway support pillars, stop signs, and news racks. There are also reflective glass surfaces such as walkway elevators and mall skylights, as well as water surfaces of ponds. Here is a screenshot showing a walkway elevator and cylindrical support. The reflections are difficult to see on the elevator glass since it's close to transparent, but they are there. Nearby buildings are mostly concrete and non-reflective.

Reflections are visible in the shiny painted metal cylindrical walkway support pillars. The elevator glass is also reflective, though it's difficult to tell in this screenshot.

Even the top glass surface of skyways is reflective. I didn't really need to add this since the player can't get up here without flying in "noclip" mode. This was picked up for free because it uses the same material as elevator walls and skylights. Skyways are up so high in the air that the reflection is mostly of the sky and clouds, which are rendered as a light blue color in the image below. This will change with time of day to match the average sky color and will fade to near black at night.

Reflective upper glass surface of the city skyway. The player normally can't get up here.

Solar panels on the roofs of parking lots also have a reflective glass layer. This gives a nice planar reflection of the surrounding city. The player can't easily get onto these, but reflections are still somewhat visible from ground level. (Note that the player can in theory fall onto these parking shades when jumping off building roofs. I haven't been able to do it, but it may be possible.)

The glass surfaces of solar panels placed on the parking lot roofs are also reflective.

I only added reflections to commercial cities with office buildings because residential cities with houses are lacking large shiny surfaces. There are a few surfaces such as park ponds and swimming pool water, but I didn't feel like those small areas were worth the cost of drawing the scene multiple times. Residential cities are actually more expensive to draw because there are many more houses than there are office buildings, which means more visible triangles.

Reflections in the surface of a city pond. Swimming pools have similar reflections.

Here is a YouTube video I recorded of city building reflections. They're far from perfect, but I still feel like the improve the look of the city.

 

I'm done with reflections for now. I may get back to this later if I come up with a better solution to the sliding problem. Various people have made suggestions that sound reasonable but don't work with my rendering system. For example, someone suggested mixing cube maps with screen space reflections and representing the closest few exterior walls as planar reflections. I'm not sure I want to do a big rewrite to fix it.

Saturday, September 20, 2025

Building Interior Cube Map Reflections

This post will be a bit different. I haven't added any new building, room, or object types. Well, technically I did add few new object types since the last post, but that's not what I'm going to talk about here. I've finally gotten around to adding cube map reflections for metal objects in 3DWorld's procedural buildings. I've been wanting to do this for a while now.

Reflective metal was one of those tasks that was easy to get the basics working but very difficult to make good looking and fast. I started from the existing cube map reflection support I had in my shaders and C++ code from all the way back in 2016: Part I, Part II, and Part III. This works by drawing the scene six times, once for each face of the cube. Each face uses a different view direction, and the total of all faces covers all 360 degrees of view. I limited drawing to the interior of the current building and the exterior of nearby buildings in the same terrain tile to save runtime. The initial reflection image is colored a light blue to match the day time sky, and will transition to a dark gray at night time. The reflection image does include dynamic objects such as people and animals in the current building.

One of the most difficult steps was determining where to put the origin of the reflection cube. It's not as simple as rendering an environment map (or skybox) where the background can be considered as infinitely far away. This had to work for building interiors, including high aspect ratio rooms and complex non-convex room shapes. I also had to use the same cube map for multiple reflecting objects at different angles and distances from the camera. I originally wanted to sample the surroundings from the center of each room and only update the cube map sparsely when the player moved to a new room to improve performance. However, this didn't work for big open areas such as malls where the center of the room was very far away from the camera and reflective objects. And in some cases the center of the room was inside an object such as an elevator.

I eventually gave up and used the camera position as the cube map origin. Since this is inside the player's head, I wasn't able to draw the reflection of the player model because it would have been inside-out. This also forced me to update all of the cube faces any time the player moved. One additional advantage of using the player position is that it should have never been inside a building object. Except, of course, for elevators and stairwells that the player is also inside and which have proper interiors to draw.

The fact that the reflection follows the camera makes it look more dynamic, but it's not physically correct. This is more obvious when the player is standing very close to an object such as a wall and that object takes up most of the reflected image. I did apply parallax correction to my cube maps in the fragment shader, but that's not perfect because it assumes the cube map extends equally in all directions. This simply isn't the case when the player is standing next to a wall and looking at a reflective surface on the other side of the room.

The next step was to determine what objects to make reflective. I started with shiny materials that I was already flagging as metal. This was then extended to include textured metal with paint, scratches, and rust. I later added other specular surfaces including dielectrics (insulators/non-metals) and panes of glass such as windows. The basic theory and equations work for all of these object types, which means I can use the same cube map and shader code for everything. I'll go over some of these object types below and show a number of screenshots.

First we have the objects that are already loaded as 3D models and have metal properties in their material files. This includes objects such as door handles. I made the brass and steel handles reflective and left the black painted handles with the original white specular light reflections only. The complex curved surface helps to hide the inaccuracy of the reflections.

Reflective brass door handle. Yes, it's a mixture of a mismatched decal on the door texture and a 3D handle model.

Railings along the sides of stairs are also reflective. These use the same material as door handles. They're composed of cylinders in various orientations, which also hide the imperfections of the reflections in their curved surfaces. I really like the way railings turned out.

Reflective brass railings to match the door handles. The metal colors don't always match, though they happen to match in this house.

Moving on to office buildings, we have reflective elevator doors. Actually, there are quite a few smaller reflective objects in office buildings, but the elevator doors stand out because they're almost like perfect mirrors. The reflection isn't as crisp because I'm only using a 512x512 texture for each cube face as another method of improving framerate. This lower resolution leads to a somewhat rougher look compared to a mirror's reflection rendered at full screen resolution.

Elevator doors with mirror-like surface. There are lockers visible in the reflection, so this must be a school rather than an office building.

Malls have the most reflective surfaces. (Factories are probably second.) This includes escalators, railings, floor trim, security gates, round trashcans, ducts, and vents. The sides of escalators are another example of near perfect mirrors. I didn't like the look of mirror reflections in the ducts and trim though, so I added support for roughness/shininess using my existing code. Rough material surfaces use a combination of texture mipmaps and 2D blur to average together adjacent pixels in the reflected image. This is another way to hide the visual artifacts in the captured reflection images.

Mall interior with reflective metal objects such as escalators, trash cans, and ducts along the ceiling.

It took quite a bit of effort to create these cube maps efficiently in large open spaces such as malls and factories. The brute force approach of drawing the building interior six times (once per cube face) cut the framerate to less than half. I was able to optimize this through a combination of reducing the draw distance of 3D models in the reflection pass and skipping materials associated with small objects that don't contribute much to reflections. These small objects are mostly from the shelf racks in retail stores and include bottles, cans, vases, balls, flashlights, candles, shoes, and various similar items. The small objects usually aren't very visible in reflections, especially with the blurring of rough materials. In addition, retail stores don't have any large reflective objects. It was time consuming to add no_reflect support to the geometry drawing system and flag dozens of individual items as non-reflecting.

While I was at this, I also marked many of these items as not shadow casters. It doesn't look quite as nice visually, but this does appear to completely fix the problem with lag when the player turns quickly. In this case the shadow manager needs to generate shadows for multiple light sources that come into view each frame. Malls are full of both lights and shadow casting objects, so this is doubly expensive. Since there are more lights than there are shadow map slots, my system that caches and reuses shadow maps across frames doesn't really help here.

After a lot of work, I was finally able to reduce the cube map generation overhead to something reasonable. My main test case was standing at the end of a very long mall. The original framerate was around 260 FPS. The initial implementation of reflection capture dropped this down to about 50 FPS, but I was able to get it all the way up to 180 FPS. Note that this only applies when the player is moving or a person is walking within the reflection distance. The worst case I was able to find was a looking out the window of the retail room of a large office building with an attached mall. This was around 110 FPS without reflections and a bit under 100 FPS with reflections. My performance goal is 150 FPS minimum on the new computer, which translates to roughly 60 FPS on my old computer that I haven't used in over a year. That old computer is what I would consider the minimum supported hardware for procedural city mode.

At this point I only had support for reflective colored metals with variable roughness. This worked for many objects, but not for something like water heaters. They're far too shiny. Making them rough doesn't look much better.

Shiny metal, mirror-like water heaters in an appliance/plumbing store.
 

The fix was to allow metalness values in the full range between 0.0 and 1.0. This allowed me to support materials such as painted metal that have a reflective layer on top of a diffuse/matte layer. My solution isn't a physically correct multi-layer material model, but I think it looks good enough. For example, a 40% metal material applied to a gray albedo (base color) on the water heaters leads to this much more reasonable result. Note that the pipes are still very reflective.

Water heaters with a more realistic surface that's darker and less reflective.

This extends to non-metal (dielectric) materials as well. These are flagged as specular with an index of refraction greater than one but metalness of 0. These objects have a white Fresnel reflection that varies based on the view angle. We can see this in the toilets that are on the other side of the same appliance/plumbing store as the water heaters shown above.

Rows of shiny toilets in the same appliance/plumbing store. Even dielectric materials can be reflective.

Some of my 3D models have the materials setup correctly, while others don't. I have to put some effort into fixing this at some point. I already had to fix some of the clothing models that had index of refraction set to 1.45 for some reason, which made them look like plastic. It would have worked if I had a raincoat model!

The same effect can be applied to glass surfaces such as TV and computer monitor screens. Here you can see that each of the TVs on the shelf of this retail area reflect the environment. The strength of the reflection is a function of the view angle. The reflection disappears when the TV is turned on as the emissive color drowns out the faint reflection.

TVs on shelves have reflective glass screens.

Here is an example of a whiteboard in an office conference room. The conference table and TV on the back wall can be seen in the whiteboard reflection. One limitation is that this system can't draw reflections inside reflections. [I do have recursive rendering set up for my older scene (in the blog posts from 2016), but that only works because each object has its own cube map. Here I only have one cube map that doesn't support reading from itself while writing to itself.] In most cases, reflective objects aren't really positioned in a way where this is obviously wrong, so I don't feel the need to fix it.

Reflective surface on an office building conference room whiteboard. The TV on the opposite wall is also reflective. So is the window on the side of the room.

Finally, we have glass found in windows, floors, and tables. Glass is technically both reflective and refractive, but the refraction term is only approximated with alpha blending. However, the reflected rays are in fact bent based on the index of refraction of the glass. This is basically the cube map equivalent of my planar reflections for retail glass floors. Sorry, I don't have a blog post to reference for that feature. The screenshot below shows a faint reflection of the mall lights and support pillars in the glass of this pet store. Some of the metal objects in the pet store are reflective as well.

This pet store glass window has a faint reflection. All glass surfaces have an index of refraction and proper Fresnel reflections. I turned the lights off in the pet store because it was easier to see the reflection in a dark window.

Here is a short YouTube video that shows off many of these reflective surfaces. Unfortunately, the YouTube compression makes it difficult to see many of the fine details. This is why I included so many high resolution screenshots above. I do need to add a video to show the way the reflections change as the player moves.


The logical next step is to make this same system work for exteriors as well as building interiors. In particular, I would like to make the exterior walls of metal and glass city office buildings reflective. That should be possible, since exteriors use the same base shader.

As a final note, I passed the 200K lines of C++ code milestone for the 3DWorld project!