2.7 create a question about the camera's flying Effect
You want to smoothly move the camera from one position to another, and also make the camera's observation target move smoothly.
Specifically, you want a feature that allows the camera to start and end along a smooth curve. This movement process should be smooth. After the process ends, you want the camera to return to the normal state and use it as a first person camera.
Solution
This process requires a time variable. when the camera is at the start position, this variable is 0 and it is 1 at the end position.
By specifying the third position as the midpoint of the camera's movement, you can define a beiser curve ). The besell curve is a smooth curve that passes through the start and end points, very close to the additional points (such as the third point) specified between the start and end points ). By specifying the current value of a time variable between 0 and 1 for a Bessert curve, it returns the position on the curve corresponding to the time.
The position of the observed target is linearly interpolated between the start and end of the target. The result is that the camera moves along the smooth curve from the starting point to the ending point, and observes that the target will also move along the straight line between the starting point and the central point.
To use the first-person camera code, you cannot simply change the camera's position and observation target, because the camera calculates the observed Target Based on the target position and the rotation around the UP and Right axes. So you need to adjust the rotation value of the camera.
After everything is ready, the smooth initial position of the process can be obtained by using the MathHelper. SmoothStep method.
Note:You can use the Curve class included in the XNA framework to write scripts for complex camera paths, but it is not perfect enough to handle the camera's fly-in because it lacks some minor features. For example, this class does not support 3D points. You need to manually set the key points of the Curve class. In short, the section that can be replaced by the Curve function in this tutorial is the bezr method. If you want to use the Curve function of the XNA framework to replace the bezr method, the Section 90% in this tutorial is still useful.
Working Principle
You need to save these variables for the beiser camera's inbound effect:
float bezTime = 1.0f;Vector3 bezStartPosition; Vector3 bezMidPosition; Vector3 bezEndPosition; Vector3 bezStartTarget; Vector3 bezEndTarget;
The variable bezTime is stored in the flying process. Set this variable to 0 when a flight starts. When it is running, it is increased to 1 during the update process. If this value is greater than 1, the process ends.
During the flight, you need to save some locations, such as the start point, end point, and observation target, but you need to calculate the midpoint to obtain a smooth curve. These variables should be specified at the beginning of the flight.
In the following code, you can find a method to trigger the Flying Behavior. This method requires the initial and final positions of the camera, the initial position and the end position of the observation target:
private void InitBezier(Vector3 startPosition, Vector3 startTarget, Vector3 endPosition, Vector3 endTarget){ bezStartPosition = startPosition; bezEndPosition = endPosition; bezMidPosition = (bezStartPosition + bezEndPosition) / 2.0f; Vector3 midShiftDirecton = new Vector3(1, 1, 0)*2; Vector3 cameraDirection = endPosition - startPosition; Vector3 perpDirection = Vector3.Cross(upV, cameraDirection); perpDirection.Normalize(); Vector3 midShiftDirecton = new Vector3(0, 1, 0) + perpDirection; bezMidPosition += cameraDirection.Length() * midShiftDirecton; bezStartTarget = startTarget; bezEndTarget = endTarget; bezTime = 0.0f;}
The startPosition, endPosition, startTarget, and endTarget parameters can be immediately stored in the corresponding variables.
The midPosition is an additional point inserted between the start point and the end point. It needs to be calculated. You can calculate the midpoint of the start point and the end point first, and then take the average value of the two. To remove a distance from the curve, you need to add a straight line perpendicular to this line that deviates from the camera's start and end points.
You can use the two methods to obtain the direction perpendicular to the two directions. In this tutorial, you want to make this direction perpendicular to the direction pointing to the camera and the Up vector. First, calculate the direction pointing to the camera. This is the same as getting the other direction: Get the final position and subtract the initial position. Then, you can obtain the direction perpendicular to the two directions by using the forward and Up directions.
If you move the midpoint to this direction, you will get a beautiful curve when the start point and the end point are very close. However, if the distance between the two points is large, the curve will be uneven. You can solve this problem by multiplying the distance between the start point and the end point by the offset, which can be obtained by finding the vector length between the start point and the end point.
Finally, set the bezTime variable to 0, indicating that the flight starts.
After initiating the fly in, you can call the updatebeier method to update each frame:
private void UpdateBezier() { bezTime += 0.01f; if (bezTime > 1.0f) return; Vector3 newCamPos = Bezier(bezStartPosition, bezMidPosition, bezEndPosition, bezTime); Vector3 newCamTarget = Vector3.Lerp(bezStartTarget, bezEndTarget, bezTime); float updownRot; float leftrightRot; AnglesFromDirection(newCamTarget - newCamPos, out updownRot, out leftrightRot); fpsCam.UpDownRot = updownRot; fpsCam.LeftRightRot = leftrightRot; fpsCam.Position = newCamPos;}
This method first adds bezTime a little bit. If the value is greater than 1, return is returned at the end of the process.
Otherwise, execute the next line of code. First, the next position of the camera is obtained by passing the bezTime variable from the besell curve. This variable indicates the progress of the Flying Behavior. Then, obtain the current target through interpolation between bezStartTarget and bezEndTarget. You can learn more about interpolation in tutorial 5-9.
If you want to combine the code of the first-person camera, you need to calculate the final updownRot and leftrightRot values, which can be done by calling the AnglesFromDirection method. This method has three parameters: camera orientation (if you subtract the camera position from the target position, you can get the observation direction) and two angles. By passing these two angles as the "out" parameter, this method can change these two values, which will be stored in the corresponding variables. Finally, update the camera's new position and rotation.
Besell Curve
The first line of code calls the bezerener method. This method returns the position on the curve based on the bezTime variable. The bezTime variable is usually between 0 and 1. This function requires three points to define a curve. You have calculated these variables in the initbeier method.
You can use the following function to calculate the location of a Bessert curve defined by three points at any given time:
P (t) = Pstart * (1-t) 2 + 2 * Pmid * (1-t) * t + Pfinal * t2
It looks very difficult, not actually. Let's take a look at the result when t = 0. Use the function P (0) = Pstart * 12 + 2 * Pmid * 1*0 + Pfinal * 02, and the result is Pstart! At the end of the process, t = 1, use the function P (1) = Pstart * 02 + 2 * Pmid * 0*1 + Pfinal * 12, and the result is Pfinal. For the value between 0 and 1, the result is between the start position and the end position, and the point is slightly pulled to the midpoint. In this tutorial, t is bezTime.
The following code calculates the previous formula:
private Vector3 Bezier(Vector3 startPoint, Vector3 midPoint, Vector3 endPoint, float time){ float invTime = 1.0f - time; float timePow = (float)Math.Pow(time, 2); float invTimePow = (float)Math.Pow(invTime, 2); Vector3 result = startPoint * invTimePow; result += 2 * midPoint * time * invTime; result += endPoint * timePow; return result; }
InvTime is (1-T) in the formula, so invTimePow is (1-T) 2. The result variable is the final output result of the function, which is returned to the call code.
Get Rotation
When you look at the last line of the updatebeener method code, you will find that the next position of the camera is calculated using the beener method, and the next target uses a simple interpolation.
This is used for the simplest inbound behavior, because you can immediately create a View matrix from this position and observation target (see tutorial 2-1 ). But when the flight ends, you want to get the motion of the camera. The camera position is automatically stored, but the rotation variable is not. So you need to get the camera location and the target location you just calculated, find the corresponding leftrightRotation and updownRotation, and store these two variables so that they can be used for the first-person camera. In addition, the first-person camera can also obtain information about the camera after the arrival, without errors.
This is the operation performed in the AnglesFromDirection method. This method calculates the value of updownRot and leftrightRot Based on the orientation of the camera. Both values are used as the "out" parameter of the method, which means they can be returned to the calling code. Therefore, changes made to this method are stored in the variables of the method to be called.
private void AnglesFromDirection(Vector3 direction, out float updownAngle, out float leftrightAngle){ Vector3 floorProjection = new Vector3(direction.X, 0, direction.Z); float directionLength = floorProjection.Length(); updownAngle = (float)Math.Atan2(direction.Y, directionLength); leftrightAngle = -(float)Math.Atan2(direction.X, -direction.Z);}
Tutorial 4-17 explains how to obtain the Rotation Angle in one direction. In this tutorial, you must obtain two angles because the direction is in 3D space. First, find the leftrightRot angle. Figure 2-5 shows the XZ plane containing the camera and observation target. The dotted line is the orientation of the camera. Line X and Z are the X and Z components of the vector. In a right triangle, if you want to obtain the top corner, all you need to do is calculate the arc tangent value of the right side and divide it by the adjacent side. In this tutorial, X is divided by Z. The Atan2 function allows you to specify two values instead of their vendors, which avoids two results. This is the method for obtaining the leftrightRot angle.
Figure 2-5 obtain the leftrightRot Angle
To find updownAngle, you can use Figure 2-6. The orientation of the dotted table camera. You want to obtain the angle between the Y direction and the projection in this direction on the XZ plane. Therefore, you can obtain the updownAngle by passing these two directions to the Atan2 method.
Figure 2-6 obtain the updownAngle
Usage
Make sure that the updatebeener method is called in the Update method:
UpdateBezier();
Now you want to start a flying over effect. All you need to do is call the initbeier method!
if (bezTime > 1.0f) InitBezier(new Vector3(0,10,0), new Vector3(0,0,0), new Vector3(0, 0, -20), new Vector3(0, 0, -10));
When you want to combine the fly-over effect with the camera, you can start with the camera's position and observation target:
if (bezTime > 1.0f) InitBezier(fpsCam.Position, fpsCam.Position + fpsCam.Forward * 10.0f, new Vector3(0, 0, -20), new Vector3(0, 0, -10));
As you can see, I place the initial observation object in 10 units before the start position, which provides a smooth result, otherwise, the camera will move rapidly when following the target.
Smooth start and acceleration
By increasing bezTime evenly from 0 to 1, the speed at which the camera moves along the curve is a constant. This will lead to a very uncomfortable start and end. What you really want is to slowly increase bezTime from 0 to 0.2, and then quickly increase it to 0.8, the last part from 0.8 to 1 is gradually increasing. This can be done through MathHelper. implementation of the SmoothStep method: Given a continuously increasing value between 0 and 1, this method returns a value between 0 and 1 with smooth start and end!
This step is performed in the updatebeener method, so use this code to replace the two lines of code in the middle:
float smoothValue = MathHelper.SmoothStep(0, 1, bezTime);Vector3 newCamPos = Bezier(bezStartPosition, bezMidPosition, bezEndPosition, smoothValue);Vector3 newCamTarget = Vector3.Lerp(bezStartTarget, bezEndTarget, smoothValue);
The smoothValue variable stores the smoothing value between 0 and 1, which is used to replace the bezTime variable added evenly to the method.
Solve the incorrect camera rotation at the end of the flying in process
This part of this tutorial solves problems that may occur in some situations. In this case, it is displayed in the left graph of Figure 2-7. At the beginning, the camera looks at the left side until the two curves intersect. at this intersection, the camera suddenly looks at the right side.
You can solve this problem by switching the midpoint of the curve to the left. In this way, you obtain the curve shown in the right figure. At the beginning, the camera looks at the right side without switching to the left side.
Figure 2-7 incorrect and correct fit curves
How do you know the direction in which the midpoint is to move? Refer to the left and right of section 1-2-8. They show two problems. The dotted line shows the target path, and the solid line shows the straight line path of the camera.
Figure 2-8 determine how to switch the midpoint
In both cases, the midpoint must be switched to another direction. This can be detected by using the cross-multiplication method. In both cases, the result of the Cross multiplication is perpendicular to the plane vector, but in one case, it is up, and in the other case, it is down. Suppose it is called upVector. Next, if you multiply this upVector with the camera direction, you will get a vector perpendicular to the camera direction and upVector. The direction of the final vector depends on whether the upVector is facing up or down, and whether it is facing down depends on the intersection between the position and the target path. Because it is perpendicular to the location path and upVector, it can be used to switch the midpoint.
Therefore, use the following code to obtain the correct midpoint of the curve in the initbeier method:
bezMidPosition = (bezStartPosition + bezEndPosition) / 2.0f; Vector3 cameraDirection = endPosition - startPosition;Vector3 targDirection = endTarget - startTarget;Vector3 upVector = Vector3.Cross(new Vector3(targDirection.X, 0, targDirection.Z), new Vector3(cameraDirection.X, 0, cameraDirection.Z));Vector3 perpDirection = Vector3.Cross(upVector, cameraDirection); perpDirection.Normalize();
UpVector can be obtained through the cross-multiplication path and the target path, both of which are projected to the XZ plane by setting the Y component to 0 (in this way, you get a straight line ). Obtain the direction perpendicular to the upVector by using the upVector cross-multiplication path.
When you find this vertical direction, you can switch the midpoint to this direction:
Vector3 midShiftDirecton = new Vector3(0, 1, 0) + perpDirection; bezMidPosition += cameraDirection.Length() * midShiftDirecton;
However, one case cannot be correctly handled. If the targDirection and cameraDirection are parallel or the upVector and cameraDirection are parallel, the cross-multiplication method may fail, resulting in the perpDirection variable being (, 0 ), an error occurs when the vector is normalized. Therefore, you need to solve this problem by setting an arbitrary value:
if (perpDirection == new Vector3()) perpDirection = new Vector3(0, 1, 0);
Put this code before normalization of the perpDirection code line.
Code
The following method initializes the variable to start a new inbound behavior:
private void InitBezier(Vector3 startPosition, Vector3 startTarget, Vector3 endPosition, Vector3 endTarget) { bezStartPosition = startPosition; bezEndPosition = endPosition; bezMidPosition = (bezStartPosition + bezEndPosition) / 2.0f; Vector3 cameraDirection = endPosition - startPosition; Vector3 targDirection = endTarget - startTarget; Vector3 upVector = Vector3.Cross(new Vector3(targDirection.X, 0,targDirection.Z), new Vector3(cameraDirection.X, 0, ~ cameraDirection.Z)); Vector3 perpDirection = Vector3.Cross(upVector, cameraDirection); if (perpDirection == new Vector3()) perpDirection = new Vector3(0, 1, 0); perpDirection.Normalize(); Vector3 midShiftDirecton = new Vector3(0, 1, 0) + perpDirection; bezMidPosition += cameraDirection.Length() * midShiftDirecton; bezStartTarget = startTarget; bezEndTarget = endTarget; bezTime = 0.0f;}
When running, each frame calls the updatebezr method to calculate the camera's new position and rotation:
private void UpdateBezier(){ bezTime += 0.01f; if (bezTime > 1.0f) return; float smoothValue = MathHelper.SmoothStep(0, 1, bezTime); Vector3 newCamPos = Bezier(bezStartPosition, bezMidPosition, bezEndPosition, smoothValue); Vector3 newCamTarget = Vector3.Lerp(bezStartTarget, bezEndTarget, smoothValue); float updownRot; float leftrightRot; AnglesFromDirection(newCamTarget - newCamPos, out updownRot, out leftrightRot); fpsCam.UpDownRot = updownRot; fpsCam.LeftRightRot = leftrightRot; fpsCam.Position = newCamPos;}
The location is calculated by the bezr method:
private Vector3 Bezier(Vector3 startPoint, Vector3 midPoint, Vector3 endPoint, float time){ float invTime = 1.0f - time; float timePow = (float)Math.Pow(time, 2); float invTimePow = (float)Math.Pow(invTime, 2); Vector3 result = startPoint * invTimePow; result += 2 * midPoint * time * invTime; result += endPoint * timePow; return result;}
The rotation is obtained by the following method:
private Vector3 Bezier(Vector3 startPoint, Vector3 midPoint, Vector3 endPoint, float time){ float invTime = 1.0f - time; float timePow = (float)Math.Pow(time, 2); float invTimePow = (float)Math.Pow(invTime, 2); Vector3 result = startPoint * invTimePow; result += 2 * midPoint * time * invTime; result += endPoint * timePow; return result; }