How To: Move Objects on a 3D Path |
This topic will show how to define a 3-dimensional path and how to move along the path with a defined speed.
This topic contains the following sections:
A 3-dimensional path is a piecewise curve that is defined by several key points. Key points define the path positions for a given path parameter. Between the key points the path positions are interpolated using spline curves. Paths can be used, for example, to define a way that an object should follow when it is animated.
Let's create a 3-dimensional path using the class Path3F:
Path3F path = new Path3F(); path.Add(new Path3FKey { Parameter = 0, Point = new Vector3F(5, 20, 0), Interpolation = SplineInterpolation.CatmullRom }); path.Add(new Path3FKey { Parameter = 1, Point = new Vector3F(10, 150, 10), Interpolation = SplineInterpolation.CatmullRom }); path.Add(new Path3FKey { Parameter = 2, Point = new Vector3F(200, 200, 20), Interpolation = SplineInterpolation.CatmullRom }); path.Add(new Path3FKey { Parameter = 3, Point = new Vector3F(300, 250, 30), Interpolation = SplineInterpolation.CatmullRom }); path.Add(new Path3FKey { Parameter = 4, Point = new Vector3F(400, 112, 40), Interpolation = SplineInterpolation.CatmullRom }); path.Add(new Path3FKey { Parameter = 5, Point = new Vector3F(300, 52, 20), Interpolation = SplineInterpolation.CatmullRom }); path.Add(new Path3FKey { Parameter = 6, Point = new Vector3F(5, 20, 0), Interpolation = SplineInterpolation.CatmullRom }); path.SmoothEnds = true;
The path is created and several path key are added. Each path key is associated with a path parameter. The path key defines the position for a path parameter and it defines the spline type that is used to interpolate positions between this path key and the subsequent path key.
When a path is used the path parameter is the input and a position on the path is the desired output. In this example, the path parameter starts at 0 and ends at 6. The path keys define the path position for the parameter values 0, 1, 2, 3, 4, 5 and 6. Between this parameter values the path positions are interpolated with the defined spline types. For example, the path position for parameter 0.5 is somewhere between (5, 20, 0) and (10, 150, 10). To get the path position we can call
Vector3F p0 = path.GetPoint(0f); // p0 == (5, 20, 0) Vector3F p1 = path.GetPoint(0.5f); // p1 is between (5, 20, 0) and (10, 150, 10)
In this example, the last path key is identical to the first path key to create a closed path. path.SmoothEnds is set, to make sure that the path is smooth where the first and the last path key meet.
Next, we define how the path should behave if the path parameter is not in the range [0, 6]:
path.PreLoop = CurveLoopType.Cycle; path.PostLoop = CurveLoopType.Cycle;
This creates a smooth "looping" behavior. For example, the path position for parameter 6.5 is equal to the path position for parameter 0.5.
The path that we have defined can be used to translate an object. We can start with parameter 0 at position (5, 20, 0) and increase the parameter to get the next positions on the path. The problem with this approach is that the path parameter is not linearly proportional to the distance along the path. If an object is animated by using this parameter, it will move with an undefined speed.
It would be easier if the parameter for each path key is identical to the distance of the path keys from the beginning of the path. This can be achieved by calling:
path.ParameterizeByLength(10, 0.01f);
This method changes the path parameters. The length parameterization is an iterative process and the method arguments (10, 0.01f) define the desired accuracy (see ParameterizeByLength(Int32, Single) ). Before the call the path parameters at the path keys where 0, 1, 2, 3, 4, 5, 6. After the call the path parameters at the path keys are identical to the length of the path from the start to the path key. For example, the parameter of the first path key is now 0 (same as before) and the parameter at the last path key is now equal to the length of the whole path.
Now, the path parameter at the path keys is equal to the path length. But between the path keys the path parameter varies in a way that is not linearly proportional to the path length - because of the nature of the Catmull-Rom splines which are used in this example. To get the path position for a given path length, for example 100, we can call
Vector3F p = path.GetPoint(path.GetParameterFromLength(100, 10, 0.01f));
GetParameterFromLength(Single, Int32, Single) computes the approximate path parameter that gives us the path position where the path is 100 long. See GetParameterFromLength(Single, Int32, Single) for more details.
The path can be used to move an object along the path. Because the path was parameterized by length, we can control the speed of the movement on the path.
To get the start position on the path call:
float parameter = 0; Vector3F startPosition = path.GetPoint(0);
To get the next position for a given speed:
parameter += speed * time; Vector3F nextPosition = path.GetPoint(path.GetParameterFromLength(parameter, 10, 0.01f));
time is the time difference since the last path position was computed, for example, 1.0f/60.0f if the object should be animated with 60 frames per second.
GetParameterFromLength(Single, Int32, Single) is a costly operation that should not be called too often. To improve performance it is recommended to create a second approximate path from the first path. The keys of the approximate path are created by sampling the exact path at constant intervals:
float pathLength = path[path.Count - 1].Parameter; // Length of the exact path. Path3F approximatePath = new Path3F() { SmoothEnds = true, PreLoop = CurveLoopType.Cycle, PostLoop = CurveLoopType.Cycle }; const int NumberOfSamples = 100; for (int i = 0; i <= NumberOfSamples; i++) { float distance = pathLength * i / NumberOfSamples; float parameter = path.GetParameterFromLength(distance, 10, 0.01f); Vector3F point = path.GetPoint(parameter); approximatePath.Add(new Path3FKey { Parameter = distance, Point = point, Interpolation = SplineInterpolation.CatmullRom }); }
Moving along this path with Vector3F nextPosition = approximatePath.GetPoint(parameter); is faster and the difference to the exact path is not noticeable if the number of samples is high enough.
GetTangent(Single) defines the movement direction for a given path parameter. If an object that is moving along the path is rotated so that it "looks" into the tangent direction, then the orientation of the object follows the path smoothly.