 Howdy guys, Andy Pixel 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, late 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 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. And so, yeah, let's take a look and see how to hook this whole system up. All right, so let's start our slow production by dropping down a geo note here inside of Fidini. 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. And here you can go and offset this guy, 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 space bar two on the keyboard to go into my top view. And I'm going to select the curve node here and just template my height field noise. And then with the curve node selected, I'm going to put my cursor over the scene view and hit enter to enter into its edit mode. And let's just go and create some sort of walking 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 one. And let's also go and turn on subdivision curves just so we get a nice smooth curve out of that. Let's hit space bar one on the keyboard to go back into our 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 resampled 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'll do is put 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 high 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 to get an idea of the slope of the terrain is we need to snap this curve now to the terrain 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. And the race op needs to use a vector, not the normal. So let's do this. 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 rain node. So I do an alt left click to copy it. Then on this guy, I just switch the normal direction to negative one. So there you go. So then I have the curve snap to the terrain. So that's how you can do it with the built-in SOP nodes there with the rain node. I'm going to call this snap to terrain. 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. Just like if you were to go and create a volume itself right here. So that's 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 min value and volume 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 what 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 zero and at p dot 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 zero is going to be, or y is going to be zero 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 minimax 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 name to 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 pot. So where do we want to sample inside of that volume? And that'll give us the height. All 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 pump it into the Y position for our points. So I'm just going to say height. You can see that we automatically sent. And like I said before, this is much more efficient and faster when you're working with Udini engine stuff. It's also here inside of Udini and slot 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 preset. So you can see I have my volume might 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 this point. So I'm going to actually set this resample 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 terrain. So step one is snap to terrain. 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 try to figure out how to find the slope of the curve. Right. So we want to find, you know, at 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 where 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 ad node. And then I'm going to split. So I 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, 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 zero, zero one for normal direction in a 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 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 to do that, I am simply going to create a new angle variable. And we're going to 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 three. 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 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 rot. 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 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 procedural modeling adventures, if you will, you'll just be given two vectors. You need to find the angle between those two. 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 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 or angle 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 how do we find the angle? Well, we know, let's actually start out with this. So we know that the dot product, so if I do the dot product of at n and 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 the arc cosine, like so. Now this will return 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 the angle, you can see, let's go and set it to something like 65. We'll 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 start working on our slope reduction. So now that we know a little bit more about how to find slope on geometry in general on points here and like that, stuff like that, let's go and drop down a wrangle node 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 what we have to do first is we need to create our reference normal. So currently on our resample node, we actually forgot to produce the 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. 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 on our last example, we're going 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 say normalize and normalize flat norm. There we go. All right, cool. So now we have two vectors that we can compare to find the angle. All 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 going to do the art cosine. So a class of the dot product. I can type today. So I'm going to do the dot product of 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 000. Just so we can visualize our slope. You can use a visualize note as well. But since we're here, I'm just going to do hit all inside the same box note here. So are the wringled note. 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 one zero zero. There we go. Let's turn it on. And let's take a look here. All right. So it's going to 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 I wanted to show you guys. One of them is just going to be kind of like a brute force approach. And the second 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 to blur it out. So I'm going to do an attribute blur. Stop 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 node 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 nay count. And we're just going to get the neighbor count. I'll show 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're equal to one, then I'm going to set you on a group called ends like so. All right. So if I look at my groups and attribute list, and I go to points, you can see I have two points at the end there that are now grouped. 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 all the points here except for the ends again. And we want to create this weight attribute. So this weight attribute basically takes a zero to one value. So it's going to blur it more where it's one. So we can use this color attribute here. So I can see at 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. 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 polyframe. 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 take the x and z value from the original curve and pump in our new y value. And that gives us our final. So those are our final slope for the final reduced slope. All right. So all we need to do is get the position of the other point. So other pause is equal to points, the incoming geometry from the second input and P and at PT num. And this works because they have the same point numbers and they're in the same order. So they're ordered their number at the same. And then finally, I just want to say at P dot y is equal to other pause dot y. So now you've got the original curve on the x and z, but we have a 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 bruised 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 would 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 step two. Basic approach. Go. Let's color this guy. And then let's move on now and do the self-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 looking at the self-solver approach. So the first thing we want to do, every single iteration 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. We can always actually already am doing all the visualization. Super cool. All right. So then what we need to do is we need to modify the 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 at your wrangle and let's call this modify neighbor height. So there we go. And let's jump inside this guy and start typing some Bex code for this. So the first things we need is our max slope. So it's creating 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 height reduction variable. And I'm going to make another float channel for this. We'll call this the same 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 salt 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 0. And then finally I want to say if 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 to 11. So if your point number is less than 211, then 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 have defined. So if our slope is greater than that, let's get the neighboring point number. So I'm going to say nay is equal to at PT num 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 one, that means I'm going to get point one because that's the point that we want to modify. 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 attributes 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 zero and we want to get the position of this guy and the reason why we're doing that is 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 than we need to move up. But if you are greater than that max slope threshold and you're higher than 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.y is less than napause.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 let's just copy 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 napause.y minus equals our height reduction like so. So now we're moving the neighboring pause 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 want 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 so. And we want to do at rib. And then finally we're going to say the weight is equal to one. We're going to use that particular attribute in a attribute blur node. All right. So with this done we can actually watch this work here. So I'm going to just do that and 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 visualizer 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. There 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 it 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 node and you put it onto 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 pods here and that is equal to 0.1 and we want to get the p attribute and at pt num. And we just want to set the at py on the original curve there to the other pods.ly. And there we go. So then we drop down an all node here just to kind of make it official. Let's call this out and let's go back here and watch this work its magic. Actually we need the points on so we can see something. Or you can go and actually let's do this. Let's do a polyframe at the end here. Do some sweep geometry. We'll put in the flow normal and then we'll put in our curve directions. There we go. Then we'll do a sweep node. It just ensures that you get proper rotations for your sweep node. Set this to a ribbon and we'll make this something like 8. Maybe even bigger. Let's do 15. There we go. Let's reverse it. Cool. So now we got some geometry we can look at. So all these red areas are 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 slope. Alright 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.