#How can I make procedural terrain deform around a Path3D node?
1 messages · Page 1 of 1 (latest)
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)],
...
}
"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"
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
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
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
absolutely! thanks for your help mate
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
yeah I don't know how to get around this? obviously something to do with global positions or somehing but if I use to_global it doesn't help?
(or if the point isnt on exactly y = 0, its a very small decimal)
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
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
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
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
it might be the distance threshold I'm using
I have great confusion
I am so confused, it feels as though distance is completely arbitrary to which vertex goes up
have you scaled or moved either the mesh object or the path?
well yeah, the path is moved up as u can see
I mean horizontally. and have you scaled either?
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
what does your code look like now?
same as before, just using ```vertex.y = point.y + path.global_position.y
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
the thing that annoys me the most is that theres a reddit post with a solution from 3 years ago with no code 😭
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
I’d say that, if you’re working on proc-gen terrain + path integration, you should expect to have to tweak the code and solution to fit your need, so no solution is likely to solve all your problems without you understanding the code and working out how to adjust it
if not, you might find it easier to modify the terrain with blender sculpting
then just import the mesh
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
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
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
yeah, that’s pretty much what I said above
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.
I think i know where the carving discrepancy comes from. I'll do something about it after my 9-to-5.
@slow musk Does it work for you ?
Oh I didn't see this I apologise, I will look at trying this implementation in the morning and let you know
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
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.
Thankfully I intend to keep all the points on the same level anyway, so it shouldn't be a problem, as long as the terrain itself deforms to it!
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?
My terrain is not centered on the origin. It spreads over +X and +Z with height being between [-h, +h].
I've used this and modified the terrain generation part with the piece of code I've write earlier https://thegodotbarn.com/contributions/snippet/422/small-terrain-generator
I'm home now, gonna try and figure something out
What values did you use for path min and max distance?
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.
I can make a test on a bigger terrain with a closed path, but in 12 hours from now.
I'm not quite sure I understand what these min and max values actually represent
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.
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
nvm I just have to reconfigure the script
Thank you both for your help, I have everything working in a way that I like now. Going to consider this sovled!
@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
You have to set some resources like noise.
Normally, I've described quick steps in the TL;DR foldable section at the beginning.
Ok I have everything working I just wonder if this terrain can have added collisin
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.