I've been working on a variety of different aspects of 3DWorld's procedural city over the past few weeks rather than a single larger topic. I decided to just combine everything together rather than making lots of small posts. This post shows the major additions and improvements I've made, starting with exterior changes and ending with interior changes.
Cars Entering/Leaving Driveways
This task took by far the most time due to the complexity of the logic required to implement it. Up until now, cars would choose destination intersections and drive between them, meaning they were always actively moving on the road network. Parked cars stayed parked forever. I added a config file option to allow a certain percentage of cars to enter driveways and temporarily park. After a few minutes have elapsed, they start up again, back out of the driveway, and continue to a different destination. This was more complex than it sounds because I had to handle both the car physics for these turns and the logic to avoid hitting other cars and people in the process.
I started with a reservation system for driveways so that no two cars try to use the same driveway at the same time. Some cars are always parked and will reserve their driveway forever, but the other driveways are available to the first taker. I limit usable driveways to ones that are long enough to fit the current car or truck without requiring the garage door to be open. While destination intersections can be in other cities, destination driveways are always in the city the car is currently in. This simplifies the logic a bit.
The most difficult step was determining when it was safe for cars to enter and leave driveways. They always pull in, and either back out or pull out depending on the orientation they started in. The tricky case is when a car is making a left turn into a driveway and needs to check for traffic in the opposite lane that it will be crossing. It's not enough to check for cars blocking the driveway at the current point in time, they need to find all cars that can potentially cross that path for the duration of their turn. This involves locating nearby cars on the current road and cars about to turn onto the road and predicting where they will be a few seconds in the future using their distance, current speed, and max speed. The test is somewhat conservative to add a safety margin. The case where a car is backing or pulling out is similar, except it needs additional time when backing out to switch from reverse to forward. However, once they're on the road, cars behind them will see them and stop if needed. So it doesn't need to be as conservative. Cars exiting driveways must also check for cars stopped at a red light and blocking the driveway, and wait until their path is clear.
Sometimes when the driveway is close to an intersection, it can be blocked for long periods of time, preventing cars from entering or leaving it. It's generally okay to let a car sit there and wait to exit a driveway. However, there's only one lane (no dedicated left turn lane), so a car stopped waiting for a left turn will block traffic on its side of the road. This can lead to gridlock, especially when this happens on both sides of the road at the same time. My fix is to have the car only wait a short time and continue to loop around the block if the driveway is blocked by a stopped car. This can result in the car looping around the block forever, but at least the system doesn't deadlock.
Here's a video showing a car entering and leaving a driveway. The video is sped up a bit, and the time a car waits in the driveway was reduced from a few minutes to a few seconds. Yes, I added back up lights.
The next step was to make these cars avoid hitting pedestrians who were walking on the sidewalk between the road and driveway. This was challenging because car and pedestrian update logic is run on two different threads at the same time, which means the cars and pedestrians can't directly access each other. I couldn't figure out how to make the cars stop to avoid pedestrians, but I did eventually figure out how to make people stop at the edges of driveways to avoid cars. The logic for this is different for cars entering driveways, leaving driveways, and parked in driveways. It's similar to the logic used for pedestrians crossing roads, except with driveways instead. It's similar to the car driveway entrance and exit checks as well because it also involves predicting the paths of nearby cars.
The system works in many cases, but there are some modes of failure. For example, pedestrians can block each other or push each other into driveways. It's possible for a car to start backing up just as someone crossed into the driveway behind it. I could add a flag to the driveway indicating a person was in it to keep cars from moving. But I'm worried that this can lead to a dependency cycle where one or more pedestrians and one or more cars are all waiting for each other to move, and none of them can make progress.
There's also the case where someone will walk around an obstacle such as a fire hydrant or another person, slightly into the road, bypassing the driveway. To handle this case, I extend the driveway partially out into the road. But then there's the even more difficult case where a pedestrian decides to cross the road directly in front of the driveway. They may stop there waiting for a car to pass before crossing, blocking the driveway in the process. It's not clear how to fix this. Even if the car was aware of this person, all it could do was wait for the person to move. So it seems that this situation must be resolved by the pedestrian moving ... somewhere. Maybe to the side of the driveway? Maybe I shouldn't let people cross in front of driveways in the first place? It's difficult to work on this because this situation is rare, and the simulation isn't deterministic enough for me to reproduce the same failure mode between tests. I saw this particular failure only once.
Here's a video where a pedestrian stops to wait for the car to enter the driveway rather than being run over.
The first time I saw this interaction there was another pedestrian coming from the other direction later who had to wait for the car to exit the driveway. That would have made a better video, but I failed to record it correctly that time and wasn't able to witness this happening again. Minor changes to the AI logic results in major changes in the simulation.
Above Ground Swimming Pools and Chain Link Fences
I showed off some in-ground swimming pools in the previous blog post. This time I've added above ground pools as well. In addition, I've placed chain link fences around the houses to separate the pools (typically in private back yards) from the public area and road.
|Residential neighborhood with both in-ground and above-ground pools, surrounded by fences.|
The fence placement logic was more complex than I expected because it had to avoid crossing the driveway, other fences/walls/hedges, trees, and AC units on the side of the house. I also added checks for fences blocking porches and exterior doors. The final placement algorithm is able to fully fence off around 90% of pools and will remove the pool and fence segments when it fails. This should ensure no pools are publicly accessible and all meet local safety requirements. Maybe people will be hit by cars when crossing driveways, but they won't be drowning in their neighbors' pools.
These screenshots show how a pool is fenced off on both sides of the house, and a close-up of a chain link fence.
|A house with a back yard in-ground pool fenced off with a chain link fence on both sides.|
Residential plots are separated by a combination of fences, walls, and hedges. I was previously drawing hedges as simple textured cubes. That may be okay for fences and walls, but plants are shaped more organically than cubes. They look fine from a distance, but bad close up. I decided to generate a cube-shaped shell of leaves draw as individual quads in random orientations, and instance these over nearby hedges. This adds no measurable frame time but definitely improves the look. The new hedges have more detail and volume, though you can still see the underlying cubes if you get close enough.
|New and improved hedges that have random leaves sticking out of a textured cube to give them more detail and volume.|
This was a silly one-off change. I thought it would be interesting to add umbrellas to most of the pedestrians when they were walking outside in the rain. (Yes, I do have full weather simulation and effects that work in all gameplay modes.) I found a simple umbrella 3D model online and added code to draw it above 75% of people when it's raining outside. Here is what that looks like.
|A partly cloudy, rainy day where most of the people on the sidewalks are carrying umbrellas.|
Now we get to the changes I made to building interiors.
I added a gameplay action key to allow the player to push objects placed inside buildings. This works with smaller objects that can be picked up and carried such as chairs and small tables. It also works with large furniture and appliances that are too heavy for the player to steal such as refrigerators, couches, beds, dressers, and bookcases. Basically anything not attached to the wall or floor is pushable. The heavier the item, the longer it takes to push it around. I allow the player to push items into closets but not out of their starting rooms, to keep the pushing logic simple. Items can be pushed into doorways or against doors to prevent them from being used. This is helpful for keeping zombies out of certain rooms, but can also restrict the player's movement. I later added another key to pull objects so that they can be moved away from doors, etc.
There are now separate keys for "take", "interact", "push", "pull", and "flashlight". I had to add multiple player crosshair colors and shapes to indicate which items can be taken vs. interacted with vs. only pushed/pulled.
Rolls of Tape
This was a fun mini-project to work on. I already had spray paint, markers, and toilet paper that the player could make a mess with in gameplay mode. I decided to add rolls of duct tape that could be used to stick tape on objects and wrap it around building interiors. Tape rolls of various colors can be found in drawers, in boxes, and on shelves. These can be picked up and used by the player. The action key will toggle between "stick tape to nearest object" and "rip off tape and stick the end to something". In between the tape will unroll as the player walks around, wrapping around objects such as walls, doors, and furniture along the way. The tape roll will shrink along the way and eventually run out, but the player can also backtrack to remove and recover the last unstuck segment.
Here's an example screenshot of two colors of tape stretched across a hallway.
My artwork created with black and gray duct tape stuck to the walls of this hallway.
This doesn't work perfectly. Sometimes tape will go slightly through objects, or won't stick to them in the correct places because their bounding/collision shapes don't exactly match how they're drawn. There's also no collision detection between the player and building AI people vs. the tape. Oh, and it doesn't quite work right if you try to pull a line of tape into the elevator with you and go to a different floor. I have no idea how to handle that case. I suppose the tape should break as it likely would in that situation in real life. But on the plus side it's fast enough that you can put down a ton of tape, and it stays in the buildings forever.
There's no real gameplay mechanic for tape at the moment. It's the same for spray paint, markers, and toilet paper. Maybe at some point I'll allow the player to block off doorways, hallways, or objects with tape to limit the movement of enemy zombies. It could be too overpowered though. I also feel that the tape should restrict the player as well, but then I would have to add a tape removal mechanic to keep the player from trapping themselves in a room. (This is also why I had to add a "pull" action to counter the "push" action.)
The basements in some houses didn't look very much like basements, and were a bit disorienting with their random interconnected rooms. I added more basement items, including water heaters with pipes and laundry baskets in laundry rooms. I also made the exterior walls a darker concrete block texture that has good contrast with the stucco/plaster interior walls. Knowing where the exterior walls are helps the player navigate in the basements of large/complex houses in the absence of windows.
Here's an example of the new basement look. The planet picture looks a bit odd here, but I don't feel the need to change that right now.
|The new basement look: water heater, laundry basket, and concrete block exterior walls. I'm still holding the infamous tape roll I used in that hallway above.|
|Close-up of a water heater, complete with warning label. Only some of them have the label.|
I've finally started to add clothing, beginning with colored shirts hanging in closets. Right now they're simple 2D textured billboards with alpha masks to make the edges of the quads (outside of the shirt fabric) transparent. I haven't figured out how to make them properly 3D, due to a combination of performance issues with high vertex count models, and the lack of free shirt 3D models. At least they're properly hung on the hangers and cast nice shadows. And as always, the player can steal both the shirts and the hangers.
|Colored shirts hanging in the closet, drawn as alpha masked billboard quads.|
I plan to make them sway back and forth when the player bumps into them eventually. That seems like a lot of complexity to add for such a simple effect though. Maybe I'll also add some designs or logos to shirts.