terrain following

Started by
16 comments, last by Motorherp 17 years, 8 months ago
Hi ive got my terrain mesh up and ive stored all of its normals in an array of Vectors so i dont have to unlock the vertex buffer every frame to read them. Now i want to make a character object rotate to the angle of the ground below it. 1. Do i have to create an average normal for each face from the 3 vertex normals? 2.how do i get the angle to rotate the character? is it the cross product of the characters position and the normal of the face it intersects with? diagram thanks [Edited by - treeway on August 18, 2006 8:36:45 AM]
Advertisement
I'll move this over to M&P - I think it'll be more suitable over there.

If you've got an algebra book to hand you may want to read up on the various tricks involving planes. If you interpolate the height and have a surface normal then you've got a plane definition.

I'll stop now before I go too far down the wrong path - my algebra isn't quite as good as it used to be [lol]

hth
Jack

<hr align="left" width="25%" />
Jack Hoxley <small>[</small><small> Forum FAQ | Revised FAQ | MVP Profile | Developer Journal ]</small>

Is the terrain a regular grid of vertices like in a height map? If so you can determine which tri you fall in directly from the character position without having to do any intersection testing. Otherwise you'll have to cast a ray downwards from the character and intersect it with the terrain in order to determine the correct tri. Do a google for ray tri intersection testing. If you're having to do this and your terrain is very large you may also have to look into spacialy partitioning the terrain to cut down on the number of intersection tests you have to perform.

Once you have the correct tri the best solution would be to interpolate the normals from the 3 verts which make up the tri in order to get the smoothest motion. You can do this by projecting the character position onto two of the tri edges and taking the distance that projection lies into the edge as a blending factor between the normals. This would look something like:

// dont trust this, I just made it up on the spotvec3 BlendNormals(vec3* pVerts, vec3* pVertNormals, vec3& point){   vec3 edges[2];   edges[0] = pVerts[1] - pVerts[0];   edges[1] = pVerts[2] - pVerts[0];   float blends[2];   blends[0] = (point - pVerts[0]).dot(edges[0]) / edges[0].length();   blends[1] = (point - pVerts[0]).dot(edges[1]) / edges[1].length();   vec3 norm = pVertNormals[0] + blends[0]*(pVertNormals[1] - pVertNormals[0]);   norm = norm + blends[1]*(pVertNormals[2] - norm);   return norm;}


Unfortunately this would involve accessing the vert array which you dont want to do. If your terrain isn't a regular grid of verts you're going to have to have access to the vert array anyway for the intersection testing so you might want to consider storing two copies, one for the graphics buffer and the other in main memory for this stuff.
[size="1"] [size="4"]:: SHMUP-DEV ::
its an irregular terrain - ive already stored each normal as a vector which can be indexed from the face intrersection with the players down vector using D3DXIntersect(see diagram i added to my first post). I dont think im blending the normals properly - im just adding the three normals of the face together then normalizing it. i will try your suggestion. thanks
Somewhat off-topic and you might already know this, but some of your normals are messed up, to an extent. If you look at the quad on the top of the ramp, you will notice that it has a slight gradient shading, instead of smooth shading like (how I assume) you want it. See, it's interpolating between the straight-up normal of the vertices that aren't next to the ramp, and the diagnol normals of the vertices that are next to the ramp. If you use these normals to change the slop of the player, this could be a fairly confusing to the player.
Hmm, been giving it some more thought and I've realised that the maths I've given you will only give a correct blending for right angled triangles. Its not far off but not perfect. A better solution might be to calculate the distance from the point to each vert of the tri and use the ratios of these distances as your blending factors between the 3 vertex normals. Something like:

vec3 BlendNormals(vec3* pVerts, vec3* pVertNormals, vec3& point){   float distances[3]   distances[0] = (pVerts[0] - point).length();   distances[1] = (pVerts[1] - point).length();   distances[2] = (pVerts[2] - point).length();   float totalDistance = distances[0] + distances[1] + distances[2];   distances[0] /= totalDistance;   distances[1] /= totalDistance;   distances[2] /= totalDistance;   vec3 norm;   norm = distances[0]*pVertNormals[0];   norm += distances[1]*pVertNormals[1];   norm += distances[2]*pVertNormals[2];   norm.Normalise();   return norm;}


