What are the rules for avoiding gaps in seams between Terrain tiles?

Discussion in 'World Building' started by TheSituation, Jul 9, 2020.

1. TheSituation

Joined:
Apr 17, 2014
Posts:
23
Hi all!

I am wondering what the rules are for avoiding gaps in seams between terrain tiles.

I have a simple game at the moment which is 12 x 4 tiles of 10mx10m each.

I can click and adjust the height of the surrounding area I click on at runtime (this is quick because we are using such small Terrain sizes per tile.

However things start getting *very* complicated to adjust the heightmaps in the terrainData if somebody clicks between tiles, and even worse where the 4 corner points of 4 tiles meet. The situation I am finding so cumbersome in my algorithm that I end up with gaps between the 4 corners.

Is there a known way to handle this? Perhaps somebody has already solved this problem before? If not, what are the rules my height adjustment algorithm should follow for the points next to each other, but on different terrain tiles.

It's worth noting, that I have figured out how to handle the border between 2 tiles.... but I am finding it hard and just complicated to handle borders between 4 tiles.

Really appreciate any and all help you can offer

(Note, please don't suggest I look at marching cubes, or infinite terrain systems as it genuinely is not what I need or am looking for ( I started there, and ended here with the Unity Terrain system which will be a great fit - performance is great in this scenario, it's just this complex scenario I need help to jump over!).

2. crysicle

Joined:
Oct 24, 2018
Posts:
95
The paintcontext already handles this for you if you paint the terrain with it. https://docs.unity3d.com/ScriptReference/Experimental.TerrainAPI.PaintContext.html

The way i solved this for my own custom terrain system was:
1. Created a Terrain_Grid class which has a hashmap that stores a reference to each terrain's data where the key is the position of a tile in terrain space rounded to int. The step from world space to terrain space is easy to calculate. Just divide 1 by the size of the terrain for each axis.

Etc.: if the general terrain tile size = (1000, 1000, 1000), then the step from world to terrain space will be (1/1000, 1/1000, 1/1000).

The key is in terrain space to avoid floating point errors for when it's time to retrieve a terrain tile by other classes.

Etc.: if the general terrain tile size = (1000, 1000, 1000) and a terrain tile has a position of (1000, 0, 1000) it's hashmap key will be (1, 0, 1), if the tiles position is (-3000, 2000, 0), the key will be (-3, 2, 0).

2. Created a custom class Terraformation which handles manipulating a terrain area in world space. The class then transforms that area into terrain space. Afterwards, it expands it to fully fit into a (1,1) grid.

Etc.: if general terrain tile size = (1000, 1000, 1000), world space xz manipulation area = (pos (500, 500), size (500,500)). The area will be transformed from world to terrain space into (pos(0.5, 0.5), size(0.5, 0.5)), which will then be expanded to (pos(0, 0), size(1, 1)).

The expanded area will then be used to retrieve all of the terrains the Terraformation is overlapping. For each terrain, a heightmap area overlap function is called. To calculate a terrain's heightmap space area and the Terraformation's heightmap space area, a world to heightmap step is calculated.

Etc.: if the general terrain tile size = (1000, 1000, 1000) and general terrain tile heightmap resolution = 257, then the step from world to heightmap space for xz axes will be (257 / 1000, 257 / 1000). For some calculations the step needs to be calculated by subtracting 1 from the heightmap resolution ((257 - 1) / 1000, (257 - 1) / 1000).

Once the area is transformed to heightmap space it once again is expanded into a (1, 1) grid. This is because an integer area is needed to properly deal with heightmap areas.

Etc.: if general terrain tile size = (1000, 1000, 1000), world space xz manipulation area = (pos (500, 500), size (500,500)). The area will be transformed from world to heightmap space into (pos(128.5, 128.5), size(128.5, 128.5)), which will then be expanded to (pos(128, 128), size(129, 129)). Though the lessened step might have been applied here before the expansion, i don't quite remember.

Once the terrain's heightmap area and the terraformation's heightmap area are calculated, an overlap between the 2 areas is made where the overlapping area is retrieved. The overlap heightmap area is then localized to the terrain. Now having the heightmap area which needs manipulating, effects are applied for heightmap distortion.

3. TheSituation

Joined:
Apr 17, 2014
Posts:
23
Hi @crysicle Thank you so much for your answer! I will study it in more depth later - but the PaintContext may end up being perfect! Not entirely sure yet, but it looks like it will allow me at runtime to adjust the height of a specific set of points on my terrain - which would be just fabulous.

It kind of looks like I have been trying to implement this on my own actually. Can you confirm my understanding of the PaintContext is right?

I will also read over your, how to do it on your own approach. Out of interest considering PaintContext exists, why did you decide to go it on your own with this approach?

4. crysicle

Joined:
Oct 24, 2018
Posts:
95
The PaintContext fills a 2D texture with heightmap data from a terrain and it's nearby terrains. It then allows you to distort the gathered heightmap PaintContext.destinationRenderTexture in a compute shader. Once you're done, just copy back the manipulated area with TerrainPaintUtility.EndPaintHeightmap. Note that using a compute shader has hardware requirements.

As for creating my own terrain engine, it was due to Unity's API being greatly limited in how efficiently it allows you to manipulate heightmaps in realtime. There's really only 2 ways to do it in Unity, either go through SetHeights(), which directly updates everything, and is really slow as it performs everything on the main core as well as has very few performance optimizations for all the stuff it syncs, such as physics, detail/tree positions, LOD and the heightmap texture on the GPU. Or go through GPU copy functions after which you call Terrain.SyncHeightmap(). This will unfortunately stall your CPU until GPU reads back the data which can go anywhere from 0.3ms to 15ms. There's no async method to do this. Neither are ideal for realtime manipulation.

I'm not sure about the level at which the terrain needs to be deformed for your needs, but if it's really high like mine, 1000x1000 heightmap area per fixedUpdate, Unity wont be able to handle such loads without crushing your FPS.

RatherGood and TheSituation like this.
5. TheSituation

Joined:
Apr 17, 2014
Posts:
23
Is there any simple examples of the Paint Context doing this that you can link me to? Your explanation is superb, but it leaves me with numbers of questions and I am not 100% sure what my step by step would be to do a little example where you cna lower the height of the terrain around where you cast a Ray and its intersection with the terrain directly forward from a camera.

6. crysicle

Joined:
Oct 24, 2018
Posts:
95
The only code i remember with the PaintContext is from @jister.

There might be some tests with the PaintContext inside Terrain Tools package which can be downloaded inside Unity via Window -> Package Manager -> All packages -> Terrain Tools. And accessed via Project Folder -> Library -> PackageCache -> Terrain Tools folder.

I'd ask about this more on discord. It's a lot faster than getting responses in forums, which sometimes is days to months apart.

Official Unity discord - https://discord.gg/9YuGb6
Game Dev League - https://discord.gg/gamedev
Unity Developer Community - https://discord.gg/GqQvw5

wyattt_ likes this.
7. TheSituation

Joined:
Apr 17, 2014
Posts:
23
Hi @crysicle , I took a look at the samples and found nothing that seemed to show what you can do at runtime with this:

Going to try the code in that post you linked, see if I can make it do something!

8. TheSituation

Joined:
Apr 17, 2014
Posts:
23
Another update @crysicle , I tried to rebuild what was in your linked forum post and shared it at this repo https://github.com/renegadeandy/gardencopy

I was reverse engineering the types of the objects etc as not all the code was shared on that post, and one in particular which stumps me is the variable 'brush' referenced in this line: https://github.com/renegadeandy/gardencopy/blob/master/Assets/MouseLook.cs#L118

I also don't have his 'brushTexture' which I don't have in this project, and I don't know what it should be.

The result of running this semi broken project is the following:

Which doesn't seem to be making the holes where the collision takes place, so I am not sure what is happening here.

You should be able to (hoping you are willing to) checkout the project, and see what I have screwed up / am missing. I am trying to make the simplest example I can of adjusting terrain height using this forum post as a starting point.

Many thanks once again for all extended help and support you are sharing.

Note - I have joined the discord but am not getting much of a response atm.

9. TheSituation

Joined:
Apr 17, 2014
Posts:
23
Apologies for bumping but I would love to get this closed off?

10. Delmar001

Joined:
Jul 12, 2016
Posts:
1

I think I am having a similar issue. After going around and adjusting heights and smoothing things over, I still have these very small gaps in between my terrains and I have not a clue how to eliminate these. Does this have to do with the floating-point accuracy issue mentioned above?

11. TheSituation

Joined:
Apr 17, 2014
Posts:
23
Hi @Delmar001, are you changing the terrain at runtime? I am actually very happy you are having this issue - as it makes 2 of us, and we can work together to figure it out...

12. TheSituation

Joined:
Apr 17, 2014
Posts:
23
Bringing this back to the discussion as I have had a break from dev and am looking at it again.

Can anyone advise? @crysicle had suggested another thread in an earlier post on this topic and I tried creating an example from snippets of code which was using the PaintContext but my example is incomplete due to lack of experience in this area - if anyone can try and complete it, it may solve my problems : https://github.com/renegadeandy/gardencopy

Alternatively, there may be a better approach I am not aware of.

@Delmar001 how did you get on?

Last edited: Aug 5, 2020
13. TheSituation

Joined:
Apr 17, 2014
Posts:
23
I made a video of my original project which showcases the problem in motion - note you can see gaps in the terrain when I 'dig' at the corner of 4 tiles :

RatherGood likes this.