#How can I make procedural terrain deform around a Path3D node?

1 messages · Page 1 of 1 (latest)

slow musk
#

bump

elfin lake
#

there are functions in Geometry2D and Geometry3D that can often help with mesh proc gen, tho I don't see anything in there that could help this specific issue https://docs.godotengine.org/en/stable/classes/class_geometry2d.html

There might be a better/simpler way, but the approach I can think of is in two parts;

Step 1: Create a 2d grid with an array of path indexes (Dictionary[Vector2i, Array[int]]). The keys would be a world coordinate (i.e. if your mesh has a point every 0.1m, then a value of Vector3(2.0, 0.0, 0.8) would become Vector2i(20, 8) ), and the values would be the nearby point indices.

So fill the dictionary by walking along the path3d curve (using the baked_points), convert the path point to a grid coordinate, and do the same for all nearby coordinates (up to the distance you care about) and then insert the index of the point into the grid value array.

i.e. so a point at Vector3(2.0, 0.0, 0.8) with index 0, and another point at Vector3(2.1, 0.0, 0.9), index 1, would produce the following grid;

{
   Vector2i(19,  7) = [0],
   Vector2i(19,  8) = [0],
   Vector2i(19,  9) = [0],
   Vector2i(20,  7) = [0],
   Vector2i(20,  8) = [0, 1],
   Vector2i(20,  9) = [0, 1],
   Vector2i(20, 10) = [1],
   Vector2i(21,  7) = [0],
   Vector2i(21,  8) = [0, 1],
   Vector2i(21,  9) = [0, 1],
   Vector2i(21, 10) = [1],
   Vector2i(22,  8) = [1],
   Vector2i(22,  9) = [1],
   Vector2i(22, 10) = [1],
}

Step 2: Now you can iterate over the mesh, and convert each mesh vertex point into the same grid coordinate. If that mesh point has a grid coordinate inside your new generated path grid, then use the indices to get all the exact baked_point position. Now take difference from the path height mesh and the vertex height, and divide by the distance from the vertex to the path point. Do that for all indices in your array to find the greatest absolute value, and then add that value to the vertex height

#

Optionally..
You could optionally skip over step 1 if you check all points in the path for every point in your mesh... but if you're dealing with terrains and large paths, that's going to be crazy slow.

It might be the better place to start if you're uncomfortable with spatial dictionaries from Step 1

#

Optionally (2)...
You could also store the whole point positions (instead of indexes) in your spatial grid. This would make the grid bigger, but you wouldn't need to re-fetch the baked-point positions when you do Step 2.

i.e. your grid would look more like

{
   ...
   Vector2i(20,  7) = [Vector3(2.0, 0.0, 0.8)],
   Vector2i(20,  8) = [Vector3(2.0, 0.0, 0.8), Vector3(2.1, 0.0, 0.9)],
   ...
}
slow musk
#

"You could optionally skip over step 1 if you check all points in the path for every point in your mesh... but if you're dealing with terrains and large paths, that's going to be crazy slow." Let's say I was going to do this - just to get the code working, I'm not sure what you mean by "convert each mesh vertex point into the same grid coordinate"

elfin lake
#

yeah, I was just thinking about how over complicated "step 1" is. it's true that it's the way I'd do it, but I forgot how much extra unnecessary work it is.

#

If we're ignoring step 1, then you can ignore this too; "convert each mesh vertex point into the same grid coordinate"

#

instead, you just want to;
a) loop over all the mesh vertexes
b) for each vertex, loop over all the baked path points.
c) define some threshold distance that's "too far to matter", and then find the nearest path point within that threshold distance
now there are two options:
d.1) Non-smooth terrain: Set the vertex y value to be your path point y value. This is a good place to start from - to make sure everything is working so far
d.2) Smoothed terrain: Take the difference between the vertex y value and the path point y value, then divide that by the distance between the two path point and the vertex, minus your threshold distance. Add the result to your vertex y value

slow musk
#

The weird thing is, I was doing almost exactly a to d in one of my first attempts, but it never changed the height of the terrain. Is getting the distance between the points as simple as subtracting the baked point and the vertex?

#

I'm going to revisit this then provide a better code sample, if you'd like to still help me out

elfin lake
#

yeah, distance between points is (point_a - point_b).length() or I think there's even a point_a.distance_to(point_b)

#

feel free to post what you've got and I'll take a look. probably not tonight though - I'm about to sign off - but I can take a look tomorrow morning-ish if you have something ready by then

