Wednesday, April 13, 2016

Animals in Tiled Terrain Mode

I just got back from my trip to Spain, and it's time to write another blog post. I guess I'm done working on reflections in 3DWorld for now, so I've moved on to other features. You won't see as many fancy screenshots in this post.

I've wanted to make infinite tiled terrain mode more interesting for a long time. I have first person shooter gameplay in ground mode, but that's just a small map area. Tiled terrain mode is a much larger area (infinite!), which makes it more difficult to place interesting items and to fight enemies in. I needed something to make it feel more alive, even if it's not really part of gameplay.

My first thought was to have animals jump out of the water, grass, and trees at the player, maybe make it some sort of hunting game - either the animals hunt the player, or the player hunts the animals. I decided to start with some simple animals that should be easy to create: fish in the water, and birds in the sky. The general idea is that they don't have to be highly detailed models since they won't be viewed from up close. Fish can swim away when the player gets too close. Birds are high in the sky where the player can't get to unless flying mode is enabled. This means that I can concentrate on the AI behavior (which is fun) rather than create detailed models (which is slow and tedious, and requires software that I don't have installed).

The general theme of tiled terrain mode is that all tiles are self-contained cubes laid out in a uniform sized X-Y grid that extends 10-12 units in all directions around the camera/player. Tiles extend in Z from the lowest point in the ocean to somewhere above the clouds. As the player moves, new tiles are created when they come into view distance range, and old tiles are deleted when they're far enough away. Everything is contained in the tiles: terrain, water, trees, grass, rocks, clouds, etc. It makes sense that animals should also belong to tiles. This allows animals to be created or "spawned" when new tiles come into view, and distant animals can be deleted when tiles fall out of the view area, so that the number of active animals is roughly constant.

The problem is, animals aren't static like the rest of the scene. They move. When an animal crosses its containing tile's boundary, it's removed from one tile and added to another tile. This complicates things, and means that my custom, high precision coordinate system doesn't work correctly for animals. An animal's location can't be relative to a tile's local coordinate system if it doesn't belong to a single tile. I haven't quite solved this issue. I suspect that when the player walks far enough away from the origin, the animals will begin to have jittery movements as floating-point error accumulates. However, I haven't actually noticed this problem, maybe because the fish and birds are far enough away. So for now I'll ignore the issue.


Fish and birds are generated within each tile. I decided on a max of 12 fish per tile and 2 birds per tile. For each tile, 12 fish xy locations are generated, and the mesh height is calculated for each position. If the mesh point is under the water, the fish is kept. A z-value (depth) is randomly chosen for that fish between the mesh z-value and the water surface z-value. The fish can move in the xy plane but stays at this constant depth/z to simplify the collision detection and AI logic. Fish swim in a straight line and occasionally make small adjustments to their directions. If they collide with the mesh, they choose a new direction to move in. If they get too close to the player, they turn around and swim away. This produces a natural looking behavior.

Fish need to perform collision detection with the mesh under the water surface each frame when they move. For procedurally generated simplex noise meshes, that requires evaluating the noise function one or more times per-fish per-frame. With 400 generated tiles, and 12 fish per tile, that's 4800 updates per frame corresponding to 6000-8000 height evaluations of 8 octaves of simplex noise per frame, which takes many CPU cycles. Normally, the noise is evaluated in parallel on the GPU. This doesn't work for the scattered, random position evaluations of fish. Evaluating it on the CPU has a noticeable impact on frame rate, which was reduced from 300 FPS to 130 FPS. I decided that there was no reason to move fish that were miles away from the player and not even visible. Reducing the simulation distance to some 100 feet reduced the number of "active" fish to 10-20 and completely solved the problem.

I originally thought that fish would be easy to simulate and draw. I could have them swim away when the player got close, preventing them from being viewed close-up. That way, they could be drawn as simple untextured ellipsoids (non-uniformly scaled spheres) that faded into the water color at a distance. Later, I realized that it was possible to corner fish in shallow water so that they couldn't swim away from the player, allowing the player to get very close to them. The ellipsoid shape just didn't work. In the end, I had to find a textured 3D fish model on TurboSquid and figure out how to integrate that into the world. This is the first time I had to add support for dynamic imported 3D models with arbitrary translation, rotation, and scale transforms. They still blend into the background water color with distance, to simulate a fog effect for murky water.

In the end, the fish models worked well. They weren't animated models, so the tails and fins were static, but that didn't seem to matter too much. Here is a screenshot of some fish under the water, taken with the camera positioned in the air above the water. Normally, the fish will swim away at this distance, but I temporarily disabled the fish AI to prevent them from moving.

Fish viewed from above the water surface.

Here is a screenshot of fish viewed from underwater.

Fish viewed from underwater.


I originally thought it would be difficult to make birds look natural and real, but birds were actually easier than fish to draw. They're viewed from a distance, so their model didn't need much complexity. I was able to get away with making birds plain black, formed from three ellipsoids representing their body and two wings. The wings of each bird are animated using a simple sine wave for up/down motion. They fly with a constant velocity in the xy plane, with an altitude/z-value randomly between the top of the highest mountain and the bottom layer of puffy clouds. Birds move in slowly varying random directions that form wide curved paths, keeping them from quickly flying out of view. These wide curving paths seem to make believable flight paths.

Birds cross through tiles relatively quickly, since they move at higher speeds than fish (and the player). The birds that cross the final edge tiles that have no generated neighbors are gone forever. I suppose if you stay in one place watching the birds for a long time, eventually they'll all fly away. However, it seems like when I look at them for minutes their numbers aren't even reduced noticeably, so it may take closer to an hour for most of them to fly away.

The birds fly above the highest peaks, so no collision detection is necessary. It might be more realistic to have them take off from and land on trees (if trees are even enabled), but that seems much more difficult. I haven't implemented collision detection with the tree crowns/leaves yet, only the trunks.

Here are two screenshots of birds flying in the sky. Note that they can fly in and out of clouds. Since clouds are volumetric, the birds alpha blend with them correctly. This likely doesn't happen in nature, except for some of the high flying species of birds (or very low clouds).

Birds flying in the sky, controlled by steering AI.

Birds in the sky, flying in and out of the clouds.

Here is a video showing birds flapping their wings and flying through the sky. Somehow YouTube didn't ruin this one with compression artifacts - I wonder what I did differently this time?

I'm thinking of adding bees and butterflies next. These would be similar to birds, except that the player can get closer to them so they need to have a more detailed drawing model. Also, they will need to do some sort of collision detection with trees and flowers. 3DWorld already has basic collision detection for some of the scenery, though it would be slow for a large number of insects.

Maybe I can also add spiders that fall out of the trees when the player walks under them. They can come down on webs and then walk around on the ground. I'm not sure how difficult it would be to animate a spider's legs, but it sure would be scary!