Billboards in JME3

Are billboards implemented in JME3?

If not, how to easily implement them?

I have a nightly release that’s 3 days old so I can’t be 100% certain, but as far as I know the node isn’t implemented yet. If you need more than a screen aligned billboard, you might be able to gleam some information from the jME2 billboardnode source. But at least this example might give you some idea of how to do it? Hell, I don’t even know if what I wrote is “correct” in the sense of being the way you’d want to do it! =p



I made this super ghetto billboard class a while back. All I know is that “it works.” But I haven’t tuned it at all. I know I wanted to get the camera another way since that was a PITA to have to pass the camera in every time. The jME2 implementation just grabs the camera from the renderer in it’s draw function… But jME3 doesn’t have a draw method to pull this off, so I hadn’t given much thought as how to do it in jME3 more efficiently.



public class ScreenBoard extends Node {



private Camera camera;



private Matrix3f orientation;

private Vector3f look;

private Vector3f left;



public ScreenBoard(Camera camera) {

this.camera = camera;



orientation = new Matrix3f();

look = new Vector3f();

left = new Vector3f();

}



@Override

public void updateLogicalState(float tpf) {

super.updateLogicalState(tpf);



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

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

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

setLocalRotation(orientation);

}

}




And a Test class:



public class ScreenBBTest extends SimpleApplication {



@Override

public void simpleInitApp() {

flyCam.setMoveSpeed(100);



Quad q = new Quad(2, 2);

Geometry g = new Geometry(“Quad”, q);

Material mat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

mat.setColor(“m_Color”, ColorRGBA.Blue);

g.setMaterial(mat);



Quad q2 = new Quad(1, 1);

Geometry g3 = new Geometry(“Quad2”, q2);

Material mat2 = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

mat2.setColor(“m_Color”, ColorRGBA.Yellow);

g3.setMaterial(mat2);

g3.setLocalTranslation(.5f, .5f, .01f);



Box b = new Box(new Vector3f(0, 0, 3), .25f, .5f, .25f);

Geometry g2 = new Geometry(“Box”, b);

g2.setMaterial(mat);



ScreenBoard bb = new ScreenBoard(cam);

bb.attachChild(g);

bb.attachChild(g3);



rootNode.attachChild(bb);

rootNode.attachChild(g2);

}



public static void main(String[] args) {

ScreenBBTest app = new ScreenBBTest();

app.start();

}

}

You can try the BillboardControl I wrote. Its basically converting the old BillboardNode to a Control instead. I’m still in the middle of porting my jME 1 app to 3, so this hasn’t been tested yet:



package net.onesaf.core.services.gui.ares.core.scene.control;



import java.io.IOException;



import com.jme3.export.InputCapsule;

import com.jme3.export.JmeExporter;

import com.jme3.export.JmeImporter;

import com.jme3.export.OutputCapsule;

import com.jme3.math.FastMath;

import com.jme3.math.Matrix3f;

import com.jme3.math.Vector3f;

import com.jme3.renderer.Camera;

import com.jme3.renderer.RenderManager;

import com.jme3.renderer.ViewPort;

import com.jme3.scene.Spatial;

import com.jme3.scene.control.AbstractControl;

import com.jme3.scene.control.Control;



public class BillboardControl extends AbstractControl {

private static final long serialVersionUID = 1L;



private Matrix3f orient;



private Vector3f look;



private Vector3f left;



private int alignment;



/** Alligns this Billboard Node to the screen. /

public static final int SCREEN_ALIGNED = 0;



/
* Alligns this Billboard Node to the screen, but keeps the Y axis fixed. /

public static final int AXIAL = 1;

public static final int AXIAL_Y = 1;



/
* Alligns this Billboard Node to the camera position. /

public static final int CAMERA_ALIGNED = 2;



/
* Alligns this Billboard Node to the screen, but keeps the Z axis fixed. */

public static final int AXIAL_Z = 3;



public BillboardControl() {

super();

orient = new Matrix3f();

look = new Vector3f();

left = new Vector3f();

alignment = SCREEN_ALIGNED;

}



public Control cloneForSpatial(Spatial spatial) {

BillboardControl control = new BillboardControl();

control.setSpatial(spatial);

return control;

}



@Override

protected void controlUpdate(float tpf) {

}



@Override

protected void controlRender(RenderManager rm, ViewPort vp) {

Camera cam = vp.getCamera();

rotateBillboard(cam);

}



/**

  • rotate the billboard based on the type set

    *
  • @param cam
  •        Camera<br />
    

*/

public void rotateBillboard(Camera cam) {

// get the scale, translation and rotation of the node in world space

spatial.updateLogicalState(0);



switch (alignment) {

case AXIAL_Y:

rotateAxial(cam, Vector3f.UNIT_Y);

break;

case AXIAL_Z:

rotateAxial(cam, Vector3f.UNIT_Z);

break;

case SCREEN_ALIGNED:

rotateScreenAligned(cam);

break;

case CAMERA_ALIGNED:

rotateCameraAligned(cam);

break;

}

}



/**

  • Alligns this Billboard Node so that it points to the camera position.

    *
  • @param camera
  •        Camera<br />
    

*/

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);



