Sprint 30 - Adam-Poppenheimer/Civ-Clone GitHub Wiki
Goals
- Natural and curving rivers that interface with standing water properly
- Culture boundaries that interact with water, rivers, and the terrain mesh properly
- Exploration
- Visibility
Risks and Mitigation
- A lot of my river code has strange organizational decisions and lacks proper unit testing. Leaving that as it is could make the code incredibly hard to change and maintain in the future.
- I'm going to need to take apart and rebuild my heightmap and alphamap code anyways. I can extend this into a more general refactoring of the rendering code. Mostly I just need to add unit tests, which'll be difficult and tedious for some of the classes but far from impossible. The important thing here is to maintain discipline and not take shortcuts.
- The requirements rivers impose on the program are heavy enough that performance has become a perennial problem. It's entirely possible that even a good-looking solution will have such poor performance that it's unusable, which'll make this whole problem a lot more complex.
- I already know that the resolution of the heightmap has an enormous influence on performance, since increasing it vastly increases the number of triangles being rendered in the scene. There's some things I can do to make this better. Using a simpler shader or disabling certain Terrain operations might help deal with the costs of individual triangles. Making use of my chunking to devisualize out-of-view regions of the map can reduce the number of rendered triangles. I can also fiddle with the camera, bringing it closer to the terrain and keeping it from getting very far away. If my current tree solution turns out to be too expensive I can switch to billboards like Civ 5 uses. If map generation and change refreshing becomes an issue, I can probably cache a bunch of important data on a point-by-point basis. For instance, the relative weights a point takes from neighboring cells and rivers shouldn't change unless the size of the map changes, so I can cache that if it becomes a performance bottleneck. That'll help deal with the complexity of cell contours and the like. I'll have to see what happens to performance when I start generating full- sized maps.
- It's still not clear how I'll add visibility or exploration to the terrain mesh, which remains a critical part of the final product. If I can't figure out how to push custom data into the terrain system, then a central component of my program will be unusable.
- I think the same observations from Sprint 29 continue to apply here: exploration and visibility mixing in the CPU, or else repurposing a pair of alphamaps to paint exploration and visibility. And at this point, cracking open Unity's Terrain system doesn't seem like that far-fetched of an idea. As long as I can get access to the source code I can probably make it work. Assuming that Terrain shaders don't make full use of all of their per-vertex data, that is. That would make things a lot harder. I'll have to see.
- The relationship of cells to one-other for the purposes of rendering is now a lot more complex than the old solid-edge-corner divide. If I can't swing that in a way that's both comprehensible and efficient, it could make my heightmap and alphamap strategy unworkable.
- I'll need to rebuild point orientation, and probably also the way that heightmaps and alphamaps weight contributions. As said before, caching might be a good way of improving performance. That becomes more complicated when contour- based information is changing, however. I'll need to build my refreshing apparatus carefully so that it can refresh individual collections of cells around an edge.
- I was struggling immensely to wrap my head around flowing water. It's not clear to me what I'll do if that struggle persists, given that the strategy I've found seems to be an industry standard.
- The rivers not flowing isn't a catastrophic result. It'll look a lot better if they flow, mind you, but the flow is subtle enough that an acceptable final product might lack it. So the worst case scenario is that the rivers look a bit weird. But I suspect there's some very important fact that I was missing while trying to develop the rivers. It must be some odd little detail. Maybe I need to procedurally generate flow maps. I know the basics of the problem I was running into. Surely I can figure it out given enough time.
- Civ 5 handles rivers by warping the whole terrain around them. Hills are lowered and cut off around rivers so that they can lie on a flat plane. My codebase and the heightmap paradigm it's using doesn't have a way to resolve that.
- I might need to add a new point orientation (which I'll need to do anyway to handle rivers). I could establish something like an InnerSolid and an OuterSolid. The InnerSolid only takes heightmap contributions from its cell. The OuterSolid does the same unless there's a river, at which point it'll ramp down towards a level plain. That should be simple enough to resolve.
Review
This sprint deviated considerably from the more organized development process I've been using for most of the project. Rather than breaking the work I had to do into separate issues, I ended up performing a lot of different tasks in the amorphous and ambiguous "Build Rivers" supertask.
I began by switching most of my terrain generation code from Vector3's to Vector2's, since heightmaps, alphamaps, contours, and the like are determined only by XZ positions. While this has led to some minor inconveniences it's for the most part clarified how these classes are supposed to function.
After that minor change I started trying to figure out point orientations in my new contours system. This led to a bunch of fiddling and experimentation regarding what, exactly, I needed PointOrientationLogic to figure out and how it should do so. This revealed all sorts of unexpected complexities involving contours. IsPointBetweenContours turned out to be more complex than expected. And somewhere along the way I made an incorrect assumption about the order in which contour points were defined. I'd assumed they were always clockwise and built point orientation around that, but that turned out not to be the case. It took several days to discover that fact, during which I struggled much with enigmatic bugs.
During all of this, I also started addressing the hills and mountains problem. Hills turned out to be fairly straightforward, and ducking them down went easily. Mountains, on the other hand, became an ordeal that I haven't resolved to my satisfaction yet. For some reason, pushing mountains back from the edge of a river leads to unusual ridges. I tore down and rebuilt MountainHeightmapLogic at least twice but only made minor progress in mitigating that effect. My current solution doesn't have ridges anymore, which is sub-optimal but also not particularly important. I ultimately stopped working on the mountains because I admitted to myself that it was a relatively low-priority task, given all the critical things in the game that have no visual representation whatsoever right now.
Alphamaps turned out to be quite easy. While I needed to modify my alphamap resolution to get a non-blurry result, the logic for setting the alphamap (even in the presence of rivers) is pretty straightforward. I still need to find a reasonable texture for riverbeds, but other than that things seem fine.
Throughout the whole process, I was filling out missing unit tests and covering the code I'd written. That came with some mixed results. I'm beginning to think I should have a professional geometry library in my project so that I don't have to unit test things like GetBarycentric2D or my BezierSpline class. But a cursory search didn't reveal any good results. I still haven't covered RiverTriangulator, which is a large and ornery class where tests seem (though might not be) both tedious and unhelpful.
Retrospective
What went well?
- I've settled on a reasonable and probably permanent solution to hills that does everything I need it to.
- I've substantially increased the test coverage of my rendering code, which has given me a lot more confidence that it's working as intended.
- I managed to dig myself most of the way out of the struggles I was dealing with during the last sprint. Even though mountains aren't going great, I've got a reasonable implementation strategy for the map that should hopefully be sufficient.
- Once I got point orientations and contours worked out, alphamaps were quite straightforward to implement and have given me very little trouble.
- I've made enough progress that the end of river development is finally in sight. I just need to resolve mountains and figure out river surface elevation and I should be done.
What could be improved?
- I haven't been organizing my work very well for the past few sprints. Tasks are no longer separated into concise, self-contained issues, which makes development a lot more difficult. I need to return to my original development process, carefully defining the issues I need to resolve and separating them into manageable chunks.
- I think I've really messed up task prioritization for the past sprint or two. I spent a lot of time worrying about small problems in mountains while ignoring the much larger problems of cultural boundaries, visibility, exploration, and poor performance. I need to return to work prioritization, to iterative development of various elements of the codebase. Particularly, I should only flesh out a particular problem until it's a good enough state that it's no longer a top priority, and then immediately switch over to something else. That should be much easier now that the architecture of map rendering is in a better place.
- I ended up losing a large chunk of work to morale issues. This was caused largely by external factors, but also by slow progress on rivers and a lack of scheduled tasks that were important but relatively easy. I should consider slotting in a bit of easier work so I've got something easier to fall back on when I'm not feeling particularly great. That way I can stay productive even when I don't have the will to grapple with the hardest problems left in the project.