Usage of ImposterNode and BillboardNode

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.

I modified the code i posted in my first post, because I encountered a few problems.



The line



    iNode = new ImposterNode("model imposter", 10, display.getWidth(), display.getHeight());



in the ImposterNodeTest made me apply the width of my display too, but actually we are rendering to a texture of the given size here, so probably the size should be different. So providing a smaller fixed value should be faster in most cases.



I also found this related topic: http://www.jmonkeyengine.com/jmeforum/index.php?topic=855.0



edit: and i modified it again because is was still not really working.

I also added a feature: the resulting billboard will be the correct size since cam frustrum (viewing angle) and size are calculated.

Argh! I am still struggeling HARD -.-

Hours and hours and hours… and the system still just behaves strange.



What i want to do, is render multiple billboards of a single object. My objects are trees and my static billboards from the side look not good if you look down on the terrain, so I had the idea to render some billboard textures from different angles and apply the texture depending on the camera direction. Okay… I cannot render more then one texture of a single model object. I have a method to render the texture:



  public static Quad renderImposter(Spatial template, Vector3f direction,
      float distance, int tSizeX, int tSizeY, BoundingBox bound) {
    template.updateGeometricState(0,true);
   
    if (bound == null) {
      bound = (BoundingBox) template.getWorldBound();
    }
    float quadSize = Models.imposterSize(bound);
   
    ZBufferState zBuffer = DisplaySystem.getDisplaySystem()
        .getRenderer().createZBufferState();
    zBuffer.setEnabled(true);
    zBuffer.setFunction(ZBufferState.CF_LEQUAL);
    template.setRenderState(zBuffer);
    template.updateRenderState();
   
    Quad quad = new Quad("quad", quadSize,quadSize);
    quad.setModelBound(new BoundingBox());
    quad.updateModelBound();
    quad.setLocalTranslation(bound.getCenter().clone());
    quad.updateGeometricState(0,true);

    TextureRenderer tRenderer = DisplaySystem.getDisplaySystem()
        .createTextureRenderer(tSizeX, tSizeY, TextureRenderer.RENDER_TEXTURE_2D);
    tRenderer.setBackgroundColor(new ColorRGBA(0, 0, 0, 0f));
    Texture texture = new Texture();
    tRenderer.setupTexture(texture);
   
    Camera cam = tRenderer.getCamera();
    cam.setLocation(quad.getCenter().subtract(
        direction.normalize().multLocal(distance)));
    cam.lookAt(quad.getCenter(), Vector3f.UNIT_Y);
    cam.setFrustumPerspective(2, 1, 1, 1000);
    cam.setFrustumPerspective(FastMath.atan(quadSize / distance)
        * FastMath.RAD_TO_DEG, 1, 1, distance * 2);
   
    tRenderer.render(template, texture);
   
    TextureState ts = DisplaySystem.getDisplaySystem().getRenderer()
        .createTextureState();
    ts.setEnabled(true);
    ts.setTexture(texture, 0);
    quad.setRenderState(ts);

    CullState cs = DisplaySystem.getDisplaySystem().getRenderer()
        .createCullState();
    cs.setCullMode(CullState.CS_NONE);
    cs.setEnabled(true);
    quad.setRenderState(cs);
    quad.updateRenderState();

    return quad;
  }



and a testMethod doing this:


  protected void simpleInitGame() {
    Spatial template = Models.loadTreePalm();
    Spatial template2 = Models.loadTreePalm();
   
    Quad quad1 = Models.renderImposter(template, Vector3f.UNIT_Z, 10, 256,
        256, null);
    Quad quad2 = Models.renderImposter(template2, Vector3f.UNIT_Z
            .add(Vector3f.UNIT_X), 10, 256, 256, null);
   
    rootNode.attachChild(quad1);
    quad1.setLocalTranslation(-10,0,0);
   
    rootNode.attachChild(quad2);
    quad2.setLocalTranslation(10,0,0);
  }



This code works, but if I replace template2 with template or a SharedNode of template, then quad2 is empty. quad1 is still perfectly okay. It seams that the TextureRenderer changes the model somehow so that it does not show up correctly on the second try. Or it stores internal information perhaps caching stuff, so that the second try will noch render.

Can anybody please tell me, what is going on here?

Thanks

EDIT: Models.loadTreePalm() just loads any Model from an jme or 3ds file. These are my trees. Please test with just any textured mesh.

EDIT: Ok, my workarround now is to use CloneImportExport since due to a (possible) bug it does not copy the renderstates and other arrays properly. I think that is the reason why it works. For that possible bug I open a new thread later anyway... Perhaps I just got something wrong but it looks like a bug :) Please tell me, if this is the wrong forum or something for my kind of problem