// The billboard must be oriented to face the camera before it is

// transformed into the world.

spatial.getWorldRotation().apply(orient);

}



/**

  • Rotate the billboard so it points directly opposite the direction the
  • camera’s facing

    *
  • @param camera
  •        Camera<br />
    

*/

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);

spatial.getWorldRotation().fromRotationMatrix(orient);

}



/**

  • Rotate the billboard towards the camera, but keeping a given axis fixed.

    *
  • @param camera
  •        Camera<br />
    

*/

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.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);

}



// The billboard must be oriented to face the camera before it is

// transformed into the world.

spatial.getWorldRotation().apply(orient);

}



/**

  • Returns the alignment this BillboardNode is set too.

    *
  • @return The alignment of rotation, AXIAL, CAMERA or SCREEN.

    */

    public int getAlignment() {

    return alignment;

    }



    /**
  • Sets the type of rotation this BillboardNode will have. The alignment can
  • be CAMERA_ALIGNED, SCREEN_ALIGNED or AXIAL. Invalid alignments will
  • assume no billboard rotation.

    */

    public void setAlignment(int alignment) {

    this.alignment = alignment;

    }



    @Override

    public void write(JmeExporter e) throws IOException {

    super.write(e);

    OutputCapsule capsule = e.getCapsule(this);

    capsule.write(orient, “orient”, new Matrix3f());

    capsule.write(look, “look”, Vector3f.ZERO);

    capsule.write(left, “left”, Vector3f.ZERO);

    capsule.write(alignment, “alignment”, SCREEN_ALIGNED);

    }



    @Override

    public void read(JmeImporter e) throws IOException {

    super.read(e);

    InputCapsule capsule = e.getCapsule(this);

    orient = (Matrix3f) capsule.readSavable(“orient”, new Matrix3f());

    look = (Vector3f) capsule.readSavable(“look”, Vector3f.ZERO.clone());

    left = (Vector3f) capsule.readSavable(“left”, Vector3f.ZERO.clone());

    alignment = capsule.readInt(“alignment”, SCREEN_ALIGNED);

    }

    }





    Then you can billboard any object with:



    spatial.addControl(new BillboardControl());

Hello!

This BillboardControl did not work for me.

I have modified this code to a “simpleRotation” by lookAt(Vector3f) (similar to the code from tehflah), now it works.

I did some other ports like Skybox, ImagebasedHighMap, and ProceduralTexturegenerator.



You can get it from my project-page

starcom



bye

Yeah, i hadn’t had time to test it yet. It was as a straight up port from the 1.0 BillboardNode to a control. lookAt(camera.getLocation()) will certainly work for screen aligned.

Here is the fixed & tested version of my prior post: http://www.gravityresearch.us/BillboardControl.java (note: consider this contributed code).



lookAt(loc,up) is a little heavy on the computation side for something as simple as screen-aligned.

is it usefull for rotating a sphere like on x y z axis fixed in space?

with your test i got two silly square colored in middle on the screen

is it right?

Hello!



Billboards are officially supported in jME3 now, so the craziness above is not as applicable. But yes, do answer your second question, yes. Two, always focused on the screen squares should’ve appeared if I recall correctly. If you want to use the built-in Billboarding, use:



[java]

BillboardControl bb = new BillboardControl();

[/java]



