Sprint 31 - Adam-Poppenheimer/Civ-Clone GitHub Wiki

Goals

  • Natural and curving rivers that interface with standing water properly
  • Exploration
  • Visibility
  • Culture boundaries that interact with water, rivers, and the terrain mesh properly
  • A better way of displaying farmland

Risks and Mitigation

  1. I still haven't figured out a way to integrate exploration and visibility into the Terrain system. If I can't manage a solution for that, then this whole exercise will have been moot.
    • I just thought of a solution, something that'll work even if I can't get cell data into the Terrain. I might be able to make an Unexplored texture for my terrains that uses the same processes the Hex Map Guide uses to suppress the appearance of cells. Then I can just paint that texture to my alphamap on unexplored cells. I can do similar things for cells that aren't visible, creating a darkened version of their terrain that I can paint in place of the fully- visible version. I might also be able to throw an opaque cloud mesh or transparent dark-colored mesh over the whole terrain grid in order to obscure or darken things. Neither of those are ideal solutions, but they're at least backups I can use if the Terrain plan fails.
  2. Throughout the river process, I've had to juggle a number of factors to make river troughs and surfaces look reasonable. To finish the river process, I'll need to balance standing water height; the base heights of flatlands, hills, and mountains; the height deviation of flatlands, hills, and mountains; river width; heightmap and alphamap resolutions; and the width and location of river surface segments. Balancing all of these factors could prove quite challenging.
    • I don't think there's any way to get around this complexity. I'm simply going to have to do it. I need to improve the appearance of coastlines, anyways. Making them shallower and smoother is important. And if I want to go for the full Civ 5 plan, I could even represent them as separated contours and use some more Bezier Splines to create sandy beaches. But for now, balancing the various factors of my graphics implementation shouldn't be too much to ask.
  3. Farmlands are a very important component of the visual aesthetic of the game. But so far I've been unable to come up with a reasonable solution for them. I'm sure I could figure out how to draw rectangles of various sizes on the map, but it's not clear how I'll turn those rectangles into decent-looking farm textures.
    • I remember a lot of my old farmland implementations tried to visualize rows of crops. But it's entirely possible that such a thing is unnecessary. It might be sufficient to grab my Grassland texture and tint it various shades. That'll certainly be better than what I have right now. It's a bit unfortunate that it'll need to be a separate mesh, but that should be fine in the long-run.

Review

I began the sprint by dealing with the last critical problem with rivers: that of the elevation of the river's surface. I had expected that balancing the elevation of land, rivers, and standing water would be somewhat difficult, but it turned out to be a fairly straightforward task. I got something solid within about 2 hours of work. After that, I spent a bit of time adding unit tests to my river code and improving the texture of the trough, but beyond that I didn't spend much time attending to rivers. The bulk of that task, thankfully, is done.

After rivers, I started digging into cultural boundaries, another feature the final game must have. This came in two parts. The first stage was building a simple triangulation that adhered smoothly and carefully to the contours of cells. This was primarily a triangulation task much like many I've done before and bore some of its basic hallmarks. As is tradition, the cell corners proved to have the largest quantity of the most complex cases, though in this case almost all of the corner cases occurred outside of the typical corner triangle (since culture is usually pushed back from the edge). This required line intersection and quadratic Bezier curves, and could probably make use of cubic Bezier curves in the future for one or two of the cases.

I did something fairly important with culture that I failed to do with rivers. Instead of trying to work out every little glitch and every suboptimal detail, I worked through this basic triangulation until it looked fine, and then moved on. This left a handful of visually sub-optimal borders that'll need to be worked out in the future, but for now it's not a high-priority task. I also opted not to throw away my current solution and replace it with Bezier Splines even though I wanted to, because it didn't seem worth the effort.

Once I had my basic triangulation done, I started looking for solutions to wrap that triangulation around the mesh. This turned out to be quite a complex task. I spent some time doing research on how to wrap a mesh around another mesh and ultimately found Blender's Shrinkwrap implementation. Unfortunately, I could not understand how it functioned. Finding no other clear guidance, I opted to roll my own solution.

After a false start or two, I came up with a somewhat effective algorithm that involved subdividing triangles into smaller triangles and then colliding their vertices against the terrain. I then used a shader offset to round out some of the clipped corners. This turned out to be a workable but ultimately mediocre solution for a number of reasons.

For starters, the heightmap resolution of my Terrains demands a very small triangle edge width to get something that fits the terrain reasonably, especially at river boundaries. But doing that ends up creating a culture mesh with a very high triangle count, much higher than the contour-adhering triangulation. With enough border regions, borders could very easily contain millions of triangles in their current state.

The large number of triangles also caused me to run into problems with my meshes. Unity indexes into its Mesh vertex arrays with 16-bit numbers, leaving a maximum vertex count of 65525, far less than an entire chunk's worth of culture. I ultimately settled upon a modification to HexMesh that abstracts the actual Meshes and allows me to have multiple Meshes under a single HexMesh, which largely resolves that issue.

