Inn the car controller post, you can see I’ve been living the Kenny asset life. I’m a huge fan of being able to focus on prototyping the gameplay.
Moving forward I’d like to be able to make content (race tracks) quickly, so today I’m going to talk about the system I made to transform an arbitrary curve in space into drivable geometry.
Process
I started a curve made with Houdini’s builtin curve SOP, which uses given points as anchors for a Bezier curve (or other curve types). Unfortunately, the curve tool is pretty clunky compared to others, and it might be easier to define elsewhere and bring in as a series of points.
Next I sample points along the curve. Corners with tight curvature need a much higher resolution to get smooth geometry, whereas straights can be a rectangle, so I used the facet SOP to consolidate colinear points. Also I only want the point data, so I chopped out the line geometry.
To transform the curve into a surface, I originally had tried sampling lines an using the sweep SOP, but this twisted the surface uncontrollably when working in all 3 axes. Instead, I now (for each point:
- Get a vector
diffV
along the curve. For a given point i, this can be done by averaging the differences between i-1,i and i,i+1. - Generate a direction vector
crossDir
, orthogonal to the road. I can get this withdiffV
crossed with the road surface’s normal (world up since the road shouldn’t twist) - Place new points at
pos(point_i) +/- crossDif * width
- With the points in place, I can create triangles between every 3 points. I originally did this
manually, but the
poly patch
SOP does this for me.
And it becomes a drivable surface! But I’d like to make this surface form ramps with walls, not just a paper thin surface. So I follow up by extruding downwards, creating a bottom face. I do another pass, projecting this bottom face onto the XZ ground plane.
From here, it’s just some optimizations before exporting. I remove the faces on the ground, and weld close points (mainly to clean up the extruded walls where the road is on the ground already), and create smoothing groups.
Banking
Unfortunately when I tried driving it in game, I had all sorts of problems. Collision detection really struggled with the triangulation, but adding a suspension instead of a box collider fixed most of the jank.
Still, curves that move in 3 axes at once were problems. The car handles poorly on them, usually flying off. And, once triangulated, the road would form “walls” because the inner side would rise more steeply than the outer side (the Y/XZ ratio is greater).
One solution to both is to try to bank the turns based on the tightness of the turn. I tried banking by a function of the Y/XZ ratio (an estimate of curvature), which I smoothed over several points. Unfortunately, I didn’t have a great way to twist the surface, so it ended up just lowering/smoothing out the ramps instead of banking. It cleaned the walls but made for a dull road.
My second approach was limiting the amount of climb, e.g. for every unit of XZ, the road can at most climb some amount on the Y axis. Critically, I measure this value separately on the inner and outer edges, so if the outer edge travels further, it can climb more.
This means each point is a function of the previous point. In Houdini each attribute wrangle can only access the attributes of others points before execution, so an iteration like this requires a single pass over the whole object, tracking points in an array manually iterating.
There are still a few artifacts that can be cleaned up, but you can see the surface banks a lot for the tight turn and the walls have be substantially smoothed.