Terrain Following/Smooth Transitions Between Triangles

Started by
4 comments, last by sof81 12 years, 6 months ago
Hey all, I have two problems I've run into that I was hoping I could get help for. I have a pretty good terrain following "algorithm" (if you could call it that) for my tanks and stuff that I have rolling across my terrain: I align the normal of the object with the normal of the terrain. To do so, I rotate my object by two angles, one angle about the X-axis, and one angle about the Z-axis. I calculate those angles by finding the X and Z angles the terrain triangle's normal makes with the Y-axis, then rotating my object by those angles (but since the angle between vectors is always positive, I make it negative when the tank is going "downhill"). So far so good when the tank is moving in the positive or negative Z direction. The problem is when I turn my tank and try to roll it in a different direction (i.e., change the yaw angle and rotate around the Y-axis), the tank doesn't line up much with the terrain. When I try to make it go in the +/- X direction, the effect is very noticeable. I realized I needed a way of making X-axis rotation angle become my Z-axis rotation angle and vice versa when the tank was fully rotated in that direction. I did some research and came across quaternions (which I've never understood fully) and gimbal lock, which I believed was at least part of my problem. I converted my Euler angles to quaternions, and then constructed my rotation matrix from a quaternion, hoping that would solve the gimbal lock problem, but it didn't, so I'm not sure that that did anything. I realize ultimately what I'm trying to do is rotate my tank around an axis that is my terrain's normal. So problem 1 in short: is there a way I can perform a RotationAxis using Euler angles (e.g. doing it as RotationX * RotationY * RotationZ)? If not, is there a way I can do it using quaternions if I know the tank's yaw angle, its normal (0 1 0) and the terrain normal? The second problem is a little easier, I've had some ideas on how to solve it: When my tank follows my terrain and adjusts to each triangle's normal, it kind of snaps and pops as it crosses triangles. I guess I should probably try smoothing its transition between normal points. What is the best way to do that? Is there a way I can determine how far across the triangle boundary the tank is to "blend" the two normals? I was thinking that if all else fails I'll just determine my angles by sampling the height at 4 points on the bottom of my tank (or car) to find the angles, but that means that I have to find the ideal 4 points for every mesh (i.e., the wheels) in advance, and I feel that that would inhibit me when I start adding objects in my game with the introduction of that manual preprocessing step for some kind of annoying metadata :). Thanks for your help. Much obliged, Steve.
Advertisement
I think you may be overcomplicating things a little. In essence, we have the following:

T: the tank normal (initially points straight up).
G: the ground normal.

Now, you need to rotate the tank such that T becomes parallel with G. The first thing is to find the axis about which to rotate, R, which can be found using the cross product:

R = T x G

Next we need the angle, A, which can be found using the dot product:

A = acos( T dot G )

And now you have an axis-angle pair, which can be converted to whatever form you need (matrix, quaternion, euler angles), or used directly.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Swift coder's suggestion is good, though it doesnt consider the facing direction of the tank. I would suggest you go directly for building a matrix.

