Billboard control having issues when parent node is being rotated

Hello,



I have used billboard control (align to screen) to put a text node on top of each of my models. As long as they are not rotated, billboarded node is following camera orientation perfectly. As soon as I change their orientation, it gets crazy. Scene graph is



Rootnode->modelroot->modelgeometrygroup

->text node (with billboard control)



As soon as I rotate modelroot from anything other than default rotation, billboard stops to work properly.



Am I missing something? Or is it some kind of bug ? I looked at the code of BillboardControl rotateScreenAligned and I cannot see the place where the model to world rotation of controlled node hierarchy is taken into account to reverse it ?

It will take in the rotation of the parent node. The billboard is a Control that will rotate the node it controls, this rotation happens in the render loop before the final rotation matrix is calculated. So you will see this extra rotation.



Try having the billboard node alongside (as a sibling) to the modelroot node: give them a common parent. Then rotate the model alone. That should fix it.

This will is a usable workaround, but I still think Billboard Control should somehow apply reverse to-world rotation, because it might be quite tricky to find the correct position of it if it is supposed to be attached to node somewhere deeper in hierarchy.

Being a control, it is updated before the actual geometry is updated. So to move control updates until after the main render updates for geometry probably won’t be an option as it could have huge side effects for other controls.

You could move the billboard update code into the render method and that would resolve the issue.

But render/update difference is just one frame off, isn’t it ? What I’m talking here about is a permanent issue. On top of that, BillboardControl seems to be using controlRender, not controlUpdate to do the update, so it should work? And regardless of anything. moving the code around won’t implement missing world-to-local/local-to-world computations.

It could affect any other controllers that might depend where in the update/render cycle it gets called. The change would have to be put out to everyone to see if they would all want it and what side effects it could have. It is worth a try, but I think the easier solution in your case is to give our model node a parent node, and put the billboard under that. That is the great thing about scene graphs: they give you a lot of flexibility,

I’m not advocating any generic change to way controllers are handled. I’m just saying that BillboardControl should align the node it is attached to to the screen, regardless of how parent nodes are rotated. Currently, in some orientations, it is making text always invisible - not much point in having it.



I’ll try to fix it (I hate working with rotations to be honest, but what to do…). Then, I can implement my own version in case the offical one is supposed to stay in ‘world-root mode’ only.



Edit:



New version of rotateScreenAligned for BillboardNode which does what I need. I behaves same way for non-rotated nodes, but still keeps the proper alignment even if parents are rotated.

If you would like to incorporate it, probably q should be cached, like the rest of the temporary structures used throughout the class.



[java]

private void rotateScreenAligned(Camera camera) {

// coopt diff for our in direction:

look.set(camera.getDirection()).negateLocal();

// coopt loc for our left direction:

left.set(camera.getLeft()).negateLocal();



Quaternion q = new Quaternion();

q.fromAxes(left, camera.getUp(), look);

Node parent = spatial.getParent();

if ( parent != null ) {

Quaternion parentRotation = parent.getWorldRotation();

Quaternion inverseParent = parentRotation.inverse();

inverseParent.mult(q,q);

q.normalize();

}

spatial.setLocalRotation(q);

spatial.updateGeometricState();

}

[/java]

You’re right.if the billboard control does not face the screen in any case it’s useless…



Thanks for your code. However, it would need to iterate over the parents in case the billboard is deep into the root node.



A better solution would be to put some kind of flag on the node to know if they are affected by parent rotation.



I’m gonna add this in the issue tracker.



EDIT : here it is http://code.google.com/p/jmonkeyengine/issues/detail?id=258&q=owner:remy.bouquet&sort=owner&colspec=ID%20Type%20Status%20Component%20Priority%20Product%20Milestone%20Owner%20Summary

I have not tried it, but I think it should work for nested case - I’m using parent.getWorldRotation, not parent.getLocalRotation (and as far as I understand worldRotation already contains collapsed transforms of all parents).

you’re right.

Fixed in last SVN

Another part of the story - this time align-to-axis. When I use it, model is rotating like crazy, slower of faster depending on the camera angle. Following change made it working for me (and it kind of makes sense, because in present form it is reading the rotation which it will be modifying, which is almost guaranteed to be unstable)



[patch]

Eclipse Workspace Patch 1.0

#P jme3

Index: src/core/com/jme3/scene/control/BillboardControl.java

===================================================================

— src/core/com/jme3/scene/control/BillboardControl.java (revision 7446)

+++ src/core/com/jme3/scene/control/BillboardControl.java (working copy)

@@ -206,7 +206,7 @@

// the model space of the billboard.

look.set(camera.getLocation()).subtractLocal(

spatial.getWorldTranslation());

  •    spatial.getWorldRotation().mult(look, left); // coopt left for our own<br />
    
  •    spatial.getParent().getWorldRotation().mult(look, left); // coopt left for our own<br />
    

// purposes.

left.x *= 1.0f / spatial.getWorldScale().x;

left.y *= 1.0f / spatial.getWorldScale().y;



