You may want to look into moving the object along a Bezier curve.
Essentially, whenever the object moves forwards, you calculate it’s angle of rotation using a dictionary of points that define the tracks, as effbeecee suggested. Each track segment would consist of 3 points. A starting point A, and a finishing point B, and then a third point C, that is used for interpolation.
The mathematical formula for a quadratic Bezier curve is as follows,
P = (1 - t)^2 * A + 2 * (1 - t) * t * C + t^2 * B
Where t is the “time”, being the distance that has been travelled along the curve represented as a value between 0 and 1. (Hence why it cannot be used directly to store the progress of your train, as it varies depending on the length of the segment)
Implementing this formula should give you a point on the curve, that can be used to position your train. And together with the difference in the position of the train in last call, you will be able to calculate the angle at which it should be rotated to counter for the positional change.
For example, the first part of the tracks make a 90 degree turn to the left. Then the points A, B and C would be:
local track1 = {
A = Vector3.new(0, 0, 0),
C = Vector3.new(10, 0, 0),
B = Vector3.new(10, 0, 10)
}
Then after reaching B, when t = 1, you would move on to the next set of tracks. If the track should be straight, then the point B should just be on the straight line between A and B, or you may use another formula.
Playing around with this may help you understand the concept of Bezier curves,
https://samgentle.com/playgrounds/bezier