Hey folks,
I'm trying to make my players & NPCs move appropriately over the terrain and objects that appear on the terrain. In other words, if a bridge appears on the terrain, a player should walk over the bridge instead of through it. Searching these forums, I found a couple threads that led me to the code in TestObjectWalking.java. It looks like it's designed to do what I'm looking for, but when I put it into service I discovered a few issues.
Below is the code from my project, which is adapted from TestObjectWalking.java. The evaluateRequiredY() method is supposed to calculate the appropriate Y coordinate value for the passed-in Graphic (a class I defined).
private float evaluateRequiredY(Graphic g, float x, float z) {
// Assertions.
if (g == null) {
String msg = "Argument 'g' [Graphic] cannot be null.";
throw new RuntimeException(msg);
}
// Not allowed to go lower than the terrain height...
TerrainBlock tb = terrainManager.getTerrain();
float highPoint = tb.getHeightFromWorld(new Vector3f(x, 0.0f, z));
// Calculate the high point of the sceneGraph using a Ray...
Vector3f origin = new Vector3f(x, 500.0f, z);
Ray r = new Ray(origin, new Vector3f(0.0f, -1.0f, 0.0f));
PickResults pr = new TrianglePickResults();
pr.setCheckDistance(true);
sceneGraph.calculatePick(r, pr);
if (pr.getNumber() > 0) {
PickData pd = pr.getPickData(0);
Geometry geo = pd.getTargetMesh();
Vector3f[] vec = new Vector3f[3];
if (geo instanceof QuadMesh) {
// Use the terrain until we can figure out a better solution...
if (log.isDebugEnabled()) {
StringBuilder sb = new StringBuilder();
sb.append("Failed to calculate highPoint because the targetMesh was a Quad...")
.append("ntg.get(Entity.NAME)=").append(g.get(Entity.NAME))
.append("ntgeo.getName()=").append(geo.getName())
.append("ntx=").append(x).append(", z=").append(z);
log.debug(sb.toString());
}
} else if (geo instanceof TriMesh) {
TriMesh mesh = (TriMesh) geo;
List<Integer> tris = pd.getTargetTris();
if(tris.size() > 0) {
int triIndex = ((Integer) tris.get(0)).intValue();
mesh.getTriangle(triIndex, vec);
for(int i=0; i < vec.length; i++) {
vec[i].multLocal(mesh.getWorldScale());
mesh.getWorldRotation().mult(vec[i], vec[i]);
vec[i].addLocal(mesh.getWorldTranslation());
}
Vector3f loc = new Vector3f();
pd.getRay().intersectWhere(vec[0], vec[1], vec[2], loc);
if (loc.getY() < highPoint) {
// How does this happen?
if (log.isErrorEnabled()) {
StringBuilder sb = new StringBuilder();
sb.append("calculatePick selected a Triangle that's lower than the terrain height...")
.append("ntg.get(Entity.NAME)=").append(g.get(Entity.NAME))
.append("ntgeo.getName()=").append(geo.getName())
.append("ntx=").append(x).append(", z=").append(z);
log.error(sb.toString());
}
} else {
highPoint = loc.getY();
}
} else {
if (log.isDebugEnabled()) {
StringBuilder sb = new StringBuilder();
sb.append("Failed to calculate highPoint because the 'tris' collection is empty...")
.append("ntg.get(Entity.NAME)=").append(g.get(Entity.NAME))
.append("ntgeo.getName()=").append(geo.getName())
.append("ntx=").append(x).append(", z=").append(z);
log.debug(sb.toString());
}
}
}
} else {
// Use the terrain until we can figure out a better solution...
if (log.isDebugEnabled()) {
StringBuilder sb = new StringBuilder();
sb.append("Failed to calculate highPoint because the PickResults were empty...")
.append("ntg.get(Entity.NAME)=").append(g.get(Entity.NAME))
.append("ntx=").append(x).append(", z=").append(z);
log.debug(sb.toString());
}
}
return highPoint + (Float) g.getAttribute(DISTANCE_TO_BOTTOM_FROM_CENTER);
}
(1) To begin with, 3/4 of the log messages that indicate failure to calculate the appropriate Y value are (in fact) occurring in the log. So I know that...
- There are QuadMesh objects in my scene graph (imported from Blender using the XMLImporter), which don't work in this code because QuadMesh doesn't extend from TriMesh and doesn't have a getTriangle() method.
- Sometimes the first entry in the PickResults indicates a Triangle that's *BELOW* the terrain, which shouldn't happen b/c the Ray should be intersecting with the terrain. (Right?)
- Sometimes the PickResults is empty even when I'm over the terrain, which (again) shouldn't happen b/c the Ray should be intersecting with the terrain. (Right?)
(2) What is the purpose of this part of the code (adapted from TestObjectWalking.java)?
Vector3f[] vec = new Vector3f[3];
mesh.getTriangle(triIndex, vec);
for(int i=0; i < vec.length; i++) {
vec[i].multLocal(mesh.getWorldScale());
mesh.getWorldRotation().mult(vec[i], vec[i]);
vec[i].addLocal(mesh.getWorldTranslation());
}
What's wrong with the triangle (vector array) fresh from the mesh? What are the methods within the for loop even doing? multLocal? mult? addLocal?
(3) All this code seems pretty low-level and complicated, and yet it seems like something that most jME projects will have to deal with. Should there be framework API methods that help with these tasks and hide the complexity? If not within jME, what about a add-on project?
Thanks to anyone who can help me advance my understanding of this tricky problem.
- haruspex