It has several methods to change the alignment and such. However, I’m not sure of their uses when it comes to billboarding actual geometries such as boxes or spheres. The main thing I use them for is displaying sprites/textures/pointsprites on a quad. It’s important to have the quad always facing the screen in this case, thus billboarding is required!



Cheers!

~FlaH

Probably you can help me.

I have a sphere fixed in screen.I drag the sphere and it rotate on x y z axis.

When I rotate it Pi/2 on the left and then pi/2 on the up direction the sphere does not follow the drag rotation.

Billboard can help me?



[java]



package it.game;



import com.jme3.app.SimpleApplication;

import com.jme3.bounding.BoundingSphere;

import com.jme3.bounding.BoundingVolume;

import com.jme3.input.KeyInput;

import com.jme3.input.MouseInput;

import com.jme3.input.RawInputListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.input.controls.MouseButtonTrigger;

import com.jme3.input.event.JoyAxisEvent;

import com.jme3.input.event.JoyButtonEvent;

import com.jme3.input.event.KeyInputEvent;

import com.jme3.input.event.MouseButtonEvent;

import com.jme3.input.event.MouseMotionEvent;

import com.jme3.light.DirectionalLight;

import com.jme3.material.Material;

import com.jme3.math.Quaternion;

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.Node;

import com.jme3.scene.shape.Sphere;

import com.jme3.system.AppSettings;

import com.jme3.texture.Texture;

import com.jme3.util.TangentBinormalGenerator;



public class HelloInput extends SimpleApplication {



public static void main(String[] args) {

HelloInput app = new HelloInput();



app.start();



}

float x=0;

float y=0;

protected Geometry shiny_rock;

protected Sphere rock;

protected BoundingVolume b;

Boolean buttonPressed=false;



Quaternion localRotation;

private Texture loadTexture;

@Override

public void simpleInitApp() {



flyCam.setEnabled(false);



AppSettings settings = new AppSettings(true);

settings.setRenderer(AppSettings.LWJGL_OPENGL3);

settings.setFrameRate(10);

setSettings(settings);

rock = new Sphere(30,30, 2f);



rock.setBound(new BoundingSphere());

shiny_rock = new Geometry("Shiny rock", rock);



rock.setTextureMode(Sphere.TextureMode.Polar); // better quality on spheres

TangentBinormalGenerator.generate(rock); // for lighting effect

loadTexture = assetManager.loadTexture("Textures/pond.png");

Material mat_lit = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");

mat_lit.setTexture("DiffuseMap", loadTexture);//assetManager.loadTexture("Textures/Terrain/Pond/Pond.png"));

mat_lit.setTexture("NormalMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png"));

mat_lit.setFloat("Shininess", 5f); // [0,128]



shiny_rock.setMaterial(mat_lit);



DirectionalLight sun = new DirectionalLight();

sun.setDirection(new Vector3f(0.2f, 0.2f, -1.0f));

rootNode.addLight(sun);

localRotation = shiny_rock.getLocalRotation().fromAxes(Vector3f.UNIT_X,

Vector3f.UNIT_Y, Vector3f.UNIT_Z);

rootNode.attachChild(shiny_rock);



initKeys(); // load my custom keybinding

}



/** Custom Keybinding: Map named actions to inputs. */

private void initKeys() {

// You can map one or several inputs to one named action



inputManager.addRawInputListener(rawInputListener);

}





private RawInputListener rawInputListener=new RawInputListener() {





private Quaternion initRotation;



public void onJoyAxisEvent(JoyAxisEvent evt) {



}



public void onJoyButtonEvent(JoyButtonEvent evt) {

}





public void onMouseMotionEvent(MouseMotionEvent evt) {

if (buttonPressed) {

x += evt.getDX();

y += evt.getDY();

shiny_rock.rotate(

-evt.getDY() * Vector3f.UNIT_X.length() / 180,

evt.getDX() * Vector3f.UNIT_Y.length() / 180, 0);



}



}



public void onMouseButtonEvent(MouseButtonEvent evt) {

buttonPressed = evt.isPressed();







}



public void onKeyEvent(KeyInputEvent evt) {



}



public void beginInput() {



}



public void endInput() {



}

};









}

[/java]

Haha lol, dilembo, seriously, have you just written an email to the applet author?

no answer from him