Friday, October 6, 2023

Animated Birds in Cities

It's time for another update. This month I've added animated birds that fly around 3DWorld's procedurally generated cities, above the people and cars. Birds are a good fit for cities as they fill the areas above the ground with movement. I did already have butterflies, but they were usually too small to notice. In fact, it was a challenge to even find one without the debug overlay to show their paths. I also had helicopters, though they were sparse and only visible high up in the air.

I started by placing instances of a static 3D model of a pigeon to the tops of city benches and trashcans. In addition, I added some small groups of pigeons on the open concrete ground areas between some of the buildings. These contributed a good amount of small detail to cities, but they lacked animations. Therefore, it didn't make sense to have them fly around. I wanted to use an animated bird model instead.

The 3D bird model I used was created by Paul Spooner and can be found here. This bird has five animations for standing/idle, taking off, flying, gliding, and landing. I created a new bird class that used a finite state machine to switch between these five states in a cycle that moved each bird along a path from object to object. I initially used the same bench and trashcan placement as pigeons, but later added resting spots on the tops of transformers, power poles, news racks, stop signs, fences, hedges, walls, and mailboxes. At some point I may add landing spots on houses and office buildings.

The bird flight model consists of three main stages: takeoff and orient, fly to destination, and glide down to land. A bird can apply accelerations independently in the vertical and horizontal directions to adjust its velocity to reach certain target locations along its flight path. Vertical velocity adjustment is used to change altitude, with limits applied for maximum rising and falling velocity as well as a maximum altitude that's determined by the bird's size and average building height. Horizontal acceleration is used to control steering to allow birds to change direction mid-air when orienting toward destination points. I've added a maximum turn rate similar to that of people to limit turning to smooth circular arcs.

First, each bird will choose a destination by randomly selecting an unoccupied location from the pre-generated list. Only locations visible to their current position are considered, to guarantee a valid flight path exists to that point. The visibility query includes all large objects such as buildings, telephone poles, benches, etc. Once a destination is chosen, it's marked as occupied so that no two birds try to land at the same spot. When the bird takes off, its original resting location is freed for use by other birds in the future. This way birds can continue to fly around indefinitely.

Takeoff consists of rising in the air to a target altitude while at the same time turning in a wide circle to point toward the destination. This creates a spiral path in the sky. Unfortunately, when the bird starts pointing away from its destination, this arc can leave it in a position where the landing spot is blocked by a building or other city object. If the bird was to continue to the destination from this location, it may collide with one of these objects. I spent some time trying to work out object avoidance, but eventually realized it was easier to choose a new destination in this case. I changed the bird update logic to check for ray (actually projected cylinder) collisions once every 16 frames while turning or ascending, and select a new destination each time a collision is found. This can produce more circling behavior. I feel that this actually increases the realism and variety of bird movements.

Once the bird has reached its intended flight altitude, it continues on that course until it nears the destination. I tried a number of different approaches to find the correct point where each bird starts its descent. First, I decided to calculate the distance at which the time to land was equal to the duration of the landing animation. The problem with this approach is that it assumed a constant velocity and was inaccurate if I wanted the bird to slow during descent and landing. It also treated the framerate as a constant, which may not always be true when the player is actively moving.

The second approach calculated the horizontal distance required for the bird to reach max altitude, and started descent when the horizontal distance to the destination went below that value. This assumes the ascent and descent acceleration and velocity were the same, which was a limitation. It also didn't properly account for extra time taken to reorient toward the destination, where the circular path covered less horizontal distance than the same path length in a straight line. The result was that birds would either reach the landing spot too early while they were still high in the air, or too late such that they would need to glide for long distances at low altitude. Low altitude flying is more likely to result in collisions with objects, including people and cars that normally aren't considered because they're below the flight path.

In some cases birds would overshoot their landings and have to fly back, which caused more problems with flying through ground-level objects. I tried various approaches such as having them slow down just before landing, but that only threw off the landing timing even more. My final hack was to add an attraction force from the destination that was enabled when the bird got close to pull it onto the right path. This mostly worked. I later updated the code to use the angle of approach to adjust the force.

Up until now, I had been assuming the takeoff and landing locations were at a similar height. That changed when I added the tops of telephone poles as bird resting spots. This large elevation difference broke the takeoff and especially the landing logic. It was particularly bad in the case where the two end points were close together horizontally and required a steep climb or fall. One easy partial fix was to constraint the destination to not be so close that the slope was higher than some threshold (I used 30 degrees). The second part of the fix was to shorten the ascent and descent stages to as little as the time to play that animation cycle, and then add a speed cap in the horizontal direction to allow for higher relative vertical velocity.

The landing sequence was still a bit difficult to line up with a steep descent. This sometimes resulted in the bird reaching the correct altitude for landing with a small amount of horizontal distance left to travel. This caused some instability where the bird alternated between small positive and negative vertical velocity and resulted in oscillation between flapping and gliding animations. In the end I decided to leave this behavior unchanged because these more chaotic landings provided a nice variety compared to the smooth and graceful landings of longer distance flights from objects at similar heights.

Wow, that was a wall of text! It's time to show some pictures, and a video at the end.

Let's play a game. Can you find all the birds in this scene? Look closely. There are at least 8 of them.

I know, I can't find them all either because their white color blends in with the ground and buildings. Here, I made them red, now can you find them? Did you find the one on top of the telephone pole on the very right edge?

After taking these screenshots, I decided to make some of the birds darker colors. Now they come in a random grayscale color that ranges from bright white to near black. Black birds are easier to see against some of the city objects. They're even easier to pick out in residential neighborhoods where they can often be seen against the blue sky as a background.

Here is a video where I follow a bird as it flies around the city. Please excuse the wait, there's no way for me to tell when the bird is about to take off or where it plans to land. I can only wait to see what it does.

As usual, the code can all be found in my GitHub repo. The source file that contains the bird behavior is here.

Bonus: I also worked on helicopters, since they're similar to birds. They both fly through the air from one destination to another. I now have three helicopter models, all with proper rotating rotor blades. This isn't a large enough topic for its own post, so I'll just add the video here.