You must find the grind segment that's closest to the player.
The first thing you need is a function to find the closest point on a segment as an interpolation value of 2 points we'll call i
.
// return interpolant value "between" segment_point1 (0.0f) and segment_point2 (1.0f)
// values go outside this range when past the end of a segment
float ClosestPointOnLine(Vector3 player, Vector3 segment_point1, Vector3 segment_point2)
{
Vector3 segment_vector = (segment_point2 - segment_point1);
float length_squared = DotProduct(segment_vector, segment_vector);
return DotProduct(player - segment_point1, segment_vector) / length_squared;
}
Note: You may want to check if length_squared is zero but we're assuming your level does not have any zero-length segments.
If the value returned is < 0 the player is before segment_point1
, if > 1 then the player is past segment_point2
.
You can then find the exact point on the segment by doing Lerp(segment_point1, segment_point2, i);
where i
is the returned value of the above function (in red on the image).
But what we really want is the point on the segment clamped between 0 and 1 (in yellow), this is why the above function returns an interpolation value and not an actual point.
float i = ClosestPointOnLine(player, segment[s].first, segment[s].second);
Vector3 point_on_segment = Lerp(segment_point1, segment_point2, Clamp(i, 0, 1));
What you then do is calculate the distance between the point on the segment and the player for every segment (grinding edge) in the area and find the closest one.
Then if the closest distance is less than a grind distance threshold, you can snap to that edge.

In this image, we'd snap to the B segment. (In gray is the extended segment A to show how i
is outside of 0..1 for that segment before clamping)
That's it.
Additionally for movement on the rails:
- Use
i
to move the player along that segment rather than in 3D.
- Use the segment length to adjust the speed by dividing it by the segment length (
i
is relative to the segment, not world space).
- Use the
DotProduct
of the segment direction (segment.second-segment.first)
and the player's movement direction to figure out in which direction and speed to move i
on the segment.
- When
i
moves out of the (0..1) range switch to the previous <0 or next >1 grind segment
- snap off and back in 3D if it's the last one (either ends) in the chain
Outside of this question: You need to build grind segment chains to know which segment comes next when grinding and use some way to limit the search for grind segment chains such as an octree or other method to avoid going over the >10000 segments in the entire level.