You know the normal direction, which is what you want the "up" direction of the tank to be, lets just call that Y (openglish). You also have the direction that the tank is facing (approximately, it's in another plane than the resultant tank orienation.)

so you have

Vec terrainNormal, tankDir;

And you need to create

Vec tankX, tankY, tankZ; //basis vectors for the tank model

If the model is stored so that Y is up, then tankY is easy.

tankY = terrainNormal;

Lets also say that the model is stored so that the tank "looks" in the x direction. So essentially we need to set tankX = tankDir. But as we said before, these are different planes, so you have to use a round about way to get there. Using the cross product.

First find the right vector, which under the previously mentioned assumptions would be the +Z direction.

tankZ = Normalize(Cross(tankDir, terrainNormal));

After finding this vector we can easily find the true tank facing vector.

tankX = Normalize(Cross(terrainNormal, tankZ));


So put that all together and you've got.

tankZ = Normalize(Cross(tankDir, terrainNormal));
tankY = terrainNormal;
tankX = Normalize(Cross(terrainNormal, tankZ));


Then these vectors can be the basis for your tank transformation. Combine them with a position for the full transform.

Matrix tankTransform(tankX, tankY, tankZ, tankPosition);
tank.SetModelTransform(tankTransform);
Thank you both for your replies! I was able to put them together to an extent and got a method using quaternions.

I used swiftcoder's suggestion for aligning the tank with the normal. I'm in C# and SlimDX, so to align the tank and the terrain:

Vector3 rotationAxis = Vector3.Normalize(Vector3.Cross(new Vector3(0, 1, 0), terrainnormal));float angle = (float)Math.Acos(Vector3.Dot(new Vector3(0, 1, 0), terrainnormal));Quaternion tankAligned = Quaternion.RotationAxis(rotationAxis, angle);


Then I needed to take into account the facing direction of the tank, though. So once the tank was aligned with the ground I used a quaternion to rotate it around the terrain normal's axis:

Quaterion tankHeading = Quaternion.RotationAxis(terrainnormal, (float)carDirection);


Then I multiplied those two quaternions together and built a rotation matrix out of them.

Success!

Of course I realize I asked for Euler angles first because I was a little less comfortable with quaternions, so bzroom, your method works perfectly for obtaining Euler angles from the translated local axes you get--now I have a choice of methods, which is great.

Also, bzroom, you got the rusty, heavy wheels of my math brain turning and solved another problem of mine that's been in the back of my head for some days now, which was line of sight for two enemy tanks. Because now knowing the tank's forward-facing vector, I can just dot that with the vector between friendly tanks and enemy tanks, and if it's in some range of cos(arbitrary line of sight angle), the tank can see the enemy tank. Excellent!

Thank you both!

EDIT:

As an aside, to make the transitions smoother between triangles, I'm averaging every new quaternion calculated with the one used for the previous frame, so instead of popping over triangles, the tank kind of glides over them gradually changing its angle but still reflecting sharp changes in triangle normals (i.e. bumps).

[Edited by - STufaro on July 24, 2009 7:56:35 PM]
Hi,

I have same problem with STufaro, but his solution did not work for me. I did tankHeading*tankAligned .

Result is an another quaternion. I extract euler angles from it with this function

vector3f euler_angles(bool homogenous = true) const {
float sqw = s*s;
float sqx = v.x * v.x;
float sqy = v.y * v.y;
float sqz = v.z * v.z;

vector3f euler;
if (homogenous) {
euler.x = atan2f(2.f * (v.x * v.y + v.z * s), sqx - sqy - sqz + sqw);
euler.y = asinf(-2.f * (v.x * v.z - v.y * s));
euler.z = atan2f(2.f * (v.y * v.z + v.x * s), -sqx - sqy + sqz + sqw);
} else {
euler.x = atan2f(2.f * (v.z * v.y + v.x * s), 1 - 2 * (sqx + sqy));
euler.y = asinf(-2.f * (v.x * v.z - v.y * s));
euler.z = atan2f(2.f * (v.x * v.y + v.z * s), 1 - 2 * (sqy + sqz));
}
return euler;
}

Thanks.

Note: function is not mine.

I came up with some code.
Y axis is north, x axis is east and z is up.
Vehicle heading is argv[1].


int main(int argc, char **argv) {
Vector v1x = {0, 20, 0};
Vector v2x = {-10, 0, 20};
Vector v3x = {10, 0, 20};
Vector terrainnormal = Vector_cross(Vector_subtract(v1x, v3x), Vector_subtract(v1x, v2x));
Vector_normalize(&terrainnormal);

Vector curup = {0, 0, 1}; //Current normal of vehicle (straight up.)
float angle = acos(Vector_dot(curup, terrainnormal)); //Angle between vehicle normal and terain normal.
Vector axis = Vector_cross(curup, terrainnormal);
Vector_normalize(&axis);
Quaternion quat = Quaternion_fromAxisAngle(curup, DEGtoRAD(atof(argv[1]))); //Current quaternion
Quaternion_normalize(&quat);
Quaternion_rotate(&quat, axis, angle); //Apply rotation.
Vector angles = Quaternion_toEuler(quat); //Extract euler.
std::cout << RADtoDEG(angles.x) << " " << RADtoDEG(angles.y) << " " << RADtoDEG(angles.z) << std::endl;
return 0;
}


I get;

with parameter 0 ->"135 0 0"

with parameter 90 ->"135 0 90"

I guess I am closing to solution.

This topic is closed to new replies.

Advertisement