Video summary
In this tutorial, Handypixel addresses a common challenge in procedural modeling: automatically adjusting game level paths or terrain curves to fit within a maximum slope threshold. The video introduces two distinct methods for achieving this goal, ranging from a quick and dirty approach using attribute blurring to a more advanced and accurate solution utilizing a soft solver. The process begins by setting up a basic scene with a height field volume containing noise to simulate uneven terrain and a curve representing the intended path. The creator demonstrates how to snap this curve to the underlying terrain geometry, offering two techniques: one using built-in ray operations for simplicity and another using a custom VEX script within a Wrangle node to sample volume data directly, which is noted as being more efficient for game engine workflows like Unity.
Once the path is correctly positioned on the terrain, the core logic involves calculating the slope at every point along the curve by comparing the flow normal of the curve against a flat reference vector. The tutorial explains the mathematical concept behind finding the angle between two vectors using dot products and arc cosine functions to convert radians into readable degrees. By visualizing these slopes with color coding, the video identifies areas where the path exceeds the desired limit, such as 15 degrees. To reduce these steep sections, the first method employs a brute-force technique that uses an attribute blur node to smooth out the height values iteratively. This approach effectively lowers high points and raises low points until the slope constraints are met, though it requires manual adjustment of blur iterations and can sometimes drift significantly from the original designer's intended path layout.
The second and more sophisticated method presented involves a soft solver that simulates a physical process to level the terrain dynamically. Within each iteration of the solver, the script checks neighboring points and adjusts their heights incrementally if they violate the slope threshold, moving them up or down by a specific reduction amount per frame. This simulation continues until all slopes fall within the acceptable range, providing a more organic result that respects the original path's general direction while ensuring walkability. To maintain fidelity to the designer's initial layout, the final step combines the solver's output with an attribute blur and a sweep geometry node that preserves the original X and Z coordinates of the curve. This ensures that the final terrain follows the exact footprint of the intended path but with smoothed elevation changes that adhere strictly to the maximum slope requirements, offering a robust solution for procedural level design.
Read the full video transcript
howdy guys handypixel here
and in this next procedural modeling tip
video i wanted to go over
a topic that was brought up by one of my
patrons
and the topic was about how do you go
and level paths for your
game levels or your terrains after a
designer is you know laid down the curve
how can you automatically
you know make sure the path fits
within a max slope threshold so it
doesn't you know go
up to like 80 degrees and so what we're
going to do in this video is we're going
to cover
that particular topic and what we're
going to do let me actually stop this
here what we're going to do is we're
going to cover two methods we're going
to do a really quick and dirty
version that really just involves
blurring um
the path and then we're also going to go
over and
cover how to do it with a soft solver
all right so a little bit more advanced
i would say a little more accurate
um and so yeah let's uh
take a look and see how to hook this
whole system up
all right so let's start out our slow
production by dropping down a geo node
here inside of houdini
and i want to create a curve and then i
want to create a height field
and for this height field i want to also
add some noise just so we have
some sort of terrain to work with
because we want to try to reduce the
slope on a walking path
on a terrain and so we just need some
sort of random terrain
uh in here you can go and offset the sky
use the increment ladder there to move
this thing around to find
some interesting terrain looks pretty
good
i'm not going to be too picky about it
i'm going to hit uh spacebar 2 on the
keyboard
to go into my top view and i'm going to
select the
curve node here and just template my
high field noise
and then with the curve node selected
i'm going to put my cursor over the
scene view and hit enter to edit
enter into its edit mode and let's just
go and create
some sort of locking path i just want to
make sure that it covers a good portion
of the terrain here
beautiful and then i'm going to resample
that and what we're going to do is we're
going to put a point
every meter so i'm going to set the
length on the resample node to 1.
and let's also go and turn on
subdivision curves just so we get a nice
smooth curve out of that let's hit
spacebar one on the keyboard to go back
into
our uh perspective mode here i'm going
to hold down control and then click on
the template flag just so i can display
both the terrain and the
curve at the same time cool all right so
on the curve itself inside of this
resample node i'm going to go to the
tangent attribute and turn it on and i'm
going to actually replace that tangent u
attribute name with the capital n
and what that will do is put the uh the
normal direction or the flow normal of
the curve
onto the curve here so it's kind of hard
to see right now if i go to my guides
by hitting d on the keyboard to bring up
this display option window and scale the
normal to
1.73 there it's still kind of hard to
see let's actually turn off the
height field noise so you can kind of
see it if you go all the way to the end
of the curve
you can see a little normal poking out
there so yeah
there we go all right so what we need to
do first
uh to get an idea of the slope of the
train is we need to snap this curve now
to
the train itself and i'm going to do
this a little bit differently there's a
couple ways you can do this so you can
do this in a wrangle node
or you can use a race op over here
and the raceop uh needs to use a vector
not the normal so let's do this
and uh i just did a control shift to
remove the
expressions and then i put one into the
ray direction then i have to copy this
ring node so i do an alt left click to
copy it
and then on this guy i just switch the
normal direction to negative one so
there you go so now i have
the curve snapped to the terrain so
that's how you do it with the
built-in soft nodes there with the rain
node i'm going to call this snap to
terrain uh the other way we can do it is
and it's a little bit faster especially
when you're working with the houdini
engine is to actually sample the
volume information from our height field
remember that these height fields
are volumes themselves they're just set
to display if you actually
dive inside the height field node you
can see that it's just a volume bop and
it's generating
a volume right just like if you were to
go and create
a volume itself right here right
so that's the same thing basically and
so what we can do is we can actually
pull off the height information if you
go to your geometry spreadsheet for
the terrain itself here if i click on
this guy you can see that it has this
height name and this particular volume
has
a couple intrinsic primitive attributes
on it so if you turn on volume in
value and my volume max value you can
see that we have this
min and max value so negative 96 to 90.
those are those height values on a per
voxel basis inside of the volume
so we can do is we can sample that
information inside of a wrangle node
here so let's actually take a look at
how to do this
all right so the first thing that we
need to do is we need to create a new
sample position and the sample position
is going to be the position of the point
the current point coming into this first
input here
and so i'm going to do we're going to
say
set and i'm going to do at p dot x
and 0 and at p z
all right so the reason why we don't
want to pull in the y position of each
point is because these volumes are
sampled in a 2d space
right so z 0 is going to be or y is
going to be 0 here
all right and then we need to get the
primitive id that we want to sample
and to do that we use the name to prim
vex function and we want to sample the
height
volume that's coming in so remember if
you click on the height field here it's
creating this height
volume and it has a min and max height
value of
negative 96 to 90 there all right so
then finally all we need to do is we
need to
sample the height value from our volume
so we use a volume
sample like so
there you go and so we want to sample
from the geometry coming into the second
input or input one
and we want to utilize the prim id so
that's the volume that we want to get
so what happens is this named friend
function basically looks for
any volume that has a name of height and
returns the primitive id or
zero in this case all right and then we
want to give it the sample path so where
do we want to sample
inside of that volume that'll give us
the height
right so let me actually turn on the
display flag for the curve here so you
can see we're not snapping yet we're
completely flat so now if i were to take
that sampling value
and uh pump it into the y position for
our points
so i'm just going to say height you can
see that we automatically snap
and like i said before this is much more
efficient and faster when you're working
with the unity engine stuff but it's
also here in santa fe needs a lot faster
the rain nodes work too i just tend to
do this more often
and what i do is i once i get the code
written out i go and create a
preset so i say save reset so you can
see i have my volume height sample there
all right so i just wanted to point that
out all right so at this point i'm going
to resample this again now that we're
snapped
this helps to basically make the system
run a little faster because we're
working on less points i'm going to
actually set this
race sample to a length of 10.
i find it good to start with larger
values for their sampling and then
you know you can work your way down to
smaller values
something like this we're going to stick
to 10. all right so that basically
is the first step in getting this
working so we snapped it to the train so
step one is snap to turn
and we learned a little bit of x in
there as well all right so let's move on
to the next one
our next step is to uh try to figure out
how to
um find the slope of the curve right so
we want to find you know what slope are
you you know trying to walk up this
particular path here
and so before we dive into all that i
want to explain
you know how this works for those of you
who might not know so i'm going to call
this angles
example and what i want to do in here
is set up a scenario here we have two
vectors
all right so i'm going to create two
points here let's turn these guys on and
keep them at the center there
using an add node and um then i'm going
to split
so i'd have two points and i can create
two different vectors
so i can work on them so i'm just going
to split on point zero
we're going to set this to points here
and on the first point
coming out of the side of the split node
i am going to put a new normal on it
and to do that i'm just going to do a 0
0 1 for
normal direction in z so if i zoom in on
this point
it's really tiny so now we have a normal
that's pointing in the z
direction here so i'm just going to copy
that and do that for the other point
over here and then drop down a wrangle
node
what i want to do is i want to kind of
reverse engineer the slope
um calculation for you guys so you guys
can see how it works
and so for this to work we need to have
two vectors that have
a different direction right so we're
going to use these two
vectors here to find the angle between
those two and so in order to do that
we need to have two vectors that are
pointing in different directions here so
i'm going to rotate this particular
vector over here
that's on point two all right and so uh
to do that
i'm simply gonna create a new angle
variable and we're gonna make it so we
can change it ourselves so i'm going to
create a new float channel
just call it angle for now and i'm going
to create a new matrix because we want
to rotate this particular vector
and this is going to be matrix 3 and i'm
going to just initialize it to identity
which is
basically no rotation
all right and then i'm going to do a
rotate function
we're going to rotate that rot matrix by
the negative angle
that we provide and we want to rotate it
on the x-axis
all right because our our uh normal here
is pointing in the
z-direction so i want to rotate around
that x-axis there
so that's what we're going to do and
then all we need to do is say at n
times equals a rat
there we go so now we need to go and
expose our
parameter there and let's start to add
some values into this guy
now you're going to see that we're going
to go crazy here and that's just because
our angle value we want it to be in
degrees
but these most of these functions here
in here take
radians so we need to convert those two
radians over here
there we go so now we can go and change
the angle
of our normals pretty cool
so now let's say that all we're given is
two vectors
right so a lot of times this will come
up when your procedure modeling
adventures if you will you'll just be
given two vectors you need to find the
angle between those two
uh we don't know the angle up front
right so i'm going to set this to
something like 50 degrees
and let's just uh go through that
scenario now so how do i go and find
the angle between these two vectors all
right well it's pretty simple actually
let's just drop down an attribute
wrangle node here and i'm going to feed
in my first point so this is the vector
i want to compare against
and this is the vector that i want to
compare with over here all right so i'm
just going to feed that into the first
input
and in order to do this i need to get
the
normal so we're going to call this the
other normal from
the second input there or input one
so we want to get the n attribute from
point zero
there's only one point in this
particular stream
so it's point zero all right so
uh how do we find the angle well
we know let's actually start with this
so uh we know that
the dot product so if i do the dot
product of at
n and other other normal over here
this returns the cosine of the angle
between two vectors
all right and so to find the angle from
the cosine we just need to do the arc
cosine so we say a cos so our cosine
like so now this will turn it in radians
as well so we need to convert it back to
degrees
like so and if i do that
you can see that our angle so let me
actually template this guy here
so our angle is 50 degrees and if i were
to go and change
that angle you can see let's go
and set it to something like uh 65.
when we come back down here you can see
we have 65 degrees
all right so that's the general idea of
finding the slope on a curve and getting
it back into something that's readable
the actual angle all right so let's move
on now
and uh start working on our slope
reduction
so now that we know a little bit more
about uh how to find slope on geometry
in general
on points hearing like that stuff like
that uh let's go and drop down a wrangle
note here
so let's go find the slope of our curve
here so we're going to call this
get slope and inside of this guy
we are going to go and find the slope
of this particular curve here so uh
what we have to do first is we need to
create our reference
uh normal so currently on our resample
node we actually forgot to produce the
uh normal so let's do that so we need
our flow normal first
so that gives us a normal that follows
the direction of the curve uh then we
need to go and create a reference normal
so this reference normal is just going
to be flat
so like just like we saw in our last
example we want to flatten out the
current vector
the current normal vector so this is
going to be equal to at n
then we're going to say flat norm dot y
is equal to zero and then we want to
normalize it just to make sure it's of
unit length
so we'll see normalize and normalize
flat norm
there we go all right cool so now we
have two vectors that we can compare
uh to find the angle right so all we
need to do is say f at
slope just so we can store an attribute
on these particular points that's why
i'm
creating an attribute here that's equal
to degrees
and we're gonna do the arc cosine
so a cos of the dot product
i can type today so we want to do the
dot product
of um at n and our flat norm
there we go cool
and then what i'm going to do is i'm
going to colorize this so i'm going to
say at cd is equal to
0 000 just so we can visualize our slip
knit you can use a visualize node as
well
but since we're here i'm just going to
do it all inside the same
box node here so or the wrangle node all
right so i'm going to say
that we want to clamp this by some sort
of max slope value so we're going to say
max slope
if it is greater than that max slope
we're going to color you red
so 1 0 0.
there we go let's turn it on and let's
take a look here
all right so let's go and create our max
slope value and as i increase the slope
here you can see that we're finding
where the maximum slopes are
all right so we're going to leave this
at let's say something like 15 degrees
so how
do we use this information now to reduce
the overall
slope inside of our curve over here well
it's pretty simple so there's a couple
ways
um i wanted to show you guys one of them
is just going to be kind of like a brute
force approach
and the the second uh approach is going
to be
a soft solver so we're going to you know
actually do a little bit of a simulation
for it
so the brute force approach here is just
a blurred out so i'm going to do an
attribute blur
let's drop this down i'm going to blur
our cd value here
i'm not going to pin my border points
and actually we should
pin the the ends here so let's actually
do this here
on the same wrangle note here so this
one is to
find the slope
and this one is to find the ends
so in order to find the ends i'm going
to do a new variable called naycount
and we're just going to get the neighbor
count i believe i showed this
before in one of the previous videos
here so i just want to get the neighbor
count from
our incoming geometry now if the
neighbor count is equal to one that
means you're an
endpoint this really just works for
curves
so if you go to one then i'm gonna set
you on a group called ends like so
all right so if i look at my groups and
attribute lists and i go to points you
can see
i have two points at the end there that
are now gripped so we can pin those guys
i don't want them to move at all and so
we need to put that in this attribute
blur we need to put that into
this guy and just put an exclamation
mark in front of it to say
not so basically the inverse of
the groups ends so all the other points
all right so what i want to do with this
guy is i want to go and blur
out the color so you can see that's just
going to create a nice little fall off
and you can do
you know however much you want there
all right and then finally we're going
to do an attribute blur
for this guy and we are going to just
blur
out um all the points here except for
the ends again
and we want to create this weight
attribute so this weight attribute
basically takes a 0 to 1 value so it's
going to blur it more where it's one so
we can use this color attribute here so
actually add cd
and let's turn off pin border points now
you'll see as i
smooth this out some more or add more
blurring iterations
what happens is it'll blur the areas
that are more red and
leave the areas that are not sloped
alone
so you can just keep going with this guy
and the cool thing is now
if i were to run this guy again here we
actually need to produce new points here
or new normals i should say
so do a poly frame
so now if i were to visualize my slope
you can see
i have just this guy is
at that value so if i just keep going
you can see we're getting rid of all the
sloped areas
pretty quickly except for that one
larger mountain so there you go
so there is your curve so when you are
finished with that
you're going to notice that you're
pretty far off from your original curve
right and when you're developing like
these sorts of paths for like world
building or
for game levels and stuff like that you
wanted to stick to where the designer
actually placed the path
so all we're really interested in at
this point is the y value of the point
so let's just um take the x and z value
from the original curve
and pump in our new y value and that
gives us our final
so there's our final slope
the final um reduced slope
all right so all we need to do is get
the position of the um other point
so other pause this is equal to points
the incoming geometry from the second
input and
p and that pt num and this works because
they have the same point numbers and
they're in the same order so they're
ordered
they're numbered the same and then
finally i just want to say p
dot y is equal to other pos
dot y there you go so we've got the
original curve on the x and z
but we have our new slope reduction on
the y
pretty cool all right so that's the
first technique
and that's like i said a little bit more
brute force the reason why it's brute
force is because you have to allow the
users then to
mess around with this blurring iteration
value
whereas it'd be nice if we had a
simulation that would run until all the
points were basically
below the max slope but
still valid let's call this a step two
basic approach
there we go now let's color this guy and
then let's move on now and uh do the uh
solver so i'm just going to drop down
the solver here
these things are very handy for all this
let's pump in the
first input there and start taking a
look
okay so let's finish this up by uh
looking at the
soft solver approach so the first thing
we want to do every single
iteration that we that we do on this
particular curve
we need to regenerate the flow normal so
let's go and do that let's drop down a
polyframe and let's switch the tangent
over to the normal
there we go so now we have a vector
that's pointing along the direction of
the curve
and then what we want to do is get our
slope so since we've already done that
let's go and get this guy so i'm just
going to go and copy it and
paste it over here all right so now we
know where our slope is
uh we can always actually i already am
doing all the visualization
super cool all right so then what we
need to do is we need to modify
the uh neighbor point the height of the
neighbor point so
let me talk about this a little bit so
basically if
we're on this particular point right
here okay
and our point in front of us is less
than
the max slope threshold then what we
need to do is we need to move it down or
we
need to move it up right so currently
we're on this point
we want to move this point up and down
so that's the neighbor point so
let's do attribute wrangle
and let's call this modify neighbor
height like so
there we go and let's jump inside this
guy
and start typing some vex code for this
so the first things we need
uh is our max slope so let's create a
new variable called max slope
and a new channel float so we can modify
it
so we say max slope for the actual name
of the attribute
and then i want to also do some sort of
height reduction
amount so we're going to do a height
reduction variable
and i'm going to make another float
channel for this we'll call this the
same
so height reduction
there we go let's expose these guys and
give them some initial value so i'm
going to do something like
15 and we're going to reduce it 0.1
meters every single iteration so we're
going to either move up or down
every iteration through the stop solver
here all right so
the first thing i want to do is create a
weight attribute
here so we can visualize it so we're
going to initialize it to zero
and then finally i'm going to say if um
at point
so we're going to say if you are not
equal to the number of points here so i
don't want to affect the
end point so the very end of the point
here
so if you're less than then the point
number or the point id so
these guys right here so if you're less
than that so the end point is going to
be
211 so if your point number is less than
211 then
can run we can run this operation so i'm
going to then check to see if the slope
so i'm going to say f
at slope is greater than
our max slope threshold that we've
defined
so if our slope is greater than that uh
let's get the
neighboring point number so i'm going to
say nay is equal to
at pt num uh plus one all right so
what we're doing there is let's say
we're on point zero here
so if i'm this point so i'm going to say
point number plus 1 that means i'm going
to get 0.1
because that's the point that we want to
modify uh we're going to do a
vector and i want to find the
neighboring
points position in space so let's do a
point
and we want to get that from zero and we
want to get the p
attribute and we want to get the current
neighbor
point number so basically we're getting
the position of point one here so let's
just pretend we are point 0.0 and we
want to get the position of this guy and
the reason why we're doing that because
i want to see if you are lower or you're
higher
so if your max slope basically is
greater than this max slope threshold
and you're lower then we need to move up
but if you are
greater than that max slope threshold
and you're higher then we need to move
you down
all right so that's what we're doing
right there so i'm going to say if
at p dot y is less than a
poss y then we
are going to move you down
all right so else you're above it
and actually this needs to be a capital
p here so just copy well actually
yeah this will be fine so basically if
you are
greater than then we are going to move
the point up all right so let's do that
now so i'm going to say
napos dot y
minus equals our height reduction
like so so now we're moving the
neighboring paws down a little bit more
and then we want to move it up on this
side right here
all right then finally we need to set
that point so the neighboring points
position using this new
neighbor pause here all right so the
we need to set it on the incoming
geometry we want to set the p attribute
we want to set the neighbor point so
this guy right here that we got right
here
so that's the id that we're passing in
so we're going to set that particular
point
we want to give it the new neighboring
pause and we want to make sure that we
explicitly set it
like that like so and we want to do
attrib
there we go and then finally we're going
to say
the weight is equal to 1. we're going to
use that
particular attribute in a
actually blur node all right so with
this done we can actually watch this
work here
so i'm going to just do that
and uh let's just take a look here we're
going to need the timeline for this guy
so these guys should start moving up
down and stuff like that so yeah there
we go
so you can see that it's leveling it out
appropriately until
all the max thresholds are met
and we can actually visualize this let's
do that really quick
so let's go and drop down a visualize
node here
and we want to visualize that slope
attribute as it's changing over time so
i want to do a marker
let's turn off the update visualizers
just so we get one
and let's go and find an area over here
yeah something like this over here so we
can watch it
so you can see that we have a bunch of
values for slope that are over 15.
let me actually run this in real time
here
you go so now we're going and fixing
that until they're all
below 15 degrees pretty cool
all right one last thing i want to do
here and we'll wrap this guy up
is i want to do that attribute blur and
then i also want to make sure that i
retain the original path that was drawn
so i'm going to do an attribute blur
first
and we are going to
set this to weight so that's why we
created that weight attribute and i'm
just going to do this
like 10 blurring iterations so every
single frame we're going to blur just a
little bit
it really helps just kind of smooth it
out and then finally i want to do
the set white posit i want to retain the
original curve direction so anytime you
use this attribute blur note and you put
it on to the
p attribute there it's going to start to
kind of shrink it and just change the
overall shape
so i want to go and get the other pause
here
and that is equal to point one
and we want to get the p attribute and a
pt num
and we just want to set the at p y on
the original curve there
to the other paws.y
and there we go so let me drop down a
null node here just kind of make it
official
just call this out
and let's go back here and watch this uh
work its magic actually we need the
points on so we can see something
or you can go and actually let's do this
uh let's do a polyframe at the end here
and uh do some sweep geometry
we'll put in the flow normal and then
we'll put in our curve directions
there we go
and then we'll do a sweet note it just
ensures that uh you'll get proper
rotations for your sleep node
set this to a ribbon and we'll make this
something like eight
maybe even bigger let's do 15. there we
go
let's reverse it cool so we got some
geometry we can look at
it's all these red areas where it's
above the 15 degree
threshold so let's watch this guy roll
pretty cool so you can go and see it's
trying to resolve the
total threshold for the the slope
all right so hopefully you guys enjoyed
that those are always fun to put
together
let me know if you guys have any
questions and thanks so much