I had a hard time understanding how to get a billboard/imposter tree configuration running in my application.
I could not find any hints in the tests, this forum, the documentation or via google, so I decided to post my experiences here.
The main clue is:
When you move a billboard node (calling setLocalTranslation) who's quad is rendered by a imposer then the (local) transition of the fakeScene (ImposerNode.quadScene) and the world transition of the ImposerNode must match.
I extracted the following example code. And ask if there isn't a simpler solution to my problem.
In any case I recommend that some kind of documentation and/or a TestGame is added to jME. The following code may be useful as a starting point.
Here come my code, ready to run in a SimpleGame. Just a model has to be provided.
protected void simpleInitGame() {
Node objectNode = loadModelNode();
// The actual scene could be anyware, we simulate this by moving the contents somewhere
objectNode.getChild(0).setLocalTranslation(5,0,5);
// I would use a shared Node to spread multiple copies of the object over the screen
Node sNode = new SharedNode("shared tree", objectNode);
ZBufferState buf = display.getRenderer().createZBufferState();
buf.setEnabled(true);
buf.setFunction(ZBufferState.CF_LEQUAL);
sNode.setRenderState(buf);
sNode.updateRenderState();
// Perhaps we want to set a light state. Then we must do it manually.
// spatial.setRenderState(lightState);
// Calculate the fake scene's size
sNode.updateGeometricState(0, true);
BoundingBox bound = (BoundingBox) sNode.getWorldBound();
float size = bound.xExtent;
if (bound.yExtent > size) size = bound.yExtent;
if (bound.zExtent > size) size = bound.zExtent;
final float sizeFaktor = 1.1f;
// We should make the size of the quad a little larger that the real scene
size *= 2 * sizeFaktor;
// Setup the imposter node
ImposterNode iNode = new ImposterNode("model imposter", 15, 256, 256);
// note I did not use DisplaySystem.getDisplaySystem().getWidth() but 128, because we render to a texture, not to screen
iNode.attachChild(sNode);
final float TEXTURE_CAM_DISTANCE = 100; // just a decision
iNode.setCameraDistance(TEXTURE_CAM_DISTANCE);
Camera textureCam = iNode.getTextureRenderer().getCamera();
// Setup the cam frustrum that it matches the size of the object
float viewAngle = FastMath.atan(size / TEXTURE_CAM_DISTANCE)
* FastMath.RAD_TO_DEG;
textureCam.setFrustumPerspective(viewAngle, 1, 1, TEXTURE_CAM_DISTANCE * 2);
// Setup the billboard node
BillboardNode bNode = new BillboardNode("billboard");
bNode.setAlignment(BillboardNode.CAMERA_ALIGNED); // just any alignment
// To this position we want to move the copy later
Vector3f position = new Vector3f(234,32,-53); // just any position
// add the billboard, so render states can be updated from it
rootNode.attachChild(bNode);
// We could now render the quad texture once manually and forget the
// ImposerNode and just use the quad further on.
final boolean RENDER_TEXTURES_ONLY_ONCE = false;
if (RENDER_TEXTURES_ONLY_ONCE) {
// set the imposer's position to the fake scenes position
// the camera will look here
iNode.setLocalTranslation(bound.getCenter().clone());
// We must update the world data explicitly to update the quads world
// bounds. The texture rendering camera will aim at it's position
iNode.updateWorldData(0);
// Render manually
iNode.updateCamera(bound.getCenter().add(0,0,-1)); // just provide a direction
iNode.updateScene(0);
iNode.renderTexture();
// Forget (and possibly reuse) the imposer and just use the quad
bNode.attachChild(iNode.getStandIn());
// Now we can move the billboard anywhere we want
bNode.setLocalTranslation(bound.getCenter().add(position));
} else {
// Or we want to use the imposers features setRedrawRate or
// setCameraThreshold.
iNode.setRedrawRate(.1f);
bNode.attachChild(iNode);
// Update the quad's render states manually after we attached it to the
// root node. We have to do this because the quad is not a real child of
// the imposer node.
iNode.getStandIn().updateRenderState();
// Now we want to move the object to see if translation works fine
bNode.setLocalTranslation(bound.getCenter().add(position));
// We also have to move the faked scene
sNode.setLocalTranslation(position.clone());
// The scene node's position must be updated manually
sNode.updateGeometricState(0, false);
}
cam.setLocation(bNode.getLocalTranslation().add(0, 0, 30));
cam.lookAt(bNode.getLocalTranslation(), Vector3f.UNIT_Y);
}
Please correct me, if my solution is wrong. I am still only trying to understand :)
Perhaps somebody can give some further hints also, or perhaps this helps someone.