slow musk
#

absolutely! thanks for your help mate

slow musk
#
            if path:
                var curve = path.curve
                var points = curve.get_baked_points()
                
                for point in points:
                    var dist = (point - vertex).length()

                    if dist < 5.0:
                        vertex.y = point.y
                    else:
                        vertex.y = get_height(vertex.x, vertex.z)

            else:
                vertex.y = get_height(vertex.x, vertex.z)```
#

ok, so i'm doing this and the height just isnt going up to the point

#

i think it's because each point.y is always 0 regardless of where the curve is

slow musk
#

(or if the point isnt on exactly y = 0, its a very small decimal)

slow musk
#

ok - i've had a think about it and maybe that IS the correct y value for the height, but setting the height to 0 in this terrain generation script obviously doesn't do anything, as opposed to normal vertices that juts get ``` return noise.get_noise_2d(x, y) * height

#

but I can't multiple zero by height, so I don't really understand how to get it to meet the point

elfin lake
#

it sounds like you might have moved the whole path upwards vertically? you might need to do something like

vertex.y = point.y + path.global_position.y
slow musk
#

weird, the whole path hasn't been moved at all, i moved the terrain down so that i could see more clearly if the vertices would "reach" up

#

i will try that once i get home

#

although I'm certain i set vertex.y just to the path position y and it still didnt work/show anything visually

elfin lake
#

If that still doesn’t work, print out the point.y value before you set it to the vertex. I can’t think of a reason why it wouldn’t work unless point.y is 0 for some reason.

#

Or maybe they’re at different scales? Is point.y actually 0.1, but the get_height function is much higher?

#

Also, if you’ve moved the terrain, then you might have to do something to compensate, like;

vertex.y = point.y + path.global_position.y - global_position.y
slow musk
#

it might be the distance threshold I'm using

slow musk
#

I have great confusion

#

I am so confused, it feels as though distance is completely arbitrary to which vertex goes up

elfin lake
#

have you scaled or moved either the mesh object or the path?

slow musk
#

well yeah, the path is moved up as u can see

elfin lake
#

I mean horizontally. and have you scaled either?

slow musk
#

no

#

i don't think this is a problem im gonna be able to solve

#

maybe theres another way to generate terrain around a road but

elfin lake
#

what does your code look like now?

slow musk
#

