Dynamic Terrain extension, How to make infinite terrain?

Hey guys,

So my idea is this:

  • backend generates heightmap images with perlin noise
  • frontend gets an initial chunk (let’s say a 1024*1024 heightmap image)
  • frontend generates a terrain with BABYLON.DynamicTerrain.CreateMapFromHeightMapToRef function
  • if the player moves past a certain point in direction X, a new chunk is requested from the server (another 1024x1024 heightmap image), and attached to the existing mapdata, making it seem infinite.

In the documentation there is a reference to this kind of behaviour.

This is useful if we have to download dynamically new chuncks of data as the camera moves in the World.
Example :

// change the terrain map on the fly
if (camera.position.z > someLimit) {
terrain.mapData = map2;
}

But I just can’t imagine how this would work.
I have to replace the whole mapdata?

Here is an example:
What if the player decides to go 2x1024 in direction X, then 1x1024 in direction Z? (essentially 2 chunks in X direction and 1 chunk in Z direction)
The server would have to generate the initial chunk, 2 to direction X, and 1 to direction Z, forming a letter L with generated chunks.
Now on the server side, I can manage the chunks by giving them coordinates (x,z) (for my example they would be (0,0), (1,0), (2,0), (2,1))

But how would this look with BabylonJS Dynamic Terrain extension?
My initial guess would have been to switch off infinite tiling, and create new instances of dynamic terrain, when walking on a new chunk, but apparently, it’s not a possible solution.

yes, this is the way : you can link another mapdata.

The DynamicTerrain philosophy is not to work bunch by bunch but with two different objects :

  • a map of data (or several), that can be as large as wanted as it’s only a pool of floats
  • an updatable mesh, called the terrain, depicting how is the terrain at the current camera location.

So you can preload huge (or tiny) sets of data, as the mesh will always keep the same number of vertices and the same time to be computed.

But if I replace the whole mapdata, the old data is lost, isn’t it?

How can I link another mapdata, to an existing, and how to tell DynamicTerrain to which side I want it to attach?

So I generate the initial chunk by the server, and the client wanders off of it’s edge.
The server generates another image with the height values (I use images as it’s less network traffic in .png format, than an array of floats)
How should I call BABYLON.DynamicTerrain.CreateMapFromHeightMapToRef for it to keep the current mapData, but attach a new piece to it based on the new image, and how will it know, that the new image has to be attached to a specific side?

  • With CreateMapFromHeightMapToRef() , you can create as many map of data (maps) that you need and keep a reference to each of them with a simple variable. You could store them in an array for example.

  • Each map must hold the real World coordinates. Say, the map1 is located on the East of the map2, then all the x coordinates of the map1 should be greater than the map2 ones in theory (assuming that x increases when going to East and z increases when going to North).

  • It’s probably a missing feature here, but you need to translate by your own the computed Wordl coordinates of the generated map to your wanted final positions. If the map1 is supposed to be at x+1000 from the World origin, then add +1000 to each x coordinates in the map array. Only you know where the generated map1 is supposed to be in the World if it’s not centered on the World origin (default behavior).

  • The same way, only you know that the map1 should tile the map1 on its right side. So the rule “if (camera.position.x > someLimit) { terrain.mapData = map1; }” must be implemented by the user in his own logic. More generally, here again, if several data map must be tiled, maybe a side structure may be necessary to store for each tile what are the other tiles on its sides and from what world limit coordinates the terrain should be set with the next tile. This feature is not provided by the library because the DynamicTerrain wasn’t designed to work in a tiling mode. But it can also work that way. The feature is just be done by the user with his own needs.

  • If this kind of tiling is implemented and unless the camera always keep moving in the same direction, maybe it would be pertinent to have the maps covering each other on a certain width (holding the same data and depicting a shared part of the World), so the terrain map swapping could be smooth. Example : if camera heading toward the North and camera z > someLimit and camera z still < someHigherLimit, then swap the current map to the northern map, both rendering currently, between these both limits, the very same terrain.

1 Like

Ok, I understand now, thank you very much.

The part which wasn’t clear is that when I replace the old mapData, I didn’t get how would the terrain use the old mapData.
But I see now, that it doesn’t, and in fact, I have to overlap a common part, in order for it to be not noticeable.

And I might just have an idea how to solve this without overlapping the mapData, and constantly changing it when on chunk borders.

So my idea is pretty similar to what you just described, but instead of storing the mapDatas separately, I would just store them in 1 big array.
Each time the player wanders off the edge of a chunk, I create a new mapData, modify it’s coordinates according to the chunk’s location, then add it to the mapData array which the terrain uses.

The only problem with this I guess is (correct me if I’m wrong), that mapData has to be rectangular.
So in this case:
[x][x][x]
[o][o][x]

x - generated chunks
o - unknown chunks

The mapData would be invalid, if it contains only the values on generated chunks.

I would solve this by filling the 2 unknown chunk’s coordinates with dummy 0 values.
When the player reaches the unknown flat chunks, he can download the correct chunk to that location, and replace the 0s with the correct values.

What do you think, would this work?
(Of course it has the downside of requiring more memory if the player goes 10 chunks east, then 10 chunks north)

I like your idea.
Yes, the mapData is to be rectangular.

In the standard usage, only the “chunks” where your terrain (camera) is currently located is accessed. This means that only the related part of the global map is accessed, so unknown or null values should work also … if we think before about the normals.
So, either you download and set at will both, the map coordinates and the precomputed normals, either you compute the normals (and uvs, if needed) of this dedicated section just after downloading and set the DynamicTerrain normals array.

Note 1 : if keeping null values, you’ll probably need to disable the DT automatic normal computation at creation time How to Use Dynamic Terrain - Babylon.js Documentation

Note 2 : This static method computes the normals of a passed mapData (your chunk, here) Extensions/babylon.dynamicTerrain.ts at master · BabylonJS/Extensions · GitHub
Then up to you to update the right section of the single real global mapData used by the DT.

Maybe it’s easier finally to fill the global mapData with zeros and to update only parts of it with downloaded heightmaps and downloaded pre-computed normals.

Another tip : instead of downloading an image, you could just download a flat array of altitudes (y float values only), then fill client side a mapData array by setting x and z values from a given step.
=> download only y values, populate the mapData array with computed (x,z) values. It will be simpler than dealing with heighmaps imho.

1 Like

Thank you very much for the help.
Based on this, I can start working on it now. I will probably use float array instead of images.

But first, I have to implement the backend, so I will come back with an update someday soon.

Is it possible to create a dynamic/endless tile system without the Dynamic Terrain extension? e.g. just vanilla BJS?

Yes.