Saturday, February 25, 2023

Procedural Building Skylights

I'm always looking to improve the realism and variety of 3DWorld's procedurally generated buildings. One problem I have with some of the larger office buildings is that they don't have enough natural light. Sometimes they don't even have enough artificial light when the placement algorithm fails to add light fixtures to the ceiling. I can fix this (at least for the top floor) by adding skylights to some of the buildings with flat roofs.

This was no easy task, and required implementing and fixing a variety of systems. What makes this especially challenging is that skylights form an interface between the interior and exterior of the building. These two parts are drawn using entirely different approaches and code, so it's not clear exactly how a skylight fits into this system. For example, interiors are only illuminated by room point light sources, while exteriors are illuminated by directional lights from the sun and moon. The solution I ended up using was drawing the transparent glass as part of a building's exterior, while the horizontal metal bars separating and supporting the glass panes are drawn as part of the interior. More on that below.

Before I jump into drawing and lighting skylights, I have to go over how they're placed and incorporated into the building's exterior geometry. The easiest case is to only add skylights to buildings with large flat roofs. This limits skylights to office buildings, since houses always have sloped roofs. (Plus, I'm not sure how well skylights combine with attics.) In addition, skylights are only added to cube-shaped (non-rounded/angled) buildings because these are the only type of buildings with generated interiors. If there's no interior, then what's visible through the skylight? Empty space? The ground?

Once a suitable roof is chosen, the skylight is dropped somewhere within it, in most cases near the center. The size is chosen randomly from between 20% and 40% of the roof size in each dimension. Ideally we want to place the skylight in some sort of public area like a hallway or lobby, but unfortunately rooms haven't been assigned yet. In fact the walls haven't been added at this point. Skylights can be seen on distant buildings when flying above them long before the interior walls and room objects can be seen, so we must generate them first. I guess the room placement algorithm will have to handle this later. As it turns out, this breaks my floor-planning system. Office building floor plans are shared across multiple floors and can't easily be customized for the top floor to handle a skylight. The template-based hallway assignment system of larger buildings also isn't flexible enough to work around a skylight that's larger than a single room. I want to support large skylights without this size limitation.

So in the end I simply cut the walls, doors, and door frames a bit shorter so that they can fit under the skylight, and I draw their top surfaces since they're visible in this situation. That leaves the elevators to deal with. Ideally the tops of the elevators shouldn't reach the skylight. Real elevators usually have machinery in the space above them. However, some skylights cover the entire valid elevator placement area, so there's no way to place an elevator that doesn't intersect a skylight. The best I can do is clip the top of the elevator in the same way as the walls so that it just touches the bottom glass of the skylight. The problem here is that the light and inside roof of the elevator car can extend above this point. I had to hack around this with some not-quite-volume-preserving adjustments to the elevator car z-values when it reaches the top floor. We just squish the ceiling down a little bit. It's difficult to spot though, so I expect users to not even notice.

Now that we've chosen the skylight location and hacked the geometry under it to make it look correct to the casual observer, it's time to cut the hole out of the roof. Fortunately, I can reuse the code that cuts holes in the roof for the roof access stairs. This is probably the only part of skylight addition where I can reuse existing code! The next step is to fill in that hole with a partially transparent glass plane that's recessed slightly vertically. Which of course doesn't "just work" because alpha blending is disabled for building exteriors. No problem, I can simply add the skylight as a fake "window" and that surprisingly worked the first time. I don't have to worry about lighting from the interior lights on the glass because skylights are transparent enough that the user can't tell if the lighting is incorrect.

The next step is to add the white metal horizontal supports for the glass panes. At first I wasn't sure how to do this because they need to be lit from the sun or moon above, and from the room lights below. Exterior objects are only lit by the sun/moon, while interior objects are only lit by room lights. I think that's too much geometry for the exterior, so it makes more sense to have the bars be interior room geometry. As for the sunlight from above, I can place an invisible room light in the air far above the skylight with a projected volume that exactly covers the skylight. No one said room lights had to be *in* a room! As a matter of fact, I can have this fake light illuminate the walls, floors, and objects inside the building below the skylight. If I make the bars shadow casters, I can even have them cast nice shadow lines on objects below.

With all of those additions, we have skylights that look like this viewed from above:

Office building skylight as viewed from above the roof, with a person standing in front of the elevator.

Maybe the bars and tops of the walls should have more color contrast? I'm not sure. Skylights look mostly correct when viewed from below (inside the building) as well:

A different skylight viewed from inside the building. The horizontal bars cast shadows on the walls and floors.

Did you notice something off about those two screenshots above? The sun shines on both walls of the hallway in the first screenshot, and the shadows of the bars on the far wall aren't parallel in the second screenshot. What's going on? This is because the real sun is a directional light source nearly infinitely far away. Shadows cast from parallel objects remain parallel. But the building interior system doesn't support directional lights, it only supports point lights and spotlights. My fake sun is really like a super bright spotlight hanging high up in the air above the building. The light rays diverge from that point, which is why the shadows cast on the walls and floors look that way. I don't really have a good solution for this, so I'm hoping no one notices.

Ah, but there's another problem. What do I do with the room lights that would normally be placed on the ceilings? I tried putting them on the bottom of the skylight, but that just looks wrong. Maybe I need a smaller type of light that goes onto the horizontal bars? Maybe it only matters at night when there's no light coming in from the sky. For now I can ignore this problem. There's a similar problem with floor signs above stairs. In that case it's easy enough to move the signs off to the side, and attach them to the stairs wall that holds the railing rather than to the underside of the skylight.

The next feature to implement is indirect lighting for skylights. (In reality, this task was easier to do, so I added indirect lighting before direct lighting for skylights.) Once again, I can treat skylights as windows, except that they're horizontal rather than vertical. They're also quite a bit larger than windows and need far more light rays for a smooth (non-noisy) indirect lighting result. I went with 8x more rays, which makes skylight indirect lighting quite expensive to compute. Here is what indirect lighting looks like for the same skylight as in the above screenshot:

The same skylight as above, this time with indirect lighting enabled.

Maybe that's too bright near the top? I'm using the same light intensity as I use for regular windows though. I think this is correct: skylights do let in much more direct sunlight than windows, at least when the sun is high in the sky. So this could be "correct" lighting for a skylight this large at midday.

Skylights can be placed over offices as well. Unfortunately, these offices usually don't have ceiling lights and are very dark at night. I could put a light on the wall if I had implemented non-vertical spotlights.

Another skylight above an office. There was no room to place a light fixture on the ceiling.

I did, however, add a check to avoid placing bathrooms under skylights. It just feels odd to fly over a building where I can see into the bathroom. I'm not sure if this applies to real buildings or not. My previous house in fact had a skylight in the bathroom, but I'm not sure what exactly a person "flying" above it would actually be able to see.

All of that discussion above was on placing and drawing skylights. There was even more work to be done. For example, skylights increase the ambient lighting in the building, which affects things like zombie visibility in gameplay mode. They also need to participate in occlusion culling. Objects that were previously hidden from the player by the roof may now be visible through a skylight when the player is above the building. The skylight must be treated as a visibility portal in the roof. I can't remember exactly what else I had to change in the code to implement skylights, but it was a lot of work. The only thing I didn't have to change was player collision detection because the skylight can be walked on the same as the roof.

Sunday, February 19, 2023

Procedural Buildings: Company Names, Signs, and Flags

I've been working on a variety of smaller procedural building tasks the past few weeks. Some of these items include generating company names and family names for houses, assigning street addresses, adding building signs, and placing flags in parks and on buildings. For reference, most of the code for these additions can be found in my GitHub project in this file.

Let's start with the company names and signs because I have the most screenshots of these. I have two different name generators. Each company will choose one of these randomly with a 50/50 chance. The first one I wrote myself and selects one of ten "mini generators" for each name. The core of this uses my procedural name generator - the same one I use for planets, people's last names, and some street names. The basic idea is sort of like the typical Markov Chain string generator, except with my own custom weighted table of of letters and consonant blends. Most of the time it creates names that can at least be pronounced. If you're interested, the code for that part of the name generator can be found here.

Each name created by this algorithm chooses a random generation mode. Some of these include:

  • Only the generated name
  • The name with a random prefix or suffix selected from a list of common company name prefixes/suffixes
  • Two or three names of the form "<name1> & <name2>" or "<name1>, <name2>, & <name3>" (typical of law firms)
  • Two random names separated by a space (like a first and last name)
  • Three letter acronyms

This generator produces some odd names, but they're all unique. It also produces some interesting ones such as my favorite, "Chaos Co."

The second company name generator was copied from Shamus Young's Pixel City project, which was my original inspiration for creating these city blocks with office buildings. This generator is surprisingly simple, with only three lists of text strings and three lines of code. Nearly all of the names make sense and are easily pronounceable. The downside is that the names are less unique than my own generator, and users may come across duplicates or similar sounding names on two different buildings. Adding a mix of the two name generators produces both a good number of unique names and names of reasonable quality.

I started by placing small signs above building doors, but they tend to be not well lit and hard to read. Then I decided to place larger signs on the roofs of buildings that can be seen from a distance and while flying over the city. Each sign is placed on the tallest roof section of the building and in a location that's not blocked by any other part of the building. Some examples are shown in the image below.

Many of the office buildings in a city have signs along the top edges of their roofs. The building in the center also has a flag on its roof.

Half of these signs are randomly chosen to be emissive so that they can be seen illuminated at night. This definitely adds a more colorful and interesting feel to night time cities. Unlit signs are dark colors such as black, dark blue, and dark brown. This improves contrast against their white backgrounds and the lighter daytime colors of buildings, roads, and the sky. Night time signs are brighter colors such as red, green, and light blue. These don't show up as well during the daytime, but they really pop against the dark buildings roofs and night time sky. Some night time screenshots are shown below.

Office building signs lit up in bright colors at night.

City viewed at night, lit up by countless window lights, streetlights, car headlights, glowing signs, and antenna lights.

Next we move on to signs placed on houses. I've already added "Welcome" signs above some doors. I decided to add last names of the occupants to some of the houses. The problem is that many of these generated names are odd, and it's not clear to the user that they're actually family names rather than random text. I added onscreen messages such as "Welcome to <company name>" or "Entering the <family name> Residence" when the player enters a building, which helps somewhat. These notices may become more important later when I add more gameplay elements.

So what signs do houses typically have? How about street numbers. Great, so now I have to assign addresses to houses. I already have street names, so all that's left is assigning numbers. I'll use the typical system we have in the U.S. where even numbers are on one side of the road and odd numbers are on the other. I'll also skip some numbers between houses and office buildings. Now that I've assigned street numbers, the question is where to place the signs on houses. The easiest solution I was able to come up with was placing signs on the porch roofs, on the side facing the road. This way I don't have to worry about signs blocking doors, windows, lamps, doorbells, or other items placed on the exterior of the house. Here is what this looks like.

Houses now have proper street addresses with generated road names and house numbers. Some of them have numbers on signs along their porch roofs, near their front doors. This house is at 954 Fleincoch Street.

Next up is flags. I chose to use an American flag texture since it was easy to find and the most common type of flag in my area (and probably all of the U.S.) At first I placed flags on the peak of office buildings with tall pointed roofs. You can see one of these flags in the building in the center of the first screenshot on this page. Next, I added flags to some of the city blocks and to parks. These flags are on a long flagpole placed on the ground, with a random orientation. Here's an example flag added near the center of a residential neighborhood park.

A park with trees, benches, and an American flag on a flag pole near the center.

That takes care of office buildings, city areas, and parks. Finally, it's time to place flags on some of the houses. The most common way to mount a flag to a house is angled upward and outward from the wall near the front door. It's a bit tricky to calculate the coordinates and properly texture flags at an angle though, so I made the pole extend outward horizontally from the wall with the flag hanging down. This fits with the general cube theme I have with this project. As long as flags poles are shorter than the sidewalk is wide, I don't have to worry about them clipping through power lines, telephone poles, or streetlights. This horizontal style is less common, though I do see flags like that occasionally. Here is what this looks like on a residential road where the houses on each side have American flags.

Houses with flags hanging one poles horizontally attached to their upper walls. Everything is easier when using cubes!

 

This is a pretty good start. I would like to add more types of signs and flags to buildings at some point in the future. It might be interesting to generate procedural flags for each city, or possibly signs with procedural logos for some of the company office buildings. Those ideas sound pretty time consuming and will have to be longer term sub-project.

At this point I'm moving on to other tasks. One of these is adding config options to make buildings use consistent materials/colors, shapes, or sizes across cities and individual city blocks. Another feature I'm working on is adding skylights to office building roofs. I'll have to think about which of these will be the topic of my next blog post.

Saturday, February 4, 2023

Idle Animations and Animation Blending of People

I discussed the new model animation system I wrote for people and zombies in the last post from November. (Wow, has it really been that long since my last post? I've been busy with holidays, work, changes to my schedule, etc.) During that time I've added idle animations and blending between multiple animations, among other unrelated tasks. I'll briefly describe the improvements I've made to 3DWorld's animation of people below.

First, I had to add config file support for specifying named animations loaded from model files. These are attached to previously loaded models that contain the actual vertex and bone data. Next, I added support for storing multiple animations within the same model class instance and selecting between them at rendering time. This was followed by implementing an animation blending system that took two named animations, two animation times, and a weight to use for blending between them. I then found good animations for people and zombies on Mixamo.

This turned out to be a bit more difficult than I originally expected due to the way animation time was tracked. People walked more quickly when crossing the street, and zombies moved faster when chasing the player. I had implemented running by increasing the animation rate of time, but I wanted idle animations to play back at a fixed rate, which meant I needed to track two different animation times for these cases and do something more complex when blending.

This solution mostly works, but there are still some problems. One issue is that the transition may happen mid-animation when the person's foot is raised, and interpolating from that position to the idle pose with both feet on the ground is unnatural. A second problem is that the animation state change isn't known in advance. People can stop suddenly to avoid a collision. Zombies will stop when the player blocks them with a closed door or object. Zombies will instantly transition from idle to running when they see the player. The AI controller doesn't do any prediction for these things, and the animation system has no way of blocking the AI state machine until the animation transition is complete. The two operations are even running in different threads! One of the outcomes of this is an apparent sliding when the person starts moving while the idle animation is still playing. To counter this, I had to reduce the animation transition time to only 250ms. This mostly prevents the sliding problem, but makes the first issue with the raised foot more of a problem. There's a delicate balance between these two.

Overall I think it's okay though. I don't really even notice these limitations when being chased by zombies. Maybe I'll come up with a better solution in the future, possibly when I get around to adding zombie attacks or other animations. I've attempted to implement this system in a way that makes it flexible and easy to modify later.

This video shows the people and zombies I've added to 3DWorld's buildings with their new walk and idle animations. These animation pairs are mostly different per-model, and are blended over 250ms when switching between walking vs. stopped. I had to disable collision detection and "fly" outside the house to get the zombies to stay idle rather than chasing after me. Also, sorry the camera movements are so jittery - I have my mouse sensitivity set too high for this recording.


I just realized while watching the video that shadows don't always update when playing idle animations. This is due to the optimization I added that skips regenerating room shadows when nothing is moving. In this case, the shadow maps are reused from the previous frame. The way I check for moving objects is to hash the position of every movable/animated scene object inside the light's volume, which includes the player and people in the building. This way the hash will change any time something has moved from the previous frame and will tell the lighting system to regenerate the shadow map. However, the position of a person doesn't change when they're standing idle, so the hash doesn't change either. The fix is to include the frame counter in the hash of people who are playing idle animations. I could have used the animation time, but as I said earlier there are actually two animation times, and that would have been more complex.

What's next? I like the custom procedural animations I have for rats, spiders, and snakes, so that can stay. It might be interesting to animate cars to allow their wheels to move properly when turning. I also may get back to helicopters at some point and replace my simple system that stores the blades and body as different objects and rotates the blades relative to the body to make them spin. Not that I have any idea where to get animations for these mechanical objects...