I'm not completely convinsed of the top of my head though, give it a try and see.
[size="1"] [size="4"]:: SHMUP-DEV ::
i see what you mean about the normals on the ramp but i cant think of any other way to make the character follow an arbetry terrain correctly. Are there any other ways of doing it? The player is colliding with the terrain correctly,it is just not roatating upwards when walking up hill / downards when going down hill or whatever. I thought this would be a simple x rotation to

acosf(dot(playerForwardAxis,FaceNormal)

with the face normal as the normalized sum of the three vertex normals of the face but it seems more difficult then that.
The trick to combining smooth surfaces and sharp edges in terrain data is to create degenerate verts along sharp edges so that each joining face references a different set of normals. This way its possible to have sharp changes in normal direction so that your shading and character orientation respond correctly. You can still continue to use the normal blending in these scenarious too since a flat surface will reference normals which all point in the same direction and thus will blend to give the same normal direction all over the tri.
[size="1"] [size="4"]:: SHMUP-DEV ::
thanks motorherp im working with directX so ive converted your funtion to use D3DXVectors:

D3DXVECTOR3 BlendNormals(D3DXVECTOR3* pVerts, D3DXVECTOR3* pVertNormals, D3DXVECTOR3& point)
{
D3DXVECTOR3 distances;
distances.x = D3DXVec3Length(D3DXVec3Subtract(&pVerts[0],&pVerts[0],&point));
distances.y = D3DXVec3Length(D3DXVec3Subtract(&pVerts[1],&pVerts[0],&point));
distances.z = D3DXVec3Length(D3DXVec3Subtract(&pVerts[2],&pVerts[0],&point));

float totalDistance = distances.x + distances.y + distances.z;
distances.x /= totalDistance;
distances.y /= totalDistance;
distances.z /= totalDistance;

D3DXVECTOR3 norm;
D3DXVec3Scale(&norm,&pVertNormals[0],distances.x);
norm += *D3DXVec3Scale(&norm,&pVertNormals[1],distances.y);
norm += *D3DXVec3Scale(&norm,&pVertNormals[2],distances.z);

D3DXVec3Normalize(&norm,&norm);

return norm;
}

is that right? also what is the point you specify as the third perameter?
Aha ... sorry to go back on myself but I think I've got it sussed this time. The reason my first implementation only works for right angled triangles is because otherwise the two blendings are no longer inter-dependant. The trick to solving this would be to take one of the tri edges to calculate your first blend and then use the vecor which is tangent to this edge and the tri norm to calculate the second blend. Therefore you'd end up with this:

vec3 BlendNormals(vec3* pVerts, vec3* pVertNormals, vec3& point){   vec3 edges[2];   edges[0] = pVerts[1] - pVerts[0];   edges[1] = pVerts[2] - pVerts[0];   float blends[2];   blends[0] = (point - pVerts[0]).dot(edges[0].normalise()) / edges[0].length();   // not too sure about the normalise above.  I think its needed but there might be a better way   vec3 triNorm = edges[0].cross(edges[1]);   // no need to normalise    vec3 tangent = (triNorm.cross(edges[0])).normalise();   float vert0Proj = tangent.dot(pVerts[0]);   float vert2Proj = tengent.dot(pVerts[2]);   blends[1] = (point.dot(tangent) - vert0Proj) / (vert2Proj - vert0Proj);   vec3 norm = pVertNormals[0] + blends[0]*(pVertNormals[1] - pVertNormals[0]);   norm = norm + blends[1]*(pVertNormals[2] - norm);   return norm;}


Anyways, 'point' just means the character position.
[size="1"] [size="4"]:: SHMUP-DEV ::

This topic is closed to new replies.

Advertisement