The above concerns would be fine if the triangulation looked good. And while it looks fine for almost all land cases, and is close to looking fine for standing water, it doesn't interact particularly well with rivers. The triangulation of the mesh can sometimes become obvious, especially with rivers next to hills. This occurs even with mesh welding, which my mesh subdivision abstraction has made much less useful.

But rather than agonizing over every detail of my culture implementation for weeks, I decided to call the first pass of the culture issue done and moved onto other things. Before I did that, I did modify the way in which cultures rendered relative to water, since it was having some issues when passing over the transparent water surface.

After putting culture into a reasonable state, I moved onto another big-ticket item: visibility. After resolving some organizational problems in HexGrid and GameCore related to my visibility and cell data system, I got to work on how to acquire cell data from within the Terrain shader. To do this I turned to the math for hex grids, figuring that I could index into the cell data texture by generating row and column information from the world position of vertices and fragments. And this turned out to work great for most cases.

But there were two big problems. First, calculating visibility on a per-fragment basis got rid of visibility smoothing, which is a relatively small issue. There are almost certainly ways I can simulate such smoothing by modifying my existing solution, perhaps by indexing into my cell data in multiple places. The second problem is a much bigger concern. Because my cell index information is based off of hexagon calculations, it doesn't take contours into account. This means that it does not interact with rivers well at all.

It's not yet clear how I'm going to resolve that problem, but I decided that, in the grand scheme of things, it was small enough that I shouldn't be worrying about it now. That's a refinement of visibility that could, feasibly, be ignored, though I rather don't want to. So instead of agonizing over the problem, I declared its first pass done and moved on.

After visibility, I moved on to exploration, which turned out to be fairly simple once I'd resolved visibility. Most of the Hex Map Guide's solution applied fairly directly, though I had to modify the way I was indexing into the cell data texture to prevent exploration and visibility wrapping.

Once I resolved exploration to a reasonable standard of quality, I attended to a number of important but not critical tasks. I resolved some long-standing issues with RiverCanon, improving its test fixture and fixing its implementation. This has made drawing rivers a lot easier, and will probably make map generation an easier task. After that, I fixed some issues my cell pointer events were having because of contours (and also, it turns out, from changes I introduced to triangulate culture).

I spent a full day chipping away at one of the last big tasks left to do: farmland triangulation. The terrain contour conforming class I built for culture triangulation opened up new possibilities for farmland, since I suddenly had a very clear way of applying quadrilateral meshes to a smooth, rolling surface. And I made some progress in getting farm-like structures in place for it. But the scope of the task was more than I could reasonably do in my state of exhaustion. I ended up with a quarter-baked solution that still needs a lot of work.

After that, I spent some time improving the river trough texture. To do this, I had to modify my PointOrientationLogic class to distinguish between RiverHeightmap and RiverAlphamap weights, which took some doing. I also improved the texture I was using a bit and fiddled with some configuration variables to make it more likely that rivers have sensible banks.

The last thing I did was to ensure that my Terrain chunks all have the correct neighbors, in order to make LOD calculations consistent across chunk boundaries. While doing that, I encountered a very, very big bug. It turns out that I wasn't recalculating LOD information when updating my heightmaps, which meant that my terrains had no way of simplifying their triangulation when the camera was distant. So I changed one line of code and immediately the framerate for my standard test case tripled and the number of triangles plummeted. So...good that I found that problem, I suppose. There's now a very good chance that old solutions to problems (like increasing my heightmap resolution) might be feasible now that I'm using the terrain system properly. I'll have to look into that in the future.

Retrospective

What went well?

  1. I created a successful first-pass solution for rendering culture.
  2. I developed an effective way of getting visibility and exploration data into the terrain shader.
  3. I've learned a lot about how the Terrain works, particularly its shaders. That information should prove very useful for modifying the Terrain in the future.
  4. Rather than trying to make each of my implementations perfect, I worked through each problem until they were in a reasonable enough state that their resolution wasn't the highest-priority task, at which point I switched over to that more important task.
  5. I, almost by accident, found a major bug in the program whose resolution has vastly increased my game's performance.
  6. I figured out (to a reasonable degree of accuracy) how to conform meshes to my terrain, which will prove very useful for a number of future graphics tasks.

What could be improved?

  1. It's becoming clear that I can't resolve things like culture with triangulation. By far the most efficient solution will be to burn culture into the terrain mesh itself, thereby shedding triangles. The same can probably be said of visibility, exploration, roads, and perhaps marshes and flood plains, though probably not of farmlands.
  2. I should figure out how to assign data textures to individual meshes so I can use them to render important data onto the mesh, since they're a far more efficient way of visualizing things that need to conform to the mesh.