Hey Guys,
I’ve noticed a bug in billboard-Control: Only one of the Alignments cared about the parents rotation, see this test case:
package mygame;
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.control.BillboardControl;
import com.jme3.scene.shape.Quad;
public class Main extends SimpleApplication {
Node n;
public static void main(String[] args) {
Main app = new Main();
app.start();
}
@Override
public void simpleInitApp()
{
n = new Node("myNode");
Node o = new Node("subNode");
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Blue);
Quad q = new Quad(1f, 1f);
Geometry geom = new Geometry("Box", q);
geom.setMaterial(mat);
o.attachChild(geom);
BillboardControl b = new BillboardControl();
b.setAlignment(BillboardControl.Alignment.Screen);
o.addControl(b);
n.attachChild(o);
rootNode.attachChild(n);
flyCam.setMoveSpeed(flyCam.getMoveSpeed() * 5f);
}
@Override
public void simpleUpdate(float tpf)
{
n.rotate(0f, FastMath.HALF_PI * tpf, 0f);
}
}
Play around with the different Alignments to see.
After looking into the sourcecode I altered some methods, but Alignment.AXIS does only work when not rotating the parent, I have no clue why. But I could fix Alignment.CAMERA atleast.
See also Attaching Indicators (Healthbars) to rootNode/SceneGraph - #39 by Darkchaos
/**
* Aligns this Billboard so that it points to the camera position.
*
* @param camera
* Camera
*/
private void rotateCameraAligned(Camera camera) {
look.set(camera.getLocation()).subtractLocal(
spatial.getWorldTranslation());
// coopt left for our own purposes.
Vector3f xzp = left;
// The xzp vector is the projection of the look vector on the xz plane
xzp.set(look.x, 0, look.z);
// check for undefined rotation...
if (xzp.equals(Vector3f.ZERO)) {
return;
}
look.normalizeLocal();
xzp.normalizeLocal();
float cosp = look.dot(xzp);
// compute the local orientation matrix for the billboard
orient.set(0, 0, xzp.z);
orient.set(0, 1, xzp.x * -look.y);
orient.set(0, 2, xzp.x * cosp);
orient.set(1, 0, 0);
orient.set(1, 1, cosp);
orient.set(1, 2, look.y);
orient.set(2, 0, -xzp.x);
orient.set(2, 1, xzp.z * -look.y);
orient.set(2, 2, xzp.z * cosp);
Node parent = spatial.getParent();
Quaternion rot=new Quaternion().fromRotationMatrix(orient);
if ( parent != null ) {
rot = parent.getWorldRotation().inverse().multLocal(rot);
rot.normalizeLocal();
}
// The billboard must be oriented to face the camera before it is
// transformed into the world.
spatial.setLocalRotation(rot);
fixRefreshFlags();
}
/**
* Rotate the billboard so it points directly opposite the direction the
* camera's facing
*
* @param camera
* Camera
*/
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();
orient.fromAxes(left, camera.getUp(), look);
Node parent = spatial.getParent();
Quaternion rot=new Quaternion().fromRotationMatrix(orient);
if ( parent != null ) {
rot = parent.getWorldRotation().inverse().multLocal(rot);
rot.normalizeLocal();
}
spatial.setLocalRotation(rot);
fixRefreshFlags();
}
/**
* Rotate the billboard towards the camera, but keeping a given axis fixed.
*
* @param camera
* Camera
*/
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;
// squared length of the camera projection in the xz-plane
float lengthSquared = left.x * left.x + left.z * left.z;
if (lengthSquared < FastMath.FLT_EPSILON) {
// camera on the billboard axis, rotation not defined
return;
}
// unitize the projection
float invLength = FastMath.invSqrt(lengthSquared);
if (axis.y == 1) {
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) {
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.y);
orient.set(1, 1, left.x);
orient.set(1, 2, 0);
orient.set(2, 0, 0);
orient.set(2, 1, 0);
orient.set(2, 2, 1);
}
Node parent = spatial.getParent();
Quaternion rot=new Quaternion().fromRotationMatrix(orient);
if ( parent != null ) {
rot = parent.getWorldRotation().inverse().multLocal(rot);
rot.normalizeLocal();
}
// The billboard must be oriented to face the camera before it is
// transformed into the world.
spatial.setLocalRotation(rot);
fixRefreshFlags();
}
Now it’s nearly working as I said, the Billboard.AXISY works, when moving the camera but only when removing the code from simpleUpdate.