I have another very long reply, but hopefully it will be clear. If you have another question, please ask.
The reason the vehicle is rotating about the z axis is because the quaternion you''re rotating about, to align the vehicle with the surface, is not necessarily perpendicular to the z axis. Try it. In your Car.java file, take the dot product of (ax, ay, az) with (0, 0, 1), and you''ll find the dot product is usually nonzero. The only way to avoid rotation about the z axis is to ensure this dot product is exactly zero. But to get the normals to line up for a vehicle moving in an arbitrary direction, there is no easy way you can guarantee the dot product will be zero. The solution is a two-step approach. But not doing xrot() and yrot() as you were before...read on.
You want to align the normal of the vehicle with the surface, but at the same time you want to specify a specific rotation about the z axis. How do you do it? This is actually a tricky problem, which requires a bit of geometric insight. I don''t have time to draw diagrams to illustrate the geometry, but I will tell you how to do the calculations. You''ve actually created a "multi-objective optimization problem" (as mathematicians might call it). You want to find the rotation that minimizes the difference between the vehicle normal and surface normal while at the same time minimizing the difference between the vehicle z rotation and a specified z rotation (i.e., the direction of motion). Fortunately, for your racing game, there will always be an exact solution that you can find.
I will tell you my idea for finding the rotations. It requires two steps, and you''re already doing the first step! Rotating the vehicle to align its normal is the first step! The next step is to rotate a second time, using the surface normal as the quaternion direction, and using an angle that will line the vehicle up with the direction of motion. Since the second rotation will be about the surface normal, the vehicle will continue to be aligned properly with the terrain. And since the vehicle is moving along the terrain and never in the direction of the terrain''s normal, it is guaranteed that there is an angle of rotation that will correctly orient the vehicle with its direction of moving.
Finding this new angle of rotation is trickier and more expensive than the first angle.
1) Given a direction of motion (i.e., your zrot angle called RADdir), compute DIR in the following way:
DIR = new Matrix3D();DIR.quaternion(Math.cos(RADdir/2), 0, 0, Math.sin(RADdir/2));
Notice that this rotation is purely about the z axis and so the z component of DIR will be zero.
For testing, just to make sure the vehicle isn''t rotating incorrectly, you may want to leave RADdir = 0. Then once this code is working, go back and set RADdir equal to the correct value.
2) Find the equation of the plane of the vehicle. The equation has the form:
NSurface[0]*x + NSurface[1]*y + NSurface[2]*z + D = 0
Here, (x,y,z) is a point on the surface of the plane, and D is a constant. It is D that you don''t know, but need to find. Here''s how to find it:
D = -NSurface[0]*x_vehicle + NSurface[1]*y_vehicle + NSurface[2]*z_vehicle
where (x_vehicle, y_vehicle, z_vehicle) is the current world location of the vehicle.
Now that you have D, you have the equation of the plane of the vehicle.
3) Find a point on the vehicle plane that is vertically above the point represented by the DIR vector.
x_point = DIR[0] + x_vehicle;y_point = DIR[1] + y_vehicle;z_point = (-D - NSurface[0]*x_point - NSurface[1]*y_point)/NSurface[2];
Note that this code fails if you surface is exactly vertical.
4) Find a vector from the vehicle position to (x_point, y_point, z_point):
dvector[0] = x_point - x_vehicle;dvector[1] = y_point - y_vehicle;dvector[2] = z_point - z_vehicle;
It is actually this vector, dvector, that is causing all the trouble. We need to rotate the vehicle about NSurface so that the x vector of the vehicle is parallel to dvector.
5) Compute the dot product of the vehicle x axis to dvector.
ddx = dvector[0]*xdir_x + dvector[1]*xdir_y + dvector[2]*xdir_z;
Here, (xdir_x, xdir_y, xdir_z) is the vehicle''s local x direction vector, represented in global coordinates. You can consider the vehicle''s normal to be its local z direction vector. The local x direction vector (and local y direction vector for that matter) are parallel to the plane of the vehicle. If you can grab the vehicle''s full transformation matrix, then (xdir_x, xdir_y, xdir_z) will either be the first 3 elements of the first row or the first 3 elements of the first column, depending on your 3D API. (The vehicle normal is either the first 3 elements of the third row or the first 3 elements of the third column.)
6) Now compute the new angle:
newangle = Math.acos(ddx);
7) Finally, do the last rotation:
OM.quaternion(Math.cos(newangle/2), NSurface[0]*Math.sin(newangle/2), NSurface[1]*Math.sin(newangle/2), NSurface[2]*Math.sin(newangle/2));
8) The vehicle should be aligned correctly, so that the normal is parallel to the surface normal and the vehicle is pointing in the correction direction of motion (specified by RADdir).
Please note that I have not tested my code at all, and there may be minor errors. But its basically correct.
I need to tell you that there is a slightly easier way to do all of this. Your approach of rotating the vehicle into the new orientation makes sense if you intend to include physics at some point. But if you merely want to make sure the vehicle is aligned, I''m thinking of another approach that is more direct and a bit faster to calculate. Let me know if you want to know this approach. It requires that your 3D API allow you to specify the full transformation matrix for the vehicle. For example, instead of using xrot() or quaternion() to *modify* the transformation matrix, you''d need a function like OM.set_transform_matrix(Matrix3D new_transformation_matrix).
Graham Rhodes
Senior Scientist
Applied Research Associates, Inc.