Figuring out how to render the roads correctly in OpenSAGE turned out to be a little more challenging than expected. This is the fifth post in a series describing the journey. In the last post, we rendered road segments for the first time, but they are still disconnected and overlap each other:

Disconnected road segments

Ignoring the different curve types for now, let's consider angled connections. We have two overlapping rectangles (one for each edge) that have an intersection point at the common node position (End1 - Start2). We want to modify the two corner points B1 and A2 so that they share the same position X (and then do the same for the other corner).

Intersection point X of two road segments

So, how can we calculate X? With the graph we created in part 3, we can easily find the neighbor segment and its start and end position. For both the current and the neighbor segment, we can calculate the direction vector's normal (directionNormal and neighborDirectionNormal). We can calculate the average of these two vectors, normalize it and get a direction vector toCornerDirection pointing from the node position towards X.

Direction vectors

Now we need to multiply this direction vector with the correct length to get to X. Considering the right-angled triangle End1 - Start2, X, B1, we know that

cos α = (roadWidth / 2) / toCornerLength .

cos α can be calculated without actually knowing the angle by calculating the dot product of directionNormal and toCornerDirection.

This gives us

toCornerLength = (roadWidth / 2) / Vector3.Dot(directionNormal, toCornerDirection)

and finally the vector to X:

toCorner = toCornerDirection * toCornerLength

Cosine

Now we can calculate the new shared corner positions B1 - A2 and C1 - D2 by adding and subtracting the vector to the shared node position.

New corner positions

As we saw in the last post, it's also important to adjust the U texture coordinate accordingly to avoid distortions.

Connected road segments

Next time, we'll take a first look at crossings.