Sprint 29 - 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
- I was already having a hell of a time figuring out rivers and culture when their locations were standardized and emerged directly from the triangulation. Making the rivers natural and curving (and by extension the cultural boundaries, the heightmaps, the alphamaps, etc) could make this task so much more difficult that I can't even begin to address it.
- One possible solution to the river conundrum is to change the way in which edges are calculated. Instead of defaulting to straight lines, I can do a pass of my cell graph and generate a small set of Bezier curves (maybe 1 to 3, randomly chosen) for each edge. Then I can define a single inner path that runs along those curves and a pair of outer paths that follow either side. I can run rivers along the inner path and run culture along either the outer or the inner paths based on the presence of a river. Culture might also go to the midline when there's no river. Corners seem like a complicated case here (as they always are) but I'm sure I can math them given enough time. I can perhaps add a new PointOrientation to help duck the heightmap down as it approaches rivers (differentiating between OuterEdge and InnerEdge, for instance). And I've got some ideas for how to get this to play nice with the terrain mesh itself. I think if I sample my Bezier curves.
- I've struggled a lot with farmland. It's a perennial issue for this project. Improving it is now a critical task, since it looks particularly poor against the new, more natural map. But I still don't have a clear strategy for adding natural-looking farms to the game.
- I remember I came up with a solution that worked OK for flatlands but failed on hills because of perturbation and the nature of the triangulation. One of the motivations of using Unity's Terrain was so that I could meaningfully sample the terrain and therefore wrap a rectangular mesh around it. I've got some ideas for how I can apply a rectangle to the terrain now, which should make that old solution more viable. At the very least it's an idea I can try.
- Exploration and visibility are both fairly involved tasks that require custom shaders and a lot of attention in the codebase. Since I'm not doing triangulation of the mesh anymore, I can't assign cell weights on a per-vertex basis anymore, so it's not clear how I'll resolve this very important problem.
- I can apply a custom material to my terrain. I'm sure that'll be sufficient to pop in an appropriate shader. The question, then, is how I get the correct information painted onto the terrain mesh. Right now I'm using a Vector3 to store index triplets and a Color to store weights. The Vector3 is used to key into a texture (basically just an array of visibility and exploration data) and the Color to mix those data. It's possible that I could instead reserve a pair of alphamaps for exploration and visibility and record them on a point-by-point basis, then use those alphamap values directly for visibility and exploration. That'll require performing visibility and exploration mixing in the CPU, but I'm already doing things very similar to that (getting barycentric coordinates and getting relative weights of edge zones). I feel like that's going to cause performance issues, since I'll have to redraw the alphamap every time visibility changes. But I think I can swing that. It's at least worth trying. If that doesn't work, I might have to do something really drastic like modify the Terrain system itself.
- I've been struggling to create depressions in the terrain mesh for rivers to flow through. But it's not clear that I have any other alternatives. There's not a clear way of applying rivers to the terrain without creating either a trough or a flat surface to accept a river mesh.
- It might help to standardize edge elevations a bit, or else to use somewhat different heightmaps or height strategies when there are rivers present. I've been thinking about burning a hexagonal grid into the Hills heightmap so that it goes towards zero as it gets to the cell edges. I might also apply something similar independently of the heightmap texture, perhaps using the distance from the point to the inner edge to weight the heightmap results. That might make this whole process a lot easier. Though adding curving cell boundaries like I want to do for rivers could make this a lot harder, as well. It's also possible that I could do clever things with Z offsets in my river shader to make rivers appear above the terrain mesh even when they're positionally below them. that seems like a poor solution to the problem, but it's probably worth looking into. Then I don't have to care about river troughs; I can just let the river render over the terrain.
Review
This was an unusual sprint in a lot of ways. I spent the entire time trying to figure out rivers and didn't manage a solution. And yet I feel like I learned a lot and made a ton of progress, but also that didn't make a lot of progress.
I opened the sprint by studying Civ 5's mechanisms for building rivers. This revealed a number of important pieces of information. Rivers did, in fact, involve troughs that perturbed the terrain mesh. The troughs were fairly shallow, but they did exist. There was a considerable amount of perturbation in Civ 5's solution, but they seemed to make use of either quadratic or cubic Bezier splines. I also unintentionally learned things about how they managed water that I'll have to incorporate at some point.
After some brief study, I broke the problem down into pieces and started to slowly, arduously make my way through them. I found an online solution for Bezier splines, but before I could meaningfully test that solution I had to figure out how to assemble rivers at the simulation level. This led to a complicated and messy series of maneuvers as I tried to reason about the edges between my cells. I opted not to add edges to my data structure, mostly because I had no idea how to. Building the simulation side of the rivers revealed all sorts of complexities and difficulties, particularly regarding intersecting rivers. How does one detect a looping river, for instance? I haven't produced adequate answers to that question, but I at least managed to get the normal, non-insane cases working. The current implementation sometimes produces orphans, but those cases are also map generation errors so I'm not focusing on them right now. I'll come back to them later.
Once I had my river assembled, I needed to turn it into a Bezier spline, upon which I could base the river's visual profile. That took some time and involved some confusion, but eventually I managed to get a handle on creating smooth Bezier splines. After that, I spent some time organizing what I had built and adding unit tests to what I had.
After that, I started trying to tackle the river surface. Triangulation turned out to be surprisingly easy once I had a reasonable spline going in the correct direction. I even managed variable width without much difficulty. Overlapping turned out to be a more considerable problem for the implementation as a whole, but I found a solution for mesh overlapping using the stencil buffer that at least addressed the problem for the mesh itself.
After getting the triangulation in a good place, I spent a bunch of time slamming my head into a brick wall trying to get flow working. I tried to make use of what seems like the standard flow solution, a technique developed by Valve using flow maps, but I could not wrap my head around it. Given, my construction used per-vertex flows instead of a texture map, but that should've made the problem even easier. Yet it did not. Frustrating.
I set aside river flow and moved onto heightmaps. That revealed an interesting problem I hadn't considered before, namely that the borders between cells have just become a lot more complex. I began working through the logic for determining cell contours in the face of rivers, which proved to be a complicated and fiddly endeavor. I managed to get an implementation I think works, but it's not currently covered by unit tests so it's hard to say for sure.
Following the contours, I started trying to perturb the terrain mesh. While it took some time, I ended up making very good progress. By the end of the week, I was creating rivers that clearly looked like rivers, though they had lots of issues. But the more progress I made the more I realized I needed to completely change the way in which inner and outer cell boundaries are considered, and possibly the way in which heightmaps and alphamaps are determined. Whether a cell is in a solid section, an edge boundary, or a corner is now a very complicated question that I haven't provided a thorough answer to.
This sprint ended up having more abandoned or unproductive pomodoros than most. I spent a decent chunk of time staring semi-consciously at the screen, or just getting overwhelmed by the complexity of the task before me. Rivers have proven so difficult that they've compromised my development discipline a bit.
Retrospective
What went well?
- I've learned how to generate smooth Bezier splines in a real-world process, which I'm sure will prove useful in the future.
- I've managed to achieve a pretty good basis for rivers that look much, much better than what I had before. Once all's said and done, I'm going to have a solution leaps and bounds ahead of the old Hex Map Guide's solution (which was very informative but not ambitious enough for my purposes).
- It turns out preventing self-overlapping transparent meshes is way easier than I thought. I suspect that'll come up again in the future, and when it does I'll have an effective solution for it.
- Despite the difficulties I was having maintaining focus, I did manage to get my brain back on track two or three times during periods of severe confusion. This implies that I'm making at least modest progress at managing my cognitive faculties.
What could be improved?
- I should've more carefully divided river creation into subtasks as soon as I realized it was going to be such a large undertaking. That would've helped me focus on individual components.
- I should probably take another pass at the organization for the solution I've come up with. Given how much I was struggling while building it, I'm not confident that I built what I built in a comprehensible or maintainable manner.
- I should take another pass at height and alphamap calculations, aligning them more closely with the new realities of the codebase.
- It be useful to generalize the RiverSection code I built to handle more than just rivers. My understanding is that Civ 5 uses Bezier splines to handle coastlines as well. That might be a useful play, though it's definitely not a high-priority task.