Been devloping the directed graph concept a bit more. I think I have outlined this project pretty well, and I’m soon ready to start working.
River placement
This is how a river graph is formed.
First - all graph “activity” takes place in xz space. There’s no depth involved. Also there will be no forks in the first version (and therefore no edges). Those will be added once the system is in order.
- A set of verts are provided. The verts contain xz location, width, depth and an ordinal. Width & depth are scales, not absolute values.
- The verts are connected to form a curve (in the xz-plane). The curve is smoothed out, and then distorted by noise (one or more octaves depending on the scale), to make it look more natural. The orientation is in the direction of increasing ordinals (starting from 0 of course). The curves used by the cinematics system would be an ideal basis for this, I think.
http://www.jamtlandoutdoors.se/images/dist/Graph_paths.png
Note the noise is applied in the x direction in the image (just a quick sketch), but it will of course be applied in the curves normal direction.
River generation
The curve is used to generate the river. The generation is done in several parts.
- The terrain must be reshaped to create a proper riverbed.
This is done by using a brush, sort of like how the terrain editor works. The brush I use now is a square area with size.x = size.z = 41, centered at (0,0). The active part (imprint) is the circle of that diameter, centered around the same point.
The shape is a simple paraboloid of revolution, with global minimum at (0,0) (the value there is -10). The zeroes are in a circle of diameter 41, so it’s fairly “shallow” ( a height/width ratio of .25). All values larger then 0 is clamped so the brush never raises terrain. The equation is:
f(x,y) = 0.025f*(xx + zz) - 10f
height = min(f(x,z),0)
Btw the square functions as a bounding area when selecting which parts of the heightmap to work on (it’s easy to iterate over a rectangular area). Any points inside the square but outside the circle has a function value >= 0, so they are not affected.
There will be some noise here as well. Also several shapes could be combined later to make a deep part with shallow sides (like a real river).
http://www.jamtlandoutdoors.se/images/dist/Brush.png
The brush is displaced using the curve (starting from point 0), and applied every set distance. Every time it’s applied it re-shapes the terrain heightmap. It should be a pretty short distance between applications.
This approach is good because it allows for rivers that run in any direction, and since the brush is xz-symmetric it does not have to be rotated, just applied.
The parameter used for the noise function is the total displacement of the brush along the curve, so that a tiled 2D noise can be used to create some variation in width, depth and also the fine detail.
- The river data must be generated.
This I will do in several steps as well. First, a new array of data is generated. It records data at the points where the brush was applied. Each such point contains the xz-location, the depth and width used (for volume flow calculations), and the tangent of the curve. Normal values are calculated later by (z,-x)'ing the tangent.
A float array is also used, to store every point touched by the brush. It is needed to find out which points are part of the river, and which are not. The array stores relative heights; any 0 value is considered to be outside the river. It is also used to calculate depth at each of the flowmaps sample points, so that water run slower along the edges etc (where depth is low).
This array is needed for splatting a river texture onto the terrain alpha map (among other things). I think I’m gonna do this radially from each point, using a constant value with a steep falloff near the edges (and maybe some noise to avoid banding, depends on how it looks).
- A river mesh must be created. This is why I want this approach. The height of the terrain does not matter since the brush just displaces the height values, it doesn’t set them.
Below is a representation of the above mentioned points. It is very coarse (normally there’d be much more lines). Either way, the mesh verts are created by extending the points in the curves normal direction. This way xz coordinates for the verts are created. Btw. this mesh will of course be a triangle strip.
http://www.jamtlandoutdoors.se/images/dist/River_points.png
To get the height coordinates just sample the heightmap at both xz-values, and use the lowest value. The mesh will follow the terrain pretty well with that approach. Needless to say, there might be some post processing of these values required (perhaps some smoothing).
The issue of heights increasing will be adressed as well, like terminating the process if height increases are to weird, or maybe just dig deeper.
The width of the mesh should definitely be extended to make sure the water mesh is always under the terrain. This can be done with a global widening constant (like 1.1 or 1.2). Also, a waterlevel should be defined. It should be negative, and the entire mesh should be displaced by this value (so that the water mesh isn’t the same height as the terrain, but slightly lower). It should be possible to change that value.
On top of those things, the varying elevation of the river should be stored alongside the xz values of the river curve. These values (datums) could be baked into a texture (perhaps the flowmap) to make use of in the shader.
Shading
The flow part works in the xz plane, and doesn’t care about heights. This means the flow works just as well on a flat plane as it does with varying heights. If the height varies a lot over a units length it may be necessary to scale (since the segment will be longer then if it were flat), but it’s not a problem.
Heights, and especially height deltas, could be used when calculating wave speed and turbulence. This is simple in theory, but it may be hard to implement in practice. It’s hard to make it look good.
The real problem is reflection and refraction. Gonna have to read how people do this in other games. Skyrim for example has rivers and streams that look pretty complex (not just flat).
With refraction, I think the main difficulty will be to set up the camera properly.
When the camera is set up properly, I think it’s just a matter of using both the mesh normal and wave normal. Currently it uses the wave normal (a very common approach), and it works well.
I think I will use the sky only for reflection. At least initially. That removes some problems.
Again, these are just some thoughts. Gonna read about it more before implementing anything. Also there will probably be a flat mesh alternative (that’s how it works now).
Implementing in jME
Gonna go with the simplewater approach. It has cameras set up, and seems to work pretty well. Again, I will drop the reflection cam at first and use a skybox instead.
A lot of this stuff is already prepared. It will take time tho. I figure by this fall maybe there will be a working version (considering I got lots of forester work to do, and the sky stuff, and also other work outside of programming).
EDIT: Hmm need to specify, the terrain heightmap isn’t really re-shaped, like i write. It is a typo. A new heightmap is of course created, since the modifications happens one at a time. The original heightmap is left intact, and only a copy is modified. Each time the brush is applied, an array of the same size as the brush is filled with the original heightmaps values minus the depth. This array is then written into the new heightmap.
Also, the map with the relative depth of the water is then calculated by subtracting the new one from the old and normalizing.