This is a work in progress, but here is how I am handling it at the MiTUEN project (mituen.sourceforge.net) : There are also extra dynamics in how the camera follows the terrain in other classes at the project.
public class TerrainFollower
{
Vector3d diffVec = new Vector3d();
Vector3d downVector = new Vector3d();
Vector3d Y_DOWN = new Vector3d(0, -1, 0);
PickResult pr = null;
PickIntersection pi;
Transform3D viewTx = new Transform3D();
public boolean terrainFollow(TransformGroup tg, Vector3d pos, BranchGroup terrain)
{
tg.getTransform(viewTx);
Vector3d locationVector = new Vector3d();
Transform3D worldEyeTransform = new Transform3D();
tg.getLocalToVworld(worldEyeTransform);
worldEyeTransform.mul(viewTx); // viewTx);
worldEyeTransform.get(locationVector);
Point3d locationPoint = new Point3d();
// locationPoint.set(locationVector);
worldEyeTransform.transform(Y_DOWN, downVector);
PickRay terrainPicker = new PickRay();
locationPoint.add(locationVector, pos);
locationPoint.y += 10000; // This seems to be needed for some reason to raise above terrain
System.out.println(locationVector.toString());
terrainPicker.set(locationPoint, Y_DOWN); //downVector);
// System.out.println("locationPoint: " + locationPoint);
// System.out.println("downVector: " + downVector);
// System.out.println("terrain children: "+terrain.numChildren());
if (terrain == null)
{
System.out.println("no terrain to choose");
return false;
}
SceneGraphPath ground[] = terrain.pickAll(terrainPicker);
//(terrainPicker);
if (ground == null)
{
System.out.println("tf:no terrain");
return false;
}
boolean ret_val = true;
double shortest_length = -1;
/*
for (int i = 0; (i < ground.length); i++) {
PickResult pr = new PickResult(ground[i], terrainPicker);
PickIntersection pi = pr.getClosestIntersection(locationPoint);
if (pi != null) {
Point3d intpt = pi.getPointCoordinates();
Point3d intpt1 = pi.getPointCoordinatesVW();
if (intpt != null) {
System.out.println("Terrain Intersection point: "+intpt);
System.out.println("Terrain intersection point1: "+intpt1);
diffVec.sub(locationPoint, intpt);
if (shortest_length == -1 || (diffVec.lengthSquared() < shortest_length)) {
shortest_length = diffVec.lengthSquared();
intersectionPoint.set(intpt1);
}
}
} else {System.out.println("null pi"); }
} */
if (ground.length < 1)
{
System.out.println("Empty ground array :( ");
return false;
}
pr = new PickResult(ground[0], terrainPicker);
// pr.setFirstIntersectOnly(true);
// pr.setFirstIntersectOnly(true);
// pr.getFirstPickEnable();
// System.out.println("Num intersection: " + pr.numIntersections());
if (pr.numIntersections() < 1)
{
System.out.println("Picked but no intersections");
return false;
}
pi = pr.getIntersection(0);
// System.out.println("pr: "+ pr);
Point3d intersectionPoint = pi.getPointCoordinatesVW();
// Point3d inpt1 = pi.getPointCoordinatesVW();
/* if (shortest_length == -1) {
System.out.println("no real terrain");
return false;
}*/
//IMPORTANT
//terrain_transform.transform(intersectionPoint);
/* if(lastTerrainHeight == -1){
lastTerrainHeight = intersectionPoint.y;
}
double terrain_step = intersectionPoint.y - lastTerrainHeight;
double height_above_terrain = locationPoint.y - intersectionPoint.y;
if(height_above_terrain != ainfo.amodel.height){
if(terrain_step > ainfo.speed){
ret_val = false;
}
oneFrameTranslation.y = ainfo.amodel.height - height_above_terrain;
}
*/
/*
System.out.println("intersectionPoint.y = "+intersectionPoint.y);
System.out.println("locationPoint.y = "+locationPoint.y);
System.out.println("terrain_step = "+terrain_step);
System.out.println("height_above_terrain = "+height_above_terrain);
System.out.println("oneFrameTranslation.y = "+oneFrameTranslation.y);
System.out.println("avatarHeight = "+avatarHeight);
System.out.println("lastTerrainHeight = "+lastTerrainHeight);
*/
// Quat4d quat = new Quat4d();
float lastTerrainHeight = (float) intersectionPoint.y;
double terrain_step = intersectionPoint.y - lastTerrainHeight;
double height_above_terrain = locationPoint.y - intersectionPoint.y;
System.out.println("tf: height above terrain:" + height_above_terrain);
doMoveNoTerrain(new Vector3d(0.0, -(height_above_terrain - 10002), 0.0), tg);
return ret_val;
}
public void doMoveNoTerrain(Vector3d theMove, TransformGroup transformGroup)
{
Transform3D transform3D = new Transform3D();
transformGroup.getTransform(transform3D);
Transform3D toMove = new Transform3D();
toMove.setTranslation(theMove);
transform3D.mul(toMove);
transformGroup.setTransform(transform3D);
TerrainMeshControl.uglyGlobal.viewerPositionUpdated(transform3D);
// if (sml != null)
// sml.viewerPositionUpdated(toMove);
}
}