[/patch]

3 Likes

true!

I’ll fix that asap, thanks.

Oh look, that’s the bug that was in my BillboardControl video! :smiley: Excellent to see this one gone even if my problem was something else.

Almost forgot this one.

Fixed in last SVN, thanks @abies

1 Like

Lol, just came to this issue. Tried this the first time. Now I see: fixed 33 minutes ago. So I’ll have to wait 'till tomorrow for the nightly build… :smiley:

Resurrecting old topic…

I have Z-up world (as opposed to normal Y-up world). To get Billboard with AxialZ working properly, I had to change method to
[java]
private void rotateAxial(Camera camera, Vector3f axis) {
// Compute the additional rotation required for the billboard to face
// the camera. To do this, the camera must be inverse-transformed into
// the model space of the billboard.
look.set(camera.getLocation()).subtractLocal(
spatial.getWorldTranslation());
spatial.getParent().getWorldRotation().mult(look, left); // coopt left for our own
// purposes.
left.x *= 1.0f / spatial.getWorldScale().x;
left.y *= 1.0f / spatial.getWorldScale().y;
left.z *= 1.0f / spatial.getWorldScale().z;

    if (axis.y == 1) {
        // squared length of the camera projection in the xz-plane
        float lengthSquared = left.x * left.x + left.z * left.z;
        if (lengthSquared &lt; FastMath.FLT_EPSILON) {
            // camera on the billboard axis, rotation not defined
            return;
        }

        // unitize the projection
        float invLength = FastMath.invSqrt(lengthSquared);
        left.x *= invLength;
        left.y = 0.0f;
        left.z *= invLength;

        // compute the local orientation matrix for the billboard
        orient.set(0, 0, left.z);
        orient.set(0, 1, 0);
        orient.set(0, 2, left.x);
        orient.set(1, 0, 0);
        orient.set(1, 1, 1);
        orient.set(1, 2, 0);
        orient.set(2, 0, -left.x);
        orient.set(2, 1, 0);
        orient.set(2, 2, left.z);
    } else if (axis.z == 1) {
        // squared length of the camera projection in the xy-plane
        float lengthSquared = left.x * left.x + left.y * left.y;
        if (lengthSquared &lt; FastMath.FLT_EPSILON) {
            // camera on the billboard axis, rotation not defined
            return;
        }

        // unitize the projection
        float invLength = FastMath.invSqrt(lengthSquared);
        left.x *= invLength;
        left.y *= invLength;
        left.z = 0.0f;

        // compute the local orientation matrix for the billboard
        orient.set(0, 0, -left.y);
        orient.set(0, 1, -left.x);
        orient.set(0, 2, 0);
        orient.set(1, 0, left.x);
        orient.set(1, 1, -left.y);
        orient.set(1, 2, 0);
        orient.set(2, 0, 0);
        orient.set(2, 1, 0);
        orient.set(2, 2, 1);
    }

    // The billboard must be oriented to face the camera before it is
    // transformed into the world.
    spatial.setLocalRotation(orient);
    fixRefreshFlags();
}

[/java]

Two main changes here - first, lengthSquared is based on x/y parameters, instead of x/z. Kind of makes sense, given the fact it is x and y which later multiplied?
Second - I had to reorder components in the matrix. I'm not sure if it will work for everybody and how it will affect Y-up world (what is the meaning of Z-aligned billboard in Y-up world??), but that was only way to get x/0/z billboard geometry to stay aligned to camera.

As I said, I have no idea how AxisZ one works in Y-up world, but to get AxisZ workind in Z-up world same way as AxisY is working in Y-up world, I had to make changes above.

If it is not compatible, no issues - I can clone my version of that control, as I understand that Z-up worlds are not standard.

Just as a question, why do you use Z up worlds? Are ther any particual reasons?

@Empire Phoenix said: Just as a question, why do you use Z up worlds? Are ther any particual reasons?

This is very common in the simulation space. x,y represents a position on the map and z represents elevation. It’s common enough that some scene graphs do this by default… which is kind of nice.

I keep all of my world data in Z up and then translate before it gets to the scene graph. Really funny when I forget to swap. :slight_smile:

As I’m working on boardgame-like environment with square coordinates, it was a slightly confusing to covert SquareCoord(x,y) to x,z pairs all the time. Then, all the models I’m using are using z as vertical axis, so I had to have extra rotation in front of each one. When I started combining it with some billboard, world-attached effects/particles etc, it became too much and I just flipped to z-axis. Plus, camera is often on top, looking down - so kind of have z-negative as depth axis, but randomly rotating around that.

Now I think I should have stayed with y-axis after all, but it is too much work to convert all geometry-generation code (as it is not only about swapping y with z, but often about winding triangles other way around). Plus, I thought that everything should be compatible in any case…

To be honest, I think that billboards and particles are only things which are going to give me real pain. In case of billboards, problems is very localized. In case of particle emitters, they are again coming from z-up sources and I have to reverse engineer the format details - so adding extra rotation on top of that is not going to help to experiment.