After spending a couple of hours yesterday looking for a guide on how to generate surface normals for the new terrain system in Ogre 1.7, I realized that there is very little information. So after a couple of hours worth of playing with the new terrain functions, I believe that I've found a better solution than the standard raycasting method and would like to share my findings.
the following code block uses my own environment manager class to get the loaded terrain, you must change it to return a pointer to your loaded terrain
Ogre::Image image; Ogre::Terrain* terrain; terrain = GameServices::EnvironmentManager()->GetTerrainGroup()->getTerrain(0,0); //0,0 is where my terrain was created terrain->getTerrainNormalMap()->convertToImage(image);
this is optional if you would like to save your terrain to an image file to visually verify its correctness, or to simply be able to load it from a file rather than generate it each time you run your game. The second parameter in the load function is the resource group under which the image has been loaded, be sure to change this as necessary for your application.
image.save("normalMap.png"); image.load("normalMap.png", "General");
a 2d image only has two axes, no height, so we use x and z coords (y is height)
Ogre::Vector3 posVec(m_sceneNode->getPosition().x, m_sceneNode()->getPosition().z,0); Ogre::ColourValue tempColor = image.getColourAt(posVec.x, posVec.y, 0);
RGB values map a surface normal from 0 to 1, however valid normal axis values are -1.0 to 1.0, so we convert from one coordinate system to another
Ogre::Vector3 normal((tempColor.r*2.0f)-1.0f, (tempColor.g*2.0f)-1.0f,(tempColor.b*2.0f)-1.0f);
Orient an Object to the Terrain
At this point you have your surface normal, the following code may or may not be useful to you
this will give you the axis directly to the right of your current position
CAUTION: this is not the vector you are looking for if the surface normal is the UNIT_Y, be sure to check for this beforehand
Ogre::Vector3 right = normal.crossProduct(Ogre::Vector3::UNIT_Y);
gives you a forward vector from the right vector
Ogre::Vector3 forward = right.crossProduct(normal);
If you have a rough terrain and need to smooth out the transitions as your scene node moves along the terrain, an alternate method is to use a weighted system for calculating the new normal prior to orienting your object:
Quaternion currentOrientation = sceneNode->getOrientation(); Vector3 localY = currentOrientation.yAxis(); float weight = 0.10; // Weight of the new normal Vector3 newNormal = localY * ( 1 - weight ) + terrainNormal * weight; Ogre::Radian inclinationAngle = Math::ACos(localY.dotProduct(newNormal)); if(inclinationAngle.valueRadians() != 0.0f) { Vector3 inclinationAxis = ( localY.crossProduct( newNormal) ).normalisedCopy(); Quaternion inclination = Quaternion(inclinationAngle, inclinationAxis); // Can't just call rotate, have to multiply inclination first or things get a little crazy //sceneNode->rotate(inclination); sceneNode->setOrientation( inclination * currentOrientation ); }
Play around with weight. Setting it very small will rotate your object more slowly. This is still a linear approach, so it's only useful for smoothing it out a bit. If you want it to simulate torque due to gravity, you'll have to get a little more fancy.