Moving the Camera on a Curve
Demonstrates how to use the Curve and CurveKey classes to move a camera along the shape of a curve.
Using Curves allows a path to be defined by a small number of control points with the Curves calculating the points on the path between the control points.
The Complete Sample
The code in the topic shows you the technique. You can download a complete code sample for this topic, including full source code and any additional supporting files required by the sample.
Download ScriptedCamera_Sample.zip.
Scripting the Camera to Follow a Curve
To script camera movement
Create an instance of the Curve class for each component being scripted.
In this case, you need two sets of three curves. One is for each of the x, y, and z components of the camera's position, and the other is for the position at which the camera is looking (the "look-at" position).
class Curve3D { public Curve curveX = new Curve(); public Curve curveY = new Curve(); public Curve curveZ = new Curve(); ... }
Curve3D cameraCurvePosition = new Curve3D(); Curve3D cameraCurveLookat = new Curve3D();
Set the PreLoop and PostLoop type of each Curve.
The PreLoop and PostLoop types determine how the curve will interpret positions before the first key or after the last key. In this case, the values will be set to CurveLoopType.Oscillate. Values past the ends of the curve will change direction and head toward the opposite side of the curve.
curveX.PostLoop = CurveLoopType.Oscillate; curveY.PostLoop = CurveLoopType.Oscillate; curveZ.PostLoop = CurveLoopType.Oscillate; curveX.PreLoop = CurveLoopType.Oscillate; curveY.PreLoop = CurveLoopType.Oscillate; curveZ.PreLoop = CurveLoopType.Oscillate;
Specify the time each CurveKey should be reached and the camera position when the CurveKey is reached.
In this case, each point in time will have three CurveKeys associated with it – one for each of the x, y, and z coordinates of the point on the Curve.
public void AddPoint(Vector3 point, float time) { curveX.Keys.Add(new CurveKey(time, point.X)); curveY.Keys.Add(new CurveKey(time, point.Y)); curveZ.Keys.Add(new CurveKey(time, point.Z)); }
void InitCurve() { float time = 0; cameraCurvePosition.AddPoint(new Vector3(7.5f, 0, -45), time); cameraCurveLookat.AddPoint(new Vector3(9, 0, 9), time); time += 2000; cameraCurvePosition.AddPoint(new Vector3(3f, 0, -36), time); time += 2000; cameraCurvePosition.AddPoint(new Vector3(12f, 0, -30), time); time += 2000; cameraCurvePosition.AddPoint(new Vector3(3f, 0, -24), time); time += 2000; cameraCurvePosition.AddPoint(new Vector3(12f, 0, -18), time); time += 2000; ... cameraCurvePosition.SetTangents(); cameraCurveLookat.SetTangents(); }
Loop through each Curve setting the TangentIn and TangentOut of each CurveKey.
The tangents of the CurveKeys control the shape of the Curve. Setting the tangents of the CurveKeys to the slope between the previous and next CurveKey will give a curve that moves smoothly through each point on the curve.
public void SetTangents() { CurveKey prev; CurveKey current; CurveKey next; int prevIndex; int nextIndex; for (int i = 0; i < curveX.Keys.Count; i++) { prevIndex = i - 1; if (prevIndex < 0) prevIndex = i; nextIndex = i + 1; if (nextIndex == curveX.Keys.Count) nextIndex = i; prev = curveX.Keys[prevIndex]; next = curveX.Keys[nextIndex]; current = curveX.Keys[i]; SetCurveKeyTangent(ref prev, ref current, ref next); curveX.Keys[i] = current; prev = curveY.Keys[prevIndex]; next = curveY.Keys[nextIndex]; current = curveY.Keys[i]; SetCurveKeyTangent(ref prev, ref current, ref next); curveY.Keys[i] = current; prev = curveZ.Keys[prevIndex]; next = curveZ.Keys[nextIndex]; current = curveZ.Keys[i]; SetCurveKeyTangent(ref prev, ref current, ref next); curveZ.Keys[i] = current; } }
static void SetCurveKeyTangent(ref CurveKey prev, ref CurveKey cur, ref CurveKey next) { float dt = next.Position - prev.Position; float dv = next.Value - prev.Value; if (Math.Abs(dv) < float.Epsilon) { cur.TangentIn = 0; cur.TangentOut = 0; } else { // The in and out tangents should be equal to the // slope between the adjacent keys. cur.TangentIn = dv * (cur.Position - prev.Position) / dt; cur.TangentOut = dv * (next.Position - cur.Position) / dt; } }
Add code to evaluate the x, y, and z coordinates of the Curves at any given time by passing the elapsed time to the Evaluate method of each of the Curves.
public Vector3 GetPointOnCurve(float time) { Vector3 point = new Vector3(); point.X = curveX.Evaluate(time); point.Y = curveY.Evaluate(time); point.Z = curveZ.Evaluate(time); return point; }
Create a variable to track the amount of time that has passed since the camera started moving.
double time;
In Game.Update, set the camera's position and look-at position based on the elapsed time since the camera started moving, and then set the camera's view and projection matrices as in Rotating and Moving the Camera.
// Calculate the camera's current position. Vector3 cameraPosition = cameraCurvePosition.GetPointOnCurve((float)time); Vector3 cameraLookat = cameraCurveLookat.GetPointOnCurve((float)time);
In Game.Update, use gameTime.ElapsedGameTime.TotalMilliseconds to increment the time since the camera started moving.
time += gameTime.ElapsedGameTime.TotalMilliseconds;