A terrain tool based on heightmaps, developed in our engine Cobra for four weeks (4h/day) during an individual specialization course. I took a lot of inspiration from Unreal Engine's landscape system.
The Landscape class
The structure of the landscape followed the structure in Unreal Engine. The landscape is made up out of Components (not the same components as in our ECS), which are divided into Sections that consists of quads. The specified number of quads per section is the number of quads at maximum tesselation, which is done on the GPU. Since DirectX 11 supports a maximum tesselation of 64, the specified number of vertices (number of quads + 1), would best be chosen as a multiple of 64. On the CPU side, the actual number of vertices is thus the specified number of vertices divided by 64.
The landscape itself is simply a grid with heights taken from a heightmap (sampled in the shaders). The size of the heightmap is simply the product of the number of components, number of sections per component, and number of vertices per section.
The landscape supported dynamic tesselation based on distance from the camera. This was based largely on the solution by Frank D Luna in his book Introduction to 3D Game Programming with DirectX 11.
This was a lot of fun but also put constraints on how to do the actual painting. Since there were almost no actual vertices to "paint on", the painting had to be done on textures which then could be sampled at the tesselated vertices in the shaders.
Raycast on heightmap
The first part in actually editing the heightmap was finding the picking point on the landscape. I solved this by projecting the picking ray on to the plane of the landscape. Then, at each increment on the ray, I performed a distance check between the point on the original ray and the point on the projected ray to see if the distance was below a threshold.
Debug draw for the raycast on the landscape
Once the intersection point on the landscape had been found, it was just a matter of iterating over the rows and columns and editing the height value at the vertices based on properties such as distance, falloff, strength and brush type.
Painting the textures
Since the landscape not only supported the painting of heights, but also of "regular" textures, I wanted the both cases to be handled in the same way. Both the heightmap and the texture containing the weights used for blending the other textures were represented by classes inheriting from TerrainTexture.
GetCurrentTexture() returned a pointer to a TerrainTexture, and combined with overloading the operator(size_t,size_t) for accessing elements in the texture, the calling function didn't have to care about what kind of texture it was painting on.
The GetNormalizedValue() function in the image to the right scaled the normalized distance to match the current Falloff (the name perhaps wasn't the greatest).
Examples of spherical (left) and linear (right) falloff
Sketches of the GetNormalizedValue() for two different falloff types. Alpha is the falloff value, ranging from 0 to 1.
The GetIncrement() function then calculated the actual value to add to the current, based on the current brush type (circle, noise, smooth).
The noise brush used Perlin Noise, with a factor for scaling
The smooth convoluted the texture with a Gaussian kernel
The landscape supports painting on up to four sets of textures. At the moment, all textures are blended together linearly using the weights painted. In the future, I would like to add blending based on height.
The landscape also supports painting (both for height and "regular" textures) on different layers. These can be toggled on or off, and controlled by a parameter ranging from -1 to 1, with -1 inverting the layer. For the "regular" textures, the lower bound is 0, not -1.