This is going to be another one of those long posts showing a variety of procedural building updates with lots of screenshots. I've worked on many different procedural building improvements rather than any one large task. I did some experiments with dome and onion roofs. I added roof clutter including AC units, elevator caps, roof access doors, and rooftop walls. I placed trees in house yards and building courtyards and cars in garages. I added player shadows and working elevators. Finally, I improved building indirect lighting performance. I'll discuss each of these below and show some screenshots.
Dome and Onion Roofs
Someone suggested I add onion dome roofs. I didn't think they would look good on modern city buildings, but I decided to give it a try anyway. The goal was to initially generate the onion shape and then go back and work on the textures and colors later. I started with a simple hemispherical dome shape because that was easier. Once I had domes working, I modified the z-value (height) computation to produce the pointed onion shape at the top. My first attempt didn't go very well:
|
Onion domes gone wrong (divide by zero failure). |
That's definitely an interesting look. The problem is that the vertex at the very top results in a near divide-by-zero, pushing that peak vertex up into the clouds towards infinity. Once I fixed that the results looked much more reasonable:
|
Onion domes on glass and steel office buildings? |
That's a nice shape, but it doesn't look right on glass and steel office towers, even if they're cylindrical. It looks even worse on cube-shaped buildings. Onions seem out of place on houses and brick buildings as well. I think they would look fine on a rounded stone church, not that I have any of those in my cities. I'll add a config file option to control this roof type and leave it disabled for now so that buildings will continue to have conventional flat and sloped roofs by default.
Roof AC Units
Some of the cube shaped buildings with flat roofs had roof-colored/textured cubes placed on top of them to represent random objects that were on the roof. That looked fine back when buildings were simple, but it's time to upgrade those objects. What's normally placed on a building roof? What comes to mind is AC units. Lots of buildings have big metal boxes with fans in them placed on the roof, right? If I look out the window at my work I can see some AC units on the roofs of lower adjacent buildings.
I found two AC unit textures online and got to work. Each building can have up to 8 AC units on its roof with sizes that vary according to building dimensions. The units are distributed across the various roof quads/sections that may be at different heights. They're placed randomly, generally in the same orientation and not overlapping anything else (including each other). It took a lot of tweaking to get these right. They're fine for now. I might add additional types of roof objects later. Here is a screenshot.
|
AC units have been placed on the roofs of some of the flat roof rectangular buildings. |
Roof Access Stairs
I want to allow the player to walk on building roofs and collide with the new AC units, so there has to be a way to get up there. I started with flat rectangular roofs because they're easier to work with. The simplest solution is to find a stairwell that reaches the top floor and extend it one extra level to the roof. The top of the stairs is covered with either a triangular hood (for single stairs) or a rectangular box (for U-shaped stairs). [The U-shaped roof stairs are rare and I couldn't find any around the starting area to take a screenshot of. There was one at one point in time that allowed me to get the code right, but I changed some random number generator value and now it's gone.] Then I added a door that opens out to the roof. The player can climb these stairs, walk on the roof, and climb back down. The door is animated and opens for the player just like the ground level building exterior doors. Here is an example image.
|
Roof access stairs shown from the outside, with some nearby AC units. They do cast shadows, but the shadows are behind the objects. |
Here is what the roof access stairs look like from inside the building. The triangular hood isn't well lit and appears a uniform gray. Maybe I'll add a light to the ceiling later.
|
Roof access stairs shown from the inside. Maybe the table is out of place? |
Here is a screenshot of the roof access door of a different building that's open, letting the player pass through from the roof into the building.
|
The roof access door opens for the player and allow them to enter. |
Trees
Some houses and buildings have empty space in their rectangular lots. What can I fill that space with? How about procedural trees? I have trees in the dense city area and out in the hills around the city, so why not place them in the lots. They're a good fit for the corner of L-shaped houses and the courtyards of ring-shaped buildings. Here is an example of a tree in the yard next to a house.
|
A house with a procedural tree in the yard. |
Roof Walls
I added raised walls around the edges of the roof of some rectangular buildings. I already had walls around single cube building roofs. These new walls go around the entire roof perimeter and can be more complex than one rectangular ring. They also use the exterior wall material (color and texture) rather than the roof material. Here is an example for a building with a courtyard. If you look closely you'll see that there's also a tree placed in the courtyard, but only the very top is visible. The raised bump on the left side of the roof is the top of an elevator.
|
Building with roof access door, walls along the edges of the roof, and a tree in the courtyard. |
Garages + Cars
Garages and sheds need more work. First of all, they have no doors. I can fix that by adding house doors to sheds and garage doors with a different texture to garages. Now all that's needed is a driveway!
I decided to classify a detached building as a shed vs. garage based on whether or not it can fit an average sized car. Then I updated the drawing and collision detection code to make the doors open and allow the player to enter using the same logic as the main buildings. Here is an example of a house with detached garage.
|
A house with a detached garage that may contain a car. The garage door can be opened, and the player can now enter. |
That's good, but garages and sheds are empty. They don't even have lights on the ceiling. [Now that I write this, I should probably go back and add a light in each one.] The most appropriate items to add to garages are cars. In fact, I already have the code to load and draw car models for cities. I just needed a way to add parked cars that are outside of city blocks. That wasn't too difficult. See the screenshots below.
|
A car parked in a garage, seen through the window. |
|
A car parked in a garage with the garage door (sort of) open so that the player can see inside. |
Okay, it could be better. The car would look better with lighting, and the way the garage door is cut out doesn't look right. I guess I have to model a frame-less door that opens upward and inward rather than swinging outward. I'll get to that later.
Update: I went back made some changes before publishing this post. I added lights to the ceilings of garages and sheds. I also reduced the border around the garage door. I still need the larger border at the bottom because the interior concrete floor slab is higher than the bottom of the exterior building wall and the ground, so I need to cover the edge of it. Here's an updated screenshot:
|
Garage with car, light on ceiling, and narrower garage door frame. |
There, that definitely looks better. It's still not perfect though. The car casts a shadow but it doesn't receive light from the ceiling light above it. I haven't implemented that yet. Also, the windows on the sides of the garage aren't visible through the garage door. They only become visible when the player enters the garage. The car is a collision object so the player can't really get very far into the garage. Yes, someone needs to cut that grass in front of the garage door!
Player Shadows
I somehow had forgotten to add a shadow for the player until now. The player is the only dynamic object inside a building at this point, other than elevators. Fortunately, I was recreating room light shadows every frame anyway, and not taking advantage of the static nature of the geometry. This is mostly because I was reusing the shadow code from cities where light sources (car headlights) and objects were both dynamic. Right now the player doesn't have a real 3D model, so I used a simple sphere like in other gameplay modes. Here is what this shadow looks like in an office building hallway.
|
The player now casts a dynamic spherical shadow inside buildings, for all nearby lights. |
Improved Elevators
My most recently completed task was adding functional elevators. I started with elevator shafts cut through building floors and ceilings with a light wood paneling texture on the inside and gray elevator doors that were sometimes open. This was shown in previous posts. Next I added elevator cars with dark paneling walls, tile floors, and patterned ceilings. (If you look carefully, the ceilings use the same texture as garage doors, for now.) Here is what this looks like, in a screenshot taken with bright precomputed indirect lighting.
|
Elevators now have proper walls, ceilings, and floors, and can be entered by players. (This screenshot also has indirect lighting included.) |
The next step was to make the elevators move up and down. I decided to have them move when the player entered an elevator. I haven't added any buttons or other UI yet, so I decided to let the player control the elevator by moving to one side or the other. When the player stands to the left of center the elevator goes down, and when they stand to the right the elevator goes up. That works well enough for now.
The elevator floor and ceiling are collision objects that the player can stand on so that they move vertically with the elevator. I had to add a special dynamic vertex/material list that is regenerated each frame where an elevator in the current building has moved. I thought that could be difficult, but it happened to work the first time. Shadows should work properly for moving elevators as well. Here is a screenshot of the player in an elevator that is moving downward and is currently between two floors. I'll need to add some buttons to those inside walls later.
|
Player inside an elevator that is going down. You can see a bit of the floor above because the elevator is between floors. The elevators doors remain open so that the player can enter, exit, and see where they are. |
I left the elevator doors open to allow the player to enter and exit at any time. If I add proper elevator controls, then I can have the doors open and close correctly. It's possible for the elevator to stop between floors if the player gets off at the wrong time. The player collision model is actually short enough that it can still fit through the opening of an elevator that is between floors. Maybe I should limit when the player can get off, or at least have the elevator continue until it reaches the next floor before stopping.
Indirect Lighting
There really isn't anything new to show here. I haven't solved the lighting noise or the problem with color bleeding through walls and floors. (Color bleeding can be reduced by using a finer 3D lighting volume grid, but that comes at increased frame time and memory usage.) However, I did partially address the performance problems. I made the indirect lighting computation run in a background thread so that the player can walk around while it's running instead of freezing the game until lighting has been fully computed for the current building. Lights are ray traced based on priority so that the larger lights and the ones closer to the player are added first. That way lighting will be calculated for the floor the player is on while it spends up to 100s computing lighting for other floors in the background. It was a lot of effort to implement, but seems to be working. I'll post some updated screenshots later if/when I improve the lighting quality. This post is already too long to add them here.
Next Steps
I've worked my way through most of my original TODO items. Well, the easy ones anyway. I've added a few minor items that I thought of while writing this post. There are still some improvements that I haven't figured out yet. I already discussed indirect lighting noise and light bleeding above. I would like to add movement of AI people in buildings next, which requires creating a building connectivity graph with path finding. It sounds like a lot of work but can surely be done.
Then there's the question of how to add interiors for city office buildings that use custom textures. These textures have windows included in them, which makes adding interiors significantly more difficult. The brick and block buildings I've been showing use base textures and have window holes cut into them procedurally. Textures that come with windows require the interior generation to work with the existing window positions, which it doesn't know about. Some textures have window spacing that varies on each floor, for example taller windows on the bottom floor(s) with more vertical space. Some of them have variable horizontal space between windows that may differ per floor. How exactly am I supposed to find the window locations? Using machine learning or image processing? Picking them out by hand by drawing window boxes in an image editor? Hard-coding all window spacing parameters for each texture by measuring them in pixels? I have no idea. Most of these solutions require too much work that isn't very interesting to me.
The final TODO item is to add more room objects. I currently have tables, chairs, lights, and stairs. I want to add desks, books, bookcases, trashcans, etc. There are two options for this: create the geometry procedurally in C++, or download existing 3D models. (I don't have the tools, skills, and patience to create the models myself.) Currently cars and people are downloaded 3D models, while everything else is generated with code. Neither of these solutions is great for a large variety of 3D room objects. I don't really have the patience to generate all of these different things, and 3D models take up too much space and require too much preparation work. It would be great if I could farm this off to someone else. For now I'll just leave it on the long-term list and probably add new items incrementally over time.