same as before, just using ```vertex.y = point.y + path.global_position.y

elfin lake
#

there’s lots you could do to start debugging this - eg, instead of using the path, you could set all vertexes a certain distance away from a random point to a y = 1. that would make sure your distance check and vertex override is working

slow musk
#

the thing that annoys me the most is that theres a reddit post with a solution from 3 years ago with no code 😭

elfin lake
#

you could print out the vertexes that are being overridden (the nearest path point and the vertex pos) - to see if you can recognize a pattern with the funky vertexes

elfin lake
#

if not, you might find it easier to modify the terrain with blender sculpting

#

then just import the mesh

slow musk
#

the thing is, my plan was to make a custom racing track generator with path3ds (i have code that works for this) and then be able to generate the terrain around it, so users cna create their own tracks

elfin lake
#

sounds like a great idea - I’m not saying it’s not feasible, it’s absolutely doable, but it might take a bit of work to get there

#

give those debugging options a try to see what’s going wrong

slow musk
#

the reddit post said this btw:

#

To summarize, to create the mesh, I use the SurfaceTool class, the vertices cannot be modified after their creation with this class.
If a vertex is close to a point in the path (for me the max distance is five), the y position of the vertex gonna be changed.
The closer the vertex distance is to the path, the more the y position of the vertex will be equal to the y position of the path point

elfin lake
#

yeah, that’s pretty much what I said above

dusty fable
#

Hi there ! Sorry if I'm late to the party.
I think things need to be solved at heightmap computation. Let say that your heights are stored in a PackedFloat32Array and the terrain is of size : Vector2i. You have a way to compute the base heights, let's assume you're using a Noise resource we'll refer as variable noise. Then there's the path/track, a Path3D node we'll refer as variable path. For simplicity sake, we'll assume that each point of the heightmap is located at Vector3(x, v, y) where v = noise.get_noise_2d(x, y), x and y being in range of size. To be taken into account, the path will venture in that range too.
To have smoother values, let's add a path_min_distance which is the minimum distance to the path where the terrain will be at the nearest curve point Y level, and path_max_distance which is the maximum distance to the path where the terrain will not take the path into account.
Oh, and let's add a height variable to stretch the noise value from [-1., 1.] to [-height, height]. The heights will be built as follow:

var heights := PackedFloat32Array()
heights.resize(size.x * size.y)
var idx : int = 0
for y : int in range(size.y):
    for x : int in range(size.x):
        var pos : Vector2 = Vector2(x, y)
        var v : float = max(sea_level, noise.get_noise_2d(x, y)) * height
        var t : float = path.curve.get_closest_offset(Vector3(pos.x, v, pos.y))
        var curve_pos : Vector3 = path.curve.sample_baked(t, true)
        var curve_flat : Vector2 = Vector2(curve_pos.x, curve_pos.z)
        var dist : float = curve_flat.distance_to(pos)
        var dt : float = smoothstep(path_min_distance, path_max_distance, dist)
        var new_elevation : float = (curve_pos.y * (1. - dt)) + (v * dt)
        heights[idx] = new_elevation
        idx += 1
#

Then, you're free to use heights as you see fit to build the geometry. For example, I end with that :

#

The height of the path is taken into account. The terrain is 128x128.

#

min distance to path is 4, max is 10.

#

the height of the terrain ranges from 8 to -8. As an example, i've took a path that starts at an elevation of 8 and end at an elevation of -8.

#

I guess you'll need a terrain with a bigger resolution. The result should be similar with the appropriate values.

#

The main idea is, for each point of your terrain, to find the closest point to the path (using the get_closest_offset of Curve3D) compute a distance on the 2D plane Y (using (X,Z) coordinates) and perform a mix between the curve elevation and the terrain elevation. Here, i've choose a smoothstep 'cause it looks good enough.

#

There's some artifacts tho, but i think it's due to the small resolution of the terrain.

dusty fable
#

I think i know where the carving discrepancy comes from. I'll do something about it after my 9-to-5.

dusty fable
#

@slow musk Does it work for you ?

slow musk
#

I don't quite understand anything thats actually going on here or how to implement it into the terrain generation but I will try

#

After my work tommorow I'll take a look

#

Ok I understand it a bit more clearly now, I will try tommorow

dusty fable
# slow musk Was this a resolution issue?

Partially. If i project the Curve3D on the Y plane (so, setting Y to 0. for all points), the computation of the closest offset from a terrain point is "better". Then, there's the curve itself.

#

I guess i'm not good at designing Curve3D 😅

#

if the curve is "flat" (on the same level), there's no discrepancy.

slow musk
#

I'll be able to do some testing in about 4 or 5 hours

#

For the scene setup, is both your terrain mesh and parh3d set at 0,0,0?

dusty fable
#

My terrain is not centered on the origin. It spreads over +X and +Z with height being between [-h, +h].

slow musk
#

I'm home now, gonna try and figure something out

#

What values did you use for path min and max distance?

dusty fable
#

For the earlier screenshots, I've used min=5 and max=10 for a terrain of 128x128.

#

I guess your terrain will be a bit larger.

slow musk
#

probably

#

Well it kinda works

dusty fable
#

I can make a test on a bigger terrain with a closed path, but in 12 hours from now.

slow musk
#

I'm not quite sure I understand what these min and max values actually represent

dusty fable
#

If a point of the terrain is below the min distance to the path, the elevation will be the same as the path. If a point is above the max distance from the path, the original terrain remains untouched. Between the two distances, the elevation is interpolated between terrain elevation and road elevation.

#

Just to smooth transition between terrain and road.

slow musk
#

I see

#

With the terrain script i am using, normals get added using a function to get height (which would return just the noise, ignoring any influence applied) is that gonna be a problem

slow musk
#

nvm I just have to reconfigure the script

slow musk
#

Thank you both for your help, I have everything working in a way that I like now. Going to consider this sovled!

slow musk
#

@dusty fable ok so I tried to get the terrain thing you used to work at all but it wont generate terrain even from a copy paste

#

what is going on

#

nvm

dusty fable
#

You have to set some resources like noise.

#

Normally, I've described quick steps in the TL;DR foldable section at the beginning.

slow musk
#

Ok I have everything working I just wonder if this terrain can have added collisin

dusty fable
#

The terrain itself is just a fast alternative for rendering to the godot traditional Heightmap nodes. There's no collision as-is. But it can be computed using the data available (perhaps using HeightMapShape3D )

#

However, this would require to supersample the generated heightmap with extra points obtained from the bezier interpolation. If i have enough time for this, i